{
  "name": "vue-ui-code-block-view",
  "title": "vue-ui-code-block-view",
  "type": "registry:component",
  "description": "",
  "registryDependencies": [],
  "dependencies": [
    "beautiful-mermaid",
    "prosekit@^0.22.0"
  ],
  "files": [
    {
      "path": "registry/src/vue/ui/code-block-view/code-block-view.vue",
      "type": "registry:component",
      "target": "components/editor/ui/code-block-view/code-block-view.vue",
      "content": "<script setup lang=\"ts\">\nimport { renderMermaidSVG, THEMES } from 'beautiful-mermaid'\nimport { isCodeBlockPreviewHiddenDecoration, shikiBundledLanguagesInfo, type CodeBlockAttrs } from 'prosekit/extensions/code-block'\nimport { TextSelection } from 'prosekit/pm/state'\nimport type { VueNodeViewProps } from 'prosekit/vue'\nimport { computed, type ComponentPublicInstance } from 'vue'\n\nconst props = defineProps<VueNodeViewProps>()\n\nconst attrs = computed(() => props.node.value.attrs as CodeBlockAttrs)\nconst language = computed(() => attrs.value.language || '')\nconst hidePreview = computed(() => props.decorations.value.some(isCodeBlockPreviewHiddenDecoration))\nconst showMermaidPreview = computed(() => !hidePreview.value && language.value === 'mermaid')\nlet preElement: HTMLPreElement | null = null\n\nconst mermaidPreview = computed<{ svg: string | null; error: Error | null }>(() => {\n  if (language.value !== 'mermaid') return { svg: null, error: null }\n  try {\n    return { svg: renderMermaidSVG(props.node.value.textContent, THEMES['tokyo-night']), error: null }\n  } catch (err) {\n    return { svg: null, error: err instanceof Error ? err : new Error(String(err)) }\n  }\n})\n\nfunction setLanguage(lang: string) {\n  const newAttrs: CodeBlockAttrs = { language: lang }\n  props.setAttrs(newAttrs)\n}\n\nfunction focusSource(event: MouseEvent) {\n  event.preventDefault()\n  const pos = props.getPos()\n  if (typeof pos !== 'number') return\n  const { state, dispatch } = props.view\n  const selection = TextSelection.near(state.doc.resolve(pos + 1), 1)\n  dispatch(state.tr.setSelection(selection))\n  props.view.focus()\n  preElement?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })\n}\n\nfunction bindContentRef(element: Element | ComponentPublicInstance | null, refs: Record<string, unknown>) {\n  if (typeof props.contentRef === 'function') {\n    props.contentRef(element, refs)\n  }\n  preElement = element instanceof HTMLPreElement ? element : null\n}\n</script>\n\n<template>\n  <div\n    class=\"relative mx-2 top-3 h-0 select-none overflow-visible text-xs data-preview:hidden\"\n    contentEditable=\"false\"\n    :data-preview=\"showMermaidPreview ? '' : undefined\"\n  >\n    <select\n      aria-label=\"Code block language\"\n      class=\"outline-unset focus:outline-unset relative box-border w-auto cursor-pointer select-none appearance-none rounded-sm border-none bg-transparent px-2 py-1 text-xs transition text-(--prosemirror-highlight) opacity-0 hover:opacity-80 [div[data-node-view-root]:hover_&]:opacity-50 hover:[div[data-node-view-root]:hover_&]:opacity-80\"\n      :value=\"language\"\n      @change=\"(event) => setLanguage((event.target as HTMLSelectElement).value)\"\n    >\n      <option value=\"\">Plain Text</option>\n      <option\n        v-for=\"info in shikiBundledLanguagesInfo\"\n        :key=\"info.id\"\n        :value=\"info.id\"\n      >\n        {{ info.name }}\n      </option>\n    </select>\n  </div>\n  <pre\n    :ref=\"bindContentRef\"\n    class=\"data-preview:hidden\"\n    :data-preview=\"showMermaidPreview ? '' : undefined\"\n    :data-language=\"language\"\n  ></pre>\n  <div\n    v-if=\"showMermaidPreview\"\n    aria-label=\"Edit source\"\n    class=\"block py-2 overflow-auto\"\n    contentEditable=\"false\"\n    @mousedown=\"focusSource\"\n  >\n    <pre v-if=\"mermaidPreview.error\">{{ mermaidPreview.error.message }}</pre>\n    <div v-if=\"mermaidPreview.svg\" v-html=\"mermaidPreview.svg\"></div>\n  </div>\n</template>\n"
    },
    {
      "path": "registry/src/vue/ui/code-block-view/index.ts",
      "type": "registry:component",
      "target": "components/editor/ui/code-block-view/index.ts",
      "content": "import type { Extension } from 'prosekit/core'\nimport { defineVueNodeView, type VueNodeViewComponent } from 'prosekit/vue'\n\nimport CodeBlockView from './code-block-view.vue'\n\nexport function defineCodeBlockView(): Extension {\n  return defineVueNodeView({\n    name: 'codeBlock',\n    contentAs: 'code',\n    component: CodeBlockView as VueNodeViewComponent,\n  })\n}\n"
    }
  ],
  "meta": {
    "hasIcons": false,
    "hidden": false,
    "story": "",
    "framework": "vue",
    "accumulatedFiles": [
      "registry/src/vue/ui/code-block-view/code-block-view.vue",
      "registry/src/vue/ui/code-block-view/index.ts"
    ],
    "internalDependencies": []
  },
  "$schema": "https://ui.shadcn.com/schema/registry-item.json"
}
