170 lines
4.8 KiB
JavaScript
170 lines
4.8 KiB
JavaScript
import { buildCodeFenceOpener } from '../lib/markdown-code-block.js'
|
|
import { buildCalloutOpenerLine } from '../lib/markdown-callout.js'
|
|
|
|
/**
|
|
* 관리자 라이브 Markdown 문단 편집 이벤트 상태를 만든다.
|
|
* @param {Object} options - 문단 편집 옵션
|
|
* @param {import('vue').Ref<number|null>} options.pendingFocusLine - 다음 포커스 줄
|
|
* @param {import('vue').Ref<'start'|'end'|'auto'>} options.pendingFocusPosition - 다음 포커스 커서 위치
|
|
* @param {(block: Object, replacementLines: string[]) => void} options.commitInlineBlockLines - 블록 줄 반영 콜백
|
|
* @returns {Object} 문단 이벤트 핸들러
|
|
*/
|
|
export const useContentMarkdownParagraphs = (options) => {
|
|
/** @type {number} 문단 분리 연속 호출 방지 */
|
|
let lastParagraphSplitAt = 0
|
|
|
|
/**
|
|
* 문단 줄 반영 후 포커스를 준비한다.
|
|
* @param {number} line - 줄 번호
|
|
* @returns {void}
|
|
*/
|
|
const prepareParagraphFocus = (line) => {
|
|
options.pendingFocusLine.value = line
|
|
options.pendingFocusPosition.value = 'start'
|
|
}
|
|
|
|
/**
|
|
* 문단 인라인 편집 반영
|
|
* @param {Object} block - 문단 블록
|
|
* @param {string} text - 편집된 텍스트
|
|
* @returns {void}
|
|
*/
|
|
const onParagraphInlineCommit = (block, text) => {
|
|
const value = String(text ?? '').replace(/\r/g, '')
|
|
const lines = value.split('\n').map((line) => line.trimEnd())
|
|
|
|
if (lines.length <= 1) {
|
|
options.commitInlineBlockLines(block, [lines[0] ?? ''])
|
|
return
|
|
}
|
|
|
|
options.commitInlineBlockLines(block, lines.filter((line) => line.length > 0))
|
|
}
|
|
|
|
/**
|
|
* 빈 줄(스페이서) 편집 반영
|
|
* @param {Object} block - 블록
|
|
* @param {string} text - 편집된 텍스트
|
|
* @returns {void}
|
|
*/
|
|
const onSpacerInlineCommit = (block, text) => {
|
|
if (!String(text ?? '').trim()) {
|
|
return
|
|
}
|
|
|
|
options.commitInlineBlockLines(block, [text])
|
|
}
|
|
|
|
/**
|
|
* 문단 입력 중 인용 단축 변환을 처리한다.
|
|
* @param {Object} block - 문단 블록
|
|
* @param {string} text - 입력 텍스트
|
|
* @returns {void}
|
|
*/
|
|
const onParagraphLiveInput = (block, text) => {
|
|
if (String(text ?? '').trim() !== '>') {
|
|
return
|
|
}
|
|
|
|
prepareParagraphFocus(block.meta.startLine)
|
|
options.commitInlineBlockLines(block, ['> '])
|
|
}
|
|
|
|
/**
|
|
* 문단 Enter 분리 결과 줄 배열을 만든다.
|
|
* @param {string} head - 커서 앞 텍스트
|
|
* @param {string} tail - 커서 뒤 텍스트
|
|
* @returns {string[]} 분리된 줄
|
|
*/
|
|
const buildParagraphSplitLines = (head, tail) => {
|
|
const h = String(head ?? '').replace(/\n/g, ' ')
|
|
const t = String(tail ?? '').replace(/\n/g, ' ')
|
|
|
|
if (!t.length) {
|
|
return [h, '']
|
|
}
|
|
|
|
if (!h.length) {
|
|
return ['', t]
|
|
}
|
|
|
|
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: false }) : head
|
|
|
|
prepareParagraphFocus(block.meta.startLine + 1)
|
|
options.commitInlineBlockLines(block, [opener, '', '```'])
|
|
return true
|
|
}
|
|
|
|
if (head === '!!!' || head === ':::callout') {
|
|
prepareParagraphFocus(block.meta.startLine + 1)
|
|
options.commitInlineBlockLines(block, [
|
|
buildCalloutOpenerLine({
|
|
calloutEmojiEnabled: false,
|
|
calloutEmoji: '💡',
|
|
calloutBackground: 'blue',
|
|
title: ''
|
|
}),
|
|
'',
|
|
'',
|
|
':::'
|
|
])
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* 문단 Enter 분리 — 마크다운에 빈 줄을 넣어 다음 문단을 만든다.
|
|
* @param {Object} block - 블록
|
|
* @param {{ before: string, after: string }} payload - 커서 앞·뒤 텍스트
|
|
* @returns {void}
|
|
*/
|
|
const onParagraphSplit = (block, { before, after }) => {
|
|
const now = Date.now()
|
|
|
|
if (now - lastParagraphSplitAt < 120) {
|
|
return
|
|
}
|
|
|
|
lastParagraphSplitAt = now
|
|
|
|
if (applyParagraphShortcutSplit(block, before, after)) {
|
|
return
|
|
}
|
|
|
|
const replacementLines = buildParagraphSplitLines(before, after)
|
|
const focusLine = block.meta.startLine + Math.max(replacementLines.length - 1, 1)
|
|
|
|
prepareParagraphFocus(focusLine)
|
|
options.commitInlineBlockLines(block, replacementLines)
|
|
}
|
|
|
|
return {
|
|
buildParagraphSplitLines,
|
|
onParagraphInlineCommit,
|
|
onSpacerInlineCommit,
|
|
onParagraphLiveInput,
|
|
onParagraphSplit
|
|
}
|
|
}
|