Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

nuemark

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nuemark - npm Package Compare versions

Comparing version 0.1.0-dev to 0.1.0

src/browser/nuemark.js

10

index.js
import { render } from './src/render.js'
import { renderLines } from './src/render.js'

@@ -7,3 +7,3 @@

export function nuemark(str, opts) {
return render(str.split('\n'), opts)
return renderLines(str.split('\n'), opts)
}

@@ -13,6 +13,10 @@

export function nuemarkdown(str, opts) {
delete opts?.data?.draw_sections
return nuemark(str, opts).html
}
// returns { meta, sections, headings, links }
export { parsePage } from './src/parse.js'
export { renderPage } from './src/render.js'
export { parsePage } from './src/parse.js'
{
"name": "nuemark",
"version": "0.1.0-dev",
"description": "Markdown variant for rich, interactive content",
"version": "0.1.0",
"description": "Markdown dialect for rich, interactive web content",
"homepage": "https://nuejs.org",

@@ -13,2 +13,5 @@ "license": "MIT",

},
"main": "src/browser/nuemark.js",
"dependencies": {

@@ -15,0 +18,0 @@ "js-yaml": "^4.1.0",

@@ -6,2 +6,58 @@

# Work in progress
# Nuekit
Nuemark is a Markdown dialect for rich, interactive content. Use it to create marketing pages, documentation pages, and blog entries without leaving the content. Even you all-mighty front page can be expressed with Nuemark:
![Web page in read and edit mode](https://nuejs.org/img/nuemark-content-big.png)
*In above*: The content and the result. Nuemark puts you into content-first mindset.
### Documentation
[What is Nuemark?](https://nuejs.org/blog/introducing-nuemark/)
[User guide](https://nuejs.org/docs/concepts/nuemark.html)
[Tag reference](https://nuejs.org/docs/reference/nuemark-tags.html)
[API docs](https://nuejs.org/docs/reference/nuemark-api.html)
### Getting Started
```
# Install Bun (if not done yet)
curl -fsSL https://bun.sh/install | bash
# Install website generator. This includes Nuemark
bun install nuekit --global
# Install Nuemark demo (as seen on this page)
bun create nue@latest
```
Choose “nuemark-demo” to see the above big screenshot in action.
Refer to [getting started docs](https://nuejs.org/docs/#node) if you prefer Node.
### The big picture
The ultimate goal of Nue is to build a content first alternative to **Vercel** and **Netlify**, which is extremely fast and ridiculously easy to use.
![Nue Roadmap](https://nuejs.org/img/roadmap4-big.png)
#### Why Nue?
- [Content first](https://nuejs.org/docs/why-nue/content-first.html)
- [Extreme performance](https://nuejs.org/docs/why-nue/extreme-performance.html)
- [Closer to standards](https://nuejs.org/docs/why-nue/closer-to-standards.html)
### Contributing
Please see [contributing.md](/CONTRIBUTING.md)
### Community
Please see [GitHub discussions](https://github.com/nuejs/nue/discussions)

@@ -10,19 +10,5 @@

const { str, getValue } = valueGetter(input)
let [name, ...attribs] = str.split(/\s+/)
const self = { attr: {}, data: {} }
const [specs, ...attribs] = str.split(/\s+/)
const self = { ...parseSpecs(specs), data: {} }
// #id or .class
const i = name.search(/[\#\.]/)
if (!i) {
attribs.unshift(name)
name = undefined
// <name>.class
} else if (i > 0) {
attribs.unshift(name.slice(i))
name = name.slice(0, i)
}
for (const el of attribs) {

@@ -38,15 +24,8 @@ const [key, val] = el.split('=')

} else {
// #id.foo.bar
if ('.#'.includes(key[0])) {
Object.assign(self.attr, parseAttr(key))
} else {
if (self.data._) self.data[key] = true
else self.data._ = getValue(key) || key
}
if (self.data._) self.data[key] = true
else self.data._ = getValue(key) || key
}
}
return { ...self, name }
return self
}

@@ -76,12 +55,27 @@

// .foo#bar.baz -> class: ['foo', 'bar'], id: 'bar'
// tabs.foo#bar.baz -> { name: 'tabs', class: ['foo', 'bar'], id: 'bar' }
export function parseSpecs(str) {
const self = { name: str, attr: {} }
const i = str.search(/[\#\.]/)
if (i >= 0) {
self.name = str.slice(0, i) || null
self.attr = parseAttr(str.slice(i))
}
return self
}
export function parseAttr(str) {
const attr = {}
// classes
const classes = []
const ret = {}
str.replace(/\.([\w\-]+)/g, (_, el) => classes.push(el))
str.replace(/#([\w\-]+)/, (_, el) => ret.id = el)
if (classes[0]) ret.class = classes.join(' ')
if (classes[0]) attr.class = classes.join(' ')
return Object.keys(ret)[0] && ret
// id
str.replace(/#([\w\-]+)/, (_, el) => attr.id = el)
return attr
}

@@ -88,0 +82,0 @@

import { parseAttr, parseSpecs, parseComponent } from './component.js'
import { loadAll, load as parseYAML } from 'js-yaml'
import { parseComponent } from './component.js'
import { ISOMORPHIC } from './tags.js'
import { marked } from 'marked'
const NL = '\n'
// returns { meta, sections, headings, links }
export function parsePage(lines) {
if (typeof lines == 'string') lines = lines.split(NL)
// { meta, sections, headings, links }
export function parsePage(lines) {
const sections = [], headings = [], links = {}
const { meta, rest } = parseMeta(lines)
for (const block of parseBlocks(rest)) {
const { name, data, body } = block
const sections = parseSections(rest)
const headings = [], links = {}
let isomorphic
if (body) {
const content = body.join('\n')
if (name) Object.assign(data, getNestedData(content))
else data.content = content.split('---')
delete block.body
}
for (const section of sections) {
const blocks = section.blocks = []
// component
if (data) {
sections.push(block)
for (const block of parseBlocks(section.lines)) {
const { name, data, body } = block
// markdown
} else {
const tokens = marked.lexer(block.join('\n'))
Object.assign(links, tokens.links)
headings.push(...tokens.filter(el => el.type == 'heading').map(parseHeading))
sections.push({ md: block, tokens })
if (name && ISOMORPHIC.includes(name)) isomorphic = true
// component body
if (body) {
const content = body.join(NL)
if (name) Object.assign(data, getNestedData(content))
else data.content = content.split('---')
delete block.body
}
// component or fenced code block
if (data || block.code) {
blocks.push(block)
// markdown
} else {
const tokens = marked.lexer(block.join(NL))
Object.assign(links, tokens.links)
headings.push(...tokens.filter(el => el.type == 'heading').map(el => {
return { level: el.depth, ...parseHeading(el.text) }
}))
blocks.push({ md: block, tokens })
}
}

@@ -40,17 +57,19 @@ }

return { meta, sections, headings, links }
return { meta, sections, headings, links, isomorphic }
}
export function parseHeading({ depth, text, id }) {
const i = text.indexOf(' {#')
export function parseHeading(text) {
const i = text.indexOf('{')
if (i > 0 && text.endsWith('}')) {
id = text.slice(i + 3, -1)
text = text.slice(0, i)
const attr = parseAttr(text.slice(i+1, -1).trim())
return { text: text.slice(0, i).trim(), ...attr }
}
return { level: depth, text, id: id || createHeaderId(text) }
return { text, id: createHeaderId(text) }
}
export function createHeaderId(text) {
let hash = text.replace(/\W/g, '-').replace(/-+/g, '-').toLowerCase()
let hash = text.replace(/'/g, '').replace(/\W/g, '-').replace(/-+/g, '-').toLowerCase()
if (hash[0] == '-') hash = hash.slice(1)

@@ -63,3 +82,2 @@ if (hash.endsWith('-')) hash = hash.slice(0, -1)

export function parseMeta(lines) {
const isFront = (line) => line == '---'
var start = 0, end = -1

@@ -69,10 +87,14 @@

const line = lines[i]
if (!start) { if (isFront(line)) start = i + 1 }
else if (isFront(line)) { end = i; break }
const is_front = line == '---'
if (!start) {
if (is_front) start = i + 1
else if (line.trim()) return { rest: lines, meta: {} }
}
else if (is_front) { end = i; break }
}
const front = start ? lines.slice(start, end).join('\n') : ''
const front = start ? lines.slice(start, end).join(NL) : ''
return {
meta: front ? parseYAML(front) : {},
meta: front ? parseYAML(front) || {} : {},
rest: lines.slice(end + 1)

@@ -99,5 +121,30 @@ }

export function parseSections(lines) {
const len = lines.length
const sections = []
let section = []
function push(attr) {
sections.push({ lines: section, attr })
}
push()
lines.forEach(line => {
if (line.startsWith('---')) {
section = [] // must be before push
const i = line.indexOf('- ')
push(i > 0 ? parseAttr(line.slice(i + 2).trim()) : null)
} else {
section.push(line)
}
})
return sections
}
export function parseBlocks(lines) {
const blocks = []
let md, comp
let md, fenced, comp
let spacing

@@ -107,8 +154,17 @@

// fenced code start
const ltrim = line.trimStart()
// fenced code start/end
if (line.startsWith('```')) {
if (!fenced) {
fenced = { code: [], ...parseSpecs(line.slice(3).trim()) }
} else {
blocks.push(fenced)
fenced = null
}
return
}
// comment
if (ltrim.startsWith('//# ')) return
// code line
if (fenced) return fenced.code.push(line)
// component

@@ -131,3 +187,3 @@ if (line[0] == '[' && line.slice(-1) == ']' && !line.includes('][')) {

if (!ltrim) comp.body?.push(line)
if (!line.trimStart()) comp.body?.push(line)
else if (!getIndent(line)) comp = null

@@ -138,5 +194,7 @@ }

if (!comp) {
if (line.trimStart().startsWith('//')) return
if (!md) blocks.push(md = [])
md.push(line)
}
})

@@ -148,4 +206,4 @@

function getIndent(line='') {
const ltrim = line.trimStart()
return line.length - ltrim.length
const trim = line.trimStart()
return line.length - trim.length
}

@@ -152,0 +210,0 @@

import { tags, elem, join, concat } from './tags.js'
import { parsePage, parseHeading } from './parse.js'
import { tags, elem } from './tags.js'
import { parseAttr } from './component.js'
import { marked } from 'marked'

@@ -8,35 +9,51 @@

export function renderPage(page, opts) {
const { data={}, lib=[], highlight } = opts
const { lib=[] } = opts
const data = { ...opts.data, ...page.meta }
const draw_sections = data?.draw_sections || page.sections[1]
const section_attr = data.sections || []
const ret = []
// syntax highlighter
marked.setOptions({ highlight })
const html = page.sections.map(el => {
const { name, md, attr } = el
// section_attr
page.sections.forEach((section, i) => {
const comp = name && lib.find(el => el.name == name)
const alldata = { ...data, ...el.data, attr }
const tag = tags[name]
const html = join(section.blocks.map(el => {
const { name, md, attr } = el
const comp = name && lib.find(el => [name, toCamelCase(name)].includes(el.name))
const alldata = { ...data, ...el.data, attr }
const tag = tags[name]
// tag
return tag ? tag(alldata, opts) :
// tag
return tag ? tag(alldata, opts) :
// server component
comp ? comp.render(alldata, lib) :
// component
comp ? comp.render(alldata, lib) :
// markdown
md ? renderMD(md, mergeLinks(page.links, data.links)) :
// fenced code
el.code ? renderCodeBlock(el, opts.highlight) :
// island
name ? renderIsland(el) :
// markdown
md ? renderMarkdown(md, mergeLinks(page.links, data.links)) :
// section
tags.section(alldata, opts)
// island
name ? renderIsland(el) :
}).join('\n')
// generic layout
tags.layout(alldata, opts)
return { ...page, html }
}))
const attr = section.attr || parseAttr(section_attr[i] || '')
ret.push(draw_sections ? elem('section', attr, html) : html)
})
return { ...page, html: join(ret) }
}
export function render(lines, opts={}) {
function toCamelCase(str) {
return str.split('-').map(el => el[0].toUpperCase() + el.slice(1)).join('')
}
export function renderLines(lines, opts={}) {
const page = parsePage(lines)

@@ -46,2 +63,10 @@ return renderPage(page, opts)

export function renderCodeBlock({ name, code, attr }, fn) {
// console.info(name, code, attr, fn)
if (name) attr.class = concat(`syntax-${name}`, attr.class)
const body = join(code)
return elem('pre', attr, fn ? fn(body) : body)
}
// export function renderPage()

@@ -58,8 +83,10 @@

/*
Marked does not support editing of the AST abstract syntax tree regarding links
So we have no use of the already tokenized tree and have to re-parse here :(
Sadly, Marked does not have a modifiable abstract syntax tree (AST) so
internally we must render all markdown blocks twice:
https://github.com/markedjs/marked/issues/3135
Looking for other Markdown implementations if this becomes a performance bottleneck.
*/
export function renderMD(md, links) {
export function renderMarkdown(md, links) {
md.push('')

@@ -71,3 +98,3 @@

}
return marked.parse(md.join('\n'))
return marked.parse(join(md))
}

@@ -99,10 +126,10 @@

heading(html, level, raw) {
const plain = parseHeading({ text: raw })
const rich = parseHeading({ text: html })
const plain = parseHeading(raw)
const cls = plain.class
const title = plain.text.replaceAll('"', '')
const rich = parseHeading(html)
return [
`<h${level} id="${plain.id}">${rich.text}`,
`<a href="#${plain.id}" title='Permlink for "${plain.text}"'></a>`,
`</h${level}>\n`
].join('')
delete plain.text
const a = elem('a', { href: `#${plain.id}`, title: `Permlink for '${title}'` })
return elem(`h${level}`, plain, a + rich.text)
},

@@ -109,0 +136,0 @@

@@ -24,5 +24,8 @@

import { renderCodeBlock } from './render.js'
import { nuemarkdown } from '../index.js'
import { parseInline } from 'marked'
import { nuemarkdown } from '..'
// list all tags that require a client-side Web Component
export const ISOMORPHIC = ['tabs']

@@ -43,8 +46,8 @@ export const tags = {

table({ attr, head, items=[] }) {
const ths = toArray(head).map(val => elem('th', val))
table({ attr, head, _, items=[] }) {
const ths = toArray(head || _).map(val => elem('th', parseInline(val.trim())))
const thead = elem('thead', elem('tr', join(ths)))
const trs = items.map(row => {
const tds = toArray(row).map(val => elem('td', val))
const tds = toArray(row).map(val => elem('td', parseInline(val.trim())))
return elem('tr', join(tds))

@@ -56,15 +59,4 @@ })

grid(data, opts) {
const { attr, content=[]} = data
const extra = { style: '--cols: 1fr 1fr 1fr', class: concat('grid', attr.class) }
const divs = content.map((str, i) => {
const attr = i + 1 == content.length ? { style: '--colspan: 3' } : {}
return elem('div', attr, nuemarkdown(str, opts))
})
return elem('section', { ...attr, ...extra }, join(divs))
},
section(data, opts) {
// generic layout block
layout(data, opts) {
const { content=[]} = data

@@ -78,3 +70,3 @@ // const bc = data.block_class || 'block'

return elem('section', data.attr, join(divs))
return elem(divs[1] ? 'section' : 'div', data.attr, join(divs))
},

@@ -123,3 +115,3 @@

// inneer <source> tags
// inner <source> tags
const html = sources.map(src => elem('source', { src, type: getMimeType(src) }) )

@@ -140,3 +132,3 @@

tabs(data, opts) {
const { attr, name='tab', content=[], _ } = data
const { attr, key='tab', content=[], _ } = data
const half = Math.round(content.length / 2)

@@ -148,3 +140,3 @@ const t = _ || data.tabs

const html = t ? el : nuemarkdown(el, opts)
return elem('a', { href: `#${name}-${i+1}` }, html )
return elem('a', { href: `#${key}-${i+1}` }, html )
})

@@ -154,6 +146,6 @@

const html = nuemarkdown(el, opts)
return elem('li', { id: `${name}-${i+1}` }, html )
return elem('li', { id: `${key}-${i+1}` }, html )
})
return elem('section', { is: 'nue-tabs', class: 'tabs', ...attr },
return elem('section', { is: 'nuemark-tabs', class: 'tabs', ...attr },
elem('nav', join(tabs)) +

@@ -164,2 +156,5 @@ elem('ul', join(panes))

/* later
codetabs(data, opts) {

@@ -174,4 +169,4 @@ const { content=[] } = data

content.forEach((code, i) => {
const type = types[i] || data.type || ''
content[i] = join([('``` ' + type).trim(), code, '```'])
const name = types[i] || data.type || ''
content[i] = renderCodeBlock({ name, code, attr: {} }, opts.highlight)
})

@@ -182,2 +177,16 @@

grid(data, opts) {
const { attr, content=[], _='a'} = data
const { cols, colspan } = getGridCols(content.length, _)
const extra = { style: `--cols: ${cols}`, class: concat('grid', attr.class) }
const divs = content.map((str, i) => {
const attr = colspan && i + 1 == content.length ? { style: `--colspan: ${colspan}` } : {}
return elem('div', attr, nuemarkdown(str, opts))
})
return elem('section', { ...attr, ...extra }, join(divs))
},
*/
}

@@ -221,3 +230,3 @@

function join(els, separ='\n') {
export function join(els, separ='\n') {
return els?.join ? els.join(separ) : els

@@ -227,3 +236,3 @@ }

// concat two strings (for class attribute)
function concat(a, b) {
export function concat(a, b) {
return join([a || '', b || ''], ' ').trim()

@@ -233,7 +242,7 @@ }

export function createPicture(img_attr, data) {
const { small, offset=768 } = data
const { small, offset=750 } = data
const sources = [small, img_attr.src].map(src => {
const prefix = src == small ? 'max' : 'min'
const media = `(${prefix}-width: ${offset}px)`
const media = `(${prefix}-width: ${parseInt(offset)}px)`
return elem('source', { src, media, type: getMimeType(src) })

@@ -247,2 +256,17 @@ })

/* more complex grids later
const GRID = {
a: [2, 3, 2, '2/2', 3, '3/3', 4, 3],
b: [2, '2/2', '3/3']
}
export function getGridCols(am, variant='a') {
const val = GRID[variant][am -2]
if (!val) return { cols: '1fr 1fr' }
const [count, span] = val.toString().split('/')
return { cols: Array(1 * count).fill('1fr').join(' '), colspan: 1 * span }
}
*/
function getVideoAttrs(data) {

@@ -249,0 +273,0 @@ const keys = 'autoplay controls loop muted poster preload src width'.split(' ')

import { nuemarkdown } from '../index.js'
import { promises as fs } from 'node:fs'
import { nuemarkdown } from '..'

@@ -5,0 +5,0 @@ const opts = {}

import { parseComponent, valueGetter, parseAttr } from '../src/component.js'
import { parseMeta, parseBlocks, parsePage } from '../src/parse.js'
import { renderIsland, render } from '../src/render.js'
import { parseMeta, parseBlocks, parseSections, parsePage, parseHeading } from '../src/parse.js'
import { parseComponent, valueGetter, parseAttr, parseSpecs } from '../src/component.js'
import { renderIsland, renderLines } from '../src/render.js'
import { nuemarkdown } from '../index.js'
import { tags } from '../src/tags.js'
test('nested code with comment', () => {
const { html } = renderLines(['[.hey]', ' // not rendered', ' ```', ' // here', ' ```'])
expect(html).toBe('<div class="hey"><pre>// here</pre></div>')
})
test('render fenced code', () => {
const { html } = renderLines(['``` md.foo#bar', '// hey', '```'])
expect(html).toBe('<pre class="syntax-md foo" id="bar">// hey</pre>')
})
test('parse fenced code', () => {
const blocks = parseBlocks(['# Hey', '``` md.foo#bar', '// hey', '[foo]', '```'])
const [ hey, fenced ] = blocks
expect(fenced.name).toBe('md')
expect(fenced.attr).toEqual({ class: 'foo', id: 'bar' })
expect(fenced.code).toEqual([ "// hey", "[foo]" ])
})
test('[!] img', () => {
const icon = tags['!']({ _: 'cat' })
expect(icon).toStartWith('<img src="/img/cat.svg"')
const img = tags['!']({ _: 'img.png' })

@@ -28,4 +52,4 @@ expect(img).toStartWith('<img src="img.png"')

test('[tabs] attr', () => {
const html = tags.tabs({ _: 't1, t2', name: 'hey', content: ['c1', 'c2'] })
expect(html).toInclude('<section is="nue-tabs" class="tabs">')
const html = tags.tabs({ _: 't1, t2', key: 'hey', content: ['c1', 'c2'] })
expect(html).toInclude('<section is="nuemark-tabs" class="tabs">')
expect(html).toInclude('<nav><a href="#hey-1">t1</a>')

@@ -37,3 +61,3 @@ expect(html).toInclude('<li id="hey-2"><p>c2</p>')

const html = tags.tabs({ content: 'abcd'.split(''), attr: { id: 'hey' } })
expect(html).toInclude('<section is="nue-tabs" class="tabs" id="hey">')
expect(html).toInclude('<section is="nuemark-tabs" class="tabs" id="hey">')
expect(html).toInclude('<nav><a href="#tab-1"><p>a</p>')

@@ -86,20 +110,19 @@ expect(html).toInclude('<li id="tab-2"><p>d</p>')

test('[section]', () => {
test('[layout]', () => {
const attr = { id: 'epic' }
const data = { count: 10 }
const single = tags.section({ attr, data, content: ['foo'] })
const single = tags.layout({ attr, data, content: ['foo'] })
expect(single).toInclude('<section id="epic">')
expect(single).toInclude('<div id="epic">')
expect(single).toInclude('<p>foo</p>')
const double = tags.section({ attr, data, content: ['foo', 'bar'] })
const double = tags.layout({ attr, data, content: ['foo', 'bar'] })
expect(double).toInclude('<section id="epic">')
expect(double).toInclude('block block-2')
})
test('[section] with nested component', () => {
test('[layout] with nested component', () => {
const content = ['# Hello', '## World\n[image "joo.png"]']
const html = tags.section({ content })
const html = tags.layout({ content })
expect(html).toInclude('<h1 id="hello">')
expect(html).toInclude('block block-2')
expect(html).toInclude('<div><h2 id="world">')
expect(html).toInclude('img src="joo.png"')

@@ -124,14 +147,20 @@ })

// page rendering
test('render sections', () => {
const lines = ['a', 'a', '--- #a.b', 'b', 'b', '---', 'c', 'c']
const { html } = renderLines(lines, { data: { sections: ['#foo']}})
expect(html).toStartWith('<section id="foo"><p>a')
expect(html).toInclude('<section class="b" id="a"><p>b')
expect(html).toInclude('<section><p>c')
})
test('generic section', () => {
const { html } = render(['[.info]', ' # Hello', ' para', ' ---', ' World'])
expect(html).toInclude('block block-2')
const { html } = renderLines(['[.info]', ' # Hello', ' para', ' ---', ' World'])
expect(html).toInclude('<section class="info">')
expect(html).toInclude('<p>para</p>')
})
test('reflinks', () => {
const links = { dude: '//hey.net "boom"' }
const lines = ['[hey][yolo] [dude][dude]', '[.foo]', '[yolo]: yolo.co "lol"']
const { html } = render(lines, { data: { links } })
const { html } = renderLines(lines, { data: { links } })

@@ -142,11 +171,22 @@ expect(html).toInclude('<a href="yolo.co" title="lol">hey</a>')

test('header id', () => {
const { html } = render(['# Hey baari on jotain {#custom}'])
expect(html).toInclude('<h1 id="custom">')
expect(html).toInclude('<a href="#custom"')
test('parseHeading', () => {
const h1 = parseHeading('# Hey { #me-too.hey.yo }')
expect(h1.text).toBe('# Hey')
expect(h1.id).toBe('me-too')
expect(h1.class).toBe('hey yo')
const h2 = parseHeading('## Hey')
expect(h2.text).toBe('## Hey')
expect(h2.id).toBe('hey')
})
test('heading id', () => {
const { html } = renderLines(['# Hey _boy_ { #me.too }'])
expect(html).toInclude('<h1 class="too" id="me">')
expect(html).toInclude('<a href="#me"')
expect(html).toInclude('Hey <em>boy</em>')
})
test('page island', () => {
const { html } = render(['yo', '[hey]', ' bar: 2'])
const { html } = renderLines(['yo', '[hey]', ' bar: 2'])
expect(html).toInclude('<p>yo</p>')

@@ -157,10 +197,28 @@ expect(html).toInclude('nue-island island="hey"')

// rendering blocks
test('renderIsland', () => {
const attr = { id: 'epic' }
const data = { count: 10 }
const island = renderIsland({ name: 'foo', attr, data })
expect(island).toInclude('id="epic" island="foo"')
expect(island).toInclude('{"count":10}')
})
// page parsing
test('parse sections', () => {
const els = parseSections(['a', 'a'])
expect(els[0].lines).toEqual(['a', 'a'])
expect(els.length).toBe(1)
})
test('[!] parse', () => {
const page = parsePage(['[! "/foo"]'])
const { data, name } = page.sections[0]
expect(data._).toBe('/foo')
expect(name).toBe('!')
test('parse sections', () => {
const lines = ['a', 'a', '--- #a.b', 'b', 'b', '---', 'c', 'c']
const sections = parseSections(lines)
const [a, b, c] = sections
expect(sections.length).toBe(3)
expect(a.lines).toEqual(['a', 'a'])
expect(b.attr).toEqual({ id: "a", class: "b" })
expect(c.lines).toEqual(['c', 'c'])
})

@@ -170,11 +228,22 @@

test('parse page', () => {
const page = parsePage(['# Hello', '## World', '[foo]: bar', '[hey foo=1]', ' bar: 2'])
const { sections } = page
const page = parsePage(['# Hello', '## World', '[foo]: bar', '[tabs foo=1]', ' bar: 2'])
expect(page.isomorphic).toBe(true)
expect(page.links).toHaveProperty('foo')
expect(page.headings.length).toBe(2)
expect(sections.length).toBe(2)
expect(sections[1].data).toEqual({ foo: 1, bar: 2 })
const { blocks } = page.sections[0]
expect(blocks.length).toBe(2)
expect(blocks[1].data).toEqual({ foo: 1, bar: 2 })
})
test('parse page: ! component', () => {
const page = parsePage(['[! "/foo"]'])
const { data, name } = page.sections[0].blocks[0]
expect(data._).toBe('/foo')
expect(name).toBe('!')
})
// blocks within sections
test('parse blocks', () => {

@@ -210,17 +279,3 @@ const blocks = parseBlocks(['Hello', '[foo]', ' bar: 10', 'World'])

// rendering blocks
test('renderIsland', () => {
const attr = { id: 'epic' }
const data = { count: 10 }
const island = renderIsland({ name: 'foo', attr, data })
expect(island).toInclude('id="epic" island="foo"')
expect(island).toInclude('{"count":10}')
})
// parsing components
test('valueGetter', () => {

@@ -234,3 +289,2 @@ const { str, getValue } = valueGetter(`foo="yo" bar="hey dude"`)

test('parseAttr', () => {
expect(parseAttr('#foo.bar')).toEqual({ id: 'foo', class: 'bar'})
expect(parseAttr('.bar#foo')).toEqual({ id: 'foo', class: 'bar'})

@@ -240,2 +294,6 @@ expect(parseAttr('.bar#foo.baz')).toEqual({ id: 'foo', class: 'bar baz'})

test('parseSpecs', () => {
expect(parseSpecs('tabs')).toEqual({ name: 'tabs', attr: {} })
expect(parseSpecs('tabs.#foo.bar')).toEqual({ name: 'tabs', attr: { id: 'foo', class: 'bar' }})
})

@@ -245,3 +303,3 @@ test('parseComponent', () => {

expect(parseComponent('#foo.bar')).toEqual({
attr: { id: "foo", class: "bar" }, data: {},
name: null, attr: { id: "foo", class: "bar" }, data: {},
})

@@ -265,3 +323,3 @@

expect(parseComponent('info "Sure ??" #alert class="boss"')).toEqual({
expect(parseComponent('info#alert "Sure ??" class="boss"')).toEqual({
name: 'info', attr: { class:'boss', id: 'alert' }, data: { _: 'Sure ??' },

@@ -272,5 +330,54 @@ })

/*
Required:
test('syntax highlight', async () => {
bun add react
bun add react-dom
*/
test('JSX component', async () => {
try {
// import React SSR (server side rendering) API method
const { renderToString } = await import('react-dom/server')
// import custom JSX components
const jsx = await import('./react-lib')
// make them compatible with Nuemark
const lib = Object.keys(jsx).map(name => {
return { name, render: (data) => renderToString(jsx[name](data)) }
})
// render JSX with Nuemark
const html = nuemarkdown('[my-test]', { lib, data: { message: 'Hello' } })
expect(html).toBe('<h1 style="color:red">Hello</h1>')
// react not imported
} catch (ignored) {
console.info('JSX test skipped')
}
})
// The following tags are released later
test.skip('[grid]', () => {
const html = tags.grid({ content: 'abcdefg'.split(''), attr: { class: 'foo' } })
expect(html).toInclude('<section class="grid foo" style="--cols: 1fr 1fr 1fr"')
expect(html).toInclude('<div style="--colspan: 3"><p>g</p>')
})
test.skip('grid columns', () => {
const grid = getGridCols(5, 'a')
expect(grid.cols).toBe('1fr 1fr')
expect(grid.colspan).toBe(2)
})
test.skip('nue color', async () => {
try {
const nuecolor = await import('nuecolor')

@@ -280,14 +387,19 @@ const opts = { highlight: nuecolor.default }

// syntax block
const { html } = render(['``` md', '# hey', '```'], opts)
expect(html).toInclude('<pre><code class="language-md">')
expect(html).toInclude('<b class=hl-heading> hey</b>')
const { html } = renderLines(['``` md.foo', '# hey', '```'], opts)
expect(html).toInclude('<pre class="syntax-md foo">')
expect(html).toInclude('<b class=hl-char>#</b> hey<')
// code tabs
const tabs = tags.codetabs({ _: 't1, t2', content: ['# c1', '*c2*'], type: 'md' }, opts)
expect(tabs).toInclude('<a href="#tab-1">t1</a>')
expect(tabs).toInclude('<b class=hl-heading> c1</b>')
expect(tabs).toInclude('</code></pre>')
expect(tabs).toInclude('<b class=hl-char>*</b>')
expect(tabs).toInclude('</pre>')
} catch(ignore) { console.info(ignore) /* highlighter not found */ }
// highlighter not found
} catch(ignore) {
console.info('nuecolor not found')
}
})
})
---
# draw_sections: true
# sections: [.a#b, .c#d]
---
# Hey
[codetabs tabs="yo | boy"]
hey
---
joe
[.some#dorg]
kamaa
Hello
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc