diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js
index 4bc14e19a..01fdbac96 100644
--- a/shared/naturalcrit/markdown.js
+++ b/shared/naturalcrit/markdown.js
@@ -294,117 +294,74 @@ const superSubScripts = {
}
};
-const definitionLists = {
- name : 'definitionLists', // Block because style display: block
+const definitionListsInline = {
+ name : 'definitionListsInline',
level : 'block',
- start(src) { return src.match(/^.*?::.*\n\n/m)?.index; }, // Hint to Marked.js to stop and check for a match
+ start(src) { return src.match(/^[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
- const regex = /^([^\n]*?:?)\n?::(.*)(?:\n|$)|^.*(?:\n|$)/ym;
+ const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
let match;
let endIndex = 0;
- const allDefinitions = [];
- let currentDefinition = {};
- let inList = false;
- let lastEmpty = false;
- let inlineDefinitions = false;
- let appending = false;
+ const definitions = [];
while (match = regex.exec(src)) {
- // If we aren't actively in a DL and we match just text, bail.
- // If the last loop bailed with lastEmpty and we match just text, bail
- if(((!inList) || (lastEmpty)) && (typeof match[1] == 'undefined') && (typeof match[2] == 'undefined')) {
- break;
- }
- endIndex += match[0].length;
-
- // Check to see if this a match containing the start of a DD.
- if(match[0].indexOf('::') > -1) {
- inList = true;
- // Check and see if we are currently in line appending mode, if so, match[1] should be
- // appended to the last entry instead of being used as the next DT.
- if(appending) {
- const lastPos = typeof currentDefinition.dd.length !== 'undefined' ? currentDefinition.dd.length - 1 : 0;
- currentDefinition.dd[lastPos] = `${currentDefinition.dd[lastPos]} ${match[1]?.trim()}`;
- match[1] = '';
- }
- appending = false;
- // Check for a DT value.
- if(match[1]?.trim()?.length>0) {
- if(currentDefinition?.dt?.length) {
- currentDefinition.dd.forEach((dd)=>{
- currentDefinition.ddo.push(this.lexer.inlineTokens(dd));
- });
- allDefinitions.push(currentDefinition);
- currentDefinition = {};
- }
- currentDefinition = {
- dt : this.lexer.inlineTokens(match[1].trim()),
- dd : [],
- ddo : []
- };
- } else if(_.isEmpty(currentDefinition)) {
- return;
- }
- // Test for a DD value.
- if(match[2].trim().length>0) {
- if((match[1]?.length > 0) && (match[0].indexOf('\n') > match[1]?.length)) { // Inline Style DD
- currentDefinition.dd = currentDefinition.dd.concat([match[2].trim()]);
- currentDefinition.dd.forEach((dd)=>{
- currentDefinition.ddo.push(this.lexer.inlineTokens(dd));
- });
- allDefinitions.push(currentDefinition);
- inlineDefinitions = true;
- currentDefinition = {};
- continue;
- }
- // Multi-line style DDs
- const newDefinitions = _.flatten(match[2].split('\n::').filter((item)=>item).map((s)=>s.trim()));
- if(newDefinitions?.length) {
- currentDefinition.dd = currentDefinition.dd.concat(newDefinitions);
- }
- } else {
- currentDefinition.dd.push('');
- }
- lastEmpty = false;
- } else if(inList) { // Regular line that might mark the end of a line.
- appending = false;
- if(inlineDefinitions) {
- endIndex -= match[0].length;
- break;
- }
- if(match[0].trim().length == 0) {
- if(lastEmpty) {
- continue;
- } else {
- lastEmpty = true;
- }
- } else {
- lastEmpty = false;
- const lastPos = typeof currentDefinition.dd.length !== 'undefined' ? currentDefinition.dd.length - 1 : 0;
- currentDefinition.dd[lastPos] = `${currentDefinition.dd[lastPos]} ${match[0].trim()}`;
- appending = true;
- }
- }
- }
- if(currentDefinition.hasOwnProperty('dt')) {
- currentDefinition.dd.forEach((dd)=>{
- currentDefinition.ddo.push(this.lexer.inlineTokens(dd));
+ definitions.push({
+ dt : this.lexer.inlineTokens(match[1].trim()),
+ dd : this.lexer.inlineTokens(match[2].trim())
});
- allDefinitions.push(currentDefinition);
+ endIndex = regex.lastIndex;
}
- if(allDefinitions.length) {
+ if(definitions.length) {
return {
- type : 'definitionLists',
- raw : src.slice(0, endIndex),
- inlineDefinitions,
- definitions : allDefinitions
+ type : 'definitionListsInline',
+ raw : src.slice(0, endIndex),
+ definitions
+ };
+ }
+ },
+ renderer(token) {
+ return `
${token.definitions.reduce((html, def)=>{
+ return `${html}- ${this.parser.parseInline(def.dt)}
`
+ + `- ${this.parser.parseInline(def.dd)}
`;
+ }, '')}
`;
+ }
+};
+
+const definitionListsMultiline = {
+ name : 'definitionListsMultiline',
+ 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 regex = /(\n?\n?(?!::)[^\n]+?(?=\n::))|\n::(.(?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y;
+ let match;
+ let endIndex = 0;
+ const definitions = [];
+ while (match = regex.exec(src)) {
+ if(match[1]) {
+ definitions.push({
+ dt : this.lexer.inlineTokens(match[1].trim()),
+ dds : []
+ });
+ }
+ if(match[2]) {
+ definitions[definitions.length - 1].dds.push(
+ this.lexer.inlineTokens(match[2].trim().replace(/\s/g,' '))
+ )
+ }
+ endIndex = regex.lastIndex;
+ }
+ if(definitions.length) {
+ return {
+ type : 'definitionListsMultiline',
+ raw : src.slice(0, endIndex),
+ definitions
};
}
},
renderer(token) {
let returnVal = ``;
token.definitions.forEach((def)=>{
- let dds = def.ddo.map((s)=>{
- return `${token.inlineDefinitions ? '' : '\n'}- ${this.parser.parseInline(s).trim()}
`;
+ let dds = def.dds.map((s)=>{
+ return `\n- ${this.parser.parseInline(s).trim()}
`;
}).join('');
returnVal += `- ${this.parser.parseInline(def.dt)}
${dds}\n`;
});
@@ -658,7 +615,7 @@ function MarkedVariables() {
//^=====--------------------< Variable Handling >-------------------=====^//
Marked.use(MarkedVariables());
-Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
+Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionListsInline, definitionListsMultiline, superSubScripts] });
Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
diff --git a/tests/markdown/marked-extensions.test.js b/tests/markdown/marked-extensions.test.js
index b4e5dee5c..8d6c3c1c4 100644
--- a/tests/markdown/marked-extensions.test.js
+++ b/tests/markdown/marked-extensions.test.js
@@ -3,59 +3,77 @@
const Markdown = require('naturalcrit/markdown.js');
describe('Inline Definition Lists', ()=>{
+ test('No Term 1 Definition', function() {
+ const source = ':: My First Definition\n\n';
+ const rendered = Markdown.render(source).trim();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- My First Definition
');
+ });
+
test('Single Definition Term', function() {
const source = 'My term :: My First Definition\n\n';
- const rendered = Markdown.render(source);
+ const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- My term
- My First Definition
');
});
test('Multiple Definition Terms', function() {
const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n';
- const rendered = Markdown.render(source);
- expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
- Definition of Term 1
\n- Term 2
- Definition of Term 2
');
+ const rendered = Markdown.render(source).trim();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
- Definition of Term 1
- Term 2
- Definition of Term 2
');
});
});
describe('Multiline Definition Lists', ()=>{
test('Single Term, Single Definition', function() {
const source = 'Term 1\n::Definition 1\n\n';
- const rendered = Markdown.render(source);
+ const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1
');
});
test('Single Term, Plural Definitions', function() {
const source = 'Term 1\n::Definition 1\n::Definition 2\n\n';
- const rendered = Markdown.render(source);
+ const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1
\n- Definition 2
');
});
test('Multiple Term, Single Definitions', function() {
const source = 'Term 1\n::Definition 1\n\nTerm 2\n::Definition 1\n\n';
- const rendered = Markdown.render(source);
+ const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1
\n- Term 2
\n- Definition 1
');
});
test('Multiple Term, Plural Definitions', function() {
const source = 'Term 1\n::Definition 1\n::Definition 2\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
- const rendered = Markdown.render(source);
+ const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1
\n- Definition 2
\n- Term 2
\n- Definition 1
\n- Definition 2
');
});
test('Single Term, Single multi-line definition', function() {
const source = 'Term 1\n::Definition 1\nand more and\nmore and more\n\n';
- const rendered = Markdown.render(source);
+ const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1 and more and more and more
');
});
test('Single Term, Plural multi-line definitions', function() {
- const source = 'Term 1\n::Definition 1\nand more and more\n::Definition 2\n\n::Definition 3\n\n';
- const rendered = Markdown.render(source);
- expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1 and more and more
\n- Definition 2
\n- Definition 3
');
+ const source = 'Term 1\n::Definition 1\nand more and more\n::Definition 2\nand more\nand more\n::Definition 3\n\n';
+ const rendered = Markdown.render(source).trim();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1 and more and more
\n- Definition 2 and more and more
\n- Definition 3
');
});
test('Multiple Term, Single multi-line definition', function() {
- const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n\n::Definition 2\n\n';
- const rendered = Markdown.render(source);
+ const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
+ const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1 and more and more
\n- Term 2
\n- Definition 1
\n- Definition 2
');
});
+
+ test('Multiple Term, Single multi-line definition, followed by an inline dl', function() {
+ const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)';
+ const rendered = Markdown.render(source).trim();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1 and more and more
\n- Term 2
\n- Definition 1
\n- Definition 2
- Inline Definition (no term)
');
+ });
+
+ test('Multiple Term, Single multi-line definition, followed by paragraph', function() {
+ const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\nParagraph';
+ const rendered = Markdown.render(source).trim();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('- Term 1
\n- Definition 1 and more and more
\n- Term 2
\n- Definition 1
\n- Definition 2
Paragraph
');
+ });
});