0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-24 16:22:44 +00:00

Update Marked to v4.0.7, use Extended-Tables extension

This commit is contained in:
Trevor Buckner
2021-12-13 15:21:53 -05:00
parent cd18692a53
commit 98f6ba6045
3 changed files with 35 additions and 209 deletions

32
package-lock.json generated
View File

@@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
"name": "homebrewery",
"version": "3.0.5",
"hasInstallScript": true,
"license": "MIT",
@@ -29,7 +28,8 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "3.0.8",
"marked": "4.0.7",
"marked-extended-tables": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1",
"mongoose": "^6.1.1",
@@ -6082,16 +6082,24 @@
}
},
"node_modules/marked": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.7.tgz",
"integrity": "sha512-mQrRvV2vRk7DHZsWsYJfAjmBo+lSYPhTJPThaaGpkEfmC+4oefeug2txZniQTieDS0CFpokfVhd7JuS5GtnHhA==",
"bin": {
"marked": "bin/marked"
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/marked-extended-tables": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/marked-extended-tables/-/marked-extended-tables-1.0.2.tgz",
"integrity": "sha512-QRc8EgdWNrPXYYMa2tHtlKGrMUvJI9H3DUGTLrBpsevFntignPXPDs/2Aez4tw8pAvxbQ9K7yos6u3gAajPAkA==",
"peerDependencies": {
"marked": "^3.0.0 || ^4.0.0"
}
},
"node_modules/markedLegacy": {
"name": "marked",
"version": "0.3.19",
@@ -14155,9 +14163,15 @@
}
},
"marked": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw=="
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.7.tgz",
"integrity": "sha512-mQrRvV2vRk7DHZsWsYJfAjmBo+lSYPhTJPThaaGpkEfmC+4oefeug2txZniQTieDS0CFpokfVhd7JuS5GtnHhA=="
},
"marked-extended-tables": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/marked-extended-tables/-/marked-extended-tables-1.0.2.tgz",
"integrity": "sha512-QRc8EgdWNrPXYYMa2tHtlKGrMUvJI9H3DUGTLrBpsevFntignPXPDs/2Aez4tw8pAvxbQ9K7yos6u3gAajPAkA==",
"requires": {}
},
"markedLegacy": {
"version": "npm:marked@0.3.19",

View File

@@ -59,7 +59,8 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "3.0.8",
"marked": "4.0.7",
"marked-extended-tables": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1",
"mongoose": "^6.1.1",

View File

@@ -1,7 +1,8 @@
/* eslint-disable max-lines */
const _ = require('lodash');
const Markdown = require('marked');
const renderer = new Markdown.Renderer();
const Marked = require('marked');
const MarkedExtendedTables = require('marked-extended-tables');
const renderer = new Marked.Renderer();
//Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (html) {
@@ -9,7 +10,7 @@ renderer.html = function (html) {
const openTag = html.substring(0, html.indexOf('>')+1);
html = html.substring(html.indexOf('>')+1);
html = html.substring(0, html.lastIndexOf('</div>'));
return `${openTag} ${Markdown(html)} </div>`;
return `${openTag} ${Marked.parse(html)} </div>`;
}
return html;
};
@@ -235,200 +236,10 @@ const definitionLists = {
}
};
const spanTable = {
name : 'spanTable',
level : 'block', // Is this a block-level or inline-level tokenizer?
start(src) { return src.match(/^\n *([^\n ].*\|.*)\n/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
//const regex = this.tokenizer.rules.block.table;
const regex = new RegExp('^ *([^\\n ].*\\|.*\\n(?: *[^\\s].*\\n)*?)' // Header
+ ' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)\\|?' // Align
+ '(?:\\n *((?:(?!\\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})' // Cells
+ '(?:\\n+|$)| {0,3}#{1,6} | {0,3}>| {4}[^\\n]| {0,3}(?:`{3,}'
+ '(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n| {0,3}(?:[*+-]|1[.)]) |'
+ '<\\/?(?:address|article|aside|base|basefont|blockquote|body|'
+ 'caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\\n|\\/?>)|<(?:script|pre|style|textarea|!--)).*(?:\\n|$))*)\\n*|$)'); // Cells
const cap = regex.exec(src);
if(cap) {
const item = {
type : 'spanTable',
header : cap[1].replace(/\n$/, '').split('\n'),
align : cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
rows : cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
};
// Get first header row to determine how many columns
item.header[0] = splitCells(item.header[0]);
const colCount = item.header[0].reduce((length, header)=>{
return length + header.colspan;
}, 0);
if(colCount === item.align.length) {
item.raw = cap[0];
let i, j, k, row;
// Get alignment row (:---:)
let l = item.align.length;
for (i = 0; i < l; i++) {
if(/^ *-+: *$/.test(item.align[i])) {
item.align[i] = 'right';
} else if(/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = 'center';
} else if(/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = 'left';
} else {
item.align[i] = null;
}
}
// Get any remaining header rows
l = item.header.length;
for (i = 1; i < l; i++) {
item.header[i] = splitCells(item.header[i], colCount, item.header[i-1]);
}
// Get main table cells
l = item.rows.length;
for (i = 0; i < l; i++) {
item.rows[i] = splitCells(item.rows[i], colCount, item.rows[i-1]);
}
// header child tokens
l = item.header.length;
for (j = 0; j < l; j++) {
row = item.header[j];
for (k = 0; k < row.length; k++) {
row[k].tokens = [];
this.lexer.inlineTokens(row[k].text, row[k].tokens);
}
}
// cell child tokens
l = item.rows.length;
for (j = 0; j < l; j++) {
row = item.rows[j];
for (k = 0; k < row.length; k++) {
row[k].tokens = [];
this.lexer.inlineTokens(row[k].text, row[k].tokens);
}
}
return item;
}
}
},
renderer(token) {
let i, j, row, cell, col, text;
let output = `<table>`;
output += `<thead>`;
for (i = 0; i < token.header.length; i++) {
row = token.header[i];
let col = 0;
output += `<tr>`;
for (j = 0; j < row.length; j++) {
cell = row[j];
text = this.parser.parseInline(cell.tokens);
output += getTableCell(text, cell, 'th', token.align[col]);
col += cell.colspan;
}
output += `</tr>`;
}
output += `</thead>`;
if(token.rows.length) {
output += `<tbody>`;
for (i = 0; i < token.rows.length; i++) {
row = token.rows[i];
col = 0;
output += `<tr>`;
for (j = 0; j < row.length; j++) {
cell = row[j];
text = this.parser.parseInline(cell.tokens);
output += getTableCell(text, cell, 'td', token.align[col]);
col += cell.colspan;
}
output += `</tr>`;
}
output += `</tbody>`;
}
output += `</table>`;
return output;
}
};
const getTableCell = (text, cell, type, align)=>{
if(!cell.rowspan) {
return '';
}
const tag = `<${type}`
+ `${cell.colspan > 1 ? ` colspan=${cell.colspan}` : ''}`
+ `${cell.rowspan > 1 ? ` rowspan=${cell.rowspan}` : ''}`
+ `${align ? ` align=${align}` : ''}>`;
return `${tag + text}</${type}>\n`;
};
const splitCells = (tableRow, count, prevRow = [])=>{
const cells = [...tableRow.matchAll(/(?:[^|\\]|\\.?)+(?:\|+|$)/g)].map((x)=>x[0]);
// Remove first/last cell in a row if whitespace only and no leading/trailing pipe
if(!cells[0]?.trim()) { cells.shift(); }
if(!cells[cells.length - 1]?.trim()) { cells.pop(); }
let numCols = 0;
let i, j, trimmedCell, prevCell, prevCols;
for (i = 0; i < cells.length; i++) {
trimmedCell = cells[i].split(/\|+$/)[0];
cells[i] = {
rowspan : 1,
colspan : Math.max(cells[i].length - trimmedCell.length, 1),
text : trimmedCell.trim().replace(/\\\|/g, '|')
// display escaped pipes as normal character
};
// Handle Rowspan
if(trimmedCell.slice(-1) == '^' && prevRow.length) {
// Find matching cell in previous row
prevCols = 0;
for (j = 0; j < prevRow.length; j++) {
prevCell = prevRow[j];
if((prevCols == numCols) && (prevCell.colspan == cells[i].colspan)) {
// merge into matching cell in previous row (the "target")
cells[i].rowSpanTarget = prevCell.rowSpanTarget ?? prevCell;
cells[i].rowSpanTarget.text += ` ${cells[i].text.slice(0, -1)}`;
cells[i].rowSpanTarget.rowspan += 1;
cells[i].rowspan = 0;
break;
}
prevCols += prevCell.colspan;
if(prevCols > numCols)
break;
}
}
numCols += cells[i].colspan;
}
// Force main cell rows to match header column count
if(numCols > count) {
cells.splice(count);
} else {
while (numCols < count) {
cells.push({
colspan : 1,
text : ''
});
numCols += 1;
}
}
return cells;
};
Markdown.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, spanTable] });
Markdown.use(mustacheInjectBlock);
Markdown.use({ smartypants: true });
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] });
Marked.use(MarkedExtendedTables());
Marked.use(mustacheInjectBlock);
Marked.use({ smartypants: true });
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) {
@@ -532,11 +343,11 @@ const processStyleTags = (string)=>{
};
module.exports = {
marked : Markdown,
marked : Marked,
render : (rawBrewText)=>{
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`)
.replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`);
return Markdown(
return Marked.parse(
sanatizeScriptTags(rawBrewText),
{ renderer: renderer }
);