diff --git a/packages/nuemark/src/parse-inline.js b/packages/nuemark/src/parse-inline.js index fa22213c..6ac464f9 100644 --- a/packages/nuemark/src/parse-inline.js +++ b/packages/nuemark/src/parse-inline.js @@ -50,14 +50,40 @@ const PARSERS = [ } }, - // links + // [link](/), [tag], or [^footnote] (str, char0) => { if (char0 == '[') { - return parseLink(str) || parseLink(str, true) + const i = str.indexOf(']', 1) + + if (i == -1) return { text: char0 } + + // links + if ('([]'.includes(str[i + 1])) { + const link = parseLink(str) || parseLink(str, true) + if (link) return link + } + + // parse tag + const tag = parseTag(str.slice(1, i).trim()) + const { name } = tag + const end = i + 1 + + // footnote? + if (name[0] == '^') { + const rel = name.slice(1) + return rel >= 0 || isValidName(rel) ? { is_footnote: true, href: name, end } : { text: char0 } + } + + // normal tag + if (name == '!' || isValidName(name)) return { is_tag: true, ...tag, end } + + + + return { text: char0 } } }, - // images + // ![image](/src.png) (str, char0) => { if (char0 == '!' && str[1] == '[') { const img = parseLink(str.slice(1)) @@ -69,22 +95,6 @@ const PARSERS = [ } }, - - // [tag] or [^footnote] - (str, char0) => { - if (char0 == '[') { - const i = str.indexOf(']', 2) - if (i == -1) return { text: char0 } - - const specs = str.slice(1, i).trim() - const tag = parseTag(specs) - const { name } = tag - const end = i + 1 - - return name[0] == '^' ? { is_footnote: true, href: name, end } : { is_tag: true, ...tag, end } - } - }, - // { variables } / { #id.classNames } (str, char0) => { if (char0 == '{') { @@ -107,6 +117,19 @@ const PARSERS = [ } ] + +function isValidName(name) { + + // cannot be a number + if (name >= 0) return false + + // cannot contain special characters + const i = name.search(/\W/) + const char = name[i] + return i == -1 || i > 0 && char == '-' +} + + function empty(char) { return !char || char == ' ' } @@ -138,13 +161,15 @@ export function parseInline(str) { /*** utils ****/ +// function lastIndexOf() + export function parseLink(str, is_reflink) { const [open, close] = is_reflink ? '[]' : '()' - let i = str.indexOf(']', 1) + let i = str.indexOf(']' + open, 1) // not a link - const next = str[i + 1] - if (next != open) return + // const next = str[i + 1] + if (i == -1) return let j = i > 0 ? str.indexOf(close, i + 2) : 0 diff --git a/packages/nuemark/test/block.test.js b/packages/nuemark/test/block.test.js index 801d86f5..c24945d0 100644 --- a/packages/nuemark/test/block.test.js +++ b/packages/nuemark/test/block.test.js @@ -72,6 +72,11 @@ test('parse thematic break', () => { expect(getBreak('*** yo')).toBeUndefined() }) +test.only('parse break', () => { + const html = renderLines(['***', '***Bold Italic***']) + console.info(html) +}) + test('render thematic break', () => { expect(renderLines(['hello', '***'])).toBe('

hello

\n
') }) diff --git a/packages/nuemark/test/inline.test.js b/packages/nuemark/test/inline.test.js index 3694bd8a..f575dbf0 100644 --- a/packages/nuemark/test/inline.test.js +++ b/packages/nuemark/test/inline.test.js @@ -35,10 +35,10 @@ test('formatting', () => { for (const test of tests) { const [ chars, body, tag ] = test - const ret = parseInline(`A ${chars + body + chars} here`) - expect(ret[1].tag).toBe(tag) - expect(ret[1].body).toBe(body) - expect(ret.length).toBe(3) + const html = parseInline(`A ${chars + body + chars} here`) + expect(html[1].tag).toBe(tag) + expect(html[1].body).toBe(body) + expect(html.length).toBe(3) } }) @@ -108,6 +108,20 @@ test('parse reflink', () => { expect(link).toMatchObject({ href: 'world', title: 'now', label: 'Hello' }) }) + +test('bad component names', () => { + const tests = ['[(10)] [3 % 8]', '[-hey]', '[he+y] there'] + for (const test of tests) { + const html = renderInline(test) + expect(html).toBe(test) + } +}) + +test('inline image tag', () => { + const html = renderInline('[! foo.svg]') + expect(html).toStartWith('
') +}) + test('complex label with an image', () => { const complex_label = 'Hey ![Cat](/cat.png)!' const link = `[${complex_label}](/link/ "lol")` @@ -121,6 +135,7 @@ test('complex label with an image', () => { expect(html).toEndWith('alt="Cat" loading="lazy">!') }) + test('parse complex Wikipedia-style link', () => { const [text, link, rest] = parseInline('Goto [label](/url/(master)) plan') expect(link.href).toBe('/url/(master)') @@ -174,11 +189,19 @@ test('inline tag', () => { }) test('inline tag with reflink', () => { - const [ tag, and, link] = parseInline('[tip] and [link][foo]') + const els = parseInline('[tip] and [link][foo]') + const [ tag, and, link] = els expect(tag.is_tag).toBeTrue() expect(link.is_reflink).toBeTrue() }) + +test('links with tags', () => { + const html = renderInline('lol [[! yo.svg]](/)') + expect(html).toStartWith('lol ') + expect(html).toEndWith('src="yo.svg">
') +}) + test('tag args', () => { const [ text, comp, rest] = parseInline('Hey [print foo] thing') expect(comp.name).toBe('print')