161 lines
3.8 KiB
Vue
161 lines
3.8 KiB
Vue
<script setup>
|
|
import { buildToggleBlockLines } from '../../lib/markdown-toggle.js'
|
|
import ProseToggle from './ProseToggle.vue'
|
|
import ContentMarkdownEditableInline from './ContentMarkdownEditableInline.vue'
|
|
|
|
const props = defineProps({
|
|
/** 토글 제목 */
|
|
title: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
/** 토글 본문 */
|
|
modelValue: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
/** 기본 펼침 여부 */
|
|
defaultOpen: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
/** 선언 줄 source-line(0-based) */
|
|
titleSourceLine: {
|
|
type: Number,
|
|
required: true
|
|
},
|
|
/** 본문 첫 줄 source-line(0-based) */
|
|
bodySourceLine: {
|
|
type: Number,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['commit', 'insert-below'])
|
|
|
|
const titleEditorRef = ref(null)
|
|
const bodyEditorRef = ref(null)
|
|
const titleDraft = ref(props.title)
|
|
|
|
watch(() => props.title, (value) => {
|
|
titleDraft.value = value
|
|
})
|
|
|
|
/**
|
|
* 토글 마크다운을 반영한다.
|
|
* @param {{ title?: string, body?: string }} options - 옵션
|
|
* @returns {void}
|
|
*/
|
|
const commitToggle = (options = {}) => {
|
|
emit('commit', buildToggleBlockLines({
|
|
title: options.title ?? titleDraft.value,
|
|
body: options.body ?? props.modelValue,
|
|
defaultOpen: options.defaultOpen ?? props.defaultOpen
|
|
}))
|
|
}
|
|
|
|
/**
|
|
* 제목 편집 반영
|
|
* @param {string} value - 제목
|
|
* @returns {void}
|
|
*/
|
|
const onTitleCommit = (value) => {
|
|
titleDraft.value = String(value ?? '').trim()
|
|
commitToggle({ title: titleDraft.value })
|
|
}
|
|
|
|
/**
|
|
* 제목 필드 이탈 전 로컬 초안 동기화(한글 조합·↓ 이동 시 본문 오염 방지)
|
|
* @returns {void}
|
|
*/
|
|
const syncTitleDraft = () => {
|
|
titleDraft.value = String(titleEditorRef.value?.readEditorValue?.() ?? titleDraft.value).trim()
|
|
}
|
|
|
|
/**
|
|
* 제목 Enter — 본문으로 포커스 이동
|
|
* @returns {void}
|
|
*/
|
|
const onTitleEnterAdvance = () => {
|
|
const nextTitle = String(titleEditorRef.value?.readEditorValue?.() ?? titleDraft.value).trim()
|
|
titleDraft.value = nextTitle
|
|
commitToggle({ title: titleDraft.value })
|
|
|
|
nextTick(() => {
|
|
bodyEditorRef.value?.focusEditor('start')
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 본문 편집 반영
|
|
* @param {string} body - 본문
|
|
* @returns {void}
|
|
*/
|
|
const onBodyCommit = (body) => {
|
|
commitToggle({ body })
|
|
}
|
|
|
|
/**
|
|
* 토글 아래로 이탈
|
|
* @param {Object} payload - insert-below 페이로드
|
|
* @returns {void}
|
|
*/
|
|
const onExitBelow = (payload) => {
|
|
emit('insert-below', payload)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ProseToggle
|
|
class="content-markdown-toggle-editor"
|
|
data-editable-scope
|
|
:title="titleDraft"
|
|
:default-open="true"
|
|
animated
|
|
>
|
|
<template #title>
|
|
<ContentMarkdownEditableInline
|
|
ref="titleEditorRef"
|
|
tag="div"
|
|
block-class="content-markdown-toggle-editor__title min-h-[1.75rem] outline-none"
|
|
enter-mode="focus-next"
|
|
navigation-scope="parent"
|
|
:source-line="titleSourceLine"
|
|
:model-value="titleDraft"
|
|
@mousedown.stop
|
|
@click.stop
|
|
@commit="onTitleCommit"
|
|
@enter-advance="onTitleEnterAdvance"
|
|
@leave-block="syncTitleDraft"
|
|
/>
|
|
</template>
|
|
|
|
<ContentMarkdownEditableInline
|
|
ref="bodyEditorRef"
|
|
tag="div"
|
|
block-class="content-markdown-toggle-editor__body min-h-[3rem] outline-none"
|
|
enter-mode="multiline"
|
|
navigation-scope="parent"
|
|
plain-text
|
|
:source-line="bodySourceLine"
|
|
:model-value="modelValue"
|
|
@commit="onBodyCommit"
|
|
@insert-below="onExitBelow"
|
|
/>
|
|
</ProseToggle>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.content-markdown-toggle-editor__title:empty::before {
|
|
content: '토글 제목';
|
|
color: var(--site-muted);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.content-markdown-toggle-editor__body:empty::before {
|
|
content: '펼쳤을 때 보일 내용을 입력하세요';
|
|
color: var(--site-muted);
|
|
pointer-events: none;
|
|
}
|
|
</style>
|