diff --git a/packages/nuemark/src/parse-blocks.js b/packages/nuemark/src/parse-blocks.js
index 9aa1ef33..5cdd5e34 100644
--- a/packages/nuemark/src/parse-blocks.js
+++ b/packages/nuemark/src/parse-blocks.js
@@ -114,7 +114,7 @@ export function parseBlocks(lines, capture) {
// tag
if (c == '[' && trimmed.endsWith(']') && !trimmed.includes('][')) {
const tag = parseTag(line.slice(1, -1))
- block = { is_tag: true, ...tag, body: [] }
+ block = { is_tag: true, ...tag, name: tag.name || 'div', body: [] }
return blocks.push(block)
}
@@ -182,7 +182,9 @@ function processNestedBlocks(block, capture) {
const body = block.body.join('\n')
try {
- if (body && name && isYAML(body.trim())) {
+ // TODO: add additional check for native html tags
+ // maybe new syntax? e.g. `[yaml-tag]:\n\thi` (note colon)
+ if (body && isYAML(body.trim())) {
let data = parseYAML(body)
if (Array.isArray(data)) data = { items: data }
Object.assign(block.data, data)
diff --git a/packages/nuemark/src/parse-inline.js b/packages/nuemark/src/parse-inline.js
index f32acc53..3b1e816f 100644
--- a/packages/nuemark/src/parse-inline.js
+++ b/packages/nuemark/src/parse-inline.js
@@ -78,7 +78,7 @@ const PARSERS = [
// parse tag
const tag = parseTag(str.slice(1, i).trim())
const { name } = tag
- const is_footnote = name[0] == '^'
+ const is_footnote = name && name[0] == '^'
const end = i + 1
// footnote?
@@ -88,7 +88,7 @@ const PARSERS = [
}
// normal tag
- if (name == '!' || isValidName(name)) return { is_tag: true, ...tag, end }
+ if (!name || name == '!' || isValidName(name)) return { is_inline: true, is_tag: true, ...tag, name: tag.name || 'span', end }
return { text: c }
}
diff --git a/packages/nuemark/src/render-tag.js b/packages/nuemark/src/render-tag.js
index eef01463..21716c77 100644
--- a/packages/nuemark/src/render-tag.js
+++ b/packages/nuemark/src/render-tag.js
@@ -6,6 +6,17 @@ import { elem } from './render-blocks.js'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
+// mostly the same as first block from <../../nuejs/src/fn.js>, but excludes: html, head
+const HTML_TAGS = 'a abbr acronym address applet area article aside audio b base basefont bdi bdo big\
+ blockquote body br button canvas caption center circle cite clipPath code col colgroup data datalist\
+ dd defs del details dfn dialog dir div dl dt ellipse em embed fieldset figcaption figure font footer\
+ foreignObject form frame frameset g header hgroup h1 h2 h3 h4 h5 h6 hr i iframe image img\
+ input ins kbd keygen label legend li line link main map mark marker mask menu menuitem meta meter\
+ nav noframes noscript object ol optgroup option output p param path pattern picture polygon polyline\
+ pre progress q rect rp rt ruby s samp script section select small source span strike strong style sub\
+ summary sup svg switch symbol table tbody td template text textarea textPath tfoot th thead time\
+ title tr track tspan tt u ul use var video wbr'.split(' ')
+
// built-in tags
const TAGS = {
@@ -22,22 +33,14 @@ const TAGS = {
},
block() {
- const { render, attr, blocks } = this
+ const { render, attr, data, blocks, name } = this
const divs = sectionize(blocks)
const html = !divs || !divs[1] ? render(blocks) :
divs.map(blocks => elem('div', render(blocks))).join('\n')
- return elem(attr.popover ? 'dialog' : 'div', attr, html)
- },
-
-
- button(data) {
- const { href } = data
- const label = this.renderInline(data.label || data._) || this.innerHTML || ''
-
- return href ? elem('a', { ...this.attr, href, role: 'button' }, label) :
- elem('button', this.attr, label)
+ if (this.to_block) Object.assign(attr, data)
+ return elem(attr.popover ? 'dialog' : name, attr, html)
},
define() {
@@ -52,7 +55,6 @@ const TAGS = {
return html && elem('dl', this.attr, html.join('\n'))
},
-
image() {
const { attr, data } = this
const { caption, href, loading = 'lazy' } = data
@@ -74,6 +76,15 @@ const TAGS = {
return elem('figure', attr, img)
},
+ inline() {
+ const { name, attr, data, opts } = this
+
+ const content = data._
+ delete data._
+ if (this.to_inline) Object.assign(attr, data)
+
+ return elem(name, attr, this.renderInline(content, opts))
+ },
list() {
const items = this.sections || getListItems(this.blocks)
@@ -109,7 +120,6 @@ const TAGS = {
return elem('video', attr, this.innerHTML)
},
-
// shortcut
'!': function() {
const tag = getMimeType(this.data._).startsWith('video') ? TAGS.video : TAGS.image
@@ -142,9 +152,22 @@ export function renderIcon(name, symbol, icon_dir) {
export function renderTag(tag, opts = {}) {
const tags = { ...TAGS, ...opts.tags }
- const fn = tags[tag.name || 'block']
+ const tag_fn = tag.to_block ? 'block' : tag.to_inline ? 'inline' : tag.name
+ const fn = tags[tag_fn]
+
+ if (!fn) {
+ // native html tags
+ if (HTML_TAGS.includes(tag.name)) {
+ // inline / block without blocks
+ if (tag.is_inline || !tag.blocks?.length) tag.to_inline = true
+ // block
+ else tag.to_block = true
+
+ return renderTag(tag)
+ }
- if (!fn) return renderIsland(tag, opts.data)
+ return renderIsland(tag, opts.data)
+ }
const data = { ...opts.data, ...extractData(tag.data, opts.data) }
@@ -307,4 +330,4 @@ export function parseTable(lines) {
})
return { rows, ...specs }
-}
\ No newline at end of file
+}
diff --git a/packages/nuemark/test/block.test.js b/packages/nuemark/test/block.test.js
index bd43c492..844f5479 100644
--- a/packages/nuemark/test/block.test.js
+++ b/packages/nuemark/test/block.test.js
@@ -30,6 +30,60 @@ test('nested lists', () => {
})
+test('block html tag including non-html tag', () => {
+ const { blocks } = parseBlocks(['[section.hi]', ' content', ' [subtag "data"]'])
+ const parent = blocks[0]
+ expect(blocks.length).toBe(1)
+ expect(parent.is_tag).toBe(true)
+ expect(parent.attr.class).toBe('hi')
+ expect(parent.blocks.length).toBe(2)
+ const children = blocks[0].blocks
+ expect(children[0].is_content).toBe(true)
+ expect(children[1].is_tag).toBe(true)
+ expect(children[1].data).toEqual({ _: "data" })
+
+ const html = renderBlocks(blocks)
+ expect(html).toStartWith(' content
no content
') +}) + +test('block html tag with starting ul', () => { + const { blocks } = parseBlocks(['[div]', ' - hi', ' - hello']) + expect(blocks.length).toBe(1) + expect(blocks[0].blocks.length).toBe(1) + + const html = renderBlocks(blocks) + expect(html).toBe('content
{
test('[svg] nested in [button]', () => {
const html = renderLines(['[button href="/"]', ` [svg ${svgpath}] *Yo*`])
- expect(html).toBe(' Yo')
+ expect(html).toBe('')
})