Skip to content

Commit 6f8f4b3

Browse files
atinuxfarnabaz
andauthored
fix: parse indented raw html children in html blocks (#208)
Co-authored-by: Farnabaz <farnabaz@gmail.com>
1 parent 8decb92 commit 6f8f4b3

13 files changed

Lines changed: 210 additions & 284 deletions

File tree

.oxlintrc.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
"$schema": "./node_modules/oxlint/configuration_schema.json",
33
"plugins": ["typescript", "oxc", "import"],
44
"rules": {
5-
"no-control-regex": "off",
6-
"no-unused-expressions": "off",
7-
"no-unused-vars": "off",
85
"typescript/no-useless-empty-export": "off"
96
},
107
"overrides": [

docs/app/utils/components-manifest.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
11
import { pascalCase } from 'scule'
22
import { localComponents, localComponentLoaders } from '#content/components'
3-
import {
4-
UPageHero,
5-
UPageSection,
6-
UPageCard,
7-
UPageCTA,
8-
UButton,
9-
UBadge,
10-
UPageFeature,
11-
UPageGrid,
12-
UPricingPlan,
13-
} from '#components'
143

154
// Define component imports for the docs app
165
const components: Record<string, () => Promise<unknown>> = {

docs/server/api/generate-page.post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Before generating, fetch the documentation you need:
4747
3. fetchComponentDoc for EACH component you plan to use — learn exact props and slots`
4848

4949
export default defineEventHandler(async (event) => {
50-
const { prompt, mode = 'nuxt-ui', structure } = await readBody(event)
50+
const { prompt, mode = 'nuxt-ui', _structure } = await readBody(event)
5151

5252
if (!prompt?.trim()) {
5353
throw createError({ statusCode: 400, message: 'Prompt is required' })

packages/comark/SPEC/HTML/block+component.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ Default Slot
2020
{
2121
"$": { "html": 1, "block": 1 }
2222
},
23-
[
24-
"component",
25-
{},
26-
"Default Slot"
27-
]
23+
"::component\nDefault Slot\n::"
2824
]
2925
]
3026
}
@@ -34,18 +30,18 @@ Default Slot
3430

3531
```html
3632
<hello>
37-
<component>
38-
Default Slot
39-
</component>
33+
::component
34+
Default Slot
35+
::
4036
</hello>
4137
```
4238

4339
## Markdown
4440

4541
```md
4642
<hello>
47-
::component
48-
Default Slot
49-
::
43+
::component
44+
Default Slot
45+
::
5046
</hello>
5147
```

packages/comark/SPEC/HTML/block.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@ Hello **World**
1818
{
1919
"$": { "html": 1, "block": 1 }
2020
},
21-
"Hello ",
22-
[
23-
"strong",
24-
{},
25-
"World"
26-
]
21+
"Hello **World**"
2722
]
2823
]
2924
}
@@ -33,7 +28,7 @@ Hello **World**
3328

3429
```html
3530
<hello>
36-
Hello <strong>World</strong>
31+
Hello **World**
3732
</hello>
3833
```
3934

packages/comark/SPEC/HTML/mix+paragraph.md

Lines changed: 0 additions & 55 deletions
This file was deleted.

packages/comark/SPEC/HTML/mix.md

Lines changed: 0 additions & 125 deletions
This file was deleted.

packages/comark/src/internal/parse/auto-close/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export function autoCloseMarkdown(markdown: string): string {
2222
let inFrontmatter = false
2323
let inBlockMath = false
2424
let tableStart = -1
25+
// Tag name when inside a raw-text HTML element (`<style>`, `<script>`,
26+
// `<pre>`, `<textarea>`). Their bodies must be passed through verbatim —
27+
// any `::root`/`**` markers there are CSS/JS/text, not Comark/markdown.
28+
let inRawTextElement: 'style' | 'script' | 'pre' | 'textarea' | null = null
29+
const RAW_TEXT_OPEN_RE = /^<(script|pre|style|textarea)(\s|>|$)/i
2530

2631
const componentStack: Array<{
2732
depth: number
@@ -34,6 +39,23 @@ export function autoCloseMarkdown(markdown: string): string {
3439
const line = lines[idx]
3540
const trimmed = line.trim()
3641

42+
// Raw-text HTML element: skip all line-level processing inside its body,
43+
// and update the open/close state. Open and close can sit on the same
44+
// line (e.g. `<style>body { ... }</style>` inline).
45+
if (inRawTextElement) {
46+
const closeRe = new RegExp(`</${inRawTextElement}\\s*>`, 'i')
47+
if (closeRe.test(line)) inRawTextElement = null
48+
continue
49+
}
50+
const rawTextMatch = trimmed.match(RAW_TEXT_OPEN_RE)
51+
if (rawTextMatch) {
52+
const tag = rawTextMatch[1].toLowerCase() as 'style' | 'script' | 'pre' | 'textarea'
53+
// Stay "inside" only if the close tag isn't already on this line.
54+
const closeRe = new RegExp(`</${tag}\\s*>`, 'i')
55+
if (!closeRe.test(line)) inRawTextElement = tag
56+
continue
57+
}
58+
3759
// Frontmatter: only starts at document line 0
3860
if (idx === 0 && trimmed === '---') {
3961
inFrontmatter = true

packages/comark/src/internal/parse/html/html_block_rule.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
// BASED ON https://github.com/serkodev/markdown-exit/blob/fe1351070a5841426223ab4a0a5c7874ba2b1257/packages/markdown-exit/src/parser/block/rules/html_block.ts
1+
// Standard CommonMark html_block rule — see
2+
// https://spec.commonmark.org/0.30/#html-blocks
3+
//
4+
// 7 sequences in priority order, each: [opener regex, closer regex, can-terminate-paragraph]
25

36
import type { StateBlock } from 'markdown-exit'
47
import block_names from './html_blocks.ts'
58
import { HTML_OPEN_CLOSE_TAG_RE } from './html_re.ts'
69

7-
// An array of opening and corresponding closing sequences for html tags,
8-
// last argument defines whether it can terminate a paragraph or not
9-
//
1010
const HTML_SEQUENCES: [RegExp, RegExp, boolean][] = [
11-
[new RegExp(`${HTML_OPEN_CLOSE_TAG_RE.source}\\s*$`), /^<\/[^>]+>$/, true],
1211
[/^<(script|pre|style|textarea)(?=(\s|>|$))/i, /<\/(script|pre|style|textarea)>/i, true],
1312
[/^<!--/, /-->/, true],
1413
[/^<\?/, /\?>/, true],
@@ -22,9 +21,7 @@ export default function html_block(state: StateBlock, startLine: number, endLine
2221
let pos = state.bMarks[startLine] + state.tShift[startLine]
2322
let max = state.eMarks[startLine]
2423

25-
// if it's indented more than 3 spaces, it should be a code block
2624
if (state.sCount[startLine] - state.blkIndent >= 4) return false
27-
2825
if (state.src.charCodeAt(pos) !== 0x3c /* < */) return false
2926

3027
let lineText = state.src.slice(pos, max)
@@ -33,23 +30,16 @@ export default function html_block(state: StateBlock, startLine: number, endLine
3330
for (; i < HTML_SEQUENCES.length; i++) {
3431
if (HTML_SEQUENCES[i][0].test(lineText)) break
3532
}
36-
3733
if (i === HTML_SEQUENCES.length) return false
3834

39-
if (silent) {
40-
// true if this sequence can be a terminator, false otherwise
41-
return HTML_SEQUENCES[i][2]
42-
}
35+
if (silent) return HTML_SEQUENCES[i][2]
4336

4437
let nextLine = startLine + 1
4538

46-
// If we are here - we detected HTML block.
47-
// Let's roll down till block end.
48-
if (i !== 0 && !HTML_SEQUENCES[i][1].test(lineText)) {
39+
// Walk forward until the closer regex matches or we hit a blank line.
40+
if (!HTML_SEQUENCES[i][1].test(lineText)) {
4941
for (; nextLine < endLine; nextLine++) {
50-
if (state.sCount[nextLine] < state.blkIndent) {
51-
break
52-
}
42+
if (state.sCount[nextLine] < state.blkIndent) break
5343

5444
pos = state.bMarks[nextLine] + state.tShift[nextLine]
5545
max = state.eMarks[nextLine]
@@ -61,9 +51,9 @@ export default function html_block(state: StateBlock, startLine: number, endLine
6151
}
6252
}
6353
}
64-
state.line = nextLine
6554

66-
const token = lineText.startsWith('</') ? state.push('html_block_close', '', -1) : state.push('html_block', '', 1)
55+
state.line = nextLine
56+
const token = state.push('html_block', '', 1)
6757
token.map = [startLine, nextLine]
6858
token.content = state.getLines(startLine, nextLine, state.blkIndent, true)
6959

0 commit comments

Comments
 (0)