191 lines
5.0 KiB
Vue
191 lines
5.0 KiB
Vue
<script setup>
|
|
import { buildCodeBlockLines } from '../../lib/markdown-code-block.js'
|
|
import ContentMarkdownEditableInline from './ContentMarkdownEditableInline.vue'
|
|
import ProseCodeBlock from './ProseCodeBlock.vue'
|
|
|
|
const props = defineProps({
|
|
/** 코드 본문 */
|
|
modelValue: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
/** 언어(slug) */
|
|
language: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
/** 줄 번호 표시 */
|
|
showLineNumbers: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
/** 본문 첫 줄 source-line(0-based) */
|
|
bodySourceLine: {
|
|
type: Number,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['commit', 'insert-above', 'insert-below', 'delete-line'])
|
|
|
|
const languageDraft = ref(props.language)
|
|
const lineNumbersEnabled = ref(props.showLineNumbers)
|
|
const liveBody = ref(props.modelValue)
|
|
|
|
watch(() => props.language, (value) => {
|
|
languageDraft.value = value
|
|
})
|
|
|
|
watch(() => props.showLineNumbers, (value) => {
|
|
lineNumbersEnabled.value = value
|
|
})
|
|
|
|
watch(() => props.modelValue, (value) => {
|
|
liveBody.value = value
|
|
})
|
|
|
|
/** @type {import('vue').ComputedRef<string[]>} */
|
|
const bodyLines = computed(() => {
|
|
const text = String(liveBody.value ?? '')
|
|
|
|
if (!text.length) {
|
|
return ['']
|
|
}
|
|
|
|
return text.split('\n')
|
|
})
|
|
|
|
/** @type {import('vue').ComputedRef<number[]>} */
|
|
const gutterLines = computed(() => bodyLines.value.map((_, index) => index + 1))
|
|
|
|
/**
|
|
* 마크다운에 코드 블록을 반영한다.
|
|
* @param {string} body - 본문
|
|
* @returns {void}
|
|
*/
|
|
const commitCodeBlock = (body) => {
|
|
emit('commit', buildCodeBlockLines({
|
|
language: languageDraft.value,
|
|
showLineNumbers: lineNumbersEnabled.value,
|
|
body
|
|
}))
|
|
}
|
|
|
|
/**
|
|
* 본문 편집 반영
|
|
* @param {string} body - 본문
|
|
* @returns {void}
|
|
*/
|
|
const onBodyCommit = (body) => {
|
|
liveBody.value = body
|
|
commitCodeBlock(body)
|
|
}
|
|
|
|
/**
|
|
* 입력 중 줄 번호 갱신용 본문 동기화
|
|
* @param {string} body - 본문
|
|
* @returns {void}
|
|
*/
|
|
const onBodyInput = (body) => {
|
|
liveBody.value = body
|
|
commitCodeBlock(body)
|
|
}
|
|
|
|
/**
|
|
* 코드 블록 아래로 이탈(다음 문단 생성)
|
|
* @param {Object} payload - insert-below 페이로드
|
|
* @returns {void}
|
|
*/
|
|
const onExitBelow = (payload) => {
|
|
emit('insert-below', payload)
|
|
}
|
|
|
|
/**
|
|
* 언어 입력 반영
|
|
* @returns {void}
|
|
*/
|
|
const onLanguageCommit = () => {
|
|
commitCodeBlock(props.modelValue)
|
|
}
|
|
|
|
/**
|
|
* 줄 번호 표시를 토글한다.
|
|
* @returns {void}
|
|
*/
|
|
const toggleLineNumbers = () => {
|
|
lineNumbersEnabled.value = !lineNumbersEnabled.value
|
|
commitCodeBlock(props.modelValue)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ProseCodeBlock
|
|
class="content-markdown-code-block-editor"
|
|
:show-line-numbers="lineNumbersEnabled"
|
|
:line-numbers="gutterLines"
|
|
>
|
|
<template #header-tools>
|
|
<div
|
|
class="content-markdown-code-block-editor__toolbar pointer-events-none flex items-center gap-1.5 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100"
|
|
>
|
|
<button
|
|
class="content-markdown-code-block-editor__line-numbers pointer-events-auto rounded border border-white/15 bg-white/10 px-2 py-0.5 text-xs font-medium text-white/70 transition-colors hover:bg-white/15 hover:text-white"
|
|
type="button"
|
|
:aria-pressed="lineNumbersEnabled"
|
|
:title="lineNumbersEnabled ? '줄 번호 숨기기' : '줄 번호 표시'"
|
|
@mousedown.prevent
|
|
@click="toggleLineNumbers"
|
|
>
|
|
{{ lineNumbersEnabled ? '줄번호' : '줄번호 끔' }}
|
|
</button>
|
|
<input
|
|
v-model="languageDraft"
|
|
class="content-markdown-code-block-editor__language pointer-events-auto w-[7.5rem] rounded border border-white/15 bg-white/10 px-2 py-0.5 text-xs text-white outline-none transition-colors placeholder:text-white/35 focus:border-white/30 focus:bg-white/15"
|
|
type="text"
|
|
placeholder="Language..."
|
|
spellcheck="false"
|
|
@mousedown.stop
|
|
@keydown.stop
|
|
@blur="onLanguageCommit"
|
|
@keydown.enter.prevent="onLanguageCommit"
|
|
>
|
|
</div>
|
|
</template>
|
|
|
|
<ContentMarkdownEditableInline
|
|
tag="pre"
|
|
block-class="content-markdown-code-block-editor__editor m-0 min-w-0 border-0 bg-transparent p-0 font-mono text-sm leading-6 text-white outline-none"
|
|
enter-mode="multiline"
|
|
plain-text
|
|
arrow-exit-creates-line
|
|
:source-line="bodySourceLine"
|
|
:source-line-count="bodyLines.length"
|
|
:model-value="modelValue"
|
|
@input="onBodyInput"
|
|
@commit="onBodyCommit"
|
|
@insert-above="emit('insert-above', $event)"
|
|
@insert-below="onExitBelow"
|
|
@delete-line="emit('delete-line', $event)"
|
|
/>
|
|
</ProseCodeBlock>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.content-markdown-code-block-editor :deep(.prose-code-block__content) {
|
|
padding-top: 0.75rem;
|
|
padding-bottom: 0.75rem;
|
|
}
|
|
|
|
.content-markdown-code-block-editor :deep(.prose-code-block__header) {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.content-markdown-code-block-editor__toolbar {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.content-markdown-code-block-editor__toolbar > * {
|
|
pointer-events: auto;
|
|
}
|
|
</style>
|