@tanstack/markdown 0.0.11 → 0.0.13

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
@@ -42,6 +42,8 @@ export function Article({ source }: { source: string }) @{
42
42
 
43
43
  TanStack Markdown targets controlled technical content. It supports the Markdown used by blogs and docs, then spends its remaining complexity budget on deterministic output, renderer parity, malformed-input resilience, and small entry points. It is intentionally not a complete CommonMark, GFM, MDX, or general content-processing implementation.
44
44
 
45
+ For syntax highlighting, use the tested [TanStack Highlight adapter](./docs/guides/syntax-highlighting.md#tanstack-highlight-adapter). It registers only the languages you choose and returns escaped token markup inside Markdown-owned code containers.
46
+
45
47
  ## Documentation
46
48
 
47
49
  - [Overview](./docs/overview.md)
package/dist/html.js CHANGED
@@ -83,7 +83,7 @@ function renderInlines(nodes, options) {
83
83
  function renderCodeBlock(node, options) {
84
84
  const lang = node.lang ?? 'plaintext';
85
85
  const preAttrs = [
86
- 'class="tm-code"',
86
+ `class="tm-code${options.codeLineNumbers ? ' tm-code--line-numbers' : ''}"`,
87
87
  `data-lang="${escapeAttr(lang)}"`,
88
88
  node.title ? `data-code-title="${escapeAttr(node.title)}"` : '',
89
89
  node.file ? `data-filename="${escapeAttr(node.file)}"` : '',
package/dist/inline.js CHANGED
@@ -319,12 +319,20 @@ function findDelimiter(value, start, delimiter, budget) {
319
319
  return -1;
320
320
  if (value[index - 1] === '\\')
321
321
  continue;
322
+ if (!value.startsWith(delimiter, index))
323
+ continue;
324
+ if (value[index + 1] === delimiter) {
325
+ const close = findDelimiter(value, index + 2, delimiter + delimiter, budget);
326
+ if (close > index + 2) {
327
+ index = close + 1;
328
+ continue;
329
+ }
330
+ }
322
331
  if (delimiter === '~~' && (value[index - 1] === '~' || value[index + 2] === '~'))
323
332
  continue;
324
333
  if (delimiter[0] === '_' && !canUseUnderscore(value, index, delimiter.length, false))
325
334
  continue;
326
- if (value.startsWith(delimiter, index))
327
- return index;
335
+ return index;
328
336
  }
329
337
  return -1;
330
338
  }
package/dist/octane.js CHANGED
@@ -88,7 +88,7 @@ function renderCodeBlockOctane(node, options, key) {
88
88
  }
89
89
  : undefined;
90
90
  const pre = h(options, 'pre', {
91
- className: 'tm-code',
91
+ className: `tm-code${options.codeLineNumbers ? ' tm-code--line-numbers' : ''}`,
92
92
  'data-lang': lang,
93
93
  ...(node.title ? { 'data-code-title': node.title } : {}),
94
94
  ...(node.file ? { 'data-filename': node.file } : {}),
package/dist/react.d.ts CHANGED
@@ -1,6 +1,11 @@
1
- import type { ComponentType, ReactElement, ReactNode } from 'react';
1
+ import type { ComponentPropsWithoutRef, ComponentType, JSX, ReactElement, ReactNode } from 'react';
2
2
  import type { BlockNode, InlineNode, MarkdownInput, RenderOptions } from './types.js';
3
+ type IntrinsicElementName = keyof JSX.IntrinsicElements;
3
4
  type ComponentMap = Partial<Record<string, string | ComponentType<any>>>;
5
+ export type MarkdownComponentProps<TagName extends IntrinsicElementName> = ComponentPropsWithoutRef<TagName>;
6
+ export type MarkdownComponents = Partial<{
7
+ [TagName in IntrinsicElementName]: IntrinsicElementName | ComponentType<MarkdownComponentProps<TagName>>;
8
+ }> & Record<string, string | ComponentType<any> | undefined>;
4
9
  export interface MarkdownReactOptions extends RenderOptions {
5
10
  components?: ComponentMap;
6
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAmC,UAAU,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAErI,KAAK,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAExE,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,UAAU,CAAC,EAAE,YAAY,CAAA;CAC1B;AAED,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,EAAE,aAAa,GAAG,YAAY,CAE9E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,oBAAyB,GAAG,SAAS,CAGvG;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,oBAAyB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CA+DhH;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,oBAAyB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAwC/G"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAElG,OAAO,KAAK,EAAE,SAAS,EAAmC,UAAU,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAErI,KAAK,oBAAoB,GAAG,MAAM,GAAG,CAAC,iBAAiB,CAAA;AACvD,KAAK,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAExE,MAAM,MAAM,sBAAsB,CAAC,OAAO,SAAS,oBAAoB,IAAI,wBAAwB,CAAC,OAAO,CAAC,CAAA;AAE5G,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC;KACtC,OAAO,IAAI,oBAAoB,GAAG,oBAAoB,GAAG,aAAa,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;CACzG,CAAC,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,CAAA;AAEzD,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,UAAU,CAAC,EAAE,YAAY,CAAA;CAC1B;AAED,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,EAAE,aAAa,GAAG,YAAY,CAE9E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,oBAAyB,GAAG,SAAS,CAGvG;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,oBAAyB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CA+DhH;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,oBAAyB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAwC/G"}
package/dist/react.js CHANGED
@@ -91,7 +91,7 @@ function renderCodeBlockReact(node, options, key) {
91
91
  }
92
92
  : undefined;
93
93
  const pre = h(options, 'pre', {
94
- className: 'tm-code',
94
+ className: `tm-code${options.codeLineNumbers ? ' tm-code--line-numbers' : ''}`,
95
95
  'data-lang': lang,
96
96
  ...(node.title ? { 'data-code-title': node.title } : {}),
97
97
  ...(node.file ? { 'data-filename': node.file } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/markdown",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
5
  "description": "A tiny, fast, deterministic Markdown renderer for blogs and documentation.",
6
6
  "license": "MIT",
@@ -86,6 +86,7 @@
86
86
  }
87
87
  },
88
88
  "devDependencies": {
89
+ "@tanstack/highlight": "0.0.6",
89
90
  "@tanstack/intent": "^0.3.6",
90
91
  "@types/node": "^24.0.0",
91
92
  "@types/react": "^19.0.0",
@@ -120,12 +121,15 @@
120
121
  "corpus:audit:tanstack": "tsx scripts/audit-tanstack-corpus.ts",
121
122
  "corpus:audit:external": "tsx scripts/audit-external-corpus.ts",
122
123
  "docs:verify": "node scripts/verify-docs.mjs",
123
- "test:skills": "intent validate skills --check",
124
+ "test:skills": "intent validate skills --check && pnpm run skills:check-version",
125
+ "skills:check-version": "node scripts/sync-skill-version.mjs --check",
126
+ "skills:sync-version": "node scripts/sync-skill-version.mjs",
124
127
  "skills:stale": "intent stale",
125
128
  "conformance": "tsx scripts/conformance.ts",
126
129
  "bench": "tsx scripts/bench.ts",
127
130
  "size": "tsx scripts/measure-size.ts",
128
131
  "research": "pnpm run conformance && pnpm run size && pnpm run bench",
129
- "verify": "pnpm test && pnpm run typecheck && pnpm run build && pnpm run docs:verify && pnpm run test:skills && pnpm run research && pnpm pack --dry-run"
132
+ "verify": "pnpm test && pnpm run typecheck && pnpm run build && pnpm run docs:verify && pnpm run test:skills && pnpm run research && pnpm pack --dry-run",
133
+ "version": "pnpm run skills:sync-version"
130
134
  }
131
135
  }
@@ -7,7 +7,7 @@ description: >
7
7
  metadata:
8
8
  type: core
9
9
  library: '@tanstack/markdown'
10
- library_version: '0.0.10'
10
+ library_version: '0.0.13'
11
11
  requires:
12
12
  - 'render-markdown'
13
13
  sources:
@@ -9,7 +9,7 @@ description: >
9
9
  metadata:
10
10
  type: core
11
11
  library: '@tanstack/markdown'
12
- library_version: '0.0.10'
12
+ library_version: '0.0.13'
13
13
  requires:
14
14
  - 'render-markdown'
15
15
  sources:
@@ -392,11 +392,10 @@ export const html = renderHtml(source)
392
392
  Correct:
393
393
 
394
394
  ````ts
395
- import {
396
- renderNodesToHtml,
397
- renderTokens,
398
- tokenize,
399
- } from '@tanstack/highlight'
395
+ import { createHighlighter } from '@tanstack/highlight/core'
396
+ import { plaintext } from '@tanstack/highlight/languages/plaintext'
397
+ import { ts } from '@tanstack/highlight/languages/ts'
398
+ import { createTanStackMarkdownHighlighter } from '@tanstack/highlight/markdown'
400
399
  import { renderHtml } from '@tanstack/markdown/html'
401
400
 
402
401
  const source = `\`\`\`ts file="app.ts" {2}
@@ -404,18 +403,12 @@ const one = 1
404
403
  const two = 2
405
404
  \`\`\``
406
405
 
406
+ const highlighter = createHighlighter({
407
+ languages: [plaintext, ts],
408
+ })
409
+
407
410
  export const html = renderHtml(source, {
408
- highlighter(code, lang, options) {
409
- const result = tokenize(code, { lang })
410
- return renderNodesToHtml(
411
- renderTokens(result.tokens, {
412
- decorations: options?.highlightLines?.map((line) => ({
413
- lines: line,
414
- className: 'is-highlighted',
415
- })),
416
- }),
417
- )
418
- },
411
+ highlighter: createTanStackMarkdownHighlighter(highlighter),
419
412
  })
420
413
  ````
421
414
 
@@ -9,7 +9,7 @@ metadata:
9
9
  type: framework
10
10
  library: '@tanstack/markdown'
11
11
  framework: 'octane'
12
- library_version: '0.0.10'
12
+ library_version: '0.0.13'
13
13
  requires:
14
14
  - 'render-markdown'
15
15
  sources:
@@ -8,7 +8,7 @@ description: >
8
8
  metadata:
9
9
  type: lifecycle
10
10
  library: '@tanstack/markdown'
11
- library_version: '0.0.10'
11
+ library_version: '0.0.13'
12
12
  requires:
13
13
  - 'render-markdown'
14
14
  sources:
@@ -58,23 +58,15 @@ Fix: Separate trusted and untrusted entry points, keep trusted callbacks disable
58
58
  ### Check: Return trusted code contents only
59
59
 
60
60
  Expected:
61
-
62
61
  ```ts
63
62
  import { renderHtml } from '@tanstack/markdown/html'
64
- import {
65
- renderNodesToHtml,
66
- renderTokens,
67
- tokenize,
68
- } from '@tanstack/highlight'
69
-
70
- const source = '```ts {1}\nconst answer = 42\n```'
71
-
72
- export const html = renderHtml(source, {
73
- highlighter(code, lang, options) {
74
- const result = tokenize(code, { lang: lang ?? 'plaintext' })
75
- const decorations = options?.highlightLines?.map((lines) => ({ lines, className: 'is-highlighted' }))
76
- return renderNodesToHtml(renderTokens(result.tokens, { lineNumbers: options?.lineNumbers, decorations }))
77
- },
63
+ import { createHighlighter } from '@tanstack/highlight/core'
64
+ import { plaintext } from '@tanstack/highlight/languages/plaintext'
65
+ import { ts } from '@tanstack/highlight/languages/ts'
66
+ import { createTanStackMarkdownHighlighter } from '@tanstack/highlight/markdown'
67
+ const highlighter = createHighlighter({ languages: [plaintext, ts] })
68
+ export const html = renderHtml('```ts {1}\nconst answer = 42\n```', {
69
+ highlighter: createTanStackMarkdownHighlighter(highlighter),
78
70
  })
79
71
  ```
80
72
 
@@ -135,7 +127,7 @@ import { parseMarkdown } from '@tanstack/markdown/parser'
135
127
  const cache = new Map<string, MarkdownDocument>()
136
128
 
137
129
  export function renderCachedArticle(key: string, source: string): string {
138
- const cacheKey = `markdown-0.0.10:${key}`
130
+ const cacheKey = `markdown-0.0.13:${key}`
139
131
  let document = cache.get(cacheKey)
140
132
  if (!document) {
141
133
  document = parseMarkdown(source, {
@@ -9,7 +9,7 @@ metadata:
9
9
  type: framework
10
10
  library: '@tanstack/markdown'
11
11
  framework: 'react'
12
- library_version: '0.0.10'
12
+ library_version: '0.0.13'
13
13
  requires:
14
14
  - 'render-markdown'
15
15
  sources:
@@ -48,31 +48,34 @@ export function Article({ source }: { source: string }) {
48
48
  ### Replace emitted links with an application component
49
49
 
50
50
  ```tsx
51
- import type { ComponentProps } from 'react'
52
- import { Markdown } from '@tanstack/markdown/react'
53
-
54
- function ArticleLink(props: ComponentProps<'a'>) {
55
- const external = props.href?.startsWith('http') ?? false
56
-
57
- return (
58
- <a
59
- {...props}
60
- rel={external ? 'noreferrer' : props.rel}
61
- target={external ? '_blank' : props.target}
62
- />
63
- )
64
- }
51
+ import {
52
+ Markdown,
53
+ type MarkdownComponents,
54
+ } from '@tanstack/markdown/react'
55
+
56
+ const components = {
57
+ a(props) {
58
+ const external = props.href?.startsWith('http') ?? false
59
+ return (
60
+ <a
61
+ {...props}
62
+ rel={external ? 'nofollow noopener noreferrer' : props.rel}
63
+ target={external ? '_blank' : props.target}
64
+ />
65
+ )
66
+ },
67
+ } satisfies MarkdownComponents
65
68
 
66
69
  export function Article({ source }: { source: string }) {
67
70
  return (
68
71
  <article>
69
- <Markdown components={{ a: ArticleLink }}>{source}</Markdown>
72
+ <Markdown components={components}>{source}</Markdown>
70
73
  </article>
71
74
  )
72
75
  }
73
76
  ```
74
77
 
75
- `components` is keyed by emitted intrinsic or custom-element tag names.
78
+ Known intrinsic keys infer their matching React props. Arbitrary extension component tag names remain supported.
76
79
 
77
80
  ### Parse once before repeated React rendering
78
81
 
@@ -9,7 +9,7 @@ description: >
9
9
  metadata:
10
10
  type: core
11
11
  library: '@tanstack/markdown'
12
- library_version: '0.0.10'
12
+ library_version: '0.0.13'
13
13
  sources:
14
14
  - 'TanStack/markdown:docs/quick-start.md'
15
15
  - 'TanStack/markdown:docs/core-concepts/document-model.md'
@@ -1,6 +1,6 @@
1
1
  # AST and Options Reference
2
2
 
3
- This reference targets `@tanstack/markdown@0.0.10`. Shared public types are
3
+ This reference targets `@tanstack/markdown@0.0.13`. Shared public types are
4
4
  exported from `@tanstack/markdown`.
5
5
 
6
6
  ## Entry Points