Skip to content

Commit

Permalink
Large Nuemark simplification/refactor, support for 'standard' footnotes
Browse files Browse the repository at this point in the history
  • Loading branch information
tipiirai committed Nov 1, 2024
1 parent 2a8e01a commit 43111f0
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 187 deletions.
2 changes: 1 addition & 1 deletion packages/nuejs.org/@global/navigation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 8 additions & 7 deletions packages/nuekit/src/layout/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
})
Expand All @@ -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 })

// <main>...</main>
if (!slots.main && data.main !== false) {
Expand Down
2 changes: 1 addition & 1 deletion packages/nuekit/test/nuekit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
12 changes: 6 additions & 6 deletions packages/nuemark/index.js
Original file line number Diff line number Diff line change
@@ -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'
51 changes: 32 additions & 19 deletions packages/nuemark/src/parse-blocks.js
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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))
}
}
}
Expand All @@ -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() }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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))

Expand All @@ -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 || ''
}
Expand All @@ -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(`</${name}>`)
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
}



4 changes: 1 addition & 3 deletions packages/nuemark/src/parse-inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 43111f0

Please sign in to comment.