블록 설정 패널 확장 v1.5.37
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { isImageUrl, parseImageMarkdownLine } from './markdown-image.js'
|
||||
import { CALLOUT_BACKGROUND_OPTIONS } from './markdown-callout.js'
|
||||
import { CALLOUT_BACKGROUND_OPTIONS, parseCalloutOptions } from './markdown-callout.js'
|
||||
import { parseCodeFenceLine } from './markdown-code-block.js'
|
||||
import { parseToggleOpenerLine } from './markdown-toggle.js'
|
||||
|
||||
/**
|
||||
* fenced 블록 시작 줄 인덱스를 찾는다.
|
||||
@@ -22,6 +24,27 @@ const findFencedBlockStart = (lines, currentLine, opener) => {
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* 조건에 맞는 fenced 블록 시작 줄 인덱스를 찾는다.
|
||||
* @param {string[]} lines - 본문 줄 목록
|
||||
* @param {number} currentLine - 현재 줄
|
||||
* @param {(line: string) => boolean} predicate - 시작 줄 판별 함수
|
||||
* @returns {number} 시작 줄 또는 -1
|
||||
*/
|
||||
const findFencedBlockStartBy = (lines, currentLine, predicate) => {
|
||||
for (let index = currentLine; index >= 0; index -= 1) {
|
||||
if (predicate((lines[index] || '').trim())) {
|
||||
return index
|
||||
}
|
||||
|
||||
if ((lines[index] || '').trim() === ':::') {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* fenced 블록 종료 줄 인덱스를 찾는다.
|
||||
* @param {string[]} lines - 본문 줄 목록
|
||||
@@ -204,6 +227,113 @@ const resolveEmbedBlock = (lines, currentLine) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 콜아웃 fenced 블록을 파싱한다.
|
||||
* @param {string[]} lines - 본문 줄 목록
|
||||
* @param {number} currentLine - 현재 줄
|
||||
* @returns {{ kind: 'callout', startLine: number, endLine: number, calloutEmojiEnabled: boolean, calloutEmoji: string, calloutBackground: string }|null}
|
||||
*/
|
||||
const resolveCalloutBlock = (lines, currentLine) => {
|
||||
const calloutStart = findFencedBlockStartBy(lines, currentLine, (line) => line.startsWith(':::callout'))
|
||||
|
||||
if (calloutStart === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const calloutEnd = findFencedBlockEnd(lines, calloutStart)
|
||||
|
||||
if (calloutEnd === -1 || currentLine > calloutEnd) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'callout',
|
||||
startLine: calloutStart,
|
||||
endLine: calloutEnd,
|
||||
...parseCalloutOptions(lines[calloutStart])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 토글 fenced 블록을 파싱한다.
|
||||
* @param {string[]} lines - 본문 줄 목록
|
||||
* @param {number} currentLine - 현재 줄
|
||||
* @returns {{ kind: 'toggle', startLine: number, endLine: number, title: string, defaultOpen: boolean }|null}
|
||||
*/
|
||||
const resolveToggleBlock = (lines, currentLine) => {
|
||||
const toggleStart = findFencedBlockStartBy(lines, currentLine, (line) => line.startsWith(':::toggle'))
|
||||
|
||||
if (toggleStart === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const toggleEnd = findFencedBlockEnd(lines, toggleStart)
|
||||
|
||||
if (toggleEnd === -1 || currentLine > toggleEnd) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'toggle',
|
||||
startLine: toggleStart,
|
||||
endLine: toggleEnd,
|
||||
...parseToggleOpenerLine(lines[toggleStart])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 코드 fenced 블록 종료 줄 인덱스를 찾는다.
|
||||
* @param {string[]} lines - 본문 줄 목록
|
||||
* @param {number} startLine - 시작 줄
|
||||
* @returns {number} 종료 줄 또는 -1
|
||||
*/
|
||||
const findCodeFenceEnd = (lines, startLine) => {
|
||||
for (let index = startLine + 1; index < lines.length; index += 1) {
|
||||
if ((lines[index] || '').trim().startsWith('```')) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* 코드 fenced 블록을 파싱한다.
|
||||
* @param {string[]} lines - 본문 줄 목록
|
||||
* @param {number} currentLine - 현재 줄
|
||||
* @returns {{ kind: 'code', startLine: number, endLine: number, language: string, showLineNumbers: boolean }|null}
|
||||
*/
|
||||
const resolveCodeBlock = (lines, currentLine) => {
|
||||
let codeStart = -1
|
||||
|
||||
for (let index = currentLine; index >= 0; index -= 1) {
|
||||
if ((lines[index] || '').trim().startsWith('```')) {
|
||||
codeStart = index
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (codeStart === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const codeEnd = findCodeFenceEnd(lines, codeStart)
|
||||
|
||||
if (codeEnd === -1 || currentLine > codeEnd) {
|
||||
return null
|
||||
}
|
||||
|
||||
const options = parseCodeFenceLine(lines[codeStart]) || { language: '', showLineNumbers: true }
|
||||
|
||||
return {
|
||||
kind: 'code',
|
||||
startLine: codeStart,
|
||||
endLine: codeEnd,
|
||||
language: options.language,
|
||||
showLineNumbers: options.showLineNumbers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 커서 줄 기준 활성 블록 컨텍스트를 반환한다.
|
||||
* @param {string} markdown - 본문 마크다운
|
||||
@@ -213,6 +343,12 @@ const resolveEmbedBlock = (lines, currentLine) => {
|
||||
export const resolveActiveBlockContext = (markdown, lineIndex) => {
|
||||
const lines = String(markdown || '').split('\n')
|
||||
const currentLine = Math.min(Math.max(0, lineIndex), Math.max(0, lines.length - 1))
|
||||
const code = resolveCodeBlock(lines, currentLine)
|
||||
|
||||
if (code) {
|
||||
return code
|
||||
}
|
||||
|
||||
const activeImage = parseImageMarkdownLine(lines[currentLine] || '')
|
||||
const activeImageUrl = String(lines[currentLine] || '').trim()
|
||||
|
||||
@@ -246,5 +382,17 @@ export const resolveActiveBlockContext = (markdown, lineIndex) => {
|
||||
return quote
|
||||
}
|
||||
|
||||
const callout = resolveCalloutBlock(lines, currentLine)
|
||||
|
||||
if (callout) {
|
||||
return callout
|
||||
}
|
||||
|
||||
const toggle = resolveToggleBlock(lines, currentLine)
|
||||
|
||||
if (toggle) {
|
||||
return toggle
|
||||
}
|
||||
|
||||
return resolveEmbedBlock(lines, currentLine)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
/**
|
||||
* 토글 선언 줄을 파싱한다.
|
||||
* @param {string} line - 토글 선언 줄
|
||||
* @returns {{ title: string, defaultOpen: boolean }} 토글 옵션
|
||||
*/
|
||||
export const parseToggleOpenerLine = (line) => {
|
||||
const raw = String(line ?? '').trim().replace(/^:::toggle\s*/, '').trim()
|
||||
const tokens = raw.split(/\s+/).filter(Boolean)
|
||||
const state = tokens[0]?.toLowerCase()
|
||||
const hasStateToken = ['open', 'opened', 'default-open', 'closed', 'close', 'default-closed'].includes(state)
|
||||
const defaultOpen = ['open', 'opened', 'default-open'].includes(state)
|
||||
const title = (hasStateToken ? tokens.slice(1).join(' ') : raw).trim()
|
||||
|
||||
return {
|
||||
title,
|
||||
defaultOpen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 토글 선언 줄을 만든다.
|
||||
* @param {{ title?: string, defaultOpen?: boolean }} options - 옵션
|
||||
* @returns {string} 토글 선언 줄
|
||||
*/
|
||||
export const buildToggleOpenerLine = (options = {}) => {
|
||||
const title = String(options.title ?? '').trim() || '더 보기'
|
||||
const state = options.defaultOpen ? 'open' : 'closed'
|
||||
|
||||
return `:::toggle ${state} ${title}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 토글 블록 마크다운 줄 배열을 만든다.
|
||||
* @param {{ title?: string, body?: string }} options - 옵션
|
||||
* @param {{ title?: string, body?: string, defaultOpen?: boolean }} options - 옵션
|
||||
* @returns {string[]} 마크다운 줄
|
||||
*/
|
||||
export const buildToggleBlockLines = (options = {}) => {
|
||||
const title = String(options.title ?? '').trim() || '더 보기'
|
||||
const body = String(options.body ?? '').replace(/\r/g, '')
|
||||
const bodyLines = body.length ? body.split('\n') : ['']
|
||||
|
||||
return [
|
||||
`:::toggle ${title}`,
|
||||
buildToggleOpenerLine(options),
|
||||
...bodyLines,
|
||||
':::'
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user