게시물 라이브 편집 블록 동작 개선
This commit is contained in:
@@ -17,9 +17,14 @@ import {
|
||||
stripListMarker,
|
||||
stripQuoteMarker
|
||||
} from '../../lib/markdown-live-edit.js'
|
||||
import { buildCodeBlockLines, parseCodeFenceLine } from '../../lib/markdown-code-block.js'
|
||||
import { buildCodeBlockLines, buildCodeFenceOpener, parseCodeFenceLine } from '../../lib/markdown-code-block.js'
|
||||
import { buildToggleBlockLines, parseToggleOpenerLine } from '../../lib/markdown-toggle.js'
|
||||
import { CALLOUT_BACKGROUND_OPTIONS, QUOTE_BACKGROUND_OPTIONS, parseCalloutOptions } from '../../lib/markdown-callout.js'
|
||||
import {
|
||||
buildCalloutOpenerLine,
|
||||
CALLOUT_BACKGROUND_OPTIONS,
|
||||
QUOTE_BACKGROUND_OPTIONS,
|
||||
parseCalloutOptions
|
||||
} from '../../lib/markdown-callout.js'
|
||||
import { createHeadingIdFactory } from '../../lib/markdown-toc.js'
|
||||
import ContentMarkdownCodeBlockEditor from './ContentMarkdownCodeBlockEditor.vue'
|
||||
import ProseCodeBlock from './ProseCodeBlock.vue'
|
||||
@@ -631,21 +636,29 @@ const parseMarkdownBlocks = (markdown) => {
|
||||
index += 1
|
||||
}
|
||||
|
||||
blocks.push(attachSourceRange(
|
||||
createBlock('code', codeLines.join('\n'), null, `block-${blocks.length}`, {
|
||||
codeLanguage: fenceOptions.language,
|
||||
codeShowLineNumbers: fenceOptions.showLineNumbers
|
||||
}),
|
||||
startLine,
|
||||
index
|
||||
))
|
||||
index += 1
|
||||
continue
|
||||
if (index >= lines.length) {
|
||||
index = startLine
|
||||
} else {
|
||||
blocks.push(attachSourceRange(
|
||||
createBlock('code', codeLines.join('\n'), null, `block-${blocks.length}`, {
|
||||
codeLanguage: fenceOptions.language,
|
||||
codeShowLineNumbers: fenceOptions.showLineNumbers
|
||||
}),
|
||||
startLine,
|
||||
index
|
||||
))
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmedLine === '---') {
|
||||
const startLine = index
|
||||
blocks.push(attachSourceRange(createBlock('divider', '', null, `block-${blocks.length}`), startLine, startLine))
|
||||
blocks.push(attachSourceRange(
|
||||
createBlock('divider', '', null, `block-${blocks.length}`),
|
||||
startLine,
|
||||
startLine
|
||||
))
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
@@ -907,6 +920,19 @@ const focusEditableAtLine = (lineIndex, attempt = 0, cursorPosition = 'auto', ca
|
||||
}
|
||||
|
||||
if (typeof caretOffset === 'number' && caretOffset >= 0) {
|
||||
if (isRangedLine && Number.isInteger(elementSourceLine)) {
|
||||
const text = readEditableTextFromElement(/** @type {HTMLElement} */ (element))
|
||||
const textLines = text.length ? text.split('\n') : ['']
|
||||
const targetLineIndex = Math.max(0, Math.min(lineIndex - elementSourceLine, textLines.length - 1))
|
||||
const lineOffset = textLines
|
||||
.slice(0, targetLineIndex)
|
||||
.reduce((sum, textLine) => sum + textLine.length + 1, 0)
|
||||
|
||||
setEditableCaretOffset(/** @type {HTMLElement} */ (element), lineOffset + caretOffset)
|
||||
element.scrollIntoView({ block: scrollBlock, inline: 'nearest' })
|
||||
return
|
||||
}
|
||||
|
||||
setEditableCaretOffset(/** @type {HTMLElement} */ (element), caretOffset)
|
||||
element.scrollIntoView({ block: scrollBlock, inline: 'nearest' })
|
||||
return
|
||||
@@ -1415,6 +1441,49 @@ const buildParagraphSplitLines = (head, tail) => {
|
||||
return [h, t]
|
||||
}
|
||||
|
||||
/**
|
||||
* 문단 Enter 입력을 블록 단축 입력으로 변환할지 확인한다.
|
||||
* @param {Object} block - 문단 블록
|
||||
* @param {string} before - 커서 앞 텍스트
|
||||
* @param {string} after - 커서 뒤 텍스트
|
||||
* @returns {boolean} 변환 처리 여부
|
||||
*/
|
||||
const applyParagraphShortcutSplit = (block, before, after) => {
|
||||
const head = String(before ?? '').trim()
|
||||
const tail = String(after ?? '')
|
||||
|
||||
if (tail.trim()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (/^```[A-Za-z0-9_-]*$/.test(head)) {
|
||||
const opener = head === '```' ? buildCodeFenceOpener({ language: '', showLineNumbers: true }) : head
|
||||
|
||||
pendingFocusLine.value = block.meta.startLine + 1
|
||||
pendingFocusPosition.value = 'start'
|
||||
commitInlineBlockLines(block, [opener, '', '```'])
|
||||
return true
|
||||
}
|
||||
|
||||
if (head === '!!!' || head === ':::callout') {
|
||||
pendingFocusLine.value = block.meta.startLine + 1
|
||||
pendingFocusPosition.value = 'start'
|
||||
commitInlineBlockLines(block, [
|
||||
buildCalloutOpenerLine({
|
||||
calloutEmojiEnabled: false,
|
||||
calloutEmoji: '💡',
|
||||
calloutBackground: 'blue',
|
||||
title: ''
|
||||
}),
|
||||
'',
|
||||
':::'
|
||||
])
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 문단 Enter 분리 — 마크다운에 빈 줄을 넣어 다음 문단을 만든다.
|
||||
* @param {Object} block - 블록
|
||||
@@ -1430,6 +1499,10 @@ const onParagraphSplit = (block, { before, after }) => {
|
||||
|
||||
lastParagraphSplitAt = now
|
||||
|
||||
if (applyParagraphShortcutSplit(block, before, after)) {
|
||||
return
|
||||
}
|
||||
|
||||
const replacementLines = buildParagraphSplitLines(before, after)
|
||||
const focusLine = block.meta.startLine + Math.max(replacementLines.length - 1, 1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user