${cleanStyle} ` }} />;
};
diff --git a/package-lock.json b/package-lock.json
index 1b55c40a0..9ccc49e80 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "homebrewery",
- "version": "3.12.0",
+ "version": "3.13.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "homebrewery",
- "version": "3.12.0",
+ "version": "3.13.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index b5b7824b3..83e180280 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
- "version": "3.12.0",
+ "version": "3.13.0",
"engines": {
"npm": "^10.2.x",
"node": "^20.8.x"
diff --git a/shared/naturalcrit/codeEditor/codeEditor.less b/shared/naturalcrit/codeEditor/codeEditor.less
index 0f29eff7b..cb73b0a88 100644
--- a/shared/naturalcrit/codeEditor/codeEditor.less
+++ b/shared/naturalcrit/codeEditor/codeEditor.less
@@ -8,6 +8,7 @@
@import (less) './themes/fonts/iconFonts/diceFont.less';
@import (less) './themes/fonts/iconFonts/elderberryInn.less';
@import (less) './themes/fonts/iconFonts/gameIcons.less';
+@import (less) './themes/fonts/iconFonts/fontAwesome.less';
@keyframes sourceMoveAnimation {
50% {background-color: red; color: white;}
diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js
index 95431487d..529129833 100644
--- a/shared/naturalcrit/markdown.js
+++ b/shared/naturalcrit/markdown.js
@@ -102,7 +102,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
@@ -159,7 +159,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
@@ -214,7 +214,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];
@@ -265,7 +265,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];
@@ -771,7 +771,8 @@ const processStyleTags = (string)=>{
const attributes = _.remove(tags, (tag)=>(tag.includes('='))).map((tag)=>tag.replace(/="?([^"]*)"?/g, '="$1"'))
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{
- let [key, value] = attr.split('=');
+ const index = attr.indexOf('=');
+ let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
value = value.replace(/"/g, '');
obj[key] = value;
return obj;
@@ -793,7 +794,8 @@ const extractHTMLStyleTags = (htmlString)=>{
const attributes = htmlString.match(/[a-zA-Z]+="[^"]*"/g)
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{
- let [key, value] = attr.split('=');
+ const index = attr.indexOf('=');
+ let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
value = value.replace(/"/g, '');
obj[key] = value;
return obj;
diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js
index b32876353..7b0115cae 100644
--- a/tests/markdown/mustache-syntax.test.js
+++ b/tests/markdown/mustache-syntax.test.js
@@ -338,6 +338,18 @@ describe('Injection: When an injection tag follows an element', ()=>{
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

`);
});
+
+ it('Renders an image with "=" in the url, and added attributes', function() {
+ const source = ` {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(`

`);
+ });
+
+ it('Renders an image and added attributes with "=" in the value, ', function() {
+ const source = ` {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e,otherUrl="url?auth=12345"}`;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

`);
+ });
});
describe('and that element is a block', ()=>{
diff --git a/themes/V3/5ePHB/snippets.js b/themes/V3/5ePHB/snippets.js
index c0933d70d..d5f37ac65 100644
--- a/themes/V3/5ePHB/snippets.js
+++ b/themes/V3/5ePHB/snippets.js
@@ -21,9 +21,43 @@ module.exports = [
view : 'text',
snippets : [
{
- name : 'Table of Contents',
- icon : 'fas fa-book',
- gen : TableOfContentsGen
+ name : 'Table of Contents',
+ icon : 'fas fa-book',
+ gen : TableOfContentsGen,
+ experimental : true,
+ subsnippets : [
+ {
+ name : 'Table of Contents',
+ icon : 'fas fa-book',
+ gen : TableOfContentsGen,
+ experimental : true
+ },
+ {
+ name : 'Include in ToC up to H3',
+ icon : 'fas fa-dice-three',
+ gen : dedent `\n{{tocDepthH3
+ }}\n`,
+
+ },
+ {
+ name : 'Include in ToC up to H4',
+ icon : 'fas fa-dice-four',
+ gen : dedent `\n{{tocDepthH4
+ }}\n`,
+ },
+ {
+ name : 'Include in ToC up to H5',
+ icon : 'fas fa-dice-five',
+ gen : dedent `\n{{tocDepthH5
+ }}\n`,
+ },
+ {
+ name : 'Include in ToC up to H6',
+ icon : 'fas fa-dice-six',
+ gen : dedent `\n{{tocDepthH6
+ }}\n`,
+ }
+ ]
},
{
name : 'Index',
diff --git a/themes/V3/5ePHB/snippets/tableOfContents.gen.js b/themes/V3/5ePHB/snippets/tableOfContents.gen.js
index 04ff77f3f..b212dea36 100644
--- a/themes/V3/5ePHB/snippets/tableOfContents.gen.js
+++ b/themes/V3/5ePHB/snippets/tableOfContents.gen.js
@@ -2,77 +2,68 @@ const _ = require('lodash');
const dedent = require('dedent-tabs').default;
const getTOC = (pages)=>{
- const add1 = (title, page)=>{
- res.push({
- title : title,
- page : page + 1,
- children : []
- });
- };
- const add2 = (title, page)=>{
- if(!_.last(res)) add1(null, page);
- _.last(res).children.push({
- title : title,
- page : page + 1,
- children : []
- });
- };
- const add3 = (title, page)=>{
- if(!_.last(res)) add1(null, page);
- if(!_.last(_.last(res).children)) add2(null, page);
- _.last(_.last(res).children).children.push({
- title : title,
- page : page + 1,
- children : []
- });
+
+ const recursiveAdd = (title, page, targetDepth, child, curDepth=0)=>{
+ if(curDepth > 5) return; // Something went wrong.
+ if(curDepth == targetDepth) {
+ child.push({
+ title : title,
+ page : page,
+ children : []
+ });
+ } else {
+ if(child.length == 0) {
+ child.push({
+ title : null,
+ page : page,
+ children : []
+ });
+ }
+ recursiveAdd(title, page, targetDepth, _.last(child).children, curDepth+1,);
+ }
};
const res = [];
- _.each(pages, (page, pageNum)=>{
- if(!page.includes('{{frontCover}}') && !page.includes('{{insideCover}}') && !page.includes('{{partCover}}') && !page.includes('{{backCover}}')) {
- const lines = page.split('\n');
- _.each(lines, (line)=>{
- if(_.startsWith(line, '# ')){
- const title = line.replace('# ', '');
- add1(title, pageNum);
- }
- if(_.startsWith(line, '## ')){
- const title = line.replace('## ', '');
- add2(title, pageNum);
- }
- if(_.startsWith(line, '### ')){
- const title = line.replace('### ', '');
- add3(title, pageNum);
- }
- });
+
+ const iframe = document.getElementById('BrewRenderer');
+ const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
+ const headings = iframeDocument.querySelectorAll('h1, h2, h3, h4, h5, h6');
+ const headerDepth = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
+
+ _.each(headings, (heading)=>{
+ const onPage = parseInt(heading.closest('.page').id?.replace(/^p/, ''));
+ const ToCExclude = getComputedStyle(heading).getPropertyValue('--TOC');
+
+ if(ToCExclude != 'exclude') {
+ recursiveAdd(heading.innerText.trim(), onPage, headerDepth.indexOf(heading.tagName), res);
}
});
return res;
};
+
+const ToCIterate = (entries, curDepth=0)=>{
+ const levelPad = ['- ###', ' - ####', ' - ', ' - ', ' - ', ' - '];
+ const toc = [];
+ if(entries.title !== null){
+ toc.push(`${levelPad[curDepth]} [{{ ${entries.title}}}{{ ${entries.page}}}](#p${entries.page})`);
+ }
+ if(entries.children.length) {
+ _.each(entries.children, (entry, idx)=>{
+ const children = ToCIterate(entry, entry.title == null ? curDepth : curDepth+1);
+ if(children.length) {
+ toc.push(...children);
+ }
+ });
+ }
+ return toc;
+};
+
module.exports = function(props){
const pages = props.brew.text.split('\\page');
const TOC = getTOC(pages);
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
- if(g1.title !== null) {
- r.push(`- ### [{{ ${g1.title}}}{{ ${g1.page}}}](#p${g1.page})`);
- }
- if(g1.children.length){
- _.each(g1.children, (g2, idx2)=>{
- if(g2.title !== null) {
- r.push(` - #### [{{ ${g2.title}}}{{ ${g2.page}}}](#p${g2.page})`);
- }
- if(g2.children.length){
- _.each(g2.children, (g3, idx3)=>{
- if(g2.title !== null) {
- r.push(` - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
- } else { // Don't over-indent if no level-2 parent entry
- r.push(` - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
- }
- });
- }
- });
- }
+ r.push(ToCIterate(g1).join('\n'));
return r;
}, []).join('\n');
diff --git a/themes/V3/5ePHB/style.less b/themes/V3/5ePHB/style.less
index 400174ab0..f8a14f46e 100644
--- a/themes/V3/5ePHB/style.less
+++ b/themes/V3/5ePHB/style.less
@@ -786,6 +786,39 @@
// *****************************
// * TABLE OF CONTENTS
// *****************************/
+
+// Default Exclusions
+// Anything not exlcuded is included, default Headers are H1, H2, and H3.
+h4,
+h5,
+h6,
+.page:has(.frontCover),
+.page:has(.backCover),
+.page:has(.insideCover),
+.monster,
+.noToC,
+.toc { --TOC: exclude; }
+
+.tocDepthH2 :is(h1, h2) {--TOC: include; }
+.tocDepthH3 :is(h1, h2, h3) {--TOC: include; }
+.tocDepthH4 :is(h1, h2, h3, h4) {--TOC: include; }
+.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC: include; }
+.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC: include; }
+
+.tocIncludeH1 h1 {--TOC: include; }
+.tocIncludeH2 h2 {--TOC: include; }
+.tocIncludeH3 h3 {--TOC: include; }
+.tocIncludeH4 h4 {--TOC: include; }
+.tocIncludeH5 h5 {--TOC: include; }
+.tocIncludeH6 h6 {--TOC: include; }
+
+.page:has(.partCover) {
+ --TOC: exclude;
+ & h1 {
+ --TOC: include;
+ }
+ }
+
.page {
&:has(.toc)::after { display : none; }
.toc {
diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less
index 24e87504f..0f779c38b 100644
--- a/themes/V3/Blank/style.less
+++ b/themes/V3/Blank/style.less
@@ -3,6 +3,7 @@
@import (less) './themes/fonts/iconFonts/elderberryInn.less';
@import (less) './themes/fonts/iconFonts/diceFont.less';
@import (less) './themes/fonts/iconFonts/gameIcons.less';
+@import (less) './themes/fonts/iconFonts/fontAwesome.less';
:root {
//Colors
diff --git a/themes/fonts/iconFonts/diceFont.less b/themes/fonts/iconFonts/diceFont.less
index 6fe226a05..ec80f132b 100644
--- a/themes/fonts/iconFonts/diceFont.less
+++ b/themes/fonts/iconFonts/diceFont.less
@@ -7,7 +7,7 @@
}
.df {
- display : inline-block;
+ display : inline;
font-family : 'DiceFont';
font-style : normal;
font-weight : normal;
@@ -16,8 +16,11 @@
text-decoration : inherit;
text-transform : none;
text-rendering : optimizeLegibility;
- -moz-osx-font-smoothing : grayscale;
+
+ /* Better Font Rendering =========== */
-webkit-font-smoothing : antialiased;
+ -moz-osx-font-smoothing : grayscale;
+
&.F::before { content : '\f190'; }
&.F-minus::before { content : '\f191'; }
&.F-plus::before { content : '\f192'; }
diff --git a/themes/fonts/iconFonts/elderberryInn.less b/themes/fonts/iconFonts/elderberryInn.less
index c956563fc..958d1b265 100644
--- a/themes/fonts/iconFonts/elderberryInn.less
+++ b/themes/fonts/iconFonts/elderberryInn.less
@@ -7,15 +7,16 @@
}
.ei {
- display : inline-block;
- margin-right : 3px;
+ display : inline;
font-family : 'Elderberry-Inn';
line-height : 1;
vertical-align : baseline;
- -moz-osx-font-smoothing : grayscale;
- -webkit-font-smoothing : antialiased;
text-rendering : auto;
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing : antialiased;
+ -moz-osx-font-smoothing : grayscale;
+
&.book::before { content : '\E900'; }
&.screen::before { content : '\E901'; }
diff --git a/themes/fonts/iconFonts/fontAwesome.less b/themes/fonts/iconFonts/fontAwesome.less
new file mode 100644
index 000000000..5f626c645
--- /dev/null
+++ b/themes/fonts/iconFonts/fontAwesome.less
@@ -0,0 +1,2 @@
+/* Icon Font: Font Awesome */
+.far,.fas,.fab { display : inline; }
\ No newline at end of file
diff --git a/themes/fonts/iconFonts/gameIcons.less b/themes/fonts/iconFonts/gameIcons.less
index ea7b3aba5..a32ebdd08 100644
--- a/themes/fonts/iconFonts/gameIcons.less
+++ b/themes/fonts/iconFonts/gameIcons.less
@@ -8,19 +8,15 @@
.gi {
/* use !important to prevent issues with browser extensions that change fonts */
- display : inline-block;
- margin-right : 3px;
+ display : inline;
font-family : 'Game-Icons' !important;
line-height : 1;
vertical-align : baseline;
- -moz-osx-font-smoothing : grayscale;
- -webkit-font-smoothing : antialiased;
text-rendering : auto;
/* Better Font Rendering =========== */
-webkit-font-smoothing : antialiased;
-moz-osx-font-smoothing : grayscale;
-
&.zigzag-leaf::before { content : '\e900'; }
&.zebra-shield::before { content : '\e901'; }