diff --git a/docs/content/4.api/1.components/2.content-renderer.md b/docs/content/4.api/1.components/2.content-renderer.md index 2d5a7ca25..71d3ae550 100644 --- a/docs/content/4.api/1.components/2.content-renderer.md +++ b/docs/content/4.api/1.components/2.content-renderer.md @@ -5,7 +5,7 @@ description: 'Takes your component from an AST to a wonderful template.' The ``{lang=html} component renders a document coming from a query. -It will use ``{lang=html} component to render `.md` file types. +It will use ``{lang=html} component to render `.md` file types. Other types will currently be passed to default slot via `v-slot="{ data }"` or be rendered inside `
` tag.
 
@@ -34,7 +34,7 @@ const { data } = await useAsyncData('page-data', () => queryContent('/hello').fi
   

{{ data.title }}

- +
diff --git a/playground/basic/content/1.real-content/content.md b/playground/basic/content/1.real-content/content.md index 5bb9e07b9..46bc883d6 100644 --- a/playground/basic/content/1.real-content/content.md +++ b/playground/basic/content/1.real-content/content.md @@ -46,7 +46,7 @@ const { data } = await useAsyncData(`doc-${route.path}`, () => queryContent(rout ``` -### `` +### `` > This component is used by `` under the hood. @@ -54,7 +54,7 @@ Render a markdown content. ```vue ``` diff --git a/playground/basic/content/2.features/6.variables.md b/playground/basic/content/2.features/6.variables.md new file mode 100644 index 000000000..ce80f444f --- /dev/null +++ b/playground/basic/content/2.features/6.variables.md @@ -0,0 +1,32 @@ +--- +title: Variables +cover: https://nuxtjs.org/design-kit/colored-logo.svg +--- + +:img{:src="cover"} + +# {{ $doc.title }} + +MDC stands for _**M**ark**D**own **C**omponents_. + +This syntax supercharges regular Markdown to write documents interacting deeply with any Vue component from your \`components/content/\` directory or provided by a module. + +## Next steps +- [Install Nuxt Content](/get-started) +- [Explore the MDC syntax](/guide/writing/mdc) + + +You are visiting document: {{ $doc._id }} + +Current route is: {{ $route.path }} + +::alert +--- +type: success +--- +This is an alert for {{ type }} +:: + +::alert{type="danger"} +This is an alert for {{ type }} +:: diff --git a/playground/document-driven/components/content/Hello.vue b/playground/document-driven/components/content/Hello.vue new file mode 100644 index 000000000..8d362f52c --- /dev/null +++ b/playground/document-driven/components/content/Hello.vue @@ -0,0 +1,6 @@ + diff --git a/playground/document-driven/content/8.component.md b/playground/document-driven/content/8.component.md new file mode 100644 index 000000000..6727693a4 --- /dev/null +++ b/playground/document-driven/content/8.component.md @@ -0,0 +1,5 @@ +--- +component: Hello +--- + +Will use the `` component as wrapper. diff --git a/playground/shared/nuxt.config.ts b/playground/shared/nuxt.config.ts index 0d02af73b..8ab90bd10 100644 --- a/playground/shared/nuxt.config.ts +++ b/playground/shared/nuxt.config.ts @@ -21,6 +21,10 @@ export default defineNuxtConfig({ { global: true, path: resolveThemeDir('./components') + }, + { + global: true, + path: resolveThemeDir('./components/content') } ], modules: [contentModule, '@nuxthq/admin'], diff --git a/src/runtime/components/ContentDoc.ts b/src/runtime/components/ContentDoc.ts index 0ba4fbe25..d62bb001e 100644 --- a/src/runtime/components/ContentDoc.ts +++ b/src/runtime/components/ContentDoc.ts @@ -6,6 +6,7 @@ import ContentQuery from './ContentQuery' import { useRoute, useContentHead } from '#imports' export default defineComponent({ + name: 'ContentDoc', props: { /** * Renderer props diff --git a/src/runtime/components/ContentList.ts b/src/runtime/components/ContentList.ts index dd26ac3c2..6f3acdb98 100644 --- a/src/runtime/components/ContentList.ts +++ b/src/runtime/components/ContentList.ts @@ -4,6 +4,7 @@ import type { QueryBuilderParams } from '../types' import ContentQuery from './ContentQuery' export default defineComponent({ + name: 'ContentList', props: { /** * Query props diff --git a/src/runtime/components/ContentNavigation.ts b/src/runtime/components/ContentNavigation.ts index ab598ccb1..477008ecf 100644 --- a/src/runtime/components/ContentNavigation.ts +++ b/src/runtime/components/ContentNavigation.ts @@ -5,6 +5,7 @@ import { QueryBuilder } from '../types' import { useAsyncData, fetchContentNavigation } from '#imports' export default defineComponent({ + name: 'ContentNavigation', props: { /** * A query to be passed to `fetchContentNavigation()`. diff --git a/src/runtime/components/ContentQuery.ts b/src/runtime/components/ContentQuery.ts index 3a26e84c2..6b3969202 100644 --- a/src/runtime/components/ContentQuery.ts +++ b/src/runtime/components/ContentQuery.ts @@ -4,6 +4,7 @@ import type { ParsedContent, QueryBuilder, SortParams } from '../types' import { computed, useAsyncData, queryContent } from '#imports' export default defineComponent({ + name: 'ContentQuery', props: { /** * The path of the content to load from content source. diff --git a/src/runtime/components/ContentRenderer.ts b/src/runtime/components/ContentRenderer.ts index 9cbe002aa..b81d7496f 100644 --- a/src/runtime/components/ContentRenderer.ts +++ b/src/runtime/components/ContentRenderer.ts @@ -1,7 +1,8 @@ import { defineComponent, watch, h, useSlots } from 'vue' -import MarkdownRenderer from './MarkdownRenderer' +import ContentRendererMarkdown from './ContentRendererMarkdown' export default defineComponent({ + name: 'ContentRenderer', props: { /** * The document to render. @@ -54,10 +55,10 @@ export default defineComponent({ const { value, excerpt, tag } = ctx - // Use built-in MarkdownRenderer + // Use built-in ContentRendererMarkdown if (value && value?._type === 'markdown' && value?.body?.children?.length) { return h( - MarkdownRenderer, + ContentRendererMarkdown, { value, excerpt, diff --git a/src/runtime/components/MarkdownRenderer.ts b/src/runtime/components/ContentRendererMarkdown.ts similarity index 92% rename from src/runtime/components/MarkdownRenderer.ts rename to src/runtime/components/ContentRendererMarkdown.ts index 74bd45f1f..8122620ba 100644 --- a/src/runtime/components/MarkdownRenderer.ts +++ b/src/runtime/components/ContentRendererMarkdown.ts @@ -1,4 +1,4 @@ -import { h, resolveComponent, Text, defineComponent, toRefs } from 'vue' +import { h, resolveComponent, Text, defineComponent } from 'vue' import destr from 'destr' import { pascalCase } from 'scule' import { find, html } from 'property-information' @@ -46,19 +46,13 @@ export default defineComponent({ default: 'div' } }, - setup (props) { + setup () { const { content: { tags = {} } } = useRuntimeConfig().public - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { tag: _t, value: _d, ...contentProps } = toRefs(props) - - return { - tags, - contentProps - } + return { tags } }, render (ctx) { - const { tags, tag, value, contentProps } = ctx + const { tags, tag, value } = ctx if (!value) { return null @@ -66,7 +60,7 @@ export default defineComponent({ // Get body from value let body = (value.body || value) as MarkdownNode - if (this.excerpt && value.excerpt) { + if (ctx.excerpt && value.excerpt) { body = value.excerpt } const meta: ParsedContentMeta = { @@ -92,7 +86,6 @@ export default defineComponent({ return h( component as any, { - ...contentProps, ...meta.component?.props, ...this.$attrs }, @@ -338,7 +331,7 @@ function getSlotName (node: MarkdownNode) { * Create a factory function if there is a node in the list */ function createSlotFunction (nodes: Array) { - return (nodes.length ? () => nodes : undefined) + return (nodes.length ? () => mergeTextNodes(nodes as VNode[]) : undefined) } /** @@ -354,3 +347,19 @@ function isDefaultTemplate (node: MarkdownNode) { function isTemplate (node: MarkdownNode) { return node.tag === 'template' } + +/** + * Merge consequent Text nodes into single node + */ +function mergeTextNodes (nodes: Array) { + const mergedNodes: Array = [] + for (const node of nodes) { + const previousNode = mergedNodes[mergedNodes.length - 1] + if (node.type === Text && previousNode?.type === Text) { + previousNode.children = (previousNode.children as string) + node.children + } else { + mergedNodes.push(node) + } + } + return mergedNodes +} diff --git a/src/runtime/components/DocumentDrivenEmpty.ts b/src/runtime/components/DocumentDrivenEmpty.ts index e5695b6cf..2a0d7b5f4 100644 --- a/src/runtime/components/DocumentDrivenEmpty.ts +++ b/src/runtime/components/DocumentDrivenEmpty.ts @@ -6,6 +6,7 @@ import { ParsedContent } from '../types' * Used in `src/runtime/pages/document-driven.vue` */ export default defineComponent({ + name: 'DocumentDrivenEmpty', props: { value: { type: Object as PropType, diff --git a/src/runtime/components/DocumentDrivenNotFound.ts b/src/runtime/components/DocumentDrivenNotFound.ts index 9d1bdb50b..da7caf348 100644 --- a/src/runtime/components/DocumentDrivenNotFound.ts +++ b/src/runtime/components/DocumentDrivenNotFound.ts @@ -4,6 +4,7 @@ import { defineComponent, h } from 'vue' * Used in `src/runtime/pages/document-driven.vue` */ export default defineComponent({ + name: 'DocumentDrivenNotFound', render () { return h('div', 'Document not found') } diff --git a/src/runtime/markdown-parser/utils/node.ts b/src/runtime/markdown-parser/utils/node.ts index b11cec4a1..b6bcbb495 100644 --- a/src/runtime/markdown-parser/utils/node.ts +++ b/src/runtime/markdown-parser/utils/node.ts @@ -17,7 +17,7 @@ export function isTag (vnode: VNode | MarkdownNode, tag: string | symbol): boole if (vnode.type === tag) { return true } - // Vue 3 VNode `type` can be an object (tag is provided by MarkdownRenderer) + // Vue 3 VNode `type` can be an object (tag is provided by ContentRendererMarkdown) if (typeof vnode.type === 'object' && (vnode.type as any).tag === tag) { return true } diff --git a/src/runtime/pages/document-driven.vue b/src/runtime/pages/document-driven.vue index 7f5d2dc13..30daf5fc6 100644 --- a/src/runtime/pages/document-driven.vue +++ b/src/runtime/pages/document-driven.vue @@ -13,7 +13,6 @@ useContentHead(page) - diff --git a/src/runtime/plugins/documentDriven.ts b/src/runtime/plugins/documentDriven.ts index 0930e1ba6..7323567e8 100644 --- a/src/runtime/plugins/documentDriven.ts +++ b/src/runtime/plugins/documentDriven.ts @@ -52,6 +52,7 @@ export default defineNuxtPlugin((nuxt) => { const promises: (() => Promise | any)[] = [] /** + * * `navigation` */ if (moduleOptions.navigation) { diff --git a/test/features/renderer-markdown.ts b/test/features/renderer-markdown.ts index 5bc7fd1b9..2cf5990ce 100644 --- a/test/features/renderer-markdown.ts +++ b/test/features/renderer-markdown.ts @@ -40,7 +40,7 @@ export const testMarkdownRenderer = () => { test('bindings', async () => { const rendered = await $fetch('/parse', { params: { - content + content: encodeURIComponent(content) } }) diff --git a/test/fixtures/basic/components/content/Alert.vue b/test/fixtures/basic/components/content/Alert.vue new file mode 100644 index 000000000..0903994ee --- /dev/null +++ b/test/fixtures/basic/components/content/Alert.vue @@ -0,0 +1,5 @@ + diff --git a/test/fixtures/basic/pages/parse.vue b/test/fixtures/basic/pages/parse.vue index 512012e7b..889702b54 100644 --- a/test/fixtures/basic/pages/parse.vue +++ b/test/fixtures/basic/pages/parse.vue @@ -1,6 +1,6 @@ @@ -12,7 +12,7 @@ const { data } = await useAsyncData(content, async () => { cors: true, body: { id: 'content:index.md', - content + content: decodeURIComponent(content) } }) })