라이브 모드 코드/콜아웃/토글 편집, 슬래시 명령, 홈 Latest List·Compact·Cards 보기, 사이트 설정 메인 화면 커버(720px) 및 HomeHero 반영. Co-authored-by: Cursor <cursoragent@cursor.com>
123 lines
2.9 KiB
Vue
123 lines
2.9 KiB
Vue
<script setup>
|
|
const props = defineProps({
|
|
/** 언어 라벨(공개 화면 표시) */
|
|
language: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
/** 줄 번호 표시 */
|
|
showLineNumbers: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
/** 복사 버튼 표시(공개 화면) */
|
|
showCopy: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
/** 복사할 코드 본문 */
|
|
copyText: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
/** 줄 번호 목록 */
|
|
lineNumbers: {
|
|
type: Array,
|
|
default: () => [1]
|
|
}
|
|
})
|
|
|
|
const copyDone = ref(false)
|
|
let copyDoneTimer = null
|
|
|
|
/**
|
|
* 코드 본문을 클립보드에 복사한다.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
const copyToClipboard = async () => {
|
|
const text = String(props.copyText ?? '')
|
|
|
|
if (!import.meta.client || !text) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(text)
|
|
copyDone.value = true
|
|
window.clearTimeout(copyDoneTimer)
|
|
copyDoneTimer = window.setTimeout(() => {
|
|
copyDone.value = false
|
|
}, 1600)
|
|
} catch {
|
|
copyDone.value = false
|
|
}
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
window.clearTimeout(copyDoneTimer)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="prose-code-block group relative mb-2.5 overflow-x-auto rounded bg-[#15171a] text-sm leading-6 text-white"
|
|
>
|
|
<div
|
|
v-if="$slots['header-tools'] || showCopy || language"
|
|
class="prose-code-block__header absolute right-3 top-2 z-10 flex items-center gap-2"
|
|
>
|
|
<slot name="header-tools" />
|
|
<span
|
|
v-if="language && !$slots['header-tools']"
|
|
class="prose-code-block__language text-xs text-white/50"
|
|
>{{ language }}</span>
|
|
<button
|
|
v-if="showCopy"
|
|
class="prose-code-block__copy rounded px-2 py-0.5 text-xs font-medium text-white/70 transition-colors hover:bg-white/10 hover:text-white"
|
|
type="button"
|
|
@click="copyToClipboard"
|
|
>
|
|
{{ copyDone ? '복사됨' : '복사' }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="prose-code-block__body flex">
|
|
<div
|
|
v-if="showLineNumbers"
|
|
class="prose-code-block__gutter shrink-0 select-none border-r border-white/10 py-3 pl-3 pr-2 font-mono text-xs leading-6 text-white/40 tabular-nums"
|
|
aria-hidden="true"
|
|
>
|
|
<div
|
|
v-for="lineNumber in lineNumbers"
|
|
:key="`prose-code-gutter-${lineNumber}`"
|
|
class="prose-code-block__gutter-line"
|
|
>
|
|
{{ lineNumber }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="prose-code-block__content min-w-0 flex-1 px-4 py-3 font-mono text-sm leading-6">
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.prose-code-block:focus-within {
|
|
outline: 2px solid rgb(255 255 255 / 0.22);
|
|
outline-offset: 0;
|
|
}
|
|
|
|
.prose-code-block__content :deep(code) {
|
|
display: block;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
background: transparent;
|
|
padding: 0;
|
|
color: inherit;
|
|
font-size: inherit;
|
|
line-height: inherit;
|
|
}
|
|
</style>
|