From 43111f0f981f21b34cadbe8f5b3ba13a709c2b61 Mon Sep 17 00:00:00 2001 From: Tero Piirainen Date: Fri, 1 Nov 2024 14:32:16 +0200 Subject: [PATCH] Large Nuemark simplification/refactor, support for 'standard' footnotes --- packages/nuejs.org/@global/navigation.yaml | 2 +- packages/nuekit/src/layout/page.js | 15 +-- packages/nuekit/test/nuekit.test.js | 2 +- packages/nuemark/index.js | 12 +- packages/nuemark/src/parse-blocks.js | 51 +++++--- .../src/{document.js => parse-document.js} | 115 ++++++++---------- packages/nuemark/src/parse-inline.js | 4 +- packages/nuemark/src/render-blocks.js | 43 +++++-- packages/nuemark/src/render-inline.js | 11 +- packages/nuemark/src/render-tag.js | 13 +- packages/nuemark/test/block.test.js | 74 +++++------ packages/nuemark/test/document.test.js | 50 ++++++-- packages/nuemark/test/inline.test.js | 10 +- 13 files changed, 215 insertions(+), 187 deletions(-) rename packages/nuemark/src/{document.js => parse-document.js} (51%) diff --git a/packages/nuejs.org/@global/navigation.yaml b/packages/nuejs.org/@global/navigation.yaml index 038aeec8..98fe6975 100644 --- a/packages/nuejs.org/@global/navigation.yaml +++ b/packages/nuejs.org/@global/navigation.yaml @@ -43,7 +43,7 @@ globalnav: # header toolbar (complementary) toolbar: - - "@tipiirai": //x.com/tipiirai "social" + - tipiirai: //x.com/tipiirai "social" - 6.1k: //github.com/nuejs/nue "github pill" # burger menu diff --git a/packages/nuekit/src/layout/page.js b/packages/nuekit/src/layout/page.js index d41157dd..42031c1a 100644 --- a/packages/nuekit/src/layout/page.js +++ b/packages/nuekit/src/layout/page.js @@ -96,17 +96,16 @@ export function renderSlots(data, lib) { } -// Markdown extensions -function createTags(lib, data) { +// custom components as Markdown extensions (tags) +function convertToTags(components, data) { const tags = {} - // components as Markdown tags - lib.forEach(comp => { + components.forEach(comp => { const { name } = comp if (name && !SLOTS.includes(name)) { tags[name] = function(data) { const { attr, innerHTML } = this - return comp.render({ attr, ...data, innerHTML }, lib) + return comp.render({ attr, ...data, innerHTML }, components) } } }) @@ -120,12 +119,14 @@ export function renderPage({ doc, data, lib }) { const slots = renderSlots(data, comps) const tags = { - ...createTags(lib, data), + ...convertToTags(lib, data), 'page-list': renderPageList, toc: doc.renderTOC } - const content = doc.render({ data, tags }) + // nuemark opts: { data, sections, heading_ids, links, tags } + const { heading_ids, sections, links } = data + const content = doc.render({ data, heading_ids, sections, links, tags }) //
...
if (!slots.main && data.main !== false) { diff --git a/packages/nuekit/test/nuekit.test.js b/packages/nuekit/test/nuekit.test.js index facc7344..262e98ce 100644 --- a/packages/nuekit/test/nuekit.test.js +++ b/packages/nuekit/test/nuekit.test.js @@ -181,7 +181,7 @@ test('content collection', async () => { expect(coll[0]).toMatchObject({ title: "First", slug: "first-a.html", basedir: "blog" }) }) -test('basig page generation', async () => { +test('basic page generation', async () => { await write('index.md', '# Hello\nWorld') const kit = await getKit() diff --git a/packages/nuemark/index.js b/packages/nuemark/index.js index fd1ba6be..af83161f 100644 --- a/packages/nuemark/index.js +++ b/packages/nuemark/index.js @@ -1,18 +1,18 @@ +import { parseDocument } from './src/parse-document.js' import { renderLines } from './src/render-blocks.js' -import { parseDocument } from './src/document.js' const EOL = /\r\n|\r|\n/ -export function nuemark(str, opts) { - return renderLines(str.split(EOL), opts) +export function nuedoc(str, opts) { + return parseDocument(str.split(EOL), opts) } -export function nuedoc(str) { - return parseDocument(str.split(EOL)) +export function nuemark(str, opts) { + return renderLines(str.split(EOL), opts) } /* utilities */ export { renderInline } from './src/render-inline.js' export { parseSize } from './src/render-tag.js' -export { elem } from './src/document.js' +export { elem } from './src/render-blocks.js' diff --git a/packages/nuemark/src/parse-blocks.js b/packages/nuemark/src/parse-blocks.js index 41cf23e0..29956afd 100644 --- a/packages/nuemark/src/parse-blocks.js +++ b/packages/nuemark/src/parse-blocks.js @@ -1,14 +1,19 @@ import { load as parseYAML } from 'js-yaml' -import { parseInline } from './parse-inline.js' + +import { parseInline, parseLinkTitle } from './parse-inline.js' import { parseTag } from './parse-tag.js' export function parseBlocks(lines, capture) { const blocks = [] let spaces, block - // capture stuff while recursing things - if (!capture) capture = { reflinks: {}, footnotes: [] } + // capture things while recursing blocks + capture = capture || { + footnotes: [], + reflinks: {}, + noterefs: [] + } lines.forEach(line => { const c = line[0] @@ -92,9 +97,18 @@ export function parseBlocks(lines, capture) { } } - // reflink (can be nested on any level) - const ref = parseReflink(trimmed) - if (ref) return capture.reflinks[ref.key] = ref.link + // reflink or footnote (can be nested on any level) + const ref = parseRef(trimmed) + if (ref) { + const { key, value } = ref + if (key[0] == '^') { + capture.footnotes.push(ref) + capture.noterefs.push(key) + } else { + capture.reflinks[key] = parseLinkTitle(value) + } + return + } // tag @@ -142,26 +156,25 @@ export function parseBlocks(lines, capture) { }) - /* tokenize lists and quotes. parse component data */ blocks.forEach(block => processNestedBlocks(block, capture)) - blocks.footnotes = capture.footnotes - blocks.reflinks = capture.reflinks - return blocks + return { blocks, ...capture } } - - // recursive processing of nested blocks function processNestedBlocks(block, capture) { const { name } = block if (block.is_list) { - block.items = block.entries.map(blocks => parseBlocks(blocks, capture)) + block.items = block.entries.map(lines => { + const { blocks } = parseBlocks(lines, capture) + return blocks + }) } else if (block.is_quote) { - block.blocks = parseBlocks(block.content, capture) + const { blocks } = parseBlocks(block.content, capture) + block.blocks = blocks } else if (block.is_tag) { @@ -179,11 +192,12 @@ function processNestedBlocks(block, capture) { } if (name != 'table') { - if (!block.has_data) block.blocks = parseBlocks(block.body, capture) + const { blocks } = parseBlocks(block.body, capture) + if (!block.has_data) block.blocks = blocks delete block.body if (name == 'define') { - capture.footnotes.push(...getFootnoteIds(block.blocks)) + capture.noterefs.push(...getFootnoteIds(blocks)) } } } @@ -207,13 +221,12 @@ function getFootnoteIds(blocks) { .map(el => `^${el.attr.id}`) } -function parseReflink(str) { +function parseRef(str) { if (str[0] == '[') { const i = str.indexOf(']:') if (i > 1) { const key = str.slice(1, i) - const is_footnote = key[0] == '^' - return { key, is_footnote, link: str.slice(i + 2).trim() } + return { key, value: str.slice(i + 2).trim() } } } } diff --git a/packages/nuemark/src/document.js b/packages/nuemark/src/parse-document.js similarity index 51% rename from packages/nuemark/src/document.js rename to packages/nuemark/src/parse-document.js index 254a9acb..53079caf 100644 --- a/packages/nuemark/src/document.js +++ b/packages/nuemark/src/parse-document.js @@ -1,68 +1,61 @@ import { renderBlocks, createHeadingId } from './render-blocks.js' import { parseLinkTitle } from './parse-inline.js' +import { renderInline } from './render-inline.js' import { parseBlocks } from './parse-blocks.js' import { load as parseYAML } from 'js-yaml' +import { elem } from './render-blocks.js' -export function parseDocument(lines) { - const user_meta = stripMeta(lines) - const blocks = parseBlocks(lines) - const { reflinks } = blocks - - const meta = { - - get title() { - const tag = blocks.find(el => el.is_tag) - return findTitle(blocks) || tag && findTitle(tag.blocks) || '' - }, - get description() { - const block = blocks.find(el => el.is_content) - return block?.content[0] - }, - - ...user_meta +// OPTS: { data, sections, heading_ids, links, tags } +export function parseDocument(lines) { + const meta = stripMeta(lines) + const things = parseBlocks(lines) + const { blocks } = things + + // title + if (!meta.title) { + const tag = blocks.find(el => el.is_tag) + meta.title = getTitle(blocks) || tag && getTitle(tag.blocks) || '' } - const api = { + // descrption + if (!meta.description) { + const block = blocks.find(el => el.is_content) + meta.description = block?.content[0] + } - get sections() { - return sectionize(blocks) || [ blocks ] - }, + const sections = sectionize(blocks) || [ blocks ] - get codeblocks() { - return blocks.filter(el => el.is_code) - }, + function renderSections(opts) { + const classList = Array.isArray(opts.sections) ? opts.sections : [] + const html = [] - addReflink(label, href) { - reflinks.push({ label, ...parseLinkTitle(href) }) - }, - - renderSections(classes, opts) { - const html = [] + sections.forEach((section, i) => { + html.push(elem('section', { class: classList[i] }, renderBlocks(section, opts))) + }) + return html.join('\n\n') + } - api.sections.forEach((blocks, i) => { - html.push(elem('section', { class: classes[i] }, renderBlocks(blocks, opts))) - }) - return html.join('\n\n') + return { + render(opts={}) { + Object.assign(things.reflinks, parseReflinks(opts.links)) + opts = { ...opts, ...things } + const html = opts.sections ? renderSections(opts) : renderBlocks(blocks, opts) + return html + renderFootnotes(things.footnotes) }, renderTOC(attr={}) { - const navs = api.sections.map(renderNav).join('\n').trim() + const navs = sections.map(renderNav).join('\n').trim() return elem('div', { 'aria-label': 'Table of contents', ...attr }, navs) }, - render(opts={ data: meta }) { - let classes = opts.data.sections - if (classes && !Array.isArray(classes)) classes = [] - return classes ? api.renderSections(classes, opts) : renderBlocks(blocks, opts) - }, + codeblocks: blocks.filter(el => el.is_code), + sections, + meta, } - - return { meta, reflinks, ...api } } - export function sectionize(blocks=[]) { const arr = [] let section @@ -88,6 +81,13 @@ export function sectionize(blocks=[]) { } +function renderFootnotes(arr) { + if (!arr.length) return '' + const html = arr.map(el => elem('li', elem('a', { name: el.key }) + renderInline(el.value) )) + return elem('ol', { role: 'doc-endnotes' }, html.join('\n')) +} + + function renderNav(blocks) { const headings = blocks.filter(b => [2, 3].includes(b.level)) @@ -100,7 +100,7 @@ function renderNav(blocks) { return links[0] ? elem('nav', links.join('\n')) : '' } -function findTitle(blocks) { +function getTitle(blocks) { const h1 = blocks?.find(el => el.level == 1) return h1?.text || '' } @@ -126,28 +126,13 @@ export function stripMeta(lines) { return parseYAML(front) } - -/**** utilities ****/ -const SELF_CLOSING = ['img', 'source', 'meta', 'link'] - -export function elem(name, attr, body) { - if (typeof attr == 'string') { body = attr; attr = null } - - const html = [`<${name}${renderAttrs(attr)}>`] - - if (body) html.push(body) - if (!SELF_CLOSING.includes(name)) html.push(``) - return html.join('') -} - - -function renderAttrs(attr) { - const arr = [] - for (const key in attr) { - const val = attr[key] - if (val) arr.push(val === true ? key :`${key}="${val}"`) +function parseReflinks(links={}) { + for (const key in links) { + const href = links[key] + if (typeof href == 'string') links[key] = parseLinkTitle(href) } - return arr[0] ? ' ' + arr.join(' ') : '' + return links } + diff --git a/packages/nuemark/src/parse-inline.js b/packages/nuemark/src/parse-inline.js index 74fbada0..5425ede7 100644 --- a/packages/nuemark/src/parse-inline.js +++ b/packages/nuemark/src/parse-inline.js @@ -83,7 +83,7 @@ const PARSERS = [ // footnote? if (name[0] == '^') { const rel = name.slice(1) - return rel >= 0 || isValidName(rel) ? { is_footnote: true, href: name, end } : { text: c } + return rel >= 0 || isValidName(rel) ? { href: name, end } : { text: c } } // normal tag @@ -197,13 +197,11 @@ export function parseLink(str, is_reflink) { if (str[j + 1] == ')') j++ } - // href & title let { href, title } = parseLinkTitle(str.slice(i + 2, j)) return { href, title, label, - is_footnote: href[0] == '^', is_reflink, end: j + 1 } diff --git a/packages/nuemark/src/render-blocks.js b/packages/nuemark/src/render-blocks.js index d1ef27e5..614da3ab 100644 --- a/packages/nuemark/src/render-blocks.js +++ b/packages/nuemark/src/render-blocks.js @@ -3,18 +3,16 @@ import { glow } from 'nue-glow' import { renderInline, renderTokens } from './render-inline.js' import { renderTable, renderTag, wrap } from './render-tag.js' -import { parseLinkTitle } from './parse-inline.js' import { parseBlocks } from './parse-blocks.js' -import { elem } from './document.js' +// for testing only export function renderLines(lines, opts) { - return renderBlocks(parseBlocks(lines), opts) + const things = parseBlocks(lines) + return renderBlocks(things.blocks, { ...opts, ...things }) } export function renderBlocks(blocks, opts={}) { - opts.reflinks = parseReflinks({ ...blocks.reflinks, ...opts?.data?.links }) - opts.footnotes = blocks.footnotes return blocks.map(b => renderBlock(b, opts)).join('\n') } @@ -40,16 +38,9 @@ function renderList({ items, numbered }, opts) { return elem(numbered ? 'ol' : 'ul', html.join('\n')) } -function parseReflinks(links) { - for (const key in links) { - links[key] = parseLinkTitle(links[key]) - } - return links -} - export function renderHeading(h, opts={}) { const attr = { ...h.attr } - const show_id = opts.data?.heading_ids + const show_id = opts.heading_ids if (show_id && !attr.id) attr.id = createHeadingId(h.text) // anchor @@ -87,4 +78,30 @@ function renderCode({ name, code, attr, data }, opts) { return wrap(klass, html) } +/**** utilities ****/ +const SELF_CLOSING = ['img', 'source', 'meta', 'link'] + +export function elem(name, attr, body) { + if (typeof attr == 'string') { body = attr; attr = null } + + const html = [`<${name}${renderAttrs(attr)}>`] + + if (body) html.push(body) + if (!SELF_CLOSING.includes(name)) html.push(``) + return html.join('') +} + + +function renderAttrs(attr) { + const arr = [] + for (const key in attr) { + const val = attr[key] + if (val) arr.push(val === true ? key :`${key}="${val}"`) + } + return arr[0] ? ' ' + arr.join(' ') : '' +} + + + + diff --git a/packages/nuemark/src/render-inline.js b/packages/nuemark/src/render-inline.js index c57a94f5..5b34915d 100644 --- a/packages/nuemark/src/render-inline.js +++ b/packages/nuemark/src/render-inline.js @@ -1,7 +1,7 @@ import { parseInline, ESCAPED } from './parse-inline.js' import { renderTag } from './render-tag.js' -import { elem } from './document.js' +import { elem } from './render-blocks.js' export function renderToken(token, opts={}) { const { data={} } = opts @@ -31,15 +31,16 @@ function renderImage(img) { } function renderLink(link, opts) { - const { href, title, is_footnote } = link - const { reflinks={}, footnotes=[] } = opts + const { href, title } = link + const { reflinks={}, noterefs=[] } = opts const url = reflinks[href] || { href } + const is_footnote = href[0] == '^' let label = renderInline(link.label, opts) - // footnotes + // noterefs if (is_footnote) { - const index = footnotes.findIndex(el => el == href) + const index = noterefs.findIndex(el => el == href) if (index >= 0) label += elem('sup', { role: 'doc-noteref' }, index + 1) } diff --git a/packages/nuemark/src/render-tag.js b/packages/nuemark/src/render-tag.js index 0a1b4e19..7c51207a 100644 --- a/packages/nuemark/src/render-tag.js +++ b/packages/nuemark/src/render-tag.js @@ -1,7 +1,8 @@ import { renderBlocks, renderContent } from './render-blocks.js' +import { sectionize, } from './parse-document.js' import { renderInline } from './render-inline.js' -import { sectionize, elem } from './document.js' +import { elem } from './render-blocks.js' import { readFileSync } from 'node:fs' import { join } from 'node:path' @@ -98,7 +99,7 @@ const TAGS = { let table = { rows: data.rows || data.items } if (!table.rows && body) table = parseTable(body) - const html = renderTable({ attr, ...data, ...table }, this.opts) + const html = renderTable({ attr, ...data, ...table }, opts) return wrap(data.wrapper, html) }, @@ -119,7 +120,7 @@ const TAGS = { } export function renderTag(tag, opts={}) { - const tags = opts.tags = { ...TAGS, ...opts?.tags } + const tags = { ...TAGS, ...opts.tags } const fn = tags[tag.name || 'block'] if (!fn) return renderIsland(tag, opts.data) @@ -229,7 +230,7 @@ function getInnerHTML(blocks = [], opts) { // table helpers -export function renderTable(table, md_opts) { +export function renderTable(table, opts) { const { rows, attr, head=true } = table if (!rows) return '' @@ -246,14 +247,14 @@ export function renderTable(table, md_opts) { const cells = row.map(td => { const attr = colspan > 1 ? { colspan } : null - return elem(is_head || is_foot ? 'th' : 'td', attr, renderInline(td, md_opts)) + return elem(is_head || is_foot ? 'th' : 'td', attr, renderInline(td, opts)) }) const tr = elem('tr', cells.join('')) return is_head ? elem('thead', tr) : is_foot ? elem('tfoot', tr) : tr }) - const caption = table.caption ? elem('caption', renderInline(table.caption, md_opts)) : '' + const caption = table.caption ? elem('caption', renderInline(table.caption, opts)) : '' return elem('table', attr, caption + html.join('\n')) } diff --git a/packages/nuemark/test/block.test.js b/packages/nuemark/test/block.test.js index b57642fb..b5ffe1cb 100644 --- a/packages/nuemark/test/block.test.js +++ b/packages/nuemark/test/block.test.js @@ -5,7 +5,7 @@ import { nuemark } from '..' test('paragraphs', () => { - const blocks = parseBlocks([ 'a', 'b', '', '', 'c' ]) + const { blocks } = parseBlocks([ 'a', 'b', '', '', 'c' ]) expect(blocks.length).toBe(2) const html = renderBlocks(blocks) @@ -14,28 +14,29 @@ test('paragraphs', () => { }) test('list items', () => { - const blocks = parseBlocks(['- a', '', ' a1', '- b', '', '', '- c']) + const { blocks } = parseBlocks(['- a', '', ' a1', '- b', '', '', '- c']) expect(blocks.length).toBe(1) expect(blocks[0].entries).toEqual([[ "a", "", "a1" ], [ "b", "", "" ], [ "c" ]]) }) test('nested lists', () => { - const blocks = parseBlocks(['- item', '', ' - nested 1', '', '', ' - nested 2']) + const { blocks } = parseBlocks(['- item', '', ' - nested 1', '', '', ' - nested 2']) expect(blocks.length).toBe(1) expect(blocks[0].entries[0]).toEqual([ "item", "", "- nested 1", "", "", "- nested 2" ]) + const html = renderBlocks(blocks) expect(html).toEndWith('
  • nested 2

  • ') }) test('nested tag data', () => { - const [ comp ] = parseBlocks(['[hello]', '', '', ' foo: bar', '', ' bro: 10']) - expect(comp.data).toEqual({ foo: "bar", bro: 10 }) + const { blocks } = parseBlocks(['[hello]', '', '', ' foo: bar', '', ' bro: 10']) + expect(blocks[0].data).toEqual({ foo: "bar", bro: 10 }) }) test('nested tag content', () => { - const blocks = parseBlocks(['[.stack]', '', '', ' line1', '', ' line2']) + const { blocks } = parseBlocks(['[.stack]', '', '', ' line1', '', ' line2']) expect(blocks.length).toBe(1) expect(blocks[0].blocks.length).toBe(2) @@ -44,7 +45,7 @@ test('nested tag content', () => { }) test('subsequent blockquotes', () => { - const blocks = parseBlocks(['> hey', '> boy', '', '> another']) + const { blocks } = parseBlocks(['> hey', '> boy', '', '> another']) expect(blocks.length).toBe(3) const html = renderBlocks(blocks) expect(html).toStartWith('

    hey boy

    ') @@ -52,14 +53,14 @@ test('subsequent blockquotes', () => { test('numbered items', () => { - const [ list ] = parseBlocks(['1. Yo', '10. Bruh', '* Bro']) - expect(list.numbered).toBeTrue() - expect(list.entries).toEqual([[ "Yo" ], [ "Bruh" ], [ "Bro" ]]) + const { blocks } = parseBlocks(['1. Yo', '10. Bruh', '* Bro']) + expect(blocks[0].numbered).toBeTrue() + expect(blocks[0].entries).toEqual([[ "Yo" ], [ "Bruh" ], [ "Bro" ]]) }) test('multiple thematic breaks', () => { - const blocks = parseBlocks(['A', '---', 'B', '---', 'C' ]) + const { blocks } = parseBlocks(['A', '---', 'B', '---', 'C' ]) expect(blocks.length).toBe(5) }) @@ -106,17 +107,17 @@ test('heading attr', () => { expect(renderHeading(h)).toBe('

    Hey

    ') - const html = renderHeading(h, { data: { heading_ids: true } }) + const html = renderHeading(h, { heading_ids: true }) expect(html).toInclude('') }) test('generated heading id', () => { - const html = nuemark('# Hello', { data: { heading_ids: true } }) + const html = nuemark('# Hello', { heading_ids: true }) expect(html).toBe('

    Hello

    ') }) test('heading block count', () => { - const blocks = parseBlocks(['# Yo', 'rap', '## Bruh', 'bat', '## Bro']) + const { blocks } = parseBlocks(['# Yo', 'rap', '## Bruh', 'bat', '## Bro']) expect(blocks.length).toBe(5) }) @@ -139,12 +140,12 @@ test('fenced code with caption', () => { test('multi-line list entries', () => { - const [ list ] = parseBlocks(['* foo', ' boy', '* bar']) + const list = parseBlocks(['* foo', ' boy', '* bar']).blocks[0] expect(list.entries).toEqual([ [ "foo", "boy" ], [ "bar" ] ]) }) test('nested list', () => { - const [ { items } ] = parseBlocks(['* > foo', ' 1. boy', ' 2. bar']) + const { items } = parseBlocks(['* > foo', ' 1. boy', ' 2. bar']).blocks[0] const [ [ quote, nested ] ] = items expect(quote.is_quote).toBeTrue() @@ -152,13 +153,13 @@ test('nested list', () => { }) test('blockquote', () => { - const [ quote ] = parseBlocks(['> foo', '> boy']) + const [ quote ] = parseBlocks(['> foo', '> boy']).blocks expect(quote.is_quote).toBeTrue() expect(quote.content).toEqual([ "foo", "boy" ]) }) test('fenced code blocks', () => { - const [ code ] = parseBlocks(['``` css.foo numbered', 'func()', '```']) + const [ code ] = parseBlocks(['``` css.foo numbered', 'func()', '```']).blocks expect(code.name).toBe('css') expect(code.attr).toEqual({ class: 'foo' }) @@ -175,7 +176,7 @@ test('parse table', () => { ] // parse - const [ table ] = parseBlocks(lines) + const [ table ] = parseBlocks(lines).blocks expect(table.rows[1]).toEqual([ "January", "$250" ]) expect(table.rows.length).toBe(3) expect(table.head).toBeTrue() @@ -195,35 +196,16 @@ test('parse reflinks', () => { ]) expect(reflinks).toEqual({ - 1: '//another.net "something"', - foo: '//website.com', + "1": { + href: "//another.net", + title: "something", + }, + foo: { + href: "//website.com", + }, }) }) - -test('render reflinks', () => { - const links = { external: 'https://bar.com/zappa "External link"' } - const html = renderLines([ - '[Hey *dude*][local]', - 'Inlined [Second][external]', - '[local]: /blog/yo.html "Local link"' - ], { data: { links }}) - - expect(html).toInclude('Hey dude') - expect(html).toInclude('Inlined Second') -}) - -test.skip('footnotes', () => { - const html = renderLines([ - 'This,[^1] [here][^a] goes.[^b]', - '[^1]: foo', - '[^a]: bar', - '[^b]: baz', - ]) - - -}) - test('footnotes with [define]', () => { const html = renderLines([ '[hey][^ki] [^ko]', @@ -239,7 +221,7 @@ test('footnotes with [define]', () => { test('complex tag data', () => { - const [ comp ] = parseBlocks(['[hello#foo.bar world size="10"]', ' foo: bar']) + const comp = parseBlocks(['[hello#foo.bar world size="10"]', ' foo: bar']).blocks[0] expect(comp.attr).toEqual({ class: "bar", id: "foo", }) expect(comp.data).toEqual({ world: true, size: 10, foo: "bar", }) }) diff --git a/packages/nuemark/test/document.test.js b/packages/nuemark/test/document.test.js index f7e5f7c3..8b2847fc 100644 --- a/packages/nuemark/test/document.test.js +++ b/packages/nuemark/test/document.test.js @@ -1,5 +1,5 @@ -import { parseDocument, stripMeta, sectionize } from '../src/document.js' +import { parseDocument, stripMeta, sectionize } from '../src/parse-document.js' import { parseBlocks } from '../src/parse-blocks.js' test('front matter', () => { @@ -49,15 +49,15 @@ test('sectionize', () => { ['lol', '---', 'bol'], ] - for (const blocks of tests) { - const headings = parseBlocks(blocks) - expect(sectionize(headings).length).toBe(2) + for (const test of tests) { + const { blocks } = parseBlocks(test) + expect(sectionize(blocks).length).toBe(2) } }) test('non section', () => { - const paragraphs = parseBlocks(['hello', 'world']) - expect(sectionize(paragraphs)).toBeUndefined() + const { blocks } = parseBlocks(['hello', 'world']) + expect(sectionize(blocks)).toBeUndefined() }) test('single section', () => { @@ -66,19 +66,51 @@ test('single section', () => { }) test('multiple sections', () => { - const doc = parseDocument([ + const lines = [ '# Hello', 'World', '## Foo', 'Bar', '---', 'Bruh', '***', - ]) + ] + const doc = parseDocument(lines) expect(doc.sections.length).toBe(2) - const html = doc.render({ data: { sections: ['hero'] }}) + + const html = doc.render({ sections: ['hero'] }) expect(html).toStartWith('

    Hello

    ') expect(html).toEndWith('
    ') }) +test('render reflinks', () => { + const links = { external: 'https://bar.com/zappa "External link"' } + + const doc = parseDocument([ + '[Hey *dude*][local]', + 'Inlined [Second][external]', + '[local]: /blog/yo.html "Local link"' + ]) + + const html = doc.render({ links }) + expect(html).toInclude('Hey dude') + expect(html).toInclude('Inlined Second') +}) + + +test('footnotes', () => { + const doc = parseDocument([ + 'This,[^1] [here][^a] goes.[^b]', + '[^1]: foo', + '[^a]: bar', + '[^b]: baz', + ]) + + const html = doc.render() + expect(html).toInclude('') + expect(html).toInclude('
    1. foo
    2. ') +}) + + + test('table of contents', () => { const doc = parseDocument([ '# Hello', 'World', diff --git a/packages/nuemark/test/inline.test.js b/packages/nuemark/test/inline.test.js index 23ea191b..80214615 100644 --- a/packages/nuemark/test/inline.test.js +++ b/packages/nuemark/test/inline.test.js @@ -182,27 +182,25 @@ test('parse complex Wikipedia-style link', () => { test('parse footnote link', () => { const link = parseLink('[Hello][^1]', true) - expect(link.is_footnote).toBe(true) expect(link.href).toBe('^1') }) test('parse footnote ref', () => { const [text, tag] = parseInline('Hello [^1]', true) - expect(tag.is_footnote).toBe(true) expect(tag.href).toBe('^1') }) test('render footnote link', () => { - const footnotes = ['^1', '^go'] - const rel = { is_footnote: true, href: '^go' } - const html = renderToken(rel, { footnotes }) + const noterefs = ['^1', '^go'] + const rel = { href: '^go' } + const html = renderToken(rel, { noterefs }) expect(html).toStartWith('') expect(html).toInclude('2') }) test('footnote mix', () => { - const html = renderInline('[a][^1] b [^1]', { footnotes: ['^1'] }) + const html = renderInline('[a][^1] b [^1]', { noterefs: ['^1'] }) expect(html).toInclude('a') })