diff --git a/client/homebrew/editor/snippetbar/snippets/snippets.js b/client/homebrew/editor/snippetbar/snippets/snippets.js index 6ecae9429..9abedcf28 100644 --- a/client/homebrew/editor/snippetbar/snippets/snippets.js +++ b/client/homebrew/editor/snippetbar/snippets/snippets.js @@ -51,16 +51,13 @@ module.exports = [ name : 'Image', icon : 'fas fa-image', gen : dedent` - + ![cat warrior](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width="325px"} Credit: Kyounghwan Kim` }, { name : 'Background Image', icon : 'fas fa-tree', - gen : `` + gen : `![homebrew mug](http://i.imgur.com/hMna6G0.png) {position="absolute",top="50px",right="30px",width="280px"}` }, { name : 'QR Code', diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 37784f740..580ba5b5a 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -31,7 +31,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,\-. ]*"|[^"'{}\s])*\s*|}}/g; + const inlineRegex = /{{(?:="[\w,\-(). ]*"|[^"'{}\s])*\s*|}}/g; const match = completeSpan.exec(src); if(match) { //Find closing delimiter @@ -77,7 +77,7 @@ const mustacheDivs = { start(src) { return src.match(/^ *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { const completeBlock = /^ *{{.*\n *}}/s; // Regex for the complete token - const blockRegex = /^ *{{(?:="[\w,\-. ]*"|[^"'{}\s])*$|^ *}}$/gm; + const blockRegex = /^ *{{(?:="[\w,\-(). ]*"|[^"'{}\s])*$|^ *}}$/gm; const match = completeBlock.exec(src); if(match) { //Find closing delimiter @@ -116,6 +116,82 @@ const mustacheDivs = { } }; +const mustacheInjectInline = { + name : '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,\-(). ]*"|[^"'{}\s])*)}/g; + const match = inlineRegex.exec(src); + if(match) { + const lastToken = tokens[tokens.length - 1]; + if(!lastToken) + return false; + + const tags = ` ${processStyleTags(match[1])}`; + lastToken.originalType = lastToken.type; + lastToken.type = 'mustacheInjectInline'; + lastToken.tags = tags; + return { + type : 'text', // Should match "name" above + raw : match[0], // Text to consume from the source + text : '' + }; + } + }, + renderer(token) { + token.type = token.originalType; + const text = this.parseInline([token]); + const openingTag = /(<[^\s<>]+)([^\n<>]*>.*)/s.exec(text); + if(openingTag) { + return `${openingTag[1]} class="${token.tags}${openingTag[2]}`; + } + return text; + } +}; + +const mustacheInjectBlock = { + extensions : [{ + name : 'mustacheInjectBlock', + level : 'block', + start(src) { return src.match(/^ *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match + tokenizer(src, tokens) { + const inlineRegex = /^ *{((?:="[\w,\-(). ]*"|[^"'{}\s])*)}/ym; + const match = inlineRegex.exec(src); + if(match) { + const lastToken = tokens[tokens.length - 1]; + if(!lastToken) + return false; + + lastToken.originalType = 'mustacheInjectBlock'; + lastToken.tags = ` ${processStyleTags(match[1])}`; + return { + type : 'text', // Should match "name" above + raw : match[0], // Text to consume from the source + text : '' + }; + } + }, + renderer(token) { + token.type = token.originalType; + const text = this.parse([token]); + const openingTag = /(<[^\s<>]+)([^\n<>]*>.*)/s.exec(text); + if(openingTag) { + return `${openingTag[1]} class="${token.tags}${openingTag[2]}`; + } + return text; + } + }], + walkTokens(token) { + // After token tree is finished, tag tokens to apply styles to so Renderer can find them + // Does not work with tables since Marked.js tables generate invalid "tokens", and changing "type" ruins Marked handling that edge-case + if(token.originalType == 'mustacheInjectBlock' && token.type !== 'table') { + token.originalType = token.type; + token.type = 'mustacheInjectBlock'; + } + } +}; + const definitionLists = { name : 'definitionLists', level : 'block', @@ -125,13 +201,11 @@ const definitionLists = { let match; let endIndex = 0; const definitions = []; - //if(!src.match(/^[^\n]*?::/)) {console.log('return'); return;} while (match = regex.exec(src)) { definitions.push({ dt : this.inlineTokens(match[1].trim()), dd : this.inlineTokens(match[2].trim()) }); - //console.log(regexl) endIndex = regex.lastIndex; } if(definitions.length) { @@ -146,13 +220,15 @@ const definitionLists = { return `
${token.definitions.reduce((html, def)=>{ return `${html}
${this.parseInline(def.dt)}
` - + `
${this.parseInline(def.dd)}
\n`; + + `
${this.parseInline(def.dd)}
\n`; }, '')}
`; } }; -Markdown.use({ extensions: [mustacheSpans, mustacheDivs, definitionLists] }); +Markdown.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] }); +Markdown.use(mustacheInjectBlock); +Markdown.use({ smartypants: true }); //Fix local links in the Preview iFrame to link inside the frame renderer.link = function (href, title, text) { @@ -251,9 +327,7 @@ module.exports = { marked : Markdown, render : (rawBrewText)=>{ rawBrewText = rawBrewText.replace(/^\\column$/gm, `
`) - .replace(/^(:+)$/gm, (match)=>`${`
`.repeat(match.length)}\n`) - .replace(/^}}/gm, '\n}}') - .replace(/^({{[^\n]*)$/gm, '$1\n'); + .replace(/^(:+)$/gm, (match)=>`${`
`.repeat(match.length)}\n`); return Markdown( sanatizeScriptTags(rawBrewText), { renderer: renderer } diff --git a/themes/5ePhb.style.less b/themes/5ePhb.style.less index 7db001466..a1395554d 100644 --- a/themes/5ePhb.style.less +++ b/themes/5ePhb.style.less @@ -604,6 +604,7 @@ body { } .inline { display : inline-block; + text-indent : initial; } div { column-gap : 0.5cm; //Default spacing if a div uses multicolumns