v1.2.8: 라이브 모드 인라인 편집 및 목록·인용 동작 개선
관리자 미리보기에서 문단·목록·인용을 contenteditable로 편집하고, Cmd+E 전환·사용자 지정 순서 번호·줄 삭제·화살표 줄 이동을 지원한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
130
lib/markdown-live-edit.js
Normal file
130
lib/markdown-live-edit.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 인용 접두사 제거
|
||||
* @param {string} value - 원본
|
||||
* @returns {string} 본문
|
||||
*/
|
||||
export const stripQuoteMarker = (value) => String(value ?? '').replace(/^(?:>\s*)+/, '').trim()
|
||||
|
||||
/**
|
||||
* 목록 접두사 제거
|
||||
* @param {string} value - 원본
|
||||
* @param {boolean} ordered - 순서 목록 여부
|
||||
* @returns {string} 본문
|
||||
*/
|
||||
export const stripListMarker = (value, ordered = false) => {
|
||||
const raw = String(value ?? '').trim()
|
||||
|
||||
if (ordered) {
|
||||
return raw.replace(/^\d+\.\s*/, '').trim()
|
||||
}
|
||||
|
||||
return raw.replace(/^[-*+]\s+/, '').trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 인용 마커 포함 여부
|
||||
* @param {string} value - 원본
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const hasQuoteMarker = (value) => /^\s*>/.test(String(value ?? ''))
|
||||
|
||||
/**
|
||||
* 목록 마커 포함 여부
|
||||
* @param {string} value - 원본
|
||||
* @param {boolean} [ordered=false] - 순서 목록 여부
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const hasListMarker = (value, ordered = false) => {
|
||||
const raw = String(value ?? '').trim()
|
||||
|
||||
if (ordered) {
|
||||
return /^\d+\.\s*/.test(raw)
|
||||
}
|
||||
|
||||
return /^[-*+]\s/.test(raw)
|
||||
}
|
||||
|
||||
/**
|
||||
* 순서 목록 마커를 파싱한다.
|
||||
* @param {string} value - 원본 줄
|
||||
* @returns {{ number: number, body: string }|null}
|
||||
*/
|
||||
export const parseOrderedListMarker = (value) => {
|
||||
const raw = String(value ?? '').trim()
|
||||
const match = raw.match(/^(\d+)\.\s*(.*)$/)
|
||||
|
||||
if (!match) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
number: Number(match[1]),
|
||||
body: match[2].trim()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 순서 목록 다음 줄 마커를 만든다.
|
||||
* @param {string} line - 현재 줄
|
||||
* @returns {string} 다음 줄 마커
|
||||
*/
|
||||
export const getNextOrderedListLine = (line) => {
|
||||
const parsed = parseOrderedListMarker(line)
|
||||
|
||||
if (!parsed) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `${parsed.number + 1}. `
|
||||
}
|
||||
|
||||
/**
|
||||
* 마커만 있고 본문이 없는 목록 줄인지 확인한다.
|
||||
* @param {string} line - 마크다운 줄
|
||||
* @param {boolean} ordered - 순서 목록 여부
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isEmptyListMarkerLine = (line, ordered = false) => {
|
||||
if (!hasListMarker(line, ordered)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (ordered) {
|
||||
const parsed = parseOrderedListMarker(line)
|
||||
return parsed ? !parsed.body : false
|
||||
}
|
||||
|
||||
return !stripListMarker(line, false).trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 마커만 있고 본문이 없는 인용 줄인지 확인한다.
|
||||
* @param {string} line - 마크다운 줄
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isEmptyQuoteMarkerLine = (line) => {
|
||||
if (!hasQuoteMarker(line)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !stripQuoteMarker(line).trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 제목 접두사 제거
|
||||
* @param {string} value - 원본
|
||||
* @returns {{ level: number, text: string }} 레벨·본문
|
||||
*/
|
||||
export const stripHeadingMarker = (value) => {
|
||||
const raw = String(value ?? '').trim()
|
||||
const match = raw.match(/^(#{1,6})\s+(.*)$/)
|
||||
|
||||
if (!match) {
|
||||
return { level: 0, text: raw }
|
||||
}
|
||||
|
||||
return {
|
||||
level: match[1].length,
|
||||
text: match[2].trim()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user