comark 0.1.0 → 0.1.1

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![Documentation](https://img.shields.io/badge/Documentation-black?logo=readme&logoColor=white)](https://comark.dev)
9
9
  [![license](https://img.shields.io/github/license/comarkdown/comark?color=black)](https://github.com/comarkdown/comark/blob/main/LICENSE)
10
10
 
11
- A high-performance markdown parser and renderer with Vue, React & Svelte components support.
11
+ A high-performance markdown parser and renderer with Vue, React, Svelte, HTML and ANSI terminal.
12
12
 
13
13
  ## Features
14
14
 
@@ -1,4 +1,4 @@
1
- import { comarkAttributes } from '../attributes';
1
+ import { comarkAttributes } from "../attributes.js";
2
2
  // TODO: support title & attributes
3
3
  export async function a(node, state) {
4
4
  const [_, attrs] = node;
@@ -1,4 +1,4 @@
1
- import { comarkAttributes } from '../attributes';
1
+ import { comarkAttributes } from "../attributes.js";
2
2
  import { textContent } from 'comark/utils';
3
3
  export function code(node, _state) {
4
4
  const [_, attrs] = node;
@@ -1,4 +1,4 @@
1
- import { comarkAttributes } from '../attributes';
1
+ import { comarkAttributes } from "../attributes.js";
2
2
  export async function emphesis(node, state) {
3
3
  const [_, attrs, ...children] = node;
4
4
  let content = '';
@@ -1,5 +1,5 @@
1
- import { htmlAttributes } from '../attributes';
2
- import { indent } from '../indent';
1
+ import { htmlAttributes } from "../attributes.js";
2
+ import { indent } from "../indent.js";
3
3
  const textBlocks = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'th']);
4
4
  const selfCloseTags = new Set(['br', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
5
5
  const inlineTags = new Set(['strong', 'em', 'code', 'a', 'br', 'span', 'img']);
@@ -1,4 +1,4 @@
1
- import { comarkAttributes } from '../attributes';
1
+ import { comarkAttributes } from "../attributes.js";
2
2
  export function img(node, _state) {
3
3
  const [_, attrs] = node;
4
4
  const { title, src, alt, ...rest } = attrs;
@@ -1,4 +1,4 @@
1
- import { comarkAttributes } from '../attributes';
1
+ import { comarkAttributes } from "../attributes.js";
2
2
  const fence = '```';
3
3
  export function mermaid(node, state) {
4
4
  const [_, attributes] = node;
@@ -1,4 +1,4 @@
1
- import { indent } from '../indent';
1
+ import { indent } from "../indent.js";
2
2
  export async function ol(node, state) {
3
3
  const children = node.slice(2);
4
4
  const revert = state.applyContext({ list: true, order: 1 });
@@ -1,4 +1,4 @@
1
- import { comarkAttributes } from '../attributes';
1
+ import { comarkAttributes } from "../attributes.js";
2
2
  export async function strong(node, state) {
3
3
  const [_, attrs, ...children] = node;
4
4
  let content = '';
@@ -1,4 +1,4 @@
1
- import { indent } from '../indent';
1
+ import { indent } from "../indent.js";
2
2
  export async function ul(node, state) {
3
3
  const children = node.slice(2);
4
4
  const revert = state.applyContext({ list: true, order: false });
@@ -1,6 +1,7 @@
1
1
  import type { LanguageRegistration } from 'shiki';
2
2
  import type { ComarkNode, ComarkTree, ComarkPlugin } from 'comark';
3
3
  import type { ShikiPrimitive, ThemeRegistration } from '@shikijs/primitive';
4
+ import type { ShikiTransformer } from '@shikijs/types';
4
5
  export interface HighlightOptions {
5
6
  /**
6
7
  * Whether to use the default language definitions
@@ -25,12 +26,23 @@ export interface HighlightOptions {
25
26
  light?: ThemeRegistration;
26
27
  dark?: ThemeRegistration;
27
28
  };
29
+ /**
30
+ * Transformers to apply to the code blocks
31
+ * @default undefined
32
+ */
33
+ transformers?: ShikiTransformer[];
28
34
  /**
29
35
  * Whether to add pre styles to the code blocks
30
36
  * @default false
31
37
  */
32
38
  preStyles?: boolean;
33
39
  }
40
+ export interface CodeBlockAttributes {
41
+ language?: string;
42
+ class?: string;
43
+ highlights?: number[];
44
+ meta?: string;
45
+ }
34
46
  /**
35
47
  * Get or create the Shiki highlighter instance
36
48
  * Uses a singleton pattern to avoid creating multiple highlighters
@@ -38,13 +50,9 @@ export interface HighlightOptions {
38
50
  export declare function getHighlighter(options?: HighlightOptions): Promise<ShikiPrimitive>;
39
51
  /**
40
52
  * Highlight code using Shiki with codeToTokens
41
- * Returns comark nodes built from tokens
53
+ * Returns comark nodes built from hast
42
54
  */
43
- export declare function highlightCode(code: string, attrs: {
44
- language?: string;
45
- class?: string;
46
- highlights?: number[];
47
- }, options?: HighlightOptions): Promise<{
55
+ export declare function highlightCode(code: string, attrs: CodeBlockAttributes, options?: HighlightOptions): Promise<{
48
56
  nodes: ComarkNode[];
49
57
  language: string;
50
58
  bgColor?: string;
@@ -1,5 +1,6 @@
1
- import { createShikiPrimitive, codeToTokensWithThemes } from '@shikijs/primitive';
1
+ import { createShikiPrimitive } from '@shikijs/primitive';
2
2
  import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
3
+ import { codeToHast } from 'shiki/core';
3
4
  let highlighter = null;
4
5
  let highlighterPromise = null;
5
6
  const loadedThemes = new Set();
@@ -45,20 +46,6 @@ export async function getHighlighter(options = {}) {
45
46
  throw error;
46
47
  }
47
48
  }
48
- /**
49
- * Convert color to inline style
50
- */
51
- function colorToStyle(token) {
52
- if (!token)
53
- return undefined;
54
- const variants = token.variants;
55
- if (!variants)
56
- return `color:${token.color}`;
57
- const { light: lc, dark: dc } = variants;
58
- if (!lc?.color || !dc?.color)
59
- return undefined;
60
- return lc.color === dc.color ? `color:${lc.color}` : `color:${lc.color};--shiki-dark:${dc.color}`;
61
- }
62
49
  async function registerDefaults(options) {
63
50
  const themes = Object.values(options.themes || {});
64
51
  const languages = options.languages || [];
@@ -94,7 +81,7 @@ async function loadLanguage(hl, language) {
94
81
  }
95
82
  /**
96
83
  * Highlight code using Shiki with codeToTokens
97
- * Returns comark nodes built from tokens
84
+ * Returns comark nodes built from hast
98
85
  */
99
86
  export async function highlightCode(code, attrs, options = {}) {
100
87
  // Extract language from attributes
@@ -102,28 +89,21 @@ export async function highlightCode(code, attrs, options = {}) {
102
89
  try {
103
90
  const hl = await getHighlighter(options);
104
91
  const { themes = { light: 'material-theme-lighter', dark: 'material-theme-palenight' } } = options;
92
+ const lightTheme = themes.light || themes.dark || 'material-theme-lighter';
93
+ const darkTheme = themes.dark || themes.light || 'material-theme-palenight';
105
94
  // Use codeToTokens to get raw tokens
106
- const result = codeToTokensWithThemes(hl, code, {
95
+ const result = await codeToHast(hl, code, {
107
96
  lang: language,
97
+ transformers: options.transformers,
108
98
  themes: {
109
- light: themes.light || themes.dark || 'material-theme-lighter',
110
- dark: themes.dark || themes.light || 'material-theme-palenight',
99
+ light: lightTheme,
100
+ dark: lightTheme !== darkTheme ? darkTheme : undefined,
101
+ },
102
+ meta: {
103
+ __raw: attrs.meta,
111
104
  },
112
105
  });
113
- // Build comark nodes from tokens (flatten all lines)
114
- const allTokens = Array.from({ length: result.length });
115
- const highlights = attrs.highlights;
116
- for (let i = 0; i < result.length; i++) {
117
- const lineTokens = result[i];
118
- const children = Array.from({ length: lineTokens.length });
119
- for (let j = 0; j < lineTokens.length; j++) {
120
- const token = lineTokens[j];
121
- const style = colorToStyle(token);
122
- children[j] = style ? ['span', { style }, token.content] : token.content;
123
- }
124
- const lineClass = 'line' + (highlights?.includes(i + 1) ? ' highlight' : '');
125
- allTokens[i] = ['span', { class: lineClass }, ...children];
126
- }
106
+ const allTokens = result.children.map(hastToMinimarkNode);
127
107
  return {
128
108
  nodes: allTokens,
129
109
  language,
@@ -137,6 +117,20 @@ export async function highlightCode(code, attrs, options = {}) {
137
117
  language,
138
118
  };
139
119
  }
120
+ function hastToMinimarkNode(input) {
121
+ const props = input.properties || {};
122
+ if (input.type === 'comment')
123
+ return [null, {}, input.value];
124
+ if (input.type === 'text')
125
+ return input.value;
126
+ if (input.tag === 'code' && props?.className && props.className.length === 0)
127
+ delete props.className;
128
+ return [
129
+ input.tagName,
130
+ props,
131
+ ...(input.children || []).map(hastToMinimarkNode),
132
+ ];
133
+ }
140
134
  }
141
135
  /**
142
136
  * Apply syntax highlighting to all code blocks in a Comark tree
@@ -164,19 +158,24 @@ export async function highlightCodeBlocks(tree, options = {}) {
164
158
  if (codeBlocks.length === 0)
165
159
  return tree;
166
160
  const highlightedResults = await Promise.all(codeBlocks.map(({ node }) => {
167
- const attrs = node[1];
168
- const codeContent = node[2][2];
169
- return highlightCode(codeContent, attrs, options);
161
+ return highlightCode(node[2][2], node[1], options);
170
162
  }));
171
163
  const newNodes = JSON.parse(JSON.stringify(tree.nodes));
172
164
  for (let i = 0; i < codeBlocks.length; i++) {
173
165
  const { node, path } = codeBlocks[i];
174
166
  const result = highlightedResults[i];
175
- const { nodes } = result;
167
+ const preNode = result.nodes[0];
168
+ const preNodeClasses = typeof preNode === 'string'
169
+ ? ['shiki', options.themes?.light?.name]
170
+ : (Array.isArray(preNode[1].class)
171
+ ? preNode[1].class
172
+ : String(preNode[1].class).split(' '));
173
+ const codeChildren = preNode[2].slice(2);
174
+ const children = typeof preNode === 'string' ? preNode : codeChildren.filter(element => element !== '\n');
176
175
  const preAttrs = node[1];
177
176
  const newPreAttrs = {
178
177
  ...preAttrs,
179
- class: ['shiki', options.themes?.light?.name, options.themes?.dark?.name ? `dark:${options.themes?.dark?.name}` : ''].filter(Boolean).join(' '),
178
+ class: [...preNodeClasses, options.themes?.dark?.name ? `dark:${options.themes?.dark?.name}` : ''].filter(Boolean).join(' '),
180
179
  tabindex: '0',
181
180
  };
182
181
  if (options.preStyles) {
@@ -201,7 +200,7 @@ export async function highlightCodeBlocks(tree, options = {}) {
201
200
  }
202
201
  const codeEl = node[2];
203
202
  const codeAttrs = codeEl[1] || {};
204
- const newPreNode = ['pre', newPreAttrs, ['code', codeAttrs, ...nodes]];
203
+ const newPreNode = ['pre', newPreAttrs, ['code', codeAttrs, ...children]];
205
204
  if (path.length === 1) {
206
205
  newNodes[path[0]] = newPreNode;
207
206
  }
@@ -7,5 +7,5 @@ interface SecurityOptions extends PropsValidationOptions {
7
7
  */
8
8
  blockedTags?: string[];
9
9
  }
10
- export default function security(options: SecurityOptions): ComarkPlugin;
10
+ export default function security(options?: SecurityOptions): ComarkPlugin;
11
11
  export {};
@@ -1,6 +1,6 @@
1
1
  import { visit } from 'comark/utils';
2
2
  import { validateProps } from '../internal/props-validation';
3
- export default function security(options) {
3
+ export default function security(options = {}) {
4
4
  const { blockedTags = [], allowedLinkPrefixes, allowedImagePrefixes, allowedProtocols, defaultOrigin, allowDataImages, } = options;
5
5
  const dropSet = new Set(blockedTags.map(t => t.toLowerCase()));
6
6
  const propsOptions = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "comark",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Components in Markdown (Comark) parser with streaming support for Vue, React, Svelte and HTML",
6
6
  "author": "",
7
7
  "license": "MIT",
@@ -59,6 +59,7 @@
59
59
  "devDependencies": {
60
60
  "@nuxt/kit": "^4.4.2",
61
61
  "@shikijs/primitive": "^4.0.2",
62
+ "@shikijs/twoslash": "^4.0.2",
62
63
  "@types/js-yaml": "^4.0.9",
63
64
  "github-slugger": "^2.0.0",
64
65
  "hast-util-to-string": "^3.0.1",
@@ -66,13 +67,14 @@
66
67
  "minimark": "0.2.0",
67
68
  "mitata": "^1.0.34",
68
69
  "tsx": "^4.21.0",
70
+ "twoslash": "^0.3.6",
69
71
  "vitest": "^4.1.1"
70
72
  },
71
73
  "dependencies": {
72
74
  "@comark/markdown-it": "^0.3.2",
73
75
  "entities": "^8.0.0",
74
- "js-yaml": "^4.1.1",
75
76
  "htmlparser2": "^12.0.0",
77
+ "js-yaml": "^4.1.1",
76
78
  "markdown-exit": "1.0.0-beta.9"
77
79
  }
78
80
  }