diff --git a/.eslintrc.js b/.eslintrc.js
index dd4bcd0d3..74e7bb660 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -15,7 +15,7 @@ module.exports = {
rules : {
/** Errors **/
'camelcase' : ['error', { properties: 'never' }],
- 'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
+ //'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
'no-array-constructor' : 'error',
'no-iterator' : 'error',
'no-nested-ternary' : 'error',
diff --git a/changelog.md b/changelog.md
index bfdb278e4..626a992d9 100644
--- a/changelog.md
+++ b/changelog.md
@@ -80,6 +80,63 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
+### Thursday 17/08/2023 - v3.9.2
+{{taskList
+
+##### Calculuschild
+
+* [x] Fix links to certain old Google Drive files
+
+Fixes issue [#2917](https://github.com/naturalcrit/homebrewery/issues/2917)
+
+##### G-Ambatte
+
+* [x] Menus now open on click, and internally consistent
+
+Fixes issue [#2702](https://github.com/naturalcrit/homebrewery/issues/2702), [#2782](https://github.com/naturalcrit/homebrewery/issues/2782)
+
+* [x] Add smarter footer snippet
+
+Fixes issue [#2289](https://github.com/naturalcrit/homebrewery/issues/2289)
+
+* [x] Add sanitization in Style editor
+
+Fixes issue [#1437](https://github.com/naturalcrit/homebrewery/issues/1437)
+
+* [x] Rework class table snippets to remove unnecessary randomness
+
+Fixes issue [#2964](https://github.com/naturalcrit/homebrewery/issues/2964)
+
+* [x] Add User Page link to Google Drive file for file owners, add icons for additional storage locations
+
+Fixes issue [#2954](https://github.com/naturalcrit/homebrewery/issues/2954)
+
+* [x] Add default save location selection to Account Page
+
+Fixes issue [#2943](https://github.com/naturalcrit/homebrewery/issues/2943)
+
+##### 5e-Cleric
+
+* [x] Exclude cover pages from Table of Content generation (editing on mobile is still not recommended)
+
+Fixes issue [#2920](https://github.com/naturalcrit/homebrewery/issues/2920)
+
+##### Gazook89
+
+* [x] Adjustments to improve mobile viewing
+
+}}
+
+### Wednesday 28/06/2023 - v3.9.1
+{{taskList
+
+##### G-Ambatte
+
+* [x] Better error pages with more useful information
+
+Fixes issue [#1924](https://github.com/naturalcrit/homebrewery/issues/1924)
+}}
+
### Friday 02/06/2023 - v3.9.0
{{taskList
diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index 27fef7e16..51921c8ca 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -108,6 +108,12 @@ const BrewRenderer = createClass({
return false;
},
+ sanitizeScriptTags : function(content) {
+ return content
+ .replace(/';
- const rendered = Markdown.render(source);
- expect(rendered).toMatch('
<script></script>
\n');
-});
-
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
const source = '*Bold text*
';
const rendered = Markdown.render(source);
diff --git a/themes/Legacy/5ePHB/snippets/tableOfContents.gen.js b/themes/Legacy/5ePHB/snippets/tableOfContents.gen.js
index 4082ac4ef..40d64af22 100644
--- a/themes/Legacy/5ePHB/snippets/tableOfContents.gen.js
+++ b/themes/Legacy/5ePHB/snippets/tableOfContents.gen.js
@@ -47,8 +47,8 @@ const getTOC = (pages)=>{
return res;
};
-module.exports = function(brew){
- const pages = brew.text.split('\\page');
+module.exports = function(props){
+ const pages = props.brew.text.split('\\page');
const TOC = getTOC(pages);
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`);
diff --git a/themes/V3/5ePHB/snippets.js b/themes/V3/5ePHB/snippets.js
index dba4ad8d5..506bc0edd 100644
--- a/themes/V3/5ePHB/snippets.js
+++ b/themes/V3/5ePHB/snippets.js
@@ -19,16 +19,6 @@ module.exports = [
icon : 'fas fa-pencil-alt',
view : 'text',
snippets : [
- {
- name : 'Page Number',
- icon : 'fas fa-bookmark',
- gen : '{{pageNumber 1}}\n{{footnote PART 1 | SECTION NAME}}\n\n'
- },
- {
- name : 'Auto-incrementing Page Number',
- icon : 'fas fa-sort-numeric-down',
- gen : '{{pageNumber,auto}}\n{{footnote PART 1 | SECTION NAME}}\n\n'
- },
{
name : 'Table of Contents',
icon : 'fas fa-book',
@@ -230,34 +220,51 @@ module.exports = [
view : 'text',
snippets : [
{
- name : 'Class Table',
- icon : 'fas fa-table',
- gen : ClassTableGen.full('classTable,frame,decoration,wide'),
- },
- {
- name : 'Class Table (unframed)',
- icon : 'fas fa-border-none',
- gen : ClassTableGen.full('classTable,wide'),
- },
- {
- name : '1/2 Class Table',
- icon : 'fas fa-list-alt',
- gen : ClassTableGen.half('classTable,decoration,frame'),
- },
- {
- name : '1/2 Class Table (unframed)',
- icon : 'fas fa-border-none',
- gen : ClassTableGen.half('classTable'),
- },
- {
- name : '1/3 Class Table',
- icon : 'fas fa-border-all',
- gen : ClassTableGen.third('classTable,frame'),
- },
- {
- name : '1/3 Class Table (unframed)',
- icon : 'fas fa-border-none',
- gen : ClassTableGen.third('classTable'),
+ name : 'Class Tables',
+ icon : 'fas fa-table',
+ gen : ClassTableGen.full('classTable,frame,decoration,wide'),
+ subsnippets : [
+ {
+ name : 'Martial Class Table',
+ icon : 'fas fa-table',
+ gen : ClassTableGen.non('classTable,frame,decoration'),
+ },
+ {
+ name : 'Martial Class Table (unframed)',
+ icon : 'fas fa-border-none',
+ gen : ClassTableGen.non('classTable'),
+ },
+ {
+ name : 'Full Caster Class Table',
+ icon : 'fas fa-table',
+ gen : ClassTableGen.full('classTable,frame,decoration,wide'),
+ },
+ {
+ name : 'Full Caster Class Table (unframed)',
+ icon : 'fas fa-border-none',
+ gen : ClassTableGen.full('classTable,wide'),
+ },
+ {
+ name : 'Half Caster Class Table',
+ icon : 'fas fa-list-alt',
+ gen : ClassTableGen.half('classTable,frame,decoration,wide'),
+ },
+ {
+ name : 'Half Caster Class Table (unframed)',
+ icon : 'fas fa-border-none',
+ gen : ClassTableGen.half('classTable,wide'),
+ },
+ {
+ name : 'Third Caster Spell Table',
+ icon : 'fas fa-border-all',
+ gen : ClassTableGen.third('classTable,frame,decoration'),
+ },
+ {
+ name : 'Third Caster Spell Table (unframed)',
+ icon : 'fas fa-border-none',
+ gen : ClassTableGen.third('classTable'),
+ }
+ ]
},
{
name : 'Rune Table',
diff --git a/themes/V3/5ePHB/snippets/classtable.gen.js b/themes/V3/5ePHB/snippets/classtable.gen.js
index c1f6254f9..1fdff036f 100644
--- a/themes/V3/5ePHB/snippets/classtable.gen.js
+++ b/themes/V3/5ePHB/snippets/classtable.gen.js
@@ -1,132 +1,138 @@
const _ = require('lodash');
+const dedent = require('dedent-tabs').default;
const features = [
- 'Astrological Botany',
- 'Biochemical Sorcery',
- 'Civil Divination',
- 'Consecrated Augury',
- 'Demonic Anthropology',
- 'Divinatory Mineralogy',
- 'Exo Interfacer',
- 'Genetic Banishing',
- 'Gunpowder Torturer',
- 'Gunslinger Corruptor',
- 'Hermetic Geography',
- 'Immunological Cultist',
- 'Malefic Chemist',
- 'Mathematical Pharmacy',
- 'Nuclear Biochemistry',
- 'Orbital Gravedigger',
- 'Pharmaceutical Outlaw',
- 'Phased Linguist',
- 'Plasma Gunslinger',
- 'Police Necromancer',
- 'Ritual Astronomy',
- 'Sixgun Poisoner',
- 'Seismological Alchemy',
- 'Spiritual Illusionism',
- 'Statistical Occultism',
- 'Spell Analyst',
- 'Torque Interfacer'
+ 'Astrological Botany', 'Biochemical Sorcery', 'Civil Divination',
+ 'Consecrated Augury', 'Demonic Anthropology', 'Divinatory Mineralogy',
+ 'Exo Interfacer', 'Genetic Banishing', 'Gunpowder Torturer',
+ 'Gunslinger Corruptor', 'Hermetic Geography', 'Immunological Cultist',
+ 'Malefic Chemist', 'Mathematical Pharmacy', 'Nuclear Biochemistry',
+ 'Orbital Gravedigger', 'Pharmaceutical Outlaw', 'Phased Linguist',
+ 'Plasma Gunslinger', 'Police Necromancer', 'Ritual Astronomy',
+ 'Sixgun Poisoner', 'Seismological Alchemy', 'Spiritual Illusionism',
+ 'Statistical Occultism', 'Spell Analyst', 'Torque Interfacer'
+].map((f)=>_.padEnd(f, 21)); // Pad to equal length of 21 chars long
+
+const classnames = [
+ 'Ackerman', 'Berserker-Typist', 'Concierge', 'Fishmonger',
+ 'Haberdasher', 'Manicurist', 'Netrunner', 'Weirkeeper'
];
-const classnames = ['Ackerman', 'Berserker-Typist', 'Concierge', 'Fishmonger',
- 'Haberdasher', 'Manicurist', 'Netrunner', 'Weirkeeper'];
-
-const levels = ['1st', '2nd', '3rd', '4th', '5th',
- '6th', '7th', '8th', '9th', '10th',
- '11th', '12th', '13th', '14th', '15th',
- '16th', '17th', '18th', '19th', '20th'];
-
-const profBonus = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6];
-
-const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
-
-const drawSlots = function(Slots, rows, padding){
- let slots = Number(Slots);
- return _.times(rows, function(i){
- const max = maxes[i];
- if(slots < 1) return _.pad('—', padding);
- const res = _.min([max, slots]);
- slots -= res;
- return _.pad(res.toString(), padding);
- }).join(' | ');
-};
-
module.exports = {
- full : function(classes){
- const classname = _.sample(classnames);
-
-
- let cantrips = 3;
- let spells = 1;
- let slots = 2;
- return `{{${classes}\n##### The ${classname}\n` +
- `| Level | Proficiency | Features | Cantrips | Spells | --- Spell Slots Per Spell Level ---|||||||||\n`+
- `| ^| Bonus ^| ^| Known ^| Known ^|1st |2nd |3rd |4th |5th |6th |7th |8th |9th |\n`+
- `|:-----:|:-----------:|:-------------|:--------:|:------:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|\n${
- _.map(levels, function(levelName, level){
- const res = [
- _.pad(levelName, 5),
- _.pad(`+${profBonus[level]}`, 2),
- _.padEnd(_.sample(features), 21),
- _.pad(cantrips.toString(), 8),
- _.pad(spells.toString(), 6),
- drawSlots(slots, 9, 2),
- ].join(' | ');
-
- cantrips += _.random(0, 1);
- spells += _.random(0, 1);
- slots += _.random(0, 2);
-
- return `| ${res} |`;
- }).join('\n')}\n}}\n\n`;
+ non : function(snippetClasses){
+ return dedent`
+ {{${snippetClasses}
+ ##### The ${_.sample(classnames)}
+ | Level | Proficiency Bonus | Features | ${_.sample(features)} |
+ |:-----:|:-----------------:|:---------|:---------------------:|
+ | 1st | +2 | ${_.sample(features)} | 2 |
+ | 2nd | +2 | ${_.sample(features)} | 2 |
+ | 3rd | +2 | ${_.sample(features)} | 3 |
+ | 4th | +2 | ${_.sample(features)} | 3 |
+ | 5th | +3 | ${_.sample(features)} | 3 |
+ | 6th | +3 | ${_.sample(features)} | 4 |
+ | 7th | +3 | ${_.sample(features)} | 4 |
+ | 8th | +3 | ${_.sample(features)} | 4 |
+ | 9th | +4 | ${_.sample(features)} | 4 |
+ | 10th | +4 | ${_.sample(features)} | 4 |
+ | 11th | +4 | ${_.sample(features)} | 4 |
+ | 12th | +4 | ${_.sample(features)} | 5 |
+ | 13th | +5 | ${_.sample(features)} | 5 |
+ | 14th | +5 | ${_.sample(features)} | 5 |
+ | 15th | +5 | ${_.sample(features)} | 5 |
+ | 16th | +5 | ${_.sample(features)} | 5 |
+ | 17th | +6 | ${_.sample(features)} | 6 |
+ | 18th | +6 | ${_.sample(features)} | 6 |
+ | 19th | +6 | ${_.sample(features)} | 6 |
+ | 20th | +6 | ${_.sample(features)} | unlimited |
+ }}\n\n`;
},
- half : function(classes){
- const classname = _.sample(classnames);
-
- let featureScore = 1;
- return `{{${classes}\n##### The ${classname}\n` +
- `| Level | Proficiency Bonus | Features | ${_.pad(_.sample(features), 21)} |\n` +
- `|:-----:|:-----------------:|:---------|:---------------------:|\n${
- _.map(levels, function(levelName, level){
- const res = [
- _.pad(levelName, 5),
- _.pad(`+${profBonus[level]}`, 2),
- _.padEnd(_.sample(features), 23),
- _.pad(`+${featureScore}`, 21),
- ].join(' | ');
-
- featureScore += _.random(0, 1);
-
- return `| ${res} |`;
- }).join('\n')}\n}}\n\n`;
+ full : function(snippetClasses){
+ return dedent`
+ {{${snippetClasses}
+ ##### The ${_.sample(classnames)}
+ | Level | Proficiency | Features | Cantrips | --- Spell Slots Per Spell Level ---|||||||||
+ | ^| Bonus ^| ^| Known ^|1st |2nd |3rd |4th |5th |6th |7th |8th |9th |
+ |:-----:|:-----------:|:-------------|:--------:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
+ | 1st | +2 | ${_.sample(features)} | 2 | 2 | — | — | — | — | — | — | — | — |
+ | 2nd | +2 | ${_.sample(features)} | 2 | 3 | — | — | — | — | — | — | — | — |
+ | 3rd | +2 | ${_.sample(features)} | 2 | 4 | 2 | — | — | — | — | — | — | — |
+ | 4th | +2 | ${_.sample(features)} | 3 | 4 | 3 | — | — | — | — | — | — | — |
+ | 5th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 2 | — | — | — | — | — | — |
+ | 6th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 3 | — | — | — | — | — | — |
+ | 7th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 1 | — | — | — | — | — |
+ | 8th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 2 | — | — | — | — | — |
+ | 9th | +4 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 2 | 1 | — | — | — | — |
+ | 10th | +4 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 2 | 1 | — | — | — | — |
+ | 11th | +4 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | — | — | — |
+ | 12th | +4 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | — | — | — |
+ | 13th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | — | — |
+ | 14th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | — | — |
+ | 15th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | 1 | — |
+ | 16th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | 1 | — |
+ | 17th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | 1 | 1 |
+ | 18th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 3 | 1 | 1 | 1 | 1 | 1 |
+ | 19th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 3 | 2 | 2 | 1 | 1 | 1 |
+ | 20th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | 1 |
+ }}\n\n`;
},
- third : function(classes){
- const classname = _.sample(classnames);
+ half : function(snippetClasses){
+ return dedent`
+ {{${snippetClasses}
+ ##### The ${_.sample(classnames)}
+ | Level | Proficiency | Features | Spells |--- Spell Slots Per Spell Level ---|||||
+ | ^| Bonus ^| ^| Known ^| 1st | 2nd | 3rd | 4th | 5th |
+ |:-----:|:-----------:|:-------------|:------:|:-----:|:-----:|:-----:|:-----:|:-----:|
+ | 1st | +2 | ${_.sample(features)} | — | — | — | — | — | — |
+ | 2nd | +2 | ${_.sample(features)} | 2 | 2 | — | — | — | — |
+ | 3rd | +2 | ${_.sample(features)} | 3 | 3 | — | — | — | — |
+ | 4th | +2 | ${_.sample(features)} | 3 | 3 | — | — | — | — |
+ | 5th | +3 | ${_.sample(features)} | 4 | 4 | 2 | — | — | — |
+ | 6th | +3 | ${_.sample(features)} | 4 | 4 | 2 | — | — | — |
+ | 7th | +3 | ${_.sample(features)} | 5 | 4 | 3 | — | — | — |
+ | 8th | +3 | ${_.sample(features)} | 5 | 4 | 3 | — | — | — |
+ | 9th | +4 | ${_.sample(features)} | 6 | 4 | 3 | 2 | — | — |
+ | 10th | +4 | ${_.sample(features)} | 6 | 4 | 3 | 2 | — | — |
+ | 11th | +4 | ${_.sample(features)} | 7 | 4 | 3 | 3 | — | — |
+ | 12th | +4 | ${_.sample(features)} | 7 | 4 | 3 | 3 | — | — |
+ | 13th | +5 | ${_.sample(features)} | 8 | 4 | 3 | 3 | 1 | — |
+ | 14th | +5 | ${_.sample(features)} | 8 | 4 | 3 | 3 | 1 | — |
+ | 15th | +5 | ${_.sample(features)} | 9 | 4 | 3 | 3 | 2 | — |
+ | 16th | +5 | ${_.sample(features)} | 9 | 4 | 3 | 3 | 2 | — |
+ | 17th | +6 | ${_.sample(features)} | 10 | 4 | 3 | 3 | 3 | 1 |
+ | 18th | +6 | ${_.sample(features)} | 10 | 4 | 3 | 3 | 3 | 1 |
+ | 19th | +6 | ${_.sample(features)} | 11 | 4 | 3 | 3 | 3 | 2 |
+ | 20th | +6 | ${_.sample(features)} | 11 | 4 | 3 | 3 | 3 | 2 |
+ }}\n\n`;
+ },
- let cantrips = 3;
- let spells = 1;
- let slots = 2;
- return `{{${classes}\n##### ${classname} Spellcasting\n` +
- `| Class | Cantrips | Spells |--- Spells Slots per Spell Level ---||||\n` +
- `| Level ^| Known ^| Known ^| 1st | 2nd | 3rd | 4th |\n` +
- `|:------:|:--------:|:-------:|:-------:|:-------:|:-------:|:-------:|\n${
- _.map(levels, function(levelName, level){
- const res = [
- _.pad(levelName, 6),
- _.pad(cantrips.toString(), 8),
- _.pad(spells.toString(), 7),
- drawSlots(slots, 4, 7),
- ].join(' | ');
-
- cantrips += _.random(0, 1);
- spells += _.random(0, 1);
- slots += _.random(0, 1);
-
- return `| ${res} |`;
- }).join('\n')}\n}}\n\n`;
+ third : function(snippetClasses){
+ return dedent`
+ {{${snippetClasses}
+ ##### ${_.sample(classnames)} Spellcasting
+ | Level | Cantrips | Spells |--- Spells Slots per Spell Level ---||||
+ | ^| Known ^| Known ^| 1st | 2nd | 3rd | 4th |
+ |:-----:|:--------:|:------:|:-------:|:-------:|:-------:|:-------:|
+ | 3rd | 2 | 3 | 2 | — | — | — |
+ | 4th | 2 | 4 | 3 | — | — | — |
+ | 5th | 2 | 4 | 3 | — | — | — |
+ | 6th | 2 | 4 | 3 | — | — | — |
+ | 7th | 2 | 5 | 4 | 2 | — | — |
+ | 8th | 2 | 6 | 4 | 2 | — | — |
+ | 9th | 2 | 6 | 4 | 2 | — | — |
+ | 10th | 3 | 7 | 4 | 3 | — | — |
+ | 11th | 3 | 8 | 4 | 3 | — | — |
+ | 12th | 3 | 8 | 4 | 3 | — | — |
+ | 13th | 3 | 9 | 4 | 3 | 2 | — |
+ | 14th | 3 | 10 | 4 | 3 | 2 | — |
+ | 15th | 3 | 10 | 4 | 3 | 2 | — |
+ | 16th | 3 | 11 | 4 | 3 | 3 | — |
+ | 17th | 3 | 11 | 4 | 3 | 3 | — |
+ | 18th | 3 | 11 | 4 | 3 | 3 | — |
+ | 19th | 3 | 12 | 4 | 3 | 3 | 1 |
+ | 20th | 3 | 13 | 4 | 3 | 3 | 1 |
+ }}\n\n`;
}
};
diff --git a/themes/V3/5ePHB/snippets/tableOfContents.gen.js b/themes/V3/5ePHB/snippets/tableOfContents.gen.js
index 1c52d5cf7..97d82ed40 100644
--- a/themes/V3/5ePHB/snippets/tableOfContents.gen.js
+++ b/themes/V3/5ePHB/snippets/tableOfContents.gen.js
@@ -29,27 +29,29 @@ const getTOC = (pages)=>{
const res = [];
_.each(pages, (page, pageNum)=>{
- 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);
- }
- });
+ 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);
+ }
+ });
+ }
});
return res;
};
-module.exports = function(brew){
- const pages = brew.text.split('\\page');
+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) {
diff --git a/themes/V3/5ePHB/style.less b/themes/V3/5ePHB/style.less
index c6e87c40a..90320f9be 100644
--- a/themes/V3/5ePHB/style.less
+++ b/themes/V3/5ePHB/style.less
@@ -42,7 +42,7 @@ body {
margin-top : 0.1cm;
}
}
-.useColumns(@multiplier : 1, @fillMode: balance){
+.useColumns(@multiplier : 1, @fillMode: auto){
column-count : 2;
column-fill : @fillMode;
column-gap : 0.9cm;
diff --git a/themes/V3/Blank/snippets.js b/themes/V3/Blank/snippets.js
index 9d64496c3..72372c297 100644
--- a/themes/V3/Blank/snippets.js
+++ b/themes/V3/Blank/snippets.js
@@ -2,6 +2,7 @@
const WatercolorGen = require('./snippets/watercolor.gen.js');
const ImageMaskGen = require('./snippets/imageMask.gen.js');
+const FooterGen = require('./snippets/footer.gen.js');
const dedent = require('dedent-tabs').default;
module.exports = [
@@ -21,6 +22,53 @@ module.exports = [
icon : 'fas fa-file-alt',
gen : '\n\\page\n'
},
+ {
+ name : 'Page Number',
+ icon : 'fas fa-bookmark',
+ gen : '{{pageNumber 1}}\n'
+ },
+ {
+ name : 'Auto-incrementing Page Number',
+ icon : 'fas fa-sort-numeric-down',
+ gen : '{{pageNumber,auto}}\n'
+ },
+ {
+ name : 'Footer',
+ icon : 'fas fa-shoe-prints',
+ gen : FooterGen.createFooterFunc(),
+ subsnippets : [
+ {
+ name : 'Footer from H1',
+ icon : 'fas fa-dice-one',
+ gen : FooterGen.createFooterFunc(1)
+ },
+ {
+ name : 'Footer from H2',
+ icon : 'fas fa-dice-two',
+ gen : FooterGen.createFooterFunc(2)
+ },
+ {
+ name : 'Footer from H3',
+ icon : 'fas fa-dice-three',
+ gen : FooterGen.createFooterFunc(3)
+ },
+ {
+ name : 'Footer from H4',
+ icon : 'fas fa-dice-four',
+ gen : FooterGen.createFooterFunc(4)
+ },
+ {
+ name : 'Footer from H5',
+ icon : 'fas fa-dice-five',
+ gen : FooterGen.createFooterFunc(5)
+ },
+ {
+ name : 'Footer from H6',
+ icon : 'fas fa-dice-six',
+ gen : FooterGen.createFooterFunc(6)
+ }
+ ]
+ },
{
name : 'Vertical Spacing',
icon : 'fas fa-arrows-alt-v',
diff --git a/themes/V3/Blank/snippets/footer.gen.js b/themes/V3/Blank/snippets/footer.gen.js
new file mode 100644
index 000000000..6583cd06e
--- /dev/null
+++ b/themes/V3/Blank/snippets/footer.gen.js
@@ -0,0 +1,17 @@
+const Markdown = require('../../../../shared/naturalcrit/markdown.js');
+
+module.exports = {
+ createFooterFunc : function(headerSize=1){
+ return (props)=>{
+ const cursorPos = props.cursorPos;
+
+ const markdownText = props.brew.text.split('\n').slice(0, cursorPos.line).join('\n');
+ const markdownTokens = Markdown.marked.lexer(markdownText);
+ const headerToken = markdownTokens.findLast((lexerToken)=>{ return lexerToken.type === 'heading' && lexerToken.depth === headerSize; });
+ const headerText = headerToken?.tokens.map((token)=>{ return token.text; }).join('');
+ const outputText = headerText || 'PART 1 | SECTION NAME';
+
+ return `\n{{footnote ${outputText}}}\n`;
+ };
+ }
+};
\ No newline at end of file
diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less
index 38aa42f20..f233a2347 100644
--- a/themes/V3/Blank/style.less
+++ b/themes/V3/Blank/style.less
@@ -33,7 +33,7 @@ body {
}
}
-.useColumns(@multiplier : 1, @fillMode: balance){
+.useColumns(@multiplier : 1, @fillMode: auto){
column-fill : @fillMode;
column-count : 2;
}
@@ -42,6 +42,7 @@ body {
column-span : all;
columns : inherit;
column-gap : inherit;
+ column-fill : inherit;
}
.page{
.useColumns();
diff --git a/themes/codeMirror/customEditorStyles.less b/themes/codeMirror/customEditorStyles.less
new file mode 100644
index 000000000..3318e1305
--- /dev/null
+++ b/themes/codeMirror/customEditorStyles.less
@@ -0,0 +1,88 @@
+.editor .codeEditor .CodeMirror {
+ // Themes with dark backgrounds
+ &.cm-s-3024-night,
+ &.cm-s-abbott,
+ &.cm-s-abcdef,
+ &.cm-s-ambiance,
+ &.cm-s-ayu-dark,
+ &.cm-s-ayu-mirage,
+ &.cm-s-base16-dark,
+ &.cm-s-bespin,
+ &.cm-s-blackboard,
+ &.cm-s-cobalt,
+ &.cm-s-colorforth,
+ &.cm-s-darcula,
+ &.cm-s-dracula,
+ &.cm-s-duotone-dark,
+ &.cm-s-erlang-dark,
+ &.cm-s-gruvbox-dark,
+ &.cm-s-hopscotch,
+ &.cm-s-icecoder,
+ &.cm-s-isotope,
+ &.cm-s-lesser-dark,
+ &.cm-s-liquibyte,
+ &.cm-s-lucario,
+ &.cm-s-material,
+ &.cm-s-material-darker,
+ &.cm-s-material-ocean,
+ &.cm-s-material-palenight,
+ &.cm-s-mbo,
+ &.cm-s-midnight,
+ &.cm-s-monokai,
+ &.cm-s-moxer,
+ &.cm-s-night,
+ &.cm-s-nord,
+ &.cm-s-oceanic-next,
+ &.cm-s-panda-syntax,
+ &.cm-s-paraiso-dark,
+ &.cm-s-pastel-on-dark,
+ &.cm-s-railscasts,
+ &.cm-s-rubyblue,
+ &.cm-s-seti,
+ &.cm-s-shadowfox,
+ &.cm-s-the-matrix,
+ &.cm-s-tomorrow-night-bright,
+ &.cm-s-tomorrow-night-eighties,
+ &.cm-s-twilight,
+ &.cm-s-vibrant-ink,
+ &.cm-s-xq-dark,
+ &.cm-s-yonce,
+ &.cm-s-zenburn
+ {
+ .CodeMirror-code {
+ .block:not(.cm-comment) {
+ color: magenta;
+ }
+ .columnSplit {
+ color: black;
+ background-color: rgba(35,153,153,0.5);
+ }
+ .pageLine {
+ background-color: rgba(255,255,255,0.75);
+ & ~ pre.CodeMirror-line {
+ color: black;
+ }
+ }
+ }
+ }
+ // Themes with light backgrounds
+ &.cm-s-default,
+ &.cm-s-3024-day,
+ &.cm-s-ambiance-mobile,
+ &.cm-s-base16-light,
+ &.cm-s-duotone-light,
+ &.cm-s-eclipse,
+ &.cm-s-elegant,
+ &.cm-s-juejin,
+ &.cm-s-neat,
+ &.cm-s-neo,
+ &.cm-s-paraiso-lightm
+ &.cm-s-solarized,
+ &.cm-s-ssms,
+ &.cm-s-ttcn,
+ &.cm-s-xq-light,
+ &.cm-s-yeti {
+ // Future styling for themes with light backgrounds
+ --dummyVar: 'currently unused';
+ }
+}
\ No newline at end of file
diff --git a/themes/codeMirror/editorThemes.json b/themes/codeMirror/editorThemes.json
new file mode 100644
index 000000000..a4dd74470
--- /dev/null
+++ b/themes/codeMirror/editorThemes.json
@@ -0,0 +1,68 @@
+[
+"default",
+"3024-day",
+"3024-night",
+"abbott",
+"abcdef",
+"ambiance-mobile",
+"ambiance",
+"ayu-dark",
+"ayu-mirage",
+"base16-dark",
+"base16-light",
+"bespin",
+"blackboard",
+"cobalt",
+"colorforth",
+"darcula",
+"dracula",
+"duotone-dark",
+"duotone-light",
+"eclipse",
+"elegant",
+"erlang-dark",
+"gruvbox-dark",
+"hopscotch",
+"icecoder",
+"idea",
+"isotope",
+"juejin",
+"lesser-dark",
+"liquibyte",
+"lucario",
+"material-darker",
+"material-ocean",
+"material-palenight",
+"material",
+"mbo",
+"mdn-like",
+"midnight",
+"monokai",
+"moxer",
+"neat",
+"neo",
+"night",
+"nord",
+"oceanic-next",
+"panda-syntax",
+"paraiso-dark",
+"paraiso-light",
+"pastel-on-dark",
+"railscasts",
+"rubyblue",
+"seti",
+"shadowfox",
+"solarized",
+"ssms",
+"the-matrix",
+"tomorrow-night-bright",
+"tomorrow-night-eighties",
+"ttcn",
+"twilight",
+"vibrant-ink",
+"xq-dark",
+"xq-light",
+"yeti",
+"yonce",
+"zenburn"
+]