當看 Nuxt 官網的時候,為什麼跟我們產生的網站會不一樣,這是因為他們使用官方付費元件,這些在 NuxtContent 套件裡面都沒有,但是 NuxtContent 底層技術是透過 Nuxt MDC 程式來完成 Markdown 轉 Html,Nuxt MDC 支援客製化所有的UI元件,接下來我們要來修改內建的 UI 元件。
文章中的組成包含以下內容
在 Tailwind CSS 中,將它命名為 prose
需要安裝 @tailwindcss/typography 套件才有支援,prose-h1 prose-pre prose-img 等等,命名規則 prose-[tagName]
。
在 Nuxt MDC 每個 UI 元件都有一個專屬的元件,它命名規則為 Prose[TagName].vue
而這些UI元件,都可以用自己寫得替代掉。例如要替換掉 需要在 components/content
目錄下取一個相同的名字 components/content/ProseH2.vue
增加拷貝與顯示檔名,從 github 拷貝 ProsePre.vue
Nuxt MDC - ProsePre.vue 修改後如下。
<!-- app/components/content/ProsePre.vue -->
<template>
<div class="relative">
<!-- Copy 按鈕 -->
<button
@click="copyToClipboard"
class="absolute top-2 right-2 bg-gray-800 text-white text-xs px-2 py-1 rounded hover:bg-gray-700 transition"
>
{{ copied ? "✔ Copied!" : "Copy" }}
</button>
<!-- 顯示檔名 -->
<div
v-if="$props.filename"
class="bg-slate-200 font-mono text-sm border
border-slate-300 py-2 px-3 rounded-t-md text-black"
>
{{ $props.filename }}
</div>
<pre
:class="{
[$props.class as string]: true,
'mt-0 rounded-t-none': $props.filename,
}"
><slot /></pre>
</div>
</template>
<script setup lang="ts">
defineProps({
code: {
type: String,
default: '',
},
language: {
type: String,
default: null,
},
filename: {
type: String,
default: null,
},
highlights: {
type: Array as () => number[],
default: () => [],
},
meta: {
type: String,
default: null,
},
class: {
type: String,
default: null,
},
});
const copied = ref(false);
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(props.code);
copied.value = true;
setTimeout(() => (copied.value = false), 3000); // 3秒後恢復
} catch (error) {
console.error("複製失敗", error);
}
};
</script>
<style>
pre code .line {
display: block;
}
</style>
以下 markdown 代碼會顯示引號
> 這裡是顯示 blockquote 的原始碼,會有前後會有引號。
blockquote 的內容都會有引號,這裡透過自訂的 css 風格來取消引號,加入名稱為 "no-quotes" 的 css 風格
module.exports = {
"theme": {
"extend": {
"typography": {
"no-quotes": {
css: {
'blockquote p:first-of-type::before': { content: 'none' },
'blockquote p:last-of-type::after': { content: 'none' },
},
},
}
}
}
}
然後只要加入 風格 markdown 頁面就可以依照我們的風格來呈現畫面。
<script setup>
// app/pages/[...slug].vue
const route = useRoute();
const { data: article } = await useAsyncData(route.path, () => {
return queryCollection('docs').path(route.path).first()
})
</script>
<template>
<ContentRenderer :value="article" class="prose prose-no-quotes">
<template #empty>
<p>No content found.</p>
</template>
</ContentRenderer>
</template>
結果顯示如下
blockquote 修改過後的結果,引號取消了!
原始碼就只有三行,非常的簡單只是用 p tag 將 slot 包起來,暫時不修改 ProseP.vue 保留這些內容等待以後需要時再作修改。
<template>
<p><slot /></p>
</template>
<template>
<component
:is="ImageComponent"
:src="refinedSrc"
:alt="props.alt"
:width="props.width"
:height="props.height"
/>
</template>
<script setup lang="ts">
import { withTrailingSlash, withLeadingSlash, joinURL } from 'ufo'
import { useRuntimeConfig, computed } from '#imports'
import ImageComponent from '#build/mdc-image-component.mjs'
const props = defineProps({
src: {
type: String,
default: ''
},
alt: {
type: String,
default: ''
},
width: {
type: [String, Number],
default: undefined
},
height: {
type: [String, Number],
default: undefined
}
})
const refinedSrc = computed(() => {
if (props.src?.startsWith('/') && !props.src.startsWith('//')) {
const _base = withLeadingSlash(withTrailingSlash(useRuntimeConfig().app.baseURL))
if (_base !== '/' && !props.src.startsWith(_base)) {
return joinURL(_base, props.src)
}
}
return props.src
})
</script>
備註:保留這些內容等待以後需要時再作修改
<template>
<h1 :id="props.id">
<a
v-if="generate"
:href="`#${props.id}`"
>
<slot />
</a>
<slot v-else />
</h1>
</template>
<script setup lang="ts">
import { computed, useRuntimeConfig } from '#imports'
const props = defineProps<{ id?: string }>()
const { headings } = useRuntimeConfig().public.mdc
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h1)))
</script>
備註:保留這些內容等待以後需要時再作修改
// nuxt.config.ts
export default defineNuxtConfig({
// 其他設定
future: { compatibilityVersion: 4 },
modules: ['@nuxt/content'],
content: {
build: {
markdown: {
mdc: true,
highlight: {
// Theme used in all color schemes.
theme: 'github-dark',
}
}
}
}
// 其他設定
})
AI 給的錯誤方法,透過攔截 pre 來修改。
<template>
<div class="card max-w-7xl p-4 dark:bg-gray-900">
<!-- 自訂 Markdown Slot -->
<ContentRenderer :value="article" class="prose">
<template #empty>
<p>No content found.</p>
</template>
<!-- 攔截 <pre> 來加入 Copy 按鈕 -->
<template #pre="{ children }">
<CopyableCode :code="children[0].children[0].value" />
</template>
</ContentRenderer>
</div>
</template>
Tag | Component | Source | Description |
---|---|---|---|
p | <ProseP> | ProseP.vue | Paragraph |
h1 | <ProseH1> | ProseH1.vue | Heading 1 |
h2 | <ProseH2> | ProseH2.vue | Heading 2 |
h3 | <ProseH3> | ProseH3.vue | Heading 3 |
h4 | <ProseH4> | ProseH4.vue | Heading 4 |
h5 | <ProseH5> | ProseH5.vue | Heading 5 |
h6 | <ProseH6> | ProseH6.vue | Heading 6 |
ul | <ProseUl> | ProseUl.vue | Unordered List |
ol | <ProseOl> | ProseOl.vue | Ordered List |
li | <ProseLi> | ProseLi.vue | List Item |
blockquote | <ProseBlockquote> | ProseBlockquote.vue | Blockquote |
hr | <ProseHr> | ProseHr.vue | Horizontal Rule |
pre | <ProsePre> | ProsePre.vue | Preformatted Text |
code | <ProseCode> | ProseCode.vue | Code Block |
table | <ProseTable> | ProseTable.vue | Table |
thead | <ProseThead> | ProseThead.vue | Table Head |
tbody | <ProseTbody> | ProseTbody.vue | Table Body |
tr | <ProseTr> | ProseTr.vue | Table Row |
th | <ProseTh> | ProseTh.vue | Table Header |
td | <ProseTd> | ProseTd.vue | Table Data |
a | <ProseA> | ProseA.vue | Anchor Link |
img | <ProseImg> | ProseImg.vue | Image |
em | <ProseEm> | ProseEm.vue | Emphasis |
strong | <ProseStrong> | ProseStrong.vue | Strong |
nuxt v3.15.4
@nuxt/content v3.3.0
@nuxtjs/tailwindcss v6.13.1