라이브 에디터 콜아웃 줄 삭제 수정

This commit is contained in:
2026-06-04 11:09:40 +09:00
parent 83f66a4b93
commit 675e6bca78
12 changed files with 111 additions and 11 deletions

View File

@@ -33,7 +33,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['commit']) const emit = defineEmits(['commit', 'delete-line', 'insert-below', 'merge-with-previous', 'leave-block'])
/** /**
* 콜아웃 마크다운 줄을 반영한다. * 콜아웃 마크다운 줄을 반영한다.
@@ -62,6 +62,15 @@ const commitCallout = (body) => {
const onBodyCommit = (body) => { const onBodyCommit = (body) => {
commitCallout(body) commitCallout(body)
} }
/**
* 콜아웃 아래로 이탈한다.
* @param {Object} payload - 이탈 페이로드
* @returns {void}
*/
const onExitBelow = (payload) => {
emit('insert-below', payload)
}
</script> </script>
<template> <template>
@@ -87,6 +96,10 @@ const onBodyCommit = (body) => {
:source-line="bodySourceLine" :source-line="bodySourceLine"
:model-value="modelValue" :model-value="modelValue"
@commit="onBodyCommit" @commit="onBodyCommit"
@delete-line="emit('delete-line', $event)"
@insert-below="onExitBelow"
@merge-with-previous="emit('merge-with-previous', $event)"
@leave-block="emit('leave-block', $event)"
/> />
</div> </div>
</ProseCallout> </ProseCallout>

View File

@@ -26,7 +26,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['commit', 'insert-below']) const emit = defineEmits(['commit', 'insert-below', 'delete-line'])
const languageDraft = ref(props.language) const languageDraft = ref(props.language)
const lineNumbersEnabled = ref(props.showLineNumbers) const lineNumbersEnabled = ref(props.showLineNumbers)
@@ -161,6 +161,7 @@ const toggleLineNumbers = () => {
@input="onBodyInput" @input="onBodyInput"
@commit="onBodyCommit" @commit="onBodyCommit"
@insert-below="onExitBelow" @insert-below="onExitBelow"
@delete-line="emit('delete-line', $event)"
/> />
</ProseCodeBlock> </ProseCodeBlock>
</template> </template>

View File

@@ -736,9 +736,14 @@ const onKeydown = (event) => {
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === 'k') { if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === 'k') {
if (props.sourceLine !== null) { if (props.sourceLine !== null) {
const lineContext = getCaretLineContext()
const sourceLine = resolvedEnterMode.value === 'multiline'
? props.sourceLine + lineContext.lineIndex
: props.sourceLine
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
emit('delete-line', props.sourceLine) emit('delete-line', sourceLine)
} }
return return
@@ -861,6 +866,7 @@ const onKeydown = (event) => {
if (event.key === 'Enter' && !event.shiftKey && parseSlashInput(readEditorValue())) { if (event.key === 'Enter' && !event.shiftKey && parseSlashInput(readEditorValue())) {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
event.stopImmediatePropagation?.()
emit('slash-apply', { emit('slash-apply', {
sourceLine: props.sourceLine, sourceLine: props.sourceLine,
value: readEditorValue() value: readEditorValue()

View File

@@ -1271,6 +1271,26 @@ const onCalloutBlockCommit = (block, replacementLines) => {
commitInlineBlockLines(block, replacementLines) commitInlineBlockLines(block, replacementLines)
} }
/**
* 콜아웃 마지막 줄에서 아래로 이탈한다.
* @param {Object} block - 콜아웃 블록
* @param {string|Object} payload - insert-below 페이로드
* @returns {void}
*/
const onCalloutBlockInsertBelow = (block, payload) => {
const { value } = normalizeInsertBelowPayload(payload)
const openingLine = getMarkdownLine(block.meta.startLine)
onCalloutBlockCommit(block, [
openingLine,
...String(value ?? '').replace(/\r/g, '').split('\n'),
':::'
])
pendingFocusPosition.value = 'start'
pendingFocusOffset.value = 0
onInsertBelowBlock(block, { lines: [''] })
}
/** /**
* 토글 편집 반영 * 토글 편집 반영
* @param {Object} block - 토글 블록 * @param {Object} block - 토글 블록
@@ -1782,6 +1802,39 @@ const onDeleteLine = (lineIndex) => {
return return
} }
const fencedBlock = blocks.value.find((block) => {
const startLine = block.meta?.startLine
const endLine = block.meta?.endLine
return ['callout', 'code', 'toggle'].includes(block.type)
&& typeof startLine === 'number'
&& typeof endLine === 'number'
&& lineIndex > startLine
&& lineIndex < endLine
})
if (fencedBlock) {
const startLine = fencedBlock.meta.startLine
const endLine = fencedBlock.meta.endLine
const bodyLineCount = Math.max(0, endLine - startLine - 1)
if (bodyLineCount <= 1) {
pendingFocusLine.value = startLine > 0 ? startLine - 1 : 0
pendingFocusPosition.value = startLine > 0 ? 'end' : 'start'
emit('block-content-change', {
startLine,
endLine,
replacementLines: []
})
return
}
pendingFocusLine.value = lineIndex > startLine + 1 ? lineIndex - 1 : lineIndex
pendingFocusPosition.value = lineIndex > startLine + 1 ? 'end' : 'start'
emit('delete-line', lineIndex)
return
}
const focusLine = lineIndex > 0 ? lineIndex - 1 : 0 const focusLine = lineIndex > 0 ? lineIndex - 1 : 0
pendingFocusLine.value = focusLine pendingFocusLine.value = focusLine
@@ -2555,6 +2608,9 @@ onBeforeUnmount(() => {
:body-source-line="block.meta.startLine + 1" :body-source-line="block.meta.startLine + 1"
:model-value="block.text" :model-value="block.text"
@commit="onCalloutBlockCommit(block, $event)" @commit="onCalloutBlockCommit(block, $event)"
@delete-line="onDeleteLine"
@insert-below="onCalloutBlockInsertBelow(block, $event)"
@merge-with-previous="onMergeWithPreviousLine(block.meta.startLine + 1, $event)"
/> />
<ProseCallout <ProseCallout
v-else-if="block.type === 'callout'" v-else-if="block.type === 'callout'"
@@ -2579,6 +2635,7 @@ onBeforeUnmount(() => {
:model-value="block.text" :model-value="block.text"
@commit="onToggleBlockCommit(block, $event)" @commit="onToggleBlockCommit(block, $event)"
@insert-below="onToggleBlockInsertBelow(block, $event)" @insert-below="onToggleBlockInsertBelow(block, $event)"
@delete-line="onDeleteLine"
/> />
<ProseToggle v-else-if="block.type === 'toggle'" :title="block.title || '더 보기'" :default-open="block.defaultOpen" animated> <ProseToggle v-else-if="block.type === 'toggle'" :title="block.title || '더 보기'" :default-open="block.defaultOpen" animated>
<template v-for="(segment, segmentIndex) in parseInlineSegments(block.text)" :key="`${block.id}-toggle-${segmentIndex}`"> <template v-for="(segment, segmentIndex) in parseInlineSegments(block.text)" :key="`${block.id}-toggle-${segmentIndex}`">
@@ -2771,6 +2828,7 @@ onBeforeUnmount(() => {
:model-value="block.text" :model-value="block.text"
@commit="onCodeBlockCommit(block, $event)" @commit="onCodeBlockCommit(block, $event)"
@insert-below="onCodeBlockInsertBelow(block, $event)" @insert-below="onCodeBlockInsertBelow(block, $event)"
@delete-line="onDeleteLine"
/> />
<ProseCodeBlock <ProseCodeBlock
v-else-if="block.type === 'code'" v-else-if="block.type === 'code'"

View File

@@ -31,7 +31,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['commit', 'insert-below']) const emit = defineEmits(['commit', 'insert-below', 'delete-line'])
const titleEditorRef = ref(null) const titleEditorRef = ref(null)
const bodyEditorRef = ref(null) const bodyEditorRef = ref(null)
@@ -141,6 +141,7 @@ const onExitBelow = (payload) => {
:model-value="modelValue" :model-value="modelValue"
@commit="onBodyCommit" @commit="onBodyCommit"
@insert-below="onExitBelow" @insert-below="onExitBelow"
@delete-line="emit('delete-line', $event)"
/> />
</ProseToggle> </ProseToggle>
</template> </template>

View File

@@ -1,5 +1,11 @@
# 업데이트 요약 # 업데이트 요약
## v1.5.49
- 라이브 작성 모드에서 코드·콜아웃·토글 내부 `Cmd+Shift+K` 줄 삭제가 동작하도록 수정했다.
- 콜아웃 내부 줄 삭제와 아래 방향키 이탈 동작을 보강했다.
- `/콜아웃` Enter 생성 시 한글 조합 잔여 문자가 남을 가능성을 줄였다.
## v1.5.48 ## v1.5.48
- 게시물 작성 화면 상단의 상태 표시는 텍스트만 남기고, 게시물 보기 링크는 오른쪽 `View Post` 기능으로 통일했다. - 게시물 작성 화면 상단의 상태 표시는 텍스트만 남기고, 게시물 보기 링크는 오른쪽 `View Post` 기능으로 통일했다.

View File

@@ -1,6 +1,6 @@
# 배포 가이드 # 배포 가이드
> 로컬 기준 v1.5.48에서 `npm run lint`, `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다. > 로컬 기준 v1.5.49에서 `npm run lint`, `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다.
## 빌드 유형 ## 빌드 유형
@@ -68,6 +68,14 @@ docker exec sori-studio-db pg_isready -U sori_studio -d sori_studio
docker exec sori-studio-db psql -U sori_studio -d sori_studio -c 'SELECT count(*) AS posts_count FROM posts;' docker exec sori-studio-db psql -U sori_studio -d sori_studio -c 'SELECT count(*) AS posts_count FROM posts;'
``` ```
### v1.5.49 참고
- 추가 DB 마이그레이션은 없다.
- 관리자 글쓰기 라이브 모드에서 코드·콜아웃·토글 내부 커서 위치별 `Cmd+Shift+K` 줄 삭제를 확인한다.
- 코드·콜아웃·토글 본문 마지막 1줄에서 `Cmd+Shift+K` 입력 시 블록 전체가 삭제되는지 확인한다.
- 라이브 콜아웃 마지막 줄에서 아래 방향키로 다음 블록으로 빠져나가는지 확인한다.
- `/콜아웃` Enter 생성 후 콜아웃 본문에 마지막 조합 글자가 남지 않는지 확인한다.
### v1.5.48 참고 ### v1.5.48 참고
- 추가 DB 마이그레이션은 없다. - 추가 DB 마이그레이션은 없다.

View File

@@ -93,7 +93,7 @@
| components/admin/AdminMediaVideoThumbnail.vue | 관리자 미디어 목록 비디오 항목의 초반 프레임 캔버스 썸네일 | | components/admin/AdminMediaVideoThumbnail.vue | 관리자 미디어 목록 비디오 항목의 초반 프레임 캔버스 썸네일 |
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost형 툴바(왼쪽 상태 텍스트·Publish/Update/Unpublish/Unschedule, 서버 반영 상태 기준 분기), 초안만 서버 디바운스 자동 저장·신규 임시 슬러그·발행·예약·멤버십·비공개 상태 저장, 발행 모달(중앙 배치), 좌우 설정 패널(작은 화면은 오른쪽 고정 오버레이), 오른쪽 `View Post` 링크, 미리보기 emit·미저장 이탈 가드, 추천 글 토글, 태그 색상 배지 다중 입력·메인 태그 드롭다운·부분 검색 추천 | | components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost형 툴바(왼쪽 상태 텍스트·Publish/Update/Unpublish/Unschedule, 서버 반영 상태 기준 분기), 초안만 서버 디바운스 자동 저장·신규 임시 슬러그·발행·예약·멤버십·비공개 상태 저장, 발행 모달(중앙 배치), 좌우 설정 패널(작은 화면은 오른쪽 고정 오버레이), 오른쪽 `View Post` 링크, 미리보기 emit·미저장 이탈 가드, 추천 글 토글, 태그 색상 배지 다중 입력·메인 태그 드롭다운·부분 검색 추천 |
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 게시글 작성과 같은 전체 화면 에디터·상단 저장 툴바·접이식 오른쪽 설정 패널, 페이지 공개 상태 선택, HTML 문서 기본 모드, 빈 본문/`!`+Tab HTML 골격 자동 완성, 항상 보이는 일반 텍스트/HTML 모드 선택, 한글 제목 영문 슬러그 자동 변환, HTML textarea 커서 위치 파일 URL 삽입 | | components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 게시글 작성과 같은 전체 화면 에디터·상단 저장 툴바·접이식 오른쪽 설정 패널, 페이지 공개 상태 선택, HTML 문서 기본 모드, 빈 본문/`!`+Tab HTML 골격 자동 완성, 항상 보이는 일반 텍스트/HTML 모드 선택, 한글 제목 영문 슬러그 자동 변환, HTML textarea 커서 위치 파일 URL 삽입 |
| components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, 라이브·소스 모드 `/` 슬래시 명령·미디어 모달(이미지·갤러리·비디오·오디오·파일), 커서 블록 컨텍스트·`block-panel` emit, 라이브 이미지 설정 패널·이미지↔갤러리 드래그 변환(`merge-images-to-gallery`·`insert-image-to-gallery`·`extract-gallery-image`), 블록 패널 바깥 클릭 닫기·미디어 모달 중 유지, 인용·콜아웃·코드·토글 선언 줄 옵션 수정, IME 조합 중 블록 패널 유지, 소스 모드 wrap 라인 번호 보정·라이브↔소스 위치 복원 | | components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, 라이브·소스 모드 `/` 슬래시 명령·미디어 모달(이미지·갤러리·비디오·오디오·파일), 커서 블록 컨텍스트·`block-panel` emit, 라이브 이미지 설정 패널·이미지↔갤러리 드래그 변환(`merge-images-to-gallery`·`insert-image-to-gallery`·`extract-gallery-image`), 코드·콜아웃·토글 내부 줄 삭제, 블록 패널 바깥 클릭 닫기·미디어 모달 중 유지, 인용·콜아웃·코드·토글 선언 줄 옵션 수정, IME 조합 중 블록 패널 유지, 소스 모드 wrap 라인 번호 보정·라이브↔소스 위치 복원 |
| components/admin/AdminEditorBlockPanel.vue | 게시물 설정 사이드바 오버레이 블록 설정(이미지·갤러리·임베드·인용 배경색·콜아웃·코드·토글), 갤러리 선택 이미지 강조 | | components/admin/AdminEditorBlockPanel.vue | 게시물 설정 사이드바 오버레이 블록 설정(이미지·갤러리·임베드·인용 배경색·콜아웃·코드·토글), 갤러리 선택 이미지 강조 |
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 콜아웃 Emoji on/off·이모지 프리셋·인용과 같은 배경 프리셋 선택(우측 고정 설정 패널), 갤러리 복수 미디어 선택·이미지 수별 열 배치·삽입 위치 표시 드래그 순서 변경, 한글 조합 입력 처리, Shift+Enter 줄바꿈, 코드 블록 단축 변환, AFFiNE 참고 세로 막대형 블록 핸들 선택/삭제/드래그 이동과 삽입선 표시, 하단 빈 입력 블록 유지, 본문 placeholder 표시 | | components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 콜아웃 Emoji on/off·이모지 프리셋·인용과 같은 배경 프리셋 선택(우측 고정 설정 패널), 갤러리 복수 미디어 선택·이미지 수별 열 배치·삽입 위치 표시 드래그 순서 변경, 한글 조합 입력 처리, Shift+Enter 줄바꿈, 코드 블록 단축 변환, AFFiNE 참고 세로 막대형 블록 핸들 선택/삭제/드래그 이동과 삽입선 표시, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼(이름/슬러그/설명/색상만 편집) | | components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼(이름/슬러그/설명/색상만 편집) |
@@ -111,7 +111,7 @@
| 파일 | 화면 위치 | | 파일 | 화면 위치 |
|------|-----------| |------|-----------|
| components/content/ContentRenderer.vue | 게시물/페이지 본문 | | components/content/ContentRenderer.vue | 게시물/페이지 본문 |
| components/content/ContentMarkdownRenderer.vue | 마크다운 문자열 기반 본문 렌더링, 문단 text-base(16px), 빈 줄 spacer 보존·hard break `<br>` 처리, 확장 블록 파싱, 인용 배경 옵션(`> [!bg=...]`), 라이브 콜아웃·인용 포커스 기반 오른쪽 설정 패널 연결, 라이브 방향키 이동 시 편집 가능한 줄·카드형 블록 탐색, 라이브 이미지·갤러리 드래그 병합·추가·분리 UI, 갤러리 비율 기반 행 레이아웃, 라이브 갤러리 개별 이미지 편집·삭제, 리스트 마커 파란 계열 통일 | | components/content/ContentMarkdownRenderer.vue | 마크다운 문자열 기반 본문 렌더링, 문단 text-base(16px), 빈 줄 spacer 보존·hard break `<br>` 처리, 확장 블록 파싱, 인용 배경 옵션(`> [!bg=...]`), 라이브 콜아웃·인용 포커스 기반 오른쪽 설정 패널 연결, 라이브 방향키 이동 시 편집 가능한 줄·카드형 블록 탐색, 라이브 코드·콜아웃·토글 내부 줄 삭제와 마지막 줄 블록 삭제, 라이브 이미지·갤러리 드래그 병합·추가·분리 UI, 갤러리 비율 기반 행 레이아웃, 라이브 갤러리 개별 이미지 편집·삭제, 리스트 마커 파란 계열 통일 |
| components/content/ProseHeading.vue | h1~h6 제목, 기본 mt-12 제거 | | components/content/ProseHeading.vue | h1~h6 제목, 기본 mt-12 제거 |
| components/content/ProseImage.vue | 본문 내 이미지, 로드 실패·빈 URL placeholder | | components/content/ProseImage.vue | 본문 내 이미지, 로드 실패·빈 URL placeholder |
| components/content/ProseList.vue | 목록 | | components/content/ProseList.vue | 목록 |

View File

@@ -627,7 +627,7 @@ components/content/
- 라이브 모드 갤러리 이미지를 블록 사이 얇은 삽입선(또는 문서 맨 아래 삽입선)에 드롭하면 해당 위치에 단일 이미지 마크다운 줄을 삽입하고 갤러리에서 제거한다(`extract-gallery-image`). 갤러리에 이미지가 1장만 남으면 갤러리 블록을 단일 이미지 줄로 바꾸고, 0장이면 갤러리 블록을 제거한다. - 라이브 모드 갤러리 이미지를 블록 사이 얇은 삽입선(또는 문서 맨 아래 삽입선)에 드롭하면 해당 위치에 단일 이미지 마크다운 줄을 삽입하고 갤러리에서 제거한다(`extract-gallery-image`). 갤러리에 이미지가 1장만 남으면 갤러리 블록을 단일 이미지 줄로 바꾸고, 0장이면 갤러리 블록을 제거한다.
- `ProseImage`는 URL이 비어 있거나 로드에 실패해도 최소 높이 placeholder와 「이미지를 불러올 수 없음」 안내를 표시해 라이브 모드에서 블록 선택·편집이 가능하다. - `ProseImage`는 URL이 비어 있거나 로드에 실패해도 최소 높이 placeholder와 「이미지를 불러올 수 없음」 안내를 표시해 라이브 모드에서 블록 선택·편집이 가능하다.
- 인용(`>`) 블록은 첫 인용 줄에 `> [!bg=yellow]` 또는 `> {bg=yellow}` 옵션 줄을 두면 해당 줄은 숨기고 블록 배경을 바꾼다. 지원 배경 프리셋은 콜아웃과 같은 `gray`, `blue`, `green`, `yellow`, `red`, `purple`이며, 옵션이 없으면 회색 기본 인용 스타일을 쓴다. - 인용(`>`) 블록은 첫 인용 줄에 `> [!bg=yellow]` 또는 `> {bg=yellow}` 옵션 줄을 두면 해당 줄은 숨기고 블록 배경을 바꾼다. 지원 배경 프리셋은 콜아웃과 같은 `gray`, `blue`, `green`, `yellow`, `red`, `purple`이며, 옵션이 없으면 회색 기본 인용 스타일을 쓴다.
- 관리자 **라이브 모드**(미리보기) 인라인 편집: 문단·빈 줄·제목·인용·목록·코드 블록·콜아웃·토글을 렌더 스타일 그대로 contenteditable로 수정한다. blur·문단 이동(방향키) 시 편집 영역의 `<strong>`·`<em>` 등을 `**`·`*` 마크다운으로 다시 직렬화해 저장한다. **Enter**·**Shift+Enter** 모두 다음 문단(블록) 분리. 문단 안 `/`로 슬래시 명령 메뉴(`/image`+Enter 이미지 삽입 등). **소스(작성) 모드** textarea에서도 동일한 `/` 슬래시 메뉴를 사용하며, 상단 마크다운 툴바는 두지 않는다. 슬래시 기본 제목은 **h2·h3·h4**만 표시하며, 본문 **h1**은 `/h1` 검색 시에만 삽입한다(게시물 **제목 필드**가 페이지의 유일한 h1). 콜아웃 옵션은 첫 줄 `:::callout emoji=💡 bg=blue`처럼 `emoji`·`bg`(gray|blue|green|yellow|red|purple)로 지정하며, 라이브 모드에서는 블록에 포커스가 들어오면 오른쪽 설정 패널에서 수정한다. 코드 블록은 ` ```언어`·`nolinenos`(줄 번호 숨김)를 지원한다. 라이브·공개 모두 `ProseCodeBlock`(`#15171a`, `px-4 py-3`, `text-sm leading-6`)으로 동일하게 표시한다. 라이브 모드 호버·포커스 시 Language 입력·줄번호 토글이 보인다. 공개 화면에는 언어 라벨 옆 **복사** 버튼으로 본문을 클립보드에 넣는다. 본문 하단 클릭으로 새 문단을 추가한다. - 관리자 **라이브 모드**(미리보기) 인라인 편집: 문단·빈 줄·제목·인용·목록·코드 블록·콜아웃·토글을 렌더 스타일 그대로 contenteditable로 수정한다. blur·문단 이동(방향키) 시 편집 영역의 `<strong>`·`<em>` 등을 `**`·`*` 마크다운으로 다시 직렬화해 저장한다. **Enter**·**Shift+Enter** 모두 다음 문단(블록) 분리. 문단 안 `/`로 슬래시 명령 메뉴(`/image`+Enter 이미지 삽입 등). **소스(작성) 모드** textarea에서도 동일한 `/` 슬래시 메뉴를 사용하며, 상단 마크다운 툴바는 두지 않는다. 슬래시 기본 제목은 **h2·h3·h4**만 표시하며, 본문 **h1**은 `/h1` 검색 시에만 삽입한다(게시물 **제목 필드**가 페이지의 유일한 h1). `Cmd+Shift+K`는 현재 줄을 삭제하며 코드·콜아웃·토글 블록 내부에서는 커서가 있는 본문 줄을 삭제하고, 남은 본문 줄이 1개뿐이면 fenced 블록 전체를 삭제한다. 콜아웃 옵션은 첫 줄 `:::callout emoji=💡 bg=blue`처럼 `emoji`·`bg`(gray|blue|green|yellow|red|purple)로 지정하며, 라이브 모드에서는 블록에 포커스가 들어오면 오른쪽 설정 패널에서 수정한다. 코드 블록은 ` ```언어`·`nolinenos`(줄 번호 숨김)를 지원한다. 라이브·공개 모두 `ProseCodeBlock`(`#15171a`, `px-4 py-3`, `text-sm leading-6`)으로 동일하게 표시한다. 라이브 모드 호버·포커스 시 Language 입력·줄번호 토글이 보인다. 공개 화면에는 언어 라벨 옆 **복사** 버튼으로 본문을 클립보드에 넣는다. 본문 하단 클릭으로 새 문단을 추가한다.
- 이미지 파일을 붙여넣거나 드롭하면 관리자 업로드 API로 저장한 뒤 현재 커서 위치에 이미지 또는 갤러리 마크다운을 삽입한다. - 이미지 파일을 붙여넣거나 드롭하면 관리자 업로드 API로 저장한 뒤 현재 커서 위치에 이미지 또는 갤러리 마크다운을 삽입한다.
- 툴바 `이미지`·`갤러리`는 미디어 모달을 연다. 모달 기본 탭은 **미디어 라이브러리**이며 **업로드** 탭에서 드래그·파일 선택 후 즉시 삽입한다. - 툴바 `이미지`·`갤러리`는 미디어 모달을 연다. 모달 기본 탭은 **미디어 라이브러리**이며 **업로드** 탭에서 드래그·파일 선택 후 즉시 삽입한다.
- 미디어 라이브러리에서 단일 이미지를 선택하면 `![alt](url)` 형식으로 삽입한다. - 미디어 라이브러리에서 단일 이미지를 선택하면 `![alt](url)` 형식으로 삽입한다.

View File

@@ -1,5 +1,12 @@
# 업데이트 이력 # 업데이트 이력
## v1.5.49
- 게시물 글쓰기: 라이브 모드 `Cmd+Shift+K`가 코드·콜아웃·토글 블록 내부 현재 줄을 삭제하도록 수정.
- 게시물 글쓰기: 코드·콜아웃·토글 블록 내부 마지막 본문 줄에서 `Cmd+Shift+K`를 누르면 fenced 블록 전체를 삭제하도록 수정.
- 게시물 글쓰기: 콜아웃 라이브 편집에서 내부 줄 삭제와 마지막 줄 아래 방향키 이탈 이벤트가 부모 에디터로 전달되도록 수정.
- 게시물 글쓰기: 라이브 슬래시 명령 Enter 적용 시 키 이벤트 전파를 더 강하게 차단해 한글 IME 입력 잔여 문자가 블록 본문에 남을 가능성을 줄임.
## v1.5.48 ## v1.5.48
- 게시물 글쓰기: 상단 상태 표시에서 외부 이동 아이콘과 게시물 링크 이동 기능 제거. - 게시물 글쓰기: 상단 상태 표시에서 외부 이동 아이콘과 게시물 링크 이동 기능 제거.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "sori.studio", "name": "sori.studio",
"version": "1.5.48", "version": "1.5.49",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sori.studio", "name": "sori.studio",
"version": "1.5.48", "version": "1.5.49",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0", "@nuxtjs/tailwindcss": "^6.14.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "sori.studio", "name": "sori.studio",
"version": "1.5.48", "version": "1.5.49",
"private": true, "private": true,
"type": "module", "type": "module",
"imports": { "imports": {