+
COMPONENTS IN MARKDOWN
diff --git a/docs/app/components/Playground.vue b/docs/app/components/Playground.vue
index d2891ead..bac68e3f 100644
--- a/docs/app/components/Playground.vue
+++ b/docs/app/components/Playground.vue
@@ -2,7 +2,7 @@
import { parse } from 'comark'
import highlight from '@comark/nuxt/plugins/highlight'
import math from '@comark/nuxt/plugins/math'
-import binding, { Binding } from '@comark/nuxt/plugins/binding'
+import binding from '@comark/nuxt/plugins/binding'
import emoji from '@comark/nuxt/plugins/emoji'
import mermaid from '@comark/nuxt/plugins/mermaid'
import jsonRender from '@comark/nuxt/plugins/json-render'
@@ -12,29 +12,32 @@ import breaks from '@comark/nuxt/plugins/breaks'
import { renderMarkdown } from 'comark/render'
import { Splitpanes, Pane } from 'splitpanes'
-import { airbnbMarkdown, playgroundExamples } from '~/constants'
-import Gallery from '~/components/playground/Gallery.vue'
-import RatingBar from '~/components/playground/RatingBar.vue'
-import HostInfo from '~/components/playground/HostInfo.vue'
-import Facility from '~/components/playground/Facility.vue'
-import TwoColumn from '~/components/playground/TwoColumn.vue'
-import BookingCard from '~/components/playground/BookingCard.vue'
-import Ingredients from '~/components/playground/Ingredients.vue'
-import ProseSteps from '@nuxt/ui/components/prose/Steps.vue'
+import { playgroundExamples } from '~/constants'
+import resolveComponent from '~/utils/components-manifest'
+import PromptInput from '~/components/playground/PromptInput.vue'
+import GeneratingIndicator from '~/components/playground/GeneratingIndicator.vue'
import { useLocalStorage, watchDebounced } from '@vueuse/core'
+import { useCompletion } from '@ai-sdk/vue'
import type { ComarkTree, ComarkPlugin } from 'comark'
import VueJsonPretty from 'vue-json-pretty'
-const props = defineProps<{
- compact?: boolean
-}>()
-
-const selectedExample = ref('airbnb')
-const currentExample = computed(() =>
- playgroundExamples.find(e => e.value === selectedExample.value) ?? playgroundExamples[0]!,
+const router = useRouter()
+const route = useRoute()
+const knownExamples = playgroundExamples.map((e) => e.value)
+const selectedExample = computed({
+ get: () =>
+ knownExamples.includes(route.query.example as string) ? (route.query.example as string) : knownExamples[0],
+ set: (value: string) => {
+ if (knownExamples.includes(value)) {
+ router.push({ query: { example: value } })
+ }
+ },
+})
+const currentExample = computed(
+ () => playgroundExamples.find((e) => e.value === selectedExample.value) ?? playgroundExamples[0]!
)
-const markdown = ref
(airbnbMarkdown.trim())
+const markdown = ref(currentExample.value.content.trim())
const tree = ref(null)
const parseTime = ref(0)
const nodeCount = ref(0)
@@ -44,22 +47,31 @@ const parsing = ref(false)
const colorMode = useColorMode()
const isDark = computed(() => colorMode.value === 'dark')
-const pluginToggles = useLocalStorage('comark-playground-plugins', {
- highlight: true,
- math: true,
- emoji: true,
- mermaid: true,
- jsonRender: true,
- footnotes: true,
- punctuation: false,
- breaks: false,
-}, { mergeDefaults: true })
-
-const parseOptions = useLocalStorage('comark-playground-parse-options', {
- autoUnwrap: true,
- autoClose: true,
- html: true,
-}, { mergeDefaults: true })
+const pluginToggles = useLocalStorage(
+ 'comark-playground-plugins',
+ {
+ highlight: true,
+ math: true,
+ emoji: true,
+ mermaid: true,
+ jsonRender: true,
+ footnotes: true,
+ punctuation: false,
+ breaks: false,
+ binding: true,
+ },
+ { mergeDefaults: true }
+)
+
+const parseOptions = useLocalStorage(
+ 'comark-playground-parse-options',
+ {
+ autoUnwrap: true,
+ autoClose: true,
+ html: true,
+ },
+ { mergeDefaults: true }
+)
const pluginDefs = [
{
@@ -137,9 +149,7 @@ const parseOptionDefs = [
] as const
const activePlugins = computed(() =>
- pluginDefs
- .filter(p => pluginToggles.value[p.key as keyof typeof pluginToggles.value])
- .map(p => p.factory()),
+ pluginDefs.filter((p) => pluginToggles.value[p.key as keyof typeof pluginToggles.value]).map((p) => p.factory())
)
const enabledPluginCount = computed(() => Object.values(pluginToggles.value).filter(Boolean).length)
@@ -151,8 +161,7 @@ const tabs = [
{ label: 'Formatted', value: 'formatted', icon: 'i-lucide-code' },
]
-// In compact mode the tab is always locked to preview
-const currentTab = computed(() => props.compact ? 'preview' : activeTab.value)
+const currentTab = computed(() => activeTab.value)
function countNodes(nodes: unknown[]): number {
let count = 0
@@ -162,8 +171,7 @@ function countNodes(nodes: unknown[]): number {
for (let i = 2; i < node.length; i++) {
if (Array.isArray(node[i])) {
count += countNodes([node[i]])
- }
- else if (typeof node[i] === 'string') {
+ } else if (typeof node[i] === 'string') {
count++
}
}
@@ -193,11 +201,10 @@ async function parseMarkdown(): Promise {
parseTime.value = Math.round((performance.now() - start) * 10) / 10
nodeCount.value = countNodes(result.nodes)
error.value = null
- }
- catch (err: any) {
+ } catch (err: any) {
error.value = err.message || 'Failed to parse markdown'
- }
- finally {
+ console.error('[Comark] Parse error:', err)
+ } finally {
parsing.value = false
}
}
@@ -227,16 +234,58 @@ const formattedOutputModel = computed({
set: () => {},
})
-const isMatch = computed(() =>
- !!formattedOutput.value && formattedOutput.value.trim() === markdown.value.trim(),
-)
+const isMatch = computed(() => !!formattedOutput.value && formattedOutput.value.trim() === markdown.value.trim())
+
+const markdownEditor = useTemplateRef<{ scrollToBottom: () => void }>('markdownEditor')
+
+function scrollEditorToBottom() {
+ nextTick(() => markdownEditor.value?.scrollToBottom())
+}
+
+const {
+ completion,
+ complete,
+ isLoading: isGenerating,
+} = useCompletion({
+ api: '/api/generate-page',
+ streamProtocol: 'text',
+ onError: () => {
+ error.value = 'Generation failed'
+ },
+ onFinish: async () => {
+ await parseMarkdown()
+ scrollEditorToBottom()
+ },
+})
+
+watch(completion, async (md) => {
+ if (!md) return
+ markdown.value = md
+ try {
+ tree.value = await parse(md, {
+ plugins: activePlugins.value,
+ autoUnwrap: parseOptions.value.autoUnwrap,
+ autoClose: true,
+ html: parseOptions.value.html,
+ })
+ } catch {
+ /* ignore intermediate parse errors */
+ }
+ scrollEditorToBottom()
+})
+
+function handleGenerate(prompt: string) {
+ const example = currentExample.value
+ if (!example.mode) return
+ markdown.value = ''
+ tree.value = null
+ error.value = null
+ complete(prompt, { body: { mode: example.mode, structure: example.content } })
+}
-
+
-
+