Files
sori.studio/components/content/ContentMarkdownCodeBlockEditor.vue

187 lines
4.8 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-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
}
/**
* 코드 블록 아래로 이탈(다음 문단 생성)
* @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
:source-line="bodySourceLine"
:model-value="modelValue"
@input="onBodyInput"
@commit="onBodyCommit"
@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>