Skip to content

Commit

Permalink
feat: block and inline native html tags
Browse files Browse the repository at this point in the history
  • Loading branch information
nobkd committed Feb 3, 2025
1 parent a07dd2d commit a6986a2
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 19 deletions.
4 changes: 1 addition & 3 deletions packages/nuemark/src/parse-inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ const PARSERS = [
}

// normal tag
if (name == '!' || isValidName(name)) return { is_tag: true, ...tag, end }
// span
if (!name) return { is_span: true, ...tag, end }
if (!name || name == '!' || isValidName(name)) return { is_inline: true, is_tag: true, ...tag, name: tag.name || 'span', end }

return { text: c }
}
Expand Down
1 change: 0 additions & 1 deletion packages/nuemark/src/render-inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export function renderToken(token, opts = {}) {
const { text } = token

return text ? text :
token.is_span ? elem('span', token.attr, renderInline(token.data?._)) :
token.is_format ? formatText(token, opts) :
token.is_var ? renderVariable(token.name, data) :
token.is_image ? renderImage(token) :
Expand Down
31 changes: 27 additions & 4 deletions packages/nuemark/src/render-tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -22,13 +33,13 @@ const TAGS = {
},

block() {
const { render, attr, blocks } = this
const { render, attr, 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)
return elem(attr.popover ? 'dialog' : name || 'div', attr, html)
},


Expand Down Expand Up @@ -142,9 +153,21 @@ export function renderIcon(name, symbol, icon_dir) {

export function renderTag(tag, opts = {}) {
const tags = { ...TAGS, ...opts.tags }
const fn = tags[tag.name || 'block']
const fn = tags[!tag.is_block && tag.name || 'block']

if (!fn) {
// native html tags
if (HTML_TAGS.includes(tag.name)) {
// inline / block without blocks, but with '_' data
if (tag.is_inline || (!tag.blocks?.length && tag.data?._)) return elem(tag.name, tag.attr, renderInline(tag.data?._, opts))

if (!fn) return renderIsland(tag, opts.data)
// block
tag.is_block = true
return renderTag(tag)
}

return renderIsland(tag, opts.data)
}

const data = { ...opts.data, ...extractData(tag.data, opts.data) }
const { blocks } = tag
Expand Down
26 changes: 26 additions & 0 deletions packages/nuemark/test/block.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ 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('<section class="hi"><p>content</p>')
expect(html).toInclude('<subtag custom="subtag"><script type="application/json">{"_":"data"}</script></subtag>')
expect(html).toEndWith('</section>')
})

test('block html tag without children with content', () => {
const { blocks } = parseBlocks(['[section "content"]', 'no content'])
expect(blocks.length).toBe(2)

const html = renderBlocks(blocks)
expect(html).toBe('<section>content</section>\n<p>no content</p>')
})

test('nested tag data', () => {
const { blocks } = parseBlocks(['[hello]', '', '', ' foo: bar', '', ' bro: 10'])
expect(blocks[0].data).toEqual({ foo: "bar", bro: 10 })
Expand Down
33 changes: 22 additions & 11 deletions packages/nuemark/test/inline.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,23 +217,33 @@ test('parse simple image', () => {
expect(img.href).toBe('yo.svg')
})

// anonymous tag
test('inline span', () => {
const md = 'hello [.green "world"]!'
const [text, span] = parseInline(md)
expect(span.is_span).toBeTrue()

const result = renderInline(md)
expect(result).toBe('hello <span class="green">world</span>!')
const html = renderInline('hello [.green "world"]!')
expect(html).toBe('hello <span class="green">world</span>!')
})

test('empty inline span', () => {
const result = renderInline('[.myclass#myid]')
expect(result).toStartWith('<span ')
expect(result).toInclude('class="myclass"')
expect(result).toInclude('id="myid"')
expect(result).toEndWith('></span>')
const html = renderInline('[.myclass#myid]')
expect(html).toStartWith('<span ')
expect(html).toInclude('class="myclass"')
expect(html).toInclude('id="myid"')
expect(html).toEndWith('></span>')
})

// named default html tag
test('inline html tag', () => {
const html = renderInline('[b "*content*"]')
expect(html).toBe('<b><em>content</em></b>')
})

test('empty inline html tag', () => {
const html = renderInline('[del.pink.border#myid]')
expect(html).toStartWith('<del ')
expect(html).toInclude('id="myid"')
expect(html).toInclude('class="pink border"')
expect(html).toEndWith('></del>')
})

// parse tags and args
test('inline tag', () => {
Expand All @@ -245,6 +255,7 @@ test('inline tag with reflink', () => {
const els = parseInline('[tip] and [link][foo]')
const [ tag, and, link] = els
expect(tag.is_tag).toBeTrue()
expect(tag.name).toBe('tip')
expect(link.is_reflink).toBeTrue()
})

Expand Down

0 comments on commit a6986a2

Please sign in to comment.