슬래시 명령 로직 분리
This commit is contained in:
@@ -19,8 +19,11 @@ import {
|
||||
import {
|
||||
filterSlashCommands,
|
||||
getSlashCommandFocusTarget,
|
||||
insertMarkdownLinesAtSlashTarget,
|
||||
isMediaSlashAction,
|
||||
parseSlashInput,
|
||||
resolveSlashCommand
|
||||
resolveSlashCommand,
|
||||
shouldIgnorePendingMediaSlashCommit
|
||||
} from '../../lib/markdown-slash-commands.js'
|
||||
import { buildCalloutOpenerLine, CALLOUT_BACKGROUND_OPTIONS, QUOTE_BACKGROUND_OPTIONS } from '../../lib/markdown-callout.js'
|
||||
import { buildCodeFenceOpener } from '../../lib/markdown-code-block.js'
|
||||
@@ -2340,9 +2343,11 @@ const onPreviewBlockContentChange = ({ startLine, endLine, replacementLines }) =
|
||||
return
|
||||
}
|
||||
|
||||
if (liveSlashInsertLine.value === startLine
|
||||
&& replacementLines.length === 1
|
||||
&& parseSlashInput(replacementLines[0])) {
|
||||
if (shouldIgnorePendingMediaSlashCommit({
|
||||
pendingInsertLine: liveSlashInsertLine.value,
|
||||
startLine,
|
||||
replacementLines
|
||||
})) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2794,7 +2799,7 @@ const applyLiveSlashCommand = (command) => {
|
||||
|
||||
const line = liveSlashSourceLine.value
|
||||
|
||||
if (['media-image', 'media-gallery', 'media-video', 'media-audio', 'media-file'].includes(command.action)) {
|
||||
if (isMediaSlashAction(command.action)) {
|
||||
liveSlashInsertLine.value = line
|
||||
replaceLineRange(line, line, [], false)
|
||||
onLiveSlashEnd()
|
||||
@@ -2843,14 +2848,7 @@ const onLiveSlashApply = (payload) => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const insertMarkdownAtLine = (lineIndex, insertLines) => {
|
||||
const sourceLines = (markdownValue.value ?? '').split('\n')
|
||||
const replaceCount = parseSlashInput(sourceLines[lineIndex] ?? '') ? 1 : 0
|
||||
|
||||
markdownValue.value = [
|
||||
...sourceLines.slice(0, lineIndex),
|
||||
...insertLines,
|
||||
...sourceLines.slice(lineIndex + replaceCount)
|
||||
].join('\n')
|
||||
markdownValue.value = insertMarkdownLinesAtSlashTarget(markdownValue.value, lineIndex, insertLines)
|
||||
}
|
||||
|
||||
const onPreviewMergeWithPreviousLine = ({ lineIndex, mergedLine }) => {
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
| lib/markdown-image.js | 이미지 마크다운 직렬화·파싱, 단독 이미지 URL 판별 |
|
||||
| lib/markdown-table.js | 공개 Markdown 표 행·구분선·정렬·블록 파싱 |
|
||||
| lib/markdown-toc.js | 공개 게시글 TOC용 H1~H3 제목 추출과 앵커 ID 생성 |
|
||||
| lib/markdown-slash-commands.js | 관리자 Markdown-first 에디터 슬래시 명령 목록과 삽입 줄 정의 |
|
||||
| lib/markdown-slash-commands.js | 관리자 Markdown-first 에디터 슬래시 명령 목록과 삽입 줄 정의, 미디어 명령 action 판별, 미디어 명령 지연 commit 무시·슬래시 원문 줄 교체 삽입 helper |
|
||||
| lib/markdown-inline.js | 공개·관리자 공통 인라인 마크다운 파서, 링크·굵게·기울임·인라인 코드·첨자·인라인 색상 토큰(`::color[텍스트]`) 변환, 링크·굵게·기울임·색상 내부 중첩 인라인 문법과 `<br>` 줄바꿈 처리, 라이브 멀티라인 본문의 기본 불릿/번호 목록 HTML 변환과 contenteditable 목록 마커 기준 커서 offset 계산·직렬화 |
|
||||
| lib/post-tag-limit.js | 게시물별 태그 최대 개수 기본값·허용 범위·정규화 |
|
||||
| lib/analytics-shared.js | 통계 추적 경로 필터·체류/스크롤 상수(클라이언트·서버 공용) |
|
||||
@@ -117,6 +117,7 @@
|
||||
| tests/content-gallery-images.test.js | 공개 본문 라이트박스 이미지 목록과 갤러리 행 분할 단위 회귀 테스트 |
|
||||
| tests/content-image-urls.test.js | 공개 본문 이미지 파생 URL·srcset·갤러리 sizes 단위 회귀 테스트 |
|
||||
| tests/markdown-block-context.test.js | 관리자 블록 설정 패널의 활성 블록 컨텍스트 단위 회귀 테스트 |
|
||||
| tests/markdown-slash-commands.test.js | 관리자 슬래시 명령 파싱·미디어 action·포커스 대상·원문 줄 교체 삽입 단위 회귀 테스트 |
|
||||
| tests/markdown-block-parser.test.js | 공개 Markdown 본문 블록 파싱 단위 회귀 테스트 |
|
||||
| tests/markdown-fenced-meta.test.js | Markdown fenced 블록 시작·종료 줄 탐색, 본문 수집, 메타 파싱 단위 회귀 테스트 |
|
||||
| tests/markdown-quote.test.js | Markdown 인용 옵션 파싱과 편집 줄 변환 단위 회귀 테스트 |
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 업데이트 이력
|
||||
|
||||
## v1.11.67
|
||||
|
||||
- 관리자 Markdown 에디터의 슬래시 명령 미디어 액션 판별·지연 commit 무시·명령 줄 교체 삽입 로직을 공통 helper로 분리.
|
||||
- 슬래시 명령 파싱·미디어 액션·포커스 대상·미디어 삽입 줄 교체 단위 테스트 추가.
|
||||
|
||||
## v1.11.66
|
||||
|
||||
- 라이브 작성 모드에서 오른쪽 코드 블록 설정의 줄번호 토글을 누르면 패널이 닫히는 문제 수정.
|
||||
|
||||
@@ -219,6 +219,47 @@ export const resolveSlashCommand = (query) => {
|
||||
return exact ?? matches[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* 미디어 선택 모달을 여는 슬래시 명령인지 확인한다.
|
||||
* @param {string} action - 명령 action
|
||||
* @returns {boolean} 미디어 명령 여부
|
||||
*/
|
||||
export const isMediaSlashAction = (action) => ['media-image', 'media-gallery', 'media-video', 'media-audio', 'media-file']
|
||||
.includes(String(action || ''))
|
||||
|
||||
/**
|
||||
* 라이브 모드 미디어 슬래시 명령 처리 중 지연 commit을 무시할지 확인한다.
|
||||
* @param {Object} options - 판별 옵션
|
||||
* @param {number|null} options.pendingInsertLine - 미디어 삽입 대기 줄
|
||||
* @param {number} options.startLine - commit 시작 줄
|
||||
* @param {string[]} options.replacementLines - commit 대체 줄
|
||||
* @returns {boolean} 무시 여부
|
||||
*/
|
||||
export const shouldIgnorePendingMediaSlashCommit = ({ pendingInsertLine, startLine, replacementLines }) => pendingInsertLine === startLine
|
||||
&& Array.isArray(replacementLines)
|
||||
&& replacementLines.length === 1
|
||||
&& Boolean(parseSlashInput(replacementLines[0]))
|
||||
|
||||
/**
|
||||
* 지정 줄에 마크다운 줄을 삽입하되, 해당 줄이 슬래시 명령이면 교체한다.
|
||||
* @param {string} markdown - 원본 마크다운
|
||||
* @param {number} lineIndex - 삽입 위치
|
||||
* @param {string[]} insertLines - 삽입 줄
|
||||
* @returns {string} 갱신된 마크다운
|
||||
*/
|
||||
export const insertMarkdownLinesAtSlashTarget = (markdown, lineIndex, insertLines) => {
|
||||
const sourceLines = String(markdown ?? '').split('\n')
|
||||
const safeLine = Math.max(0, Math.min(Number.isFinite(lineIndex) ? lineIndex : 0, sourceLines.length))
|
||||
const lines = Array.isArray(insertLines) ? insertLines : []
|
||||
const replaceCount = parseSlashInput(sourceLines[safeLine] ?? '') ? 1 : 0
|
||||
|
||||
return [
|
||||
...sourceLines.slice(0, safeLine),
|
||||
...lines,
|
||||
...sourceLines.slice(safeLine + replaceCount)
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* 슬래시 명령 적용 후 포커스할 줄·커서 오프셋을 계산한다.
|
||||
* @param {number} startLine - 교체 시작 줄(0-based)
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.66",
|
||||
"version": "1.11.67",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.66",
|
||||
"version": "1.11.67",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.66",
|
||||
"version": "1.11.67",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"imports": {
|
||||
|
||||
73
tests/markdown-slash-commands.test.js
Normal file
73
tests/markdown-slash-commands.test.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import {
|
||||
getSlashCommandFocusTarget,
|
||||
insertMarkdownLinesAtSlashTarget,
|
||||
isMediaSlashAction,
|
||||
parseSlashInput,
|
||||
resolveSlashCommand,
|
||||
shouldIgnorePendingMediaSlashCommit
|
||||
} from '../lib/markdown-slash-commands.js'
|
||||
|
||||
test('슬래시 입력은 단일 줄 명령만 파싱한다', () => {
|
||||
assert.deepEqual(parseSlashInput('/image'), {
|
||||
query: 'image',
|
||||
raw: '/image'
|
||||
})
|
||||
assert.equal(parseSlashInput(' /image'), null)
|
||||
assert.equal(parseSlashInput('/image\n다음 줄'), null)
|
||||
})
|
||||
|
||||
test('정확한 슬래시 명령과 미디어 액션을 판별한다', () => {
|
||||
assert.equal(resolveSlashCommand('image')?.action, 'media-image')
|
||||
assert.equal(resolveSlashCommand('img')?.id, 'image')
|
||||
assert.equal(isMediaSlashAction('media-image'), true)
|
||||
assert.equal(isMediaSlashAction('lines'), false)
|
||||
})
|
||||
|
||||
test('미디어 슬래시 명령 지연 commit은 같은 줄 slash 원문일 때만 무시한다', () => {
|
||||
assert.equal(shouldIgnorePendingMediaSlashCommit({
|
||||
pendingInsertLine: 3,
|
||||
startLine: 3,
|
||||
replacementLines: ['/image']
|
||||
}), true)
|
||||
assert.equal(shouldIgnorePendingMediaSlashCommit({
|
||||
pendingInsertLine: 3,
|
||||
startLine: 4,
|
||||
replacementLines: ['/image']
|
||||
}), false)
|
||||
assert.equal(shouldIgnorePendingMediaSlashCommit({
|
||||
pendingInsertLine: 3,
|
||||
startLine: 3,
|
||||
replacementLines: ['본문']
|
||||
}), false)
|
||||
})
|
||||
|
||||
test('슬래시 명령 줄에 미디어를 삽입할 때 원문 줄을 교체한다', () => {
|
||||
const markdown = [
|
||||
'첫 줄',
|
||||
'/image',
|
||||
'마지막 줄'
|
||||
].join('\n')
|
||||
|
||||
assert.equal(insertMarkdownLinesAtSlashTarget(markdown, 1, ['']), [
|
||||
'첫 줄',
|
||||
'',
|
||||
'마지막 줄'
|
||||
].join('\n'))
|
||||
})
|
||||
|
||||
test('일반 줄에는 지정 위치 앞에 마크다운 줄을 삽입한다', () => {
|
||||
assert.equal(insertMarkdownLinesAtSlashTarget('첫 줄\n마지막 줄', 1, ['중간 줄']), '첫 줄\n중간 줄\n마지막 줄')
|
||||
})
|
||||
|
||||
test('슬래시 명령 포커스 대상은 단일 줄과 블록 삽입을 구분한다', () => {
|
||||
assert.deepEqual(getSlashCommandFocusTarget(2, ['## ']), {
|
||||
line: 2,
|
||||
offset: 3
|
||||
})
|
||||
assert.deepEqual(getSlashCommandFocusTarget(2, ['```', '', '```']), {
|
||||
line: 3,
|
||||
offset: 0
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user