관리자 라이브 전환 포커스 계산 분리
This commit is contained in:
@@ -32,6 +32,10 @@ import {
|
||||
isEditorModeToggleShortcut,
|
||||
isMediaPickerCloseShortcut
|
||||
} from '../../lib/markdown-editor-keyboard.js'
|
||||
import {
|
||||
isCodeFenceBodyLine,
|
||||
resolvePreviewFocusPosition as resolveMarkdownPreviewFocusPosition
|
||||
} from '../../lib/markdown-editor-focus.js'
|
||||
import {
|
||||
buildMediaPickerApplyState,
|
||||
buildMediaPickerEmptyMessage,
|
||||
@@ -606,94 +610,6 @@ const getLineIndexAtOffset = (offset) => {
|
||||
return getMarkdownLineIndexAtOffset(markdownValue.value, offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* 닫는 fenced 줄에서 대응하는 여는 줄을 찾는다.
|
||||
* @param {string[]} lines - 마크다운 줄
|
||||
* @param {number} closingLine - 닫는 줄 번호
|
||||
* @param {(line: string) => boolean} isOpeningLine - 여는 줄 판별
|
||||
* @returns {number|null} 여는 줄 번호
|
||||
*/
|
||||
const findPreviousFencedOpeningLine = (lines, closingLine, isOpeningLine) => {
|
||||
for (let index = closingLine - 1; index >= 0; index -= 1) {
|
||||
const trimmed = String(lines[index] ?? '').trim()
|
||||
|
||||
if (isOpeningLine(trimmed)) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 여는 fenced 줄에서 닫는 줄을 찾는다.
|
||||
* @param {string[]} lines - 마크다운 줄
|
||||
* @param {number} openingLine - 여는 줄 번호
|
||||
* @param {string} closingMarker - 닫는 표식
|
||||
* @returns {number|null} 닫는 줄 번호
|
||||
*/
|
||||
const findNextFencedClosingLine = (lines, openingLine, closingMarker) => {
|
||||
for (let index = openingLine + 1; index < lines.length; index += 1) {
|
||||
if (String(lines[index] ?? '').trim() === closingMarker) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 지정 줄이 속한 코드 펜스 범위를 찾는다.
|
||||
* @param {string[]} lines - 마크다운 줄
|
||||
* @param {number} targetLine - 확인할 줄 번호
|
||||
* @returns {{ openingLine: number, closingLine: number|null }|null} 코드 펜스 범위
|
||||
*/
|
||||
const findCodeFenceRangeAtLine = (lines, targetLine) => {
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
if (!String(lines[index] ?? '').trim().startsWith('```')) {
|
||||
continue
|
||||
}
|
||||
|
||||
const closingLine = findNextFencedClosingLine(lines, index, '```')
|
||||
|
||||
if (targetLine === index || (closingLine !== null && targetLine >= index && targetLine <= closingLine)) {
|
||||
return { openingLine: index, closingLine }
|
||||
}
|
||||
|
||||
if (closingLine !== null) {
|
||||
index = closingLine
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 지정 줄이 코드 펜스 본문 줄인지 확인한다.
|
||||
* @param {string[]} lines - 마크다운 줄 목록
|
||||
* @param {number} lineIndex - 줄 번호
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isCodeFenceBodyLine = (lines, lineIndex) => {
|
||||
for (let index = 0; index <= lineIndex; index += 1) {
|
||||
if (!String(lines[index] ?? '').trim().startsWith('```')) {
|
||||
continue
|
||||
}
|
||||
|
||||
const closingLine = findNextFencedClosingLine(lines, index, '```')
|
||||
|
||||
if (lineIndex > index && (closingLine === null || lineIndex < closingLine)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (closingLine !== null) {
|
||||
index = closingLine
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 선택 위치에 맞춰 에디터 포커스를 복원한다.
|
||||
* @param {{ start: number, end: number }|null} selection - 복원할 선택 영역
|
||||
@@ -739,46 +655,11 @@ const {
|
||||
* @returns {{ line: number, offset: number }} 라이브 포커스 대상
|
||||
*/
|
||||
const resolvePreviewFocusPosition = (line, offset) => {
|
||||
const lines = (markdownValue.value ?? '').split('\n')
|
||||
const safeLine = Math.min(Math.max(0, line), Math.max(0, lines.length - 1))
|
||||
const trimmed = String(lines[safeLine] ?? '').trim()
|
||||
|
||||
if (trimmed.startsWith('```')) {
|
||||
const codeFenceRange = findCodeFenceRangeAtLine(lines, safeLine)
|
||||
|
||||
if (!codeFenceRange || codeFenceRange.closingLine === null) {
|
||||
return { line: safeLine, offset }
|
||||
}
|
||||
|
||||
const focusLine = Math.min(
|
||||
Math.max(codeFenceRange.openingLine + 1, safeLine),
|
||||
Math.max(codeFenceRange.openingLine + 1, codeFenceRange.closingLine - 1)
|
||||
)
|
||||
|
||||
return { line: focusLine, offset: safeLine === codeFenceRange.openingLine ? 0 : offset }
|
||||
}
|
||||
|
||||
if (trimmed.startsWith(':::callout') || trimmed.startsWith(':::toggle')) {
|
||||
return { line: safeLine + 1, offset: 0 }
|
||||
}
|
||||
|
||||
if (trimmed === ':::') {
|
||||
const previousFencedOpening = findPreviousFencedOpeningLine(
|
||||
lines,
|
||||
safeLine,
|
||||
(candidate) => candidate.startsWith(':::callout') || candidate.startsWith(':::toggle')
|
||||
)
|
||||
|
||||
if (previousFencedOpening !== null) {
|
||||
return { line: Math.max(previousFencedOpening + 1, safeLine - 1), offset }
|
||||
}
|
||||
}
|
||||
|
||||
if (/^>\s*(?:\[!bg=|\{bg=)/.test(trimmed) && String(lines[safeLine + 1] ?? '').trim().startsWith('>')) {
|
||||
return { line: safeLine + 1, offset: 0 }
|
||||
}
|
||||
|
||||
return { line: safeLine, offset }
|
||||
return resolveMarkdownPreviewFocusPosition({
|
||||
value: markdownValue.value,
|
||||
line,
|
||||
offset
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user