From c58c8777f1d9fc9fe46671471c0fd6b9d5219c2f Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 7 Nov 2023 17:43:24 -0600 Subject: [PATCH 01/12] Add arbitrary tag attr assign. in moustaches This adds the ability to include attribute values for any element that can be altered by a moustache. Form: ``` {attribute=value} example: ![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a=b and c,g=h} ``` In order to permit spaces, the pattern matches for moustache code had to remove the space character as a delimiter. I believe I have adequate compensated. This should solve #1488 --- client/homebrew/editor/editor.jsx | 6 +++--- shared/naturalcrit/markdown.js | 23 ++++++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index b4f1fc824..f2fd81646 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -162,7 +162,7 @@ const Editor = createClass({ // Highlight injectors {style} if(line.includes('{') && line.includes('}')){ - const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm; + const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\2})/gm; let match; while ((match = regex.exec(line)) != null) { codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' }); @@ -170,7 +170,7 @@ const Editor = createClass({ } // Highlight inline spans {{content}} if(line.includes('{{') && line.includes('}}')){ - const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g; + const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1 *|}}/g; let match; let blockCount = 0; while ((match = regex.exec(line)) != null) { @@ -189,7 +189,7 @@ const Editor = createClass({ // Highlight block divs {{\n Content \n}} let endCh = line.length+1; - const match = line.match(/^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/); + const match = line.match(/^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1 *$|^ *}}$/); if(match) endCh = match.index+match[0].length; codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 114229887..0fd4fdd8f 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -132,7 +132,7 @@ const mustacheInjectInline = { level : 'inline', start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g; + const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1}/g; const match = inlineRegex.exec(src); if(match) { const lastToken = tokens[tokens.length - 1]; @@ -167,7 +167,7 @@ const mustacheInjectBlock = { level : 'block', start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/ym; + const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1}/ym; const match = inlineRegex.exec(src); if(match) { const lastToken = tokens[tokens.length - 1]; @@ -328,14 +328,23 @@ const voidTags = new Set([ const processStyleTags = (string)=>{ //split tags up. quotes can only occur right after colons. //TODO: can we simplify to just split on commas? - const tags = string.match(/(?:[^, ":]+|:(?:"[^"]*"|))+/g); + const tags = string.match(/(?:[^,":]+|:(?:"[^"]*"|))+/g); if(!tags) return '"'; - const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; - const classes = _.remove(tags, (tag)=>!tag.includes(':')); - const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;')); - return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles.length ? `style="${styles.join(' ')}"` : ''}`; + const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; + const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))); + let attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); + const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()); + + if(attributes.length) { + attributes = attributes.map((attribute)=>attribute.replace(/(\w+)=(.+)/, '$1="$2"')); + } + + return `${classes.join(' ')}" ` + + `${id ? `id="${id}"` : ''} ` + + `${styles.length ? `style="${styles.join(' ')}"` : ''} ` + + `${attributes.length ? attributes.join(' ') : ''}`; }; module.exports = { From 837306c9a75d2a999971a46b8bf9fdc73afb8abc Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 7 Nov 2023 19:07:58 -0600 Subject: [PATCH 02/12] Add tests for arbitrary attributes. Also shifted around the adding of spaces for the attributes. --- shared/naturalcrit/markdown.js | 6 +++--- tests/markdown/mustache-syntax.test.js | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 0fd4fdd8f..5b2f47309 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -338,13 +338,13 @@ const processStyleTags = (string)=>{ const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()); if(attributes.length) { - attributes = attributes.map((attribute)=>attribute.replace(/(\w+)=(.+)/, '$1="$2"')); + attributes = attributes.map((attribute)=>attribute.replace(/(\w+)=(.+)/, ' $1="$2"')); } return `${classes.join(' ')}" ` + `${id ? `id="${id}"` : ''} ` + - `${styles.length ? `style="${styles.join(' ')}"` : ''} ` + - `${attributes.length ? attributes.join(' ') : ''}`; + `${styles.length ? `style="${styles.join(' ')}"` : ''}` + + `${attributes.length ? attributes.join('') : ''}`; }; module.exports = { diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index d9e1ce6f9..6669cc6cb 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -124,6 +124,12 @@ describe('Inline: When using the Inline syntax {{ }}', ()=>{ const rendered = Markdown.render(source); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text'); }); + + it('Renders an image with added attributes', function() { + const source = dedent`![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a=b and c,d=e}`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + }); }); // BLOCK SYNTAX @@ -216,6 +222,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{ // FIXME: adds extra \s before closing `>` in opening tag, and another after class names expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

Sample text.

`); }); + }); // MUSTACHE INJECTION SYNTAX From 7b85995b4a56ecc382219c1f6be3f1b74843bf3d Mon Sep 17 00:00:00 2001 From: David Bolack Date: Fri, 10 Nov 2023 00:28:25 -0600 Subject: [PATCH 03/12] Updated attribute assignment. Wraps with quotes ( a="b and c" ) Still does not work on Mustache Divs. UNsure where the failure is at the moment. Even regressed "a:b and c" pattern on those. --- shared/naturalcrit/markdown.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 5b2f47309..98cfa7c8c 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -34,7 +34,7 @@ const mustacheSpans = { start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token - const inlineRegex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g; + const inlineRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g; const match = completeSpan.exec(src); if(match) { //Find closing delimiter @@ -132,13 +132,12 @@ const mustacheInjectInline = { level : 'inline', start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1}/g; + const inlineRegex = /^ *{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/g; const match = inlineRegex.exec(src); if(match) { const lastToken = tokens[tokens.length - 1]; if(!lastToken || lastToken.type == 'mustacheInjectInline') return false; - const tags = ` ${processStyleTags(match[1])}`; lastToken.originalType = lastToken.type; lastToken.type = 'mustacheInjectInline'; @@ -167,7 +166,7 @@ const mustacheInjectBlock = { level : 'block', start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1}/ym; + const inlineRegex = /^ *{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/ym; const match = inlineRegex.exec(src); if(match) { const lastToken = tokens[tokens.length - 1]; @@ -328,19 +327,13 @@ const voidTags = new Set([ const processStyleTags = (string)=>{ //split tags up. quotes can only occur right after colons. //TODO: can we simplify to just split on commas? - const tags = string.match(/(?:[^,":]+|:(?:"[^"]*"|))+/g); - - if(!tags) return '"'; + const tags = string.match(/(?:[^,":=]+|[:=](?:"[^"]*"|))+/g); const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))); - let attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); + const attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()); - if(attributes.length) { - attributes = attributes.map((attribute)=>attribute.replace(/(\w+)=(.+)/, ' $1="$2"')); - } - return `${classes.join(' ')}" ` + `${id ? `id="${id}"` : ''} ` + `${styles.length ? `style="${styles.join(' ')}"` : ''}` + From c068aca9ff3a2ec92e1a95c0a0bd6b8e1131179f Mon Sep 17 00:00:00 2001 From: David Bolack Date: Sat, 11 Nov 2023 15:47:27 -0600 Subject: [PATCH 04/12] Small tweaks to processStyle. This changes the output on arbitrary outputs to always wrap the value in quotes instead of only doing so on whitespaced values. --- shared/naturalcrit/markdown.js | 4 ++-- tests/markdown/mustache-syntax.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 98cfa7c8c..78a8a269c 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -331,13 +331,13 @@ const processStyleTags = (string)=>{ const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))); - const attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); + const attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))).map((attr)=>attr.replace(/="?([^"]*)"?/g, '="$1"'));; const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()); return `${classes.join(' ')}" ` + `${id ? `id="${id}"` : ''} ` + `${styles.length ? `style="${styles.join(' ')}"` : ''}` + - `${attributes.length ? attributes.join('') : ''}`; + `${attributes.length ? ` ${attributes.join(' ')}` : ''}`; }; module.exports = { diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index 6669cc6cb..d7323874a 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -126,7 +126,7 @@ describe('Inline: When using the Inline syntax {{ }}', ()=>{ }); it('Renders an image with added attributes', function() { - const source = dedent`![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a=b and c,d=e}`; + const source = dedent`![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); From c858c705d2388792a17fa7968ce99cc3b986bd37 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Sat, 11 Nov 2023 22:23:43 -0600 Subject: [PATCH 05/12] Complete mustache div updates for attr --- shared/naturalcrit/markdown.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 78a8a269c..837daf850 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -84,7 +84,7 @@ const mustacheDivs = { start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token - const blockRegex = /^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/gm; + const blockRegex = /^ *{{(?=((?:[=:](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"'=:{}\s]*)*))\1 *$|^ *}}$/gm; const match = completeBlock.exec(src); if(match) { //Find closing delimiter @@ -331,13 +331,17 @@ const processStyleTags = (string)=>{ const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))); - const attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))).map((attr)=>attr.replace(/="?([^"]*)"?/g, '="$1"'));; - const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()); + let attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); + const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()) : []; + + if(attributes) { + attributes = attributes.map((attr)=>attr.replace(/="?([^"]*)"?/g, '="$1"')); + } return `${classes.join(' ')}" ` + `${id ? `id="${id}"` : ''} ` + - `${styles.length ? `style="${styles.join(' ')}"` : ''}` + - `${attributes.length ? ` ${attributes.join(' ')}` : ''}`; + `${styles?.length ? `style="${styles.join(' ')}"` : ''}` + + `${attributes?.length ? ` ${attributes.join(' ')}` : ''}`; }; module.exports = { From d6e63604acfc4f0bce84093102186e7abaf80905 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 22 Nov 2023 13:29:37 -0600 Subject: [PATCH 06/12] Add tests --- tests/markdown/mustache-syntax.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index d7323874a..fc531cfdc 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -223,6 +223,14 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

Sample text.

`); }); + it('Renders a div with an ID, class, style and text, and a variable assignment', function() { + const source = dedent`{{color:red,cat,#dog,a="b and c",d="e" + Sample text. + }}`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

Sample text.

`); + }); + }); // MUSTACHE INJECTION SYNTAX @@ -242,6 +250,12 @@ describe('Injection: When an injection tag follows an element', ()=>{ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text'); }); + it.failing('Renders a span "text" with injected attribute', function() { + const source = '{{ text}}{a="b and c"}'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text'); + }); + it.failing('Renders a span "text" with injected style', function() { const source = '{{ text}}{color:red}'; const rendered = Markdown.render(source); From fcb9a8cdc51c7f3c55f89eabba614a69bed6c696 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 6 Dec 2023 16:54:28 -0600 Subject: [PATCH 07/12] Update Editor highlighting to handle attribut assignments --- client/homebrew/editor/editor.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 5ad4e065f..f475bc6b0 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -162,7 +162,7 @@ const Editor = createClass({ // Highlight injectors {style} if(line.includes('{') && line.includes('}')){ - const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\2})/gm; + const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; let match; while ((match = regex.exec(line)) != null) { codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' }); @@ -170,7 +170,7 @@ const Editor = createClass({ } // Highlight inline spans {{content}} if(line.includes('{{') && line.includes('}}')){ - const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1 *|}}/g; + const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; let match; let blockCount = 0; while ((match = regex.exec(line)) != null) { @@ -189,7 +189,7 @@ const Editor = createClass({ // Highlight block divs {{\n Content \n}} let endCh = line.length+1; - const match = line.match(/^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\n\r\t\f]*)*))\1 *$|^ *}}$/); + const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/); if(match) endCh = match.index+match[0].length; codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); From 688eca05e1c2c01d7769698191370a9829a6bae0 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 6 Dec 2023 16:59:53 -0600 Subject: [PATCH 08/12] Update Regex pattern to be consistant. --- shared/naturalcrit/markdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index a86adbc59..592de4896 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -84,7 +84,7 @@ const mustacheDivs = { start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token - const blockRegex = /^ *{{(?=((?:[=:](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"'=:{}\s]*)*))\1 *$|^ *}}$/gm; + const blockRegex = /^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm; const match = completeBlock.exec(src); if(match) { //Find closing delimiter From 769f636db2b13606d1a411ba45ebc5b480552e4b Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 6 Dec 2023 17:48:51 -0600 Subject: [PATCH 09/12] Small fix and test updates Discovered that classes ( and possibly other splits could end up with an empty/null member that still gets joined so I added a trim to the end of all the joins in processStyleTags. Added tests that SHOULD test for bloc-level and inline-span moustaches with added attributes ( a=b ) --- shared/naturalcrit/markdown.js | 7 ++++--- tests/markdown/mustache-syntax.test.js | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 592de4896..fdc1b06ce 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -359,6 +359,7 @@ const processStyleTags = (string)=>{ const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))); + console.log(classes); let attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()) : []; @@ -366,10 +367,10 @@ const processStyleTags = (string)=>{ attributes = attributes.map((attr)=>attr.replace(/="?([^"]*)"?/g, '="$1"')); } - return `${classes.join(' ')}" ` + + return `${classes.join(' ').trim()}" ` + `${id ? `id="${id}"` : ''} ` + - `${styles?.length ? `style="${styles.join(' ')}"` : ''}` + - `${attributes?.length ? ` ${attributes.join(' ')}` : ''}`; + `${styles?.length ? `style="${styles.join(' ').trim()}"` : ''}` + + `${attributes?.length ? ` ${attributes.join(' ').trim()}` : ''}`; }; module.exports = { diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index fc531cfdc..790af84f6 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -130,6 +130,18 @@ describe('Inline: When using the Inline syntax {{ }}', ()=>{ const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); + + it('Render a span with added attributes', function() { + const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

Text and text and more text!

\n'); + }); + + it('Render a div with added attributes', function() { + const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

Text and text and more text!

\n
'); + }); }); // BLOCK SYNTAX From ef00231c5bc875bccb2ba6d5e72b55db0a68c773 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 14 Dec 2023 13:46:44 -0500 Subject: [PATCH 10/12] Move tests to the correct section --- tests/markdown/mustache-syntax.test.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index e6bb9cee5..bfe6261fc 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -110,23 +110,11 @@ describe('Inline: When using the Inline syntax {{ }}', ()=>{ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text'); }); - it('Renders an image with added attributes', function() { - const source = dedent`![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; - const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); - }); - - it('Render a span with added attributes', function() { + it('Renders a span with added attributes', function() { const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!'; const rendered = Markdown.render(source); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

Text and text and more text!

\n'); }); - - it('Render a div with added attributes', function() { - const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n'; - const rendered = Markdown.render(source); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

Text and text and more text!

\n
'); - }); }); // BLOCK SYNTAX @@ -219,6 +207,11 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

Sample text.

`); }); + it('Renders a div with added attributes', function() { + const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

Text and text and more text!

\n
'); + }); }); // MUSTACHE INJECTION SYNTAX @@ -279,6 +272,12 @@ describe('Injection: When an injection tag follows an element', ()=>{ const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

text{background:blue}

'); }); + + it('Renders an image with added attributes', function() { + const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + }); }); describe('and that element is a block', ()=>{ From 90b4e47861b2773942f64b0fdd794ba095b3dfbe Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 14 Dec 2023 13:47:36 -0500 Subject: [PATCH 11/12] Fix ' ' removed from processStyleTags regex Removes the case of "empty properties" that needed `.trim()` --- shared/naturalcrit/markdown.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 38a6c685b..d88b3aa70 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -354,24 +354,23 @@ const voidTags = new Set([ ]); const processStyleTags = (string)=>{ - //split tags up. quotes can only occur right after colons. + //split tags up. quotes can only occur right after : or =. //TODO: can we simplify to just split on commas? - const tags = string.match(/(?:[^,":=]+|[:=](?:"[^"]*"|))+/g); + const tags = string.match(/(?:[^, ":=]+|[:=](?:"[^"]*"|))+/g); const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))); - console.log(classes); - let attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); + let attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()) : []; if(attributes) { attributes = attributes.map((attr)=>attr.replace(/="?([^"]*)"?/g, '="$1"')); } - return `${classes.join(' ').trim()}" ` + - `${id ? `id="${id}"` : ''} ` + - `${styles?.length ? `style="${styles.join(' ').trim()}"` : ''}` + - `${attributes?.length ? ` ${attributes.join(' ').trim()}` : ''}`; + return `${classes?.length ? ` ${classes.join(' ')}` : ''}"` + + `${id ? ` id="${id}"` : ''}` + + `${styles?.length ? ` style="${styles.join(' ')}"` : ''}` + + `${attributes?.length ? ` ${attributes.join(' ')}` : ''}`; }; module.exports = { From 9b59f47536c3041e9810dd2889a045579e344699 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 14 Dec 2023 13:58:38 -0500 Subject: [PATCH 12/12] Simplify Attributes parsing logic --- shared/naturalcrit/markdown.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index d88b3aa70..48059e33d 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -360,13 +360,9 @@ const processStyleTags = (string)=>{ const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))); - let attributes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('#'))); + const attributes = _.remove(tags, (tag)=>(tag.includes('='))).map((tag)=>tag.replace(/="?([^"]*)"?/g, '="$1"')); const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()) : []; - if(attributes) { - attributes = attributes.map((attr)=>attr.replace(/="?([^"]*)"?/g, '="$1"')); - } - return `${classes?.length ? ` ${classes.join(' ')}` : ''}"` + `${id ? ` id="${id}"` : ''}` + `${styles?.length ? ` style="${styles.join(' ')}"` : ''}` +