Repository: naturalcrit/homebrewery
Pull request #3066 (2023-09-27 16:22:44)

Single Quote in in-line styles
This PR introduces the possibility of using single quotation marks inside inline styles to be able to use strings as values.

It does so by changing the regex pattern for mustache spans, blocks and injects.

Note: in order to use them, you still need double quotation marks:
`--variable:"'string'"` is a correct and expected syntax.



Patch:
diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js
index 48059e33..b0844deb 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
@@ -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
@@ -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\-()#%.]*)|[^"=':{}\s]*)*))\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\-()#%.]*)|[^"=':{}\s]*)*))\1}/ym;
 			const match = inlineRegex.exec(src);
 			if(match) {
 				const lastToken = tokens[tokens.length - 1];


Test patch:
diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js
index bfe6261f..835bcc57 100644
--- a/tests/markdown/mustache-syntax.test.js
+++ b/tests/markdown/mustache-syntax.test.js
@@ -98,12 +98,20 @@ describe('Inline: When using the Inline syntax {{ }}', ()=>{
 	});
 
 
-	it('Renders a mustache span with text with quotes and css property which contains quotes', function() {
+	it('Renders a mustache span with text with quotes and css property which contains double quotes', function() {
 		const source = '{{font-family:"trebuchet ms" text "with quotes"}}';
 		const rendered = Markdown.render(source);
 		expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>');
 	});
 
+
+	it('Renders a mustache span with text with quotes and css property which contains double and simple quotes', function() {
+		const source = `{{--stringVariable:"'string'" text "with quotes"}}`;
+		const rendered = Markdown.render(source);
+		expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text “with quotes”</span>`);
+	});
+
+
 	it('Renders a mustache span with text, id, class and a couple of css properties', function() {
 		const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}';
 		const rendered = Markdown.render(source);
@@ -175,6 +183,22 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
 		expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red;"><p>Sample text.</p></div>`);
 	});
 
+	it('Renders a div with a style that has a string variable, and text', function() {
+		const source = dedent`{{--stringVariable:"'string'"
+		Sample text.
+		}}`;
+		const rendered = Markdown.render(source).trimReturns();
+		expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
+	});
+
+	it('Renders a div with a style that has a string variable, and text', function() {
+		const source = dedent`{{--stringVariable:"'string'"
+		Sample text.
+		}}`;
+		const rendered = Markdown.render(source).trimReturns();
+		expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
+	});
+
 	it('Renders a div with a class, style and text', function() {
 		const source = dedent`{{cat,color:red
 		Sample text.
@@ -243,6 +267,12 @@ describe('Injection: When an injection tag follows an element', ()=>{
 			expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
 		});
 
+		it.failing('Renders a span "text" with injected style using a string variable', function() {
+			const source = `{{ text}}{--stringVariable:"'string'"}`;
+			const rendered = Markdown.render(source);
+			expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text</span>`);
+		});
+
 		it.failing('Renders a span "text" with two injected styles', function() {
 			const source = '{{ text}}{color:red,background:blue}';
 			const rendered = Markdown.render(source);
@@ -305,7 +335,16 @@ describe('Injection: When an injection tag follows an element', ()=>{
 			}}
 			{color:red,background:blue}`;
 			const rendered = Markdown.render(source).trimReturns();
-			expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; background:blue;"><p>text</p></div>');
+			expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red; background:blue"><p>text</p></div>`);
+		});
+
+		it.failing('renders a div "text" with injected variable string', function() {
+			const source = dedent`{{
+			text
+			}}
+			{--stringVariable:"'string'"}`;
+			const rendered = Markdown.render(source).trimReturns();
+			expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string'"><p>text</p></div>`);
 		});
 
 		it.failing('renders an h2 header "text" with injected class name', function() {