@comark/vue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/components/Comark.d.ts +77 -0
  2. package/dist/components/Comark.js +131 -0
  3. package/dist/components/ComarkRenderer.d.ts +57 -0
  4. package/dist/components/ComarkRenderer.js +288 -0
  5. package/dist/components/Math.d.ts +33 -0
  6. package/dist/components/Math.js +47 -0
  7. package/dist/components/Mermaid.d.ts +62 -0
  8. package/dist/components/Mermaid.js +101 -0
  9. package/dist/index.d.ts +18 -0
  10. package/dist/index.js +164 -0
  11. package/dist/parse.d.ts +1 -0
  12. package/dist/parse.js +1 -0
  13. package/dist/plugins/alert.d.ts +2 -0
  14. package/dist/plugins/alert.js +2 -0
  15. package/dist/plugins/emoji.d.ts +2 -0
  16. package/dist/plugins/emoji.js +2 -0
  17. package/dist/plugins/headings.d.ts +2 -0
  18. package/dist/plugins/headings.js +2 -0
  19. package/dist/plugins/highlight.d.ts +2 -0
  20. package/dist/plugins/highlight.js +2 -0
  21. package/dist/plugins/math.d.ts +3 -0
  22. package/dist/plugins/math.js +3 -0
  23. package/dist/plugins/mermaid.d.ts +3 -0
  24. package/dist/plugins/mermaid.js +3 -0
  25. package/dist/plugins/security.d.ts +2 -0
  26. package/dist/plugins/security.js +2 -0
  27. package/dist/plugins/summary.d.ts +2 -0
  28. package/dist/plugins/summary.js +2 -0
  29. package/dist/plugins/task-list.d.ts +2 -0
  30. package/dist/plugins/task-list.js +2 -0
  31. package/dist/plugins/toc.d.ts +2 -0
  32. package/dist/plugins/toc.js +2 -0
  33. package/dist/render.d.ts +1 -0
  34. package/dist/render.js +1 -0
  35. package/dist/utils/caret.d.ts +7 -0
  36. package/dist/utils/caret.js +38 -0
  37. package/dist/utils/index.d.ts +1 -0
  38. package/dist/utils/index.js +1 -0
  39. package/dist/utils/node.d.ts +8 -0
  40. package/dist/utils/node.js +83 -0
  41. package/dist/utils/slot.d.ts +3 -0
  42. package/dist/utils/slot.js +8 -0
  43. package/dist/utils/ssrSlot.d.ts +1 -0
  44. package/dist/utils/ssrSlot.js +8 -0
  45. package/dist/vite.d.ts +21 -0
  46. package/dist/vite.js +175 -0
  47. package/package.json +58 -0
@@ -0,0 +1,101 @@
1
+ import { defineComponent, h, ref, onMounted, watch, computed } from 'vue';
2
+ import { renderMermaidSVG, THEMES } from 'beautiful-mermaid';
3
+ export const Mermaid = defineComponent({
4
+ name: 'Mermaid',
5
+ props: {
6
+ content: {
7
+ type: String,
8
+ required: true,
9
+ },
10
+ class: {
11
+ type: String,
12
+ default: '',
13
+ },
14
+ height: {
15
+ type: String,
16
+ default: '400px',
17
+ },
18
+ width: {
19
+ type: String,
20
+ default: '100%',
21
+ },
22
+ theme: {
23
+ type: [String, Object],
24
+ default: undefined,
25
+ },
26
+ themeDark: {
27
+ type: [String, Object],
28
+ default: undefined,
29
+ },
30
+ },
31
+ setup(props) {
32
+ const svgContent = ref('');
33
+ const error = ref(null);
34
+ const isDark = ref(false);
35
+ const beautifulTheme = computed(() => {
36
+ // Determine which theme to use based on dark mode and props
37
+ const isDarkMode = isDark.value;
38
+ // Get theme-dark prop (using bracket notation for kebab-case prop)
39
+ const themeDarkProp = props.themeDark;
40
+ // If dark mode, prefer theme-dark, otherwise prefer theme
41
+ const themeProp = isDarkMode ? themeDarkProp : props.theme;
42
+ let theme;
43
+ if (typeof themeProp === 'string') {
44
+ theme = THEMES[themeProp];
45
+ }
46
+ else if (typeof themeProp === 'object') {
47
+ theme = themeProp;
48
+ }
49
+ // Fallback to default themes if no prop is set
50
+ if (!theme) {
51
+ theme = THEMES[isDarkMode ? 'tokyo-night' : 'tokyo-light'];
52
+ }
53
+ return theme;
54
+ });
55
+ const renderDiagram = () => {
56
+ try {
57
+ error.value = null;
58
+ const svg = renderMermaidSVG(props.content, beautifulTheme.value);
59
+ svgContent.value = svg;
60
+ }
61
+ catch (err) {
62
+ error.value = err instanceof Error ? err.message : 'Failed to render diagram';
63
+ }
64
+ };
65
+ onMounted(() => {
66
+ const htmlEl = document.querySelector('html');
67
+ if (htmlEl) {
68
+ isDark.value = htmlEl.classList.contains('dark') || false;
69
+ // Watch for class changes on HTML element
70
+ const observer = new MutationObserver(() => {
71
+ const newIsDark = htmlEl.classList.contains('dark');
72
+ if (newIsDark !== isDark.value) {
73
+ isDark.value = newIsDark;
74
+ }
75
+ });
76
+ observer.observe(htmlEl, {
77
+ attributes: true,
78
+ attributeFilter: ['class'],
79
+ });
80
+ }
81
+ renderDiagram();
82
+ });
83
+ // Watch for theme changes (including isDark changes that affect beautifulTheme)
84
+ watch([beautifulTheme, () => props.content, isDark, () => props.theme, () => props.themeDark], () => {
85
+ renderDiagram();
86
+ });
87
+ return () => {
88
+ return h('div', {
89
+ 'class': `mermaid ${props.class}`,
90
+ 'style': {
91
+ display: 'flex',
92
+ justifyContent: 'center',
93
+ width: props.width,
94
+ height: props.height,
95
+ },
96
+ 'data-error': error.value,
97
+ 'innerHTML': svgContent.value,
98
+ });
99
+ };
100
+ },
101
+ });
@@ -0,0 +1,18 @@
1
+ import { Comark } from './components/Comark.ts';
2
+ import type { ParseOptions } from 'comark';
3
+ import { ComarkRenderer } from './components/ComarkRenderer.ts';
4
+ export { ComarkRenderer } from './components/ComarkRenderer.ts';
5
+ export { Comark } from './components/Comark.ts';
6
+ export type * from 'comark';
7
+ interface DefineComarkComponentOptions extends ParseOptions {
8
+ extends?: typeof Comark;
9
+ name?: string;
10
+ components?: Record<string, any>;
11
+ }
12
+ interface DefineComarkRendererOptions {
13
+ extends?: typeof ComarkRenderer;
14
+ name?: string;
15
+ components?: Record<string, any>;
16
+ }
17
+ export declare function defineComarkComponent(config?: DefineComarkComponentOptions): typeof Comark;
18
+ export declare function defineComarkRendererComponent(config?: DefineComarkRendererOptions): typeof ComarkRenderer;
package/dist/index.js ADDED
@@ -0,0 +1,164 @@
1
+ import { computed, defineComponent, h } from 'vue';
2
+ import { Comark } from "./components/Comark.js";
3
+ import { ComarkRenderer } from "./components/ComarkRenderer.js";
4
+ export { ComarkRenderer } from "./components/ComarkRenderer.js";
5
+ export { Comark } from "./components/Comark.js";
6
+ export function defineComarkComponent(config = {}) {
7
+ const { name, ...parseOptions } = config;
8
+ return defineComponent({
9
+ name: name ?? 'ComarkComponent',
10
+ props: {
11
+ /**
12
+ * The markdown content to parse and render
13
+ */
14
+ markdown: {
15
+ type: String,
16
+ default: undefined,
17
+ },
18
+ /**
19
+ * Parser options
20
+ */
21
+ options: {
22
+ type: Object,
23
+ default: () => ({}),
24
+ },
25
+ /**
26
+ * Additional plugins to use
27
+ */
28
+ plugins: {
29
+ type: Array,
30
+ default: () => [],
31
+ },
32
+ /**
33
+ * Custom component mappings for element tags
34
+ * Key: tag name (e.g., 'h1', 'p', 'MyComponent')
35
+ * Value: Vue component
36
+ */
37
+ components: {
38
+ type: Object,
39
+ default: () => ({}),
40
+ },
41
+ /**
42
+ * Dynamic component resolver function
43
+ * Used to resolve components that aren't in the components map
44
+ */
45
+ componentsManifest: {
46
+ type: Function,
47
+ default: undefined,
48
+ },
49
+ /**
50
+ * Enable streaming mode with stream-specific components
51
+ */
52
+ streaming: {
53
+ type: Boolean,
54
+ default: false,
55
+ },
56
+ /**
57
+ * If document has a <!-- more --> comment, only render the content before the comment
58
+ */
59
+ summary: {
60
+ type: Boolean,
61
+ default: false,
62
+ },
63
+ /**
64
+ * If caret is true, a caret will be appended to the last text node in the tree
65
+ */
66
+ caret: {
67
+ type: [Boolean, Object],
68
+ default: false,
69
+ },
70
+ },
71
+ setup(props, { slots }) {
72
+ const options = computed(() => ({
73
+ ...parseOptions,
74
+ ...props.options,
75
+ }));
76
+ const plugins = computed(() => [
77
+ ...(config.plugins || []),
78
+ ...(props.plugins || []),
79
+ ]);
80
+ const components = computed(() => ({
81
+ ...config.components,
82
+ ...props.components,
83
+ }));
84
+ return () => {
85
+ const component = config.extends || Comark;
86
+ return h(component, {
87
+ markdown: props.markdown,
88
+ options: options.value,
89
+ plugins: plugins.value,
90
+ components: components.value,
91
+ componentsManifest: props.componentsManifest,
92
+ streaming: props.streaming,
93
+ summary: props.summary,
94
+ caret: props.caret,
95
+ }, {
96
+ default: slots.default,
97
+ });
98
+ };
99
+ },
100
+ });
101
+ }
102
+ export function defineComarkRendererComponent(config = {}) {
103
+ return defineComponent({
104
+ name: config.name ?? 'ComarkRendererComponent',
105
+ props: {
106
+ /**
107
+ * The Comark tree to render
108
+ */
109
+ tree: {
110
+ type: Object,
111
+ required: true,
112
+ },
113
+ /**
114
+ * Custom component mappings for element tags
115
+ * Key: tag name (e.g., 'h1', 'p', 'MyComponent')
116
+ * Value: Vue component
117
+ */
118
+ components: {
119
+ type: Object,
120
+ default: () => ({}),
121
+ },
122
+ /**
123
+ * Dynamic component resolver function
124
+ * Used to resolve components that aren't in the components map
125
+ */
126
+ componentsManifest: {
127
+ type: Function,
128
+ default: undefined,
129
+ },
130
+ /**
131
+ * Enable streaming mode with stream-specific components
132
+ */
133
+ streaming: {
134
+ type: Boolean,
135
+ default: false,
136
+ },
137
+ /**
138
+ * If caret is true, a caret will be appended to the last text node in the tree
139
+ */
140
+ caret: {
141
+ type: [Boolean, Object],
142
+ default: false,
143
+ },
144
+ },
145
+ setup(props, { slots }) {
146
+ const components = computed(() => ({
147
+ ...config.components,
148
+ ...props.components,
149
+ }));
150
+ return () => {
151
+ const component = config.extends || ComarkRenderer;
152
+ return h(component, {
153
+ tree: props.tree,
154
+ components: components.value,
155
+ componentsManifest: props.componentsManifest,
156
+ streaming: props.streaming,
157
+ caret: props.caret,
158
+ }, {
159
+ default: slots.default,
160
+ });
161
+ };
162
+ },
163
+ });
164
+ }
@@ -0,0 +1 @@
1
+ export * from 'comark/parse';
package/dist/parse.js ADDED
@@ -0,0 +1 @@
1
+ export * from 'comark/parse';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/alert';
2
+ export { default } from 'comark/plugins/alert';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/alert';
2
+ export { default } from 'comark/plugins/alert';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/emoji';
2
+ export { default } from 'comark/plugins/emoji';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/emoji';
2
+ export { default } from 'comark/plugins/emoji';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/headings';
2
+ export { default } from 'comark/plugins/headings';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/headings';
2
+ export { default } from 'comark/plugins/headings';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/highlight';
2
+ export { default } from 'comark/plugins/highlight';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/highlight';
2
+ export { default } from 'comark/plugins/highlight';
@@ -0,0 +1,3 @@
1
+ export * from 'comark/plugins/math';
2
+ export { default } from 'comark/plugins/math';
3
+ export { Math } from '../components/Math.ts';
@@ -0,0 +1,3 @@
1
+ export * from 'comark/plugins/math';
2
+ export { default } from 'comark/plugins/math';
3
+ export { Math } from "../components/Math.js";
@@ -0,0 +1,3 @@
1
+ export * from 'comark/plugins/mermaid';
2
+ export { default } from 'comark/plugins/mermaid';
3
+ export { Mermaid } from '../components/Mermaid.ts';
@@ -0,0 +1,3 @@
1
+ export * from 'comark/plugins/mermaid';
2
+ export { default } from 'comark/plugins/mermaid';
3
+ export { Mermaid } from "../components/Mermaid.js";
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/security';
2
+ export { default } from 'comark/plugins/security';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/security';
2
+ export { default } from 'comark/plugins/security';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/summary';
2
+ export { default } from 'comark/plugins/summary';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/summary';
2
+ export { default } from 'comark/plugins/summary';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/task-list';
2
+ export { default } from 'comark/plugins/task-list';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/task-list';
2
+ export { default } from 'comark/plugins/task-list';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/toc';
2
+ export { default } from 'comark/plugins/toc';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/toc';
2
+ export { default } from 'comark/plugins/toc';
@@ -0,0 +1 @@
1
+ export * from 'comark/render';
package/dist/render.js ADDED
@@ -0,0 +1 @@
1
+ export * from 'comark/render';
@@ -0,0 +1,7 @@
1
+ import type { ComarkElement } from 'comark';
2
+ interface CaretOptions {
3
+ class?: string;
4
+ }
5
+ export declare function getCaret(options: boolean | CaretOptions): ComarkElement | null;
6
+ export declare function findLastTextNodeAndAppendNode(parent: ComarkElement, nodeToAppend: ComarkElement): boolean;
7
+ export {};
@@ -0,0 +1,38 @@
1
+ const CARET_TEXT = ' '; // thin space is used to avoid wide spaces between text and caret
2
+ const CARET_STYLE = 'background-color: currentColor; display: inline-block; margin-left: 0.25rem; margin-right: 0.25rem; animation: pulse 0.75s cubic-bezier(0.4,0,0.6,1) infinite;';
3
+ export function getCaret(options) {
4
+ if (options === true) {
5
+ return ['span', { key: 'stream-caret', style: CARET_STYLE }, CARET_TEXT];
6
+ }
7
+ if (typeof options === 'object') {
8
+ const userClass = options?.class || '';
9
+ return [
10
+ 'span',
11
+ {
12
+ key: 'stream-caret',
13
+ style: CARET_STYLE,
14
+ ...(userClass ? { class: userClass } : {}),
15
+ },
16
+ CARET_TEXT,
17
+ ];
18
+ }
19
+ return null;
20
+ }
21
+ export function findLastTextNodeAndAppendNode(parent, nodeToAppend) {
22
+ // Traverse nodes backwards to find the last text node
23
+ for (let i = parent.length - 1; i >= 2; i--) {
24
+ const node = parent[i];
25
+ if (typeof node === 'string' && parent[1]?.key !== 'stream-caret') {
26
+ // Found a text node - insert stream indicator after it
27
+ parent.push(nodeToAppend);
28
+ return true;
29
+ }
30
+ if (Array.isArray(node)) {
31
+ // This is an element node - recursively check its children
32
+ if (findLastTextNodeAndAppendNode(node, nodeToAppend)) {
33
+ return true;
34
+ }
35
+ }
36
+ }
37
+ return false;
38
+ }
@@ -0,0 +1 @@
1
+ export * from 'comark/utils';
@@ -0,0 +1 @@
1
+ export * from 'comark/utils';
@@ -0,0 +1,8 @@
1
+ import type { VNode } from 'vue';
2
+ export declare const TEXT_TAGS: string[];
3
+ export declare function isTag(vnode: VNode, tag: string | symbol): boolean;
4
+ export declare function isText(vnode: VNode): boolean;
5
+ export declare function nodeChildren(node: VNode): any;
6
+ export declare function nodeTextContent(node: VNode): string;
7
+ export declare function unwrap(vnode: VNode, tags?: string[]): VNode | VNode[];
8
+ export declare function flatUnwrap(vnodes: VNode | VNode[], tags?: string | string[]): Array<VNode | string> | VNode;
@@ -0,0 +1,83 @@
1
+ export const TEXT_TAGS = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li'];
2
+ export function isTag(vnode, tag) {
3
+ if (vnode.type === tag) {
4
+ return true;
5
+ }
6
+ if (typeof vnode.type === 'object' && vnode.type.tag === tag) {
7
+ return true;
8
+ }
9
+ return false;
10
+ }
11
+ export function isText(vnode) {
12
+ return isTag(vnode, 'text') || isTag(vnode, Symbol.for('v-txt'));
13
+ }
14
+ export function nodeChildren(node) {
15
+ if (Array.isArray(node.children) || typeof node.children === 'string') {
16
+ return node.children;
17
+ }
18
+ if (typeof node.children?.default === 'function') {
19
+ return node.children.default();
20
+ }
21
+ return [];
22
+ }
23
+ export function nodeTextContent(node) {
24
+ if (!node) {
25
+ return '';
26
+ }
27
+ if (Array.isArray(node)) {
28
+ return node.map(nodeTextContent).join('');
29
+ }
30
+ if (isText(node)) {
31
+ return node.children || '';
32
+ }
33
+ const children = nodeChildren(node);
34
+ if (Array.isArray(children)) {
35
+ return children.map(nodeTextContent).filter(Boolean).join('');
36
+ }
37
+ return '';
38
+ }
39
+ export function unwrap(vnode, tags = []) {
40
+ if (Array.isArray(vnode)) {
41
+ return vnode.flatMap(node => unwrap(node, tags));
42
+ }
43
+ let result = vnode;
44
+ if (tags.some(tag => tag === '*' || isTag(vnode, tag))) {
45
+ result = nodeChildren(vnode) || vnode;
46
+ if (!Array.isArray(result) && TEXT_TAGS.some(tag => isTag(vnode, tag))) {
47
+ result = [result];
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+ function _flatUnwrap(vnodes, tags = []) {
53
+ vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
54
+ if (!tags.length) {
55
+ return vnodes;
56
+ }
57
+ return vnodes
58
+ .flatMap(vnode => _flatUnwrap(unwrap(vnode, [tags[0]]), tags.slice(1)))
59
+ .filter(vnode => !(isText(vnode) && nodeTextContent(vnode).trim() === ''));
60
+ }
61
+ export function flatUnwrap(vnodes, tags = []) {
62
+ if (typeof tags === 'string') {
63
+ tags = tags.split(/[,\s]/).map(tag => tag.trim()).filter(Boolean);
64
+ }
65
+ if (!tags.length) {
66
+ return vnodes;
67
+ }
68
+ return _flatUnwrap(vnodes, tags)
69
+ .reduce((acc, item) => {
70
+ if (isText(item)) {
71
+ if (typeof acc[acc.length - 1] === 'string') {
72
+ acc[acc.length - 1] += item.children;
73
+ }
74
+ else {
75
+ acc.push(item.children);
76
+ }
77
+ }
78
+ else {
79
+ acc.push(item);
80
+ }
81
+ return acc;
82
+ }, []);
83
+ }
@@ -0,0 +1,3 @@
1
+ export declare const renderSlot: (slots: Record<string, any>, name: string, props: any, ...rest: any[]) => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
2
+ [key: string]: any;
3
+ }>;
@@ -0,0 +1,8 @@
1
+ import { renderSlot as _renderSlot } from 'vue';
2
+ import { flatUnwrap } from "./node.js";
3
+ export const renderSlot = (slots, name, props, ...rest) => {
4
+ if (slots[name]) {
5
+ return _renderSlot({ ...slots, [name]: () => flatUnwrap(slots[name](), props?.unwrap || props?.mdcUnwrap || props?.comarkUnwrap) }, name, props, ...rest);
6
+ }
7
+ return _renderSlot(slots, name, props, ...rest);
8
+ };
@@ -0,0 +1 @@
1
+ export declare const ssrRenderSlot: (slots: Record<string, any>, name: string, props: any, fallbackRenderFn: (() => void) | null, push: any, parentComponent: any, slotScopeId?: string | undefined) => void;
@@ -0,0 +1,8 @@
1
+ import { ssrRenderSlot as _ssrRenderSlot } from 'vue/server-renderer';
2
+ import { flatUnwrap } from "./node.js";
3
+ export const ssrRenderSlot = (slots, name, props, fallbackRenderFn, push, parentComponent, slotScopeId) => {
4
+ if (slots[name]) {
5
+ return _ssrRenderSlot({ ...slots, [name]: () => flatUnwrap(slots[name](), props?.unwrap || props?.mdcUnwrap || props?.comarkUnwrap) }, name, props, fallbackRenderFn, push, parentComponent, slotScopeId);
6
+ }
7
+ return _ssrRenderSlot(slots, name, props, fallbackRenderFn, push, parentComponent, slotScopeId);
8
+ };
package/dist/vite.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * Vite plugin for @comark/vue.
4
+ *
5
+ * - Adds `<slot unwrap="...">` support inside custom markdown components.
6
+ * - Auto-registers every `.vue` file in `src/components/prose` as a global
7
+ * component
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // vite.config.ts
12
+ * import { defineConfig } from 'vite'
13
+ * import vue from '@vitejs/plugin-vue'
14
+ * import comark from '@comark/vue/vite'
15
+ *
16
+ * export default defineConfig({
17
+ * plugins: [vue(), comark()],
18
+ * })
19
+ * ```
20
+ */
21
+ export default function comark(): Plugin;