132 lines
4.4 KiB
JavaScript
132 lines
4.4 KiB
JavaScript
import { nextTick, unref } from 'vue'
|
|
|
|
/**
|
|
* 관리자 Markdown 에디터의 공통 포커스 실행을 관리한다.
|
|
* @param {Object} options - 포커스 실행 옵션
|
|
* @param {import('vue').Ref<'write'|'preview'|string>} options.activeMode - 현재 에디터 모드
|
|
* @param {import('vue').Ref<string>} options.value - Markdown 본문
|
|
* @param {import('vue').Ref<number>} options.activeLogicalLineIndex - 현재 논리 줄
|
|
* @param {() => HTMLTextAreaElement|null} options.getWriteTextareaElement - 작성 textarea 반환 함수
|
|
* @param {(offset: number) => number} options.getLineIndexAtOffset - 오프셋 기준 줄 계산 함수
|
|
* @param {(lineIndex: number) => number} options.getLineStartOffset - 줄 시작 오프셋 계산 함수
|
|
* @param {(start: number, end?: number, options?: { preserveScroll?: boolean }) => void} options.setTextareaSelection - textarea 선택 복원 함수
|
|
* @param {(lineIndex: number) => void} options.focusTextareaAtLine - 지정 줄 textarea 포커스 함수
|
|
* @param {() => void} options.refreshCaretLogicalLine - 현재 커서 줄 갱신 함수
|
|
* @param {() => void} options.scrollPreviewToTop - 라이브 미리보기 상단 이동 함수
|
|
* @param {(line: number, childIndex: number, position: string, offset?: number, scrollAlign?: string) => void} options.focusPreviewEditableAtLine - 라이브 편집 줄 포커스 함수
|
|
* @returns {{
|
|
* focusFirstBlock: () => void,
|
|
* getInitialContentFocusLineIndex: () => number,
|
|
* ensureContentFocusLineExists: (lineIndex: number) => number,
|
|
* restoreEditorFocusAfterHistory: (selection?: { start: number, end: number }|null) => void
|
|
* }} 공통 포커스 액션
|
|
*/
|
|
export const useAdminMarkdownEditorFocusActions = (options) => {
|
|
/**
|
|
* 본문 작성 시작 시 포커스할 첫 줄 인덱스를 반환한다.
|
|
* @returns {number} 줄 번호(0-based)
|
|
*/
|
|
const getInitialContentFocusLineIndex = () => {
|
|
const lines = String(unref(options.value) ?? '').split('\n')
|
|
|
|
if (!lines.length) {
|
|
return 0
|
|
}
|
|
|
|
const firstLine = lines[0]?.trim() ?? ''
|
|
|
|
if (/^#{1,6}\s+/.test(firstLine)) {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
/**
|
|
* 본문 작성 시작 줄이 없으면 빈 줄을 추가한다.
|
|
* @param {number} lineIndex - 포커스할 줄
|
|
* @returns {number} 보정된 줄 번호
|
|
*/
|
|
const ensureContentFocusLineExists = (lineIndex) => {
|
|
const value = String(unref(options.value) ?? '')
|
|
const lines = value.split('\n')
|
|
|
|
if (lineIndex < lines.length) {
|
|
return lineIndex
|
|
}
|
|
|
|
options.value.value = lines.length ? `${value}\n` : ''
|
|
return lines.length
|
|
}
|
|
|
|
/**
|
|
* 본문 에디터에 포커스한다. 현재 모드(write/preview)를 유지한다.
|
|
* @returns {void}
|
|
*/
|
|
const focusFirstBlock = () => {
|
|
const focusLine = ensureContentFocusLineExists(getInitialContentFocusLineIndex())
|
|
|
|
if (options.activeMode.value === 'preview') {
|
|
options.scrollPreviewToTop()
|
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
options.focusPreviewEditableAtLine(focusLine, 0, 'start')
|
|
})
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
nextTick(() => {
|
|
const textarea = options.getWriteTextareaElement()
|
|
const lineStart = options.getLineStartOffset(focusLine)
|
|
|
|
if (textarea) {
|
|
textarea.focus()
|
|
textarea.setSelectionRange(lineStart, lineStart)
|
|
textarea.scrollTop = 0
|
|
}
|
|
|
|
options.activeLogicalLineIndex.value = focusLine
|
|
options.refreshCaretLogicalLine()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 히스토리 복원 후 현재 모드에 맞게 포커스를 복원한다.
|
|
* @param {{ start: number, end: number }|null} [selection=null] - 복원할 선택 영역
|
|
* @returns {void}
|
|
*/
|
|
const restoreEditorFocusAfterHistory = (selection = null) => {
|
|
const focusLine = selection
|
|
? options.getLineIndexAtOffset(selection.start)
|
|
: options.activeLogicalLineIndex.value
|
|
|
|
if (options.activeMode.value === 'write') {
|
|
if (selection) {
|
|
options.setTextareaSelection(selection.start, selection.end, { preserveScroll: true })
|
|
return
|
|
}
|
|
|
|
options.focusTextareaAtLine(focusLine)
|
|
return
|
|
}
|
|
|
|
nextTick(() => {
|
|
const offset = selection
|
|
? Math.max(0, selection.start - options.getLineStartOffset(focusLine))
|
|
: 0
|
|
|
|
options.focusPreviewEditableAtLine(focusLine, 0, 'auto', offset, 'center')
|
|
})
|
|
}
|
|
|
|
return {
|
|
focusFirstBlock,
|
|
getInitialContentFocusLineIndex,
|
|
ensureContentFocusLineExists,
|
|
restoreEditorFocusAfterHistory
|
|
}
|
|
}
|