From f2001b3a14d54561288b605b385a0e5e83e6da8f Mon Sep 17 00:00:00 2001 From: Tero Piirainen Date: Thu, 24 Oct 2024 03:39:24 +0300 Subject: [PATCH] Nuemark: fix formatting characters within a word. 3379 --- packages/nuemark/src/parse-inline.js | 55 ++++++++++++---------------- packages/nuemark/test/block.test.js | 1 + packages/nuemark/test/inline.test.js | 12 +++++- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/packages/nuemark/src/parse-inline.js b/packages/nuemark/src/parse-inline.js index 6bfe8410..6d732a9a 100644 --- a/packages/nuemark/src/parse-inline.js +++ b/packages/nuemark/src/parse-inline.js @@ -27,44 +27,45 @@ export const ESCAPED = { '<': '<', '>': '>' } const SIGNIFICANT = /[\*_\["`~\\/|•{\\<>]|!\[/ +// c == first character (for quick tests) const PARSERS = [ // \{ escaped } - (str, char0) => { - if (char0 == '\\') return { text: str.slice(1, 2), end: 2 } + (str, c) => { + if (c == '\\') return { text: str.slice(1, 2), end: 2 } }, // < and > - (str, char0) => { - const text = ESCAPED[char0] - if (text) return { text, end: 1 } + (str, c) => { + const text = ESCAPED[c] + return text ? { text, end: 1 } : null }, + // bold, italics, etc.. - (str, char0) => { + (str, c) => { for (const fmt in FORMATTING) { if (str.startsWith(fmt)) { const len = fmt.length - const i = str.indexOf(fmt, len + 1) + const i = str.endsWith(fmt) ? str.length - len : str.indexOf(fmt + ' ', len + 1) const body = str.slice(len, i) // no spaces before/after the body - if (i == -1 || body.length != body.trim().length) { + if (i == -1 || !body || body.length != body.trim().length) { return { text: str.slice(0, len) } } - const tag = FORMATTING[fmt] - return { is_format: true, tag, body, end: i + len } + return { is_format: true, tag: FORMATTING[fmt], body, end: i + len } } } }, // [link](/), [tag], or [^footnote] - (str, char0) => { - if (char0 == '[') { + (str, c) => { + if (c == '[') { const i = str.indexOf(']', 1) - if (i == -1) return { text: char0 } + if (i == -1) return { text: c } // links if ('([]'.includes(str[i + 1])) { @@ -80,37 +81,35 @@ const PARSERS = [ // footnote? if (name[0] == '^') { const rel = name.slice(1) - return rel >= 0 || isValidName(rel) ? { is_footnote: true, href: name, end } : { text: char0 } + return rel >= 0 || isValidName(rel) ? { is_footnote: true, href: name, end } : { text: c } } // normal tag if (name == '!' || isValidName(name)) return { is_tag: true, ...tag, end } - - - return { text: char0 } + return { text: c } } }, // ![image](/src.png) - (str, char0) => { - if (char0 == '!' && str[1] == '[') { + (str, c) => { + if (c == '!' && str[1] == '[') { const img = parseLink(str.slice(1)) if (img) { img.end++ return img && { is_image: true, ...img } } else { - return { text: char0 } + return { text: c } } } }, // { variables } / { #id.classNames } - (str, char0) => { - if (char0 == '{') { + (str, c) => { + if (c == '{') { const i = str.indexOf('}', 2) - if (i == -1) return { text: char0 } + if (i == -1) return { text: c } const name = str.slice(1, i).trim() return '.#'.includes(name[0]) ? { is_attr: true, attr: parseAttr(name), end: i + 1 } : @@ -122,9 +121,7 @@ const PARSERS = [ // plain text (text) => { const i = text.search(SIGNIFICANT) - const prev = text[i - 1] - const next = text[i + 1] - return i >= 0 && (empty(prev) || empty(next)) ? { text: text.slice(0, i) } : { text } + return i >= 0 ? { text: text.slice(0, i) } : { text } } ] @@ -141,9 +138,6 @@ function isValidName(name) { } -function empty(char) { - return !char || char == ' ' -} export function parseInline(str) { const tokens = [] @@ -170,9 +164,8 @@ export function parseInline(str) { } -/*** utils ****/ +/*** link / image parsing ****/ -// function lastIndexOf() export function parseLink(str, is_reflink) { const [open, close] = is_reflink ? '[]' : '()' diff --git a/packages/nuemark/test/block.test.js b/packages/nuemark/test/block.test.js index 0e8c936d..5b335b11 100644 --- a/packages/nuemark/test/block.test.js +++ b/packages/nuemark/test/block.test.js @@ -199,6 +199,7 @@ test('parse reflinks', () => { }) }) + test('render reflinks', () => { const links = { external: 'https://bar.com/zappa "External link"' } const html = renderLines([ diff --git a/packages/nuemark/test/inline.test.js b/packages/nuemark/test/inline.test.js index 95df1a39..ca1f4677 100644 --- a/packages/nuemark/test/inline.test.js +++ b/packages/nuemark/test/inline.test.js @@ -5,7 +5,6 @@ import { parseInline, parseLink } from '../src/parse-inline.js' test('plain text', () => { const tests = [ - 'grid_item_component', 'Hello, World!', 'Unclosed "quote', 'Unclosed ****format', @@ -42,6 +41,13 @@ test('formatting', () => { } }) +test('formatting inside string', () => { + expect(renderInline('a_b_c')).toBe('a_b_c') + expect(renderInline('a/c/d')).toBe('a/c/d') + expect(renderInline('a _b_')).toBe('a b') + expect(renderInline('a _b_ c')).toBe('a b c') +}) + test('formatting and spaces', () => { expect(renderInline('**hello**')).toBe('hello') expect(renderInline('**hello*')).toBe('**hello*') @@ -58,6 +64,7 @@ test('bold + em', () => { test('unclosed formatting', () => { expect(renderInline('_foo *bar*')).toBe('_foo bar') expect(renderInline('yo /foo /bar _baz_')).toBe('yo /foo /bar baz') + expect(renderInline('foo/bar *baz*')).toBe('foo/bar baz') }) test('inline render basics', () => { @@ -87,6 +94,9 @@ test('unclosed image', () => { expect(renderInline('![foo]')).toStartWith('! { + expect(renderInline('')).toBe('<! kama >') +}) test('inline code', () => { const html = renderInline('Hey `[zoo] *boo*`')