0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-05 21:02:43 +00:00

Merge branch 'master' into addCreditsSnippet-#283

This commit is contained in:
G.Ambatte
2023-12-06 14:02:31 +13:00
committed by GitHub
19 changed files with 1071 additions and 762 deletions

View File

@@ -1,9 +1,8 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ /*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./brewRenderer.less'); require('./brewRenderer.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const { useState, useRef, useEffect } = React;
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js'); const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
@@ -13,244 +12,214 @@ const ErrorBar = require('./errorBar/errorBar.jsx');
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx'); const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
const NotificationPopup = require('./notificationPopup/notificationPopup.jsx'); const NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
const Frame = require('react-frame-component').default; const Frame = require('react-frame-component').default;
const dedent = require('dedent-tabs').default;
const Themes = require('themes/themes.json'); const Themes = require('themes/themes.json');
const PAGE_HEIGHT = 1056; const PAGE_HEIGHT = 1056;
const PPR_THRESHOLD = 50;
const BrewRenderer = createClass({ const INITIAL_CONTENT = dedent`
displayName : 'BrewRenderer', <!DOCTYPE html><html><head>
getDefaultProps : function() { <link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
return { <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
text : '', <link href='/homebrew/bundle.css' rel='stylesheet' />
style : '', <base target=_blank>
renderer : 'legacy', </head><body style='overflow: hidden'><div></div></body></html>`;
theme : '5ePHB',
lang : '',
errors : []
};
},
getInitialState : function() {
let pages;
if(this.props.renderer == 'legacy') {
pages = this.props.text.split('\\page');
} else {
pages = this.props.text.split(/^\\page$/gm);
}
return { //v=====----------------------< Brew Page Component >---------------------=====v//
viewablePageNumber : 0, const BrewPage = (props)=>{
height : 0, props = {
isMounted : false, contents : '',
index : 0,
...props
};
return <div className={props.className} id={`p${props.index + 1}`} >
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: props.contents }} />
</div>;
};
pages : pages,
usePPR : pages.length >= PPR_THRESHOLD,
visibility : 'hidden',
initialContent : `<!DOCTYPE html><html><head>
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href='/homebrew/bundle.css' rel='stylesheet' />
<base target=_blank>
</head><body style='overflow: hidden'><div></div></body></html>`
};
},
height : 0,
lastRender : <div></div>,
componentWillUnmount : function() { //v=====--------------------< Brew Renderer Component >-------------------=====v//
window.removeEventListener('resize', this.updateSize); const renderedPages = [];
}, let rawPages = [];
componentDidUpdate : function(prevProps) { const BrewRenderer = (props)=>{
if(prevProps.text !== this.props.text) { props = {
let pages; text : '',
if(this.props.renderer == 'legacy') { style : '',
pages = this.props.text.split('\\page'); renderer : 'legacy',
} else { theme : '5ePHB',
pages = this.props.text.split(/^\\page$/gm); lang : '',
} errors : [],
this.setState({ ...props
pages : pages, };
usePPR : pages.length >= PPR_THRESHOLD
});
}
},
updateSize : function() { const [state, setState] = useState({
this.setState({ viewablePageNumber : 0,
height : this.refs.main.parentNode.clientHeight, height : PAGE_HEIGHT,
}); isMounted : false,
}, visibility : 'hidden',
});
handleScroll : function(e){ const mainRef = useRef(null);
const target = e.target;
this.setState((prevState)=>({ if(props.renderer == 'legacy') {
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * prevState.pages.length) rawPages = props.text.split('\\page');
} else {
rawPages = props.text.split(/^\\page$/gm);
}
useEffect(()=>{ // Unmounting steps
return ()=>{window.removeEventListener('resize', updateSize);};
}, []);
const updateSize = ()=>{
setState((prevState)=>({
...prevState,
height : mainRef.current.parentNode.clientHeight,
})); }));
}, };
shouldRender : function(pageText, index){ const handleScroll = (e)=>{
if(!this.state.isMounted) return false; const target = e.target;
setState((prevState)=>({
...prevState,
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * rawPages.length)
}));
};
const viewIndex = this.state.viewablePageNumber; const shouldRender = (index)=>{
if(index == viewIndex - 3) return true; if(!state.isMounted) return false;
if(index == viewIndex - 2) return true;
if(index == viewIndex - 1) return true;
if(index == viewIndex) return true;
if(index == viewIndex + 1) return true;
if(index == viewIndex + 2) return true;
if(index == viewIndex + 3) return true;
//Check for style tages if(Math.abs(index - state.viewablePageNumber) <= 3)
if(pageText.indexOf('<style>') !== -1) return true; return true;
return false; return false;
}, };
sanitizeScriptTags : function(content) { const sanitizeScriptTags = (content)=>{
return content return content
.replace(/<script/ig, '&lt;script') .replace(/<script/ig, '&lt;script')
.replace(/<\/script>/ig, '&lt;/script&gt;'); .replace(/<\/script>/ig, '&lt;/script&gt;');
}, };
renderPageInfo : function(){ const renderPageInfo = ()=>{
return <div className='pageInfo' ref='main'> return <div className='pageInfo' ref={mainRef}>
<div> <div>
{this.props.renderer} {props.renderer}
</div> </div>
<div> <div>
{this.state.viewablePageNumber + 1} / {this.state.pages.length} {state.viewablePageNumber + 1} / {rawPages.length}
</div> </div>
</div>; </div>;
}, };
renderPPRmsg : function(){ const renderDummyPage = (index)=>{
if(!this.state.usePPR) return;
return <div className='ppr_msg'>
Partial Page Renderer is enabled, because your brew is so large. May affect rendering.
</div>;
},
renderDummyPage : function(index){
return <div className='phb page' id={`p${index + 1}`} key={index}> return <div className='phb page' id={`p${index + 1}`} key={index}>
<i className='fas fa-spinner fa-spin' /> <i className='fas fa-spinner fa-spin' />
</div>; </div>;
}, };
renderStyle : function() { const renderStyle = ()=>{
if(!this.props.style) return; if(!props.style) return;
const cleanStyle = this.sanitizeScriptTags(this.props.style); const cleanStyle = sanitizeScriptTags(props.style);
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.sanitizeScriptTags(this.props.style)}\n} </style>` }} />; //return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${sanitizeScriptTags(props.style)}\n} </style>` }} />;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${cleanStyle} </style>` }} />; return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${cleanStyle} </style>` }} />;
}, };
renderPage : function(pageText, index){ const renderPage = (pageText, index)=>{
let cleanPageText = this.sanitizeScriptTags(pageText); let cleanPageText = sanitizeScriptTags(pageText);
if(this.props.renderer == 'legacy') if(props.renderer == 'legacy') {
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(cleanPageText) }} key={index} />; const html = MarkdownLegacy.render(cleanPageText);
else { return <BrewPage className='page phb' index={index} key={index} contents={html} />;
} else {
cleanPageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear) cleanPageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
return ( const html = Markdown.render(cleanPageText);
<div className='page' id={`p${index + 1}`} key={index} > return <BrewPage className='page' index={index} key={index} contents={html} />;
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(cleanPageText) }} />
</div>
);
} }
}, };
renderPages : function(){ const renderPages = ()=>{
if(this.state.usePPR){ if(props.errors && props.errors.length)
return _.map(this.state.pages, (page, index)=>{ return renderedPages;
if(this.shouldRender(page, index) && typeof window !== 'undefined'){
return this.renderPage(page, index); _.forEach(rawPages, (page, index)=>{
} else { if((shouldRender(index) || !renderedPages[index]) && typeof window !== 'undefined'){
return this.renderDummyPage(index); renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
}
});
}
if(this.props.errors && this.props.errors.length) return this.lastRender;
this.lastRender = _.map(this.state.pages, (page, index)=>{
if(typeof window !== 'undefined') {
return this.renderPage(page, index);
} else {
return this.renderDummyPage(index);
} }
}); });
return this.lastRender; return renderedPages;
}, };
frameDidMount : function(){ //This triggers when iFrame finishes internal "componentDidMount" const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
this.updateSize(); updateSize();
window.addEventListener('resize', this.updateSize); window.addEventListener('resize', updateSize);
this.renderPages(); //Make sure page is renderable before showing renderPages(); //Make sure page is renderable before showing
this.setState({ setState((prevState)=>({
...prevState,
isMounted : true, isMounted : true,
visibility : 'visible' visibility : 'visible'
}); }));
}, 100); }, 100);
}, };
emitClick : function(){ const emitClick = ()=>{ // Allow clicks inside iFrame to interact with dropdowns, etc. from outside
// console.log('iFrame clicked');
if(!window || !document) return; if(!window || !document) return;
document.dispatchEvent(new MouseEvent('click')); document.dispatchEvent(new MouseEvent('click'));
}, };
render : function(){ const rendererPath = props.renderer == 'V3' ? 'V3' : 'Legacy';
//render in iFrame so broken code doesn't crash the site. const themePath = props.theme ?? '5ePHB';
//Also render dummy page while iframe is mounting. const baseThemePath = Themes[rendererPath][themePath].baseTheme;
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
const themePath = this.props.theme ?? '5ePHB'; return (
const baseThemePath = Themes[rendererPath][themePath].baseTheme; <>
return ( {/*render dummy page while iFrame is mounting.*/}
<React.Fragment> {!state.isMounted
{!this.state.isMounted ? <div className='brewRenderer' onScroll={handleScroll}>
? <div className='brewRenderer' onScroll={this.handleScroll}> <div className='pages'>
<div className='pages' ref='pages'> {renderDummyPage(1)}
{this.renderDummyPage(1)}
</div>
</div> </div>
: null} </div>
: null}
<Frame id='BrewRenderer' initialContent={this.state.initialContent} {/*render in iFrame so broken code doesn't crash the site.*/}
style={{ width: '100%', height: '100%', visibility: this.state.visibility }} <Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
contentDidMount={this.frameDidMount} style={{ width: '100%', height: '100%', visibility: state.visibility }}
onClick={()=>{this.emitClick();}} contentDidMount={frameDidMount}
> onClick={()=>{emitClick();}}
<div className={'brewRenderer'} >
onScroll={this.handleScroll} <div className={'brewRenderer'}
style={{ height: this.state.height }}> onScroll={handleScroll}
style={{ height: state.height }}>
<ErrorBar errors={this.props.errors} /> <ErrorBar errors={props.errors} />
<div className='popups'> <div className='popups'>
<RenderWarnings /> <RenderWarnings />
<NotificationPopup /> <NotificationPopup />
</div>
<link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/>
{baseThemePath &&
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
}
<link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/>
{/* Apply CSS from Style tab and render pages from Markdown tab */}
{this.state.isMounted
&&
<>
{this.renderStyle()}
<div className='pages' ref='pages' lang={`${this.props.lang || 'en'}`}>
{this.renderPages()}
</div>
</>
}
</div> </div>
</Frame> <link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/>
{this.renderPageInfo()} {baseThemePath &&
{this.renderPPRmsg()} <link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
</React.Fragment> }
); <link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/>
}
}); {/* Apply CSS from Style tab and render pages from Markdown tab */}
{state.isMounted
&&
<>
{renderStyle()}
<div className='pages' lang={`${props.lang || 'en'}`}>
{renderPages()}
</div>
</>
}
</div>
</Frame>
{renderPageInfo()}
</>
);
};
module.exports = BrewRenderer; module.exports = BrewRenderer;

View File

@@ -1,46 +1,44 @@
@import (multiple, less) 'shared/naturalcrit/styles/reset.less'; @import (multiple, less) 'shared/naturalcrit/styles/reset.less';
.brewRenderer{ .brewRenderer {
will-change : transform; will-change : transform;
overflow-y : scroll; overflow-y : scroll;
.pages{ .pages {
margin : 30px 0px; margin : 30px 0px;
&>.page{ & > .page {
width : 215.9mm;
height : 279.4mm;
margin-right : auto; margin-right : auto;
margin-bottom : 30px; margin-bottom : 30px;
margin-left : auto; margin-left : auto;
box-shadow : 1px 4px 14px #000; box-shadow : 1px 4px 14px #000000;
} }
} }
} }
.pane{ .pane { position : relative; }
position : relative; .pageInfo {
}
.pageInfo{
position : absolute; position : absolute;
right : 17px; right : 17px;
bottom : 0; bottom : 0;
z-index : 1000; z-index : 1000;
background-color : #333;
font-size : 10px; font-size : 10px;
font-weight : 800; font-weight : 800;
color : white; color : white;
background-color : #333333;
div { div {
display: inline-block; display : inline-block;
padding : 8px 10px; padding : 8px 10px;
&:not(:last-child){ &:not(:last-child) { border-right : 1px solid #666666; }
border-right: 1px solid #666;
}
} }
} }
.ppr_msg{ .ppr_msg {
position : absolute; position : absolute;
left : 0px;
bottom : 0; bottom : 0;
left : 0px;
z-index : 1000; z-index : 1000;
padding : 8px 10px; padding : 8px 10px;
background-color : #333;
font-size : 10px; font-size : 10px;
font-weight : 800; font-weight : 800;
color : white; color : white;
background-color : #333333;
} }

View File

@@ -160,6 +160,24 @@ const Editor = createClass({
} }
} }
// Superscript
if(line.includes('\^')) {
const regex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/g;
let match;
while ((match = regex.exec(line)) != null) {
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 1 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 1 }, { className: 'superscript' });
}
}
// Subscript
if(line.includes('^^')) {
const regex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/g;
let match;
while ((match = regex.exec(line)) != null) {
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 2 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 2 }, { className: 'subscript' });
}
}
// Highlight injectors {style} // Highlight injectors {style}
if(line.includes('{') && line.includes('}')){ if(line.includes('{') && line.includes('}')){
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm; const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;
@@ -341,6 +359,14 @@ const Editor = createClass({
return this.refs.codeEditor?.undo(); return this.refs.codeEditor?.undo();
}, },
foldCode : function(){
return this.refs.codeEditor?.foldAllCode();
},
unfoldCode : function(){
return this.refs.codeEditor?.unfoldAllCode();
},
render : function(){ render : function(){
return ( return (
<div className='editor' ref='main'> <div className='editor' ref='main'>
@@ -354,6 +380,8 @@ const Editor = createClass({
theme={this.props.brew.theme} theme={this.props.brew.theme}
undo={this.undo} undo={this.undo}
redo={this.redo} redo={this.redo}
foldCode={this.foldCode}
unfoldCode={this.unfoldCode}
historySize={this.historySize()} historySize={this.historySize()}
currentEditorTheme={this.state.editorTheme} currentEditorTheme={this.state.editorTheme}
updateEditorTheme={this.updateEditorTheme} updateEditorTheme={this.updateEditorTheme}

View File

@@ -43,6 +43,18 @@
font-weight : bold; font-weight : bold;
color : green; color : green;
} }
.superscript:not(.cm-comment) {
font-weight : bold;
color : goldenrod;
vertical-align : super;
font-size : 0.9em;
}
.subscript:not(.cm-comment) {
font-weight : bold;
color : rgb(123, 123, 15);
vertical-align : sub;
font-size : 0.9em;
}
} }
.brewJump { .brewJump {

View File

@@ -37,6 +37,8 @@ const Snippetbar = createClass({
undo : ()=>{}, undo : ()=>{},
redo : ()=>{}, redo : ()=>{},
historySize : ()=>{}, historySize : ()=>{},
foldCode : ()=>{},
unfoldCode : ()=>{},
updateEditorTheme : ()=>{}, updateEditorTheme : ()=>{},
cursorPos : {} cursorPos : {}
}; };
@@ -144,6 +146,22 @@ const Snippetbar = createClass({
renderEditorButtons : function(){ renderEditorButtons : function(){
if(!this.props.showEditButtons) return; if(!this.props.showEditButtons) return;
let foldButtons;
if(this.props.view == 'text'){
foldButtons =
<>
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
</>
}
return <div className='editors'> return <div className='editors'>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`} <div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} > onClick={this.props.undo} >
@@ -154,6 +172,7 @@ const Snippetbar = createClass({
<i className='fas fa-redo' /> <i className='fas fa-redo' />
</div> </div>
<div className='divider'></div> <div className='divider'></div>
{foldButtons}
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`} <div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} > onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' /> <i className='fas fa-palette' />

View File

@@ -10,7 +10,6 @@
top : 0px; top : 0px;
right : 0px; right : 0px;
height : @menuHeight; height : @menuHeight;
width : 125px;
justify-content : space-between; justify-content : space-between;
&>div{ &>div{
height : @menuHeight; height : @menuHeight;
@@ -46,6 +45,22 @@
color : black; color : black;
} }
} }
&.foldAll{
.tooltipLeft('Fold All');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
}
&.unfoldAll{
.tooltipLeft('Unfold All');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
}
&.editorTheme{ &.editorTheme{
.tooltipLeft('Editor Themes'); .tooltipLeft('Editor Themes');
font-size : 0.75em; font-size : 0.75em;

View File

@@ -22,18 +22,18 @@ const errorIndex = (props)=>{
## We can't find this brew in Google Drive! ## We can't find this brew in Google Drive!
This file was saved on Google Drive, but this link doesn't work anymore. This file was saved on Google Drive, but this link doesn't work anymore.
${ props.brew.authors?.length > 0 ${props.brew.authors?.length > 0
? `Note that this brew belongs to the Homebrewery account **${ props.brew.authors[0] }**, ? `Note that this brew belongs to the Homebrewery account **${props.brew.authors[0]}**,
${ props.brew.account ${props.brew.account
? `which is ? `which is
${props.brew.authors[0] == props.brew.account ${props.brew.authors[0] == props.brew.account
? `your account.` ? `your account.`
: `not your account (you are currently signed in as **${props.brew.account}**).` : `not your account (you are currently signed in as **${props.brew.account}**).`
}` }`
: 'and you are not currently signed in to any account.' : 'and you are not currently signed in to any account.'
}` }`
: '' : ''
} }
The Homebrewery cannot delete files from Google Drive on its own, so there The Homebrewery cannot delete files from Google Drive on its own, so there
are three most likely possibilities: are three most likely possibilities:
: :
@@ -75,7 +75,9 @@ const errorIndex = (props)=>{
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'} **Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}`, **Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
// User is not signed in; must be a user on the Authors List // User is not signed in; must be a user on the Authors List
'04' : dedent` '04' : dedent`

4
faq.md
View File

@@ -102,7 +102,7 @@ The best way to avoid this is to leave space at the end of a column equal to one
### Why do I need to manually create a new page? Why doesn't text flow between pages? ### Why do I need to manually create a new page? Why doesn't text flow between pages?
A Homebrewery document is at it's core an HTML & CSS document, and currently limited by the specs of those technologies. It is currently not possible to flow content from inside one box ("page") to the inside of another box. It seems likely that someday CSS will add this capability, and if/when that happens, Homebrewery will adopt it as soon as possible. A Homebrewery document is at its core an HTML & CSS document, and currently limited by the specs of those technologies. It is currently not possible to flow content from inside one box ("page") to the inside of another box. It seems likely that someday CSS will add this capability, and if/when that happens, Homebrewery will adopt it as soon as possible.
### Where do I get images? ### Where do I get images?
The Homebrewery does not provide images for use besides some page elements and example images for snippets. You will need to find your own images for use and be sure you are following the appropriate license requirements. The Homebrewery does not provide images for use besides some page elements and example images for snippets. You will need to find your own images for use and be sure you are following the appropriate license requirements.
@@ -126,4 +126,4 @@ The Homebrewery defaults to creating US Letter page sizes. If you are printing
### Typing `#### Adhesion` in the text editor doesn't show the header at all in the completed page? ### Typing `#### Adhesion` in the text editor doesn't show the header at all in the completed page?
Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software. Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.

1047
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"version": "3.10.0", "version": "3.10.0",
"engines": { "engines": {
"npm": "^10.2.x", "npm": "^10.2.x",
"node": ">=20.8.x" "node": "^20.8.x"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -79,10 +79,10 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.23.2", "@babel/core": "^7.23.5",
"@babel/plugin-transform-runtime": "^7.23.2", "@babel/plugin-transform-runtime": "^7.23.4",
"@babel/preset-env": "^7.23.2", "@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.22.15", "@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.4.0", "@googleapis/drive": "^8.4.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"classnames": "^2.3.2", "classnames": "^2.3.2",
@@ -93,37 +93,37 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7", "express-static-gzip": "2.1.7",
"fs-extra": "11.1.1", "fs-extra": "11.2.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "5.1.1", "marked": "5.1.1",
"marked-extended-tables": "^1.0.7", "marked-extended-tables": "^1.0.7",
"marked-gfm-heading-id": "^3.1.0", "marked-gfm-heading-id": "^3.1.2",
"marked-smartypants-lite": "^1.0.1", "marked-smartypants-lite": "^1.0.1",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4", "moment": "^2.29.4",
"mongoose": "^7.6.1", "mongoose": "^8.0.2",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.0", "nconf": "^0.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-frame-component": "^4.1.3", "react-frame-component": "^4.1.3",
"react-router-dom": "6.16.0", "react-router-dom": "6.20.1",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"superagent": "^8.1.2", "superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.51.0", "eslint": "^8.55.0",
"eslint-plugin-jest": "^27.4.2", "eslint-plugin-jest": "^27.6.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^15.10.3", "stylelint": "^15.11.0",
"stylelint-config-recess-order": "^4.3.0", "stylelint-config-recess-order": "^4.4.0",
"stylelint-config-recommended": "^13.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3", "stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.3" "supertest": "^6.3.3"

View File

@@ -101,7 +101,10 @@ fs.emptyDirSync('./build');
//v==---------------------------MOVE CM EDITOR THEMES -----------------------------==v// //v==---------------------------MOVE CM EDITOR THEMES -----------------------------==v//
editorThemeFiles = fs.readdirSync('./node_modules/codemirror/theme'); const editorThemesBuildDir = './build/homebrew/cm-themes';
await fs.copy('./node_modules/codemirror/theme', editorThemesBuildDir);
await fs.copy('./themes/codeMirror/customThemes', editorThemesBuildDir);
editorThemeFiles = fs.readdirSync(editorThemesBuildDir);
const editorThemeFile = './themes/codeMirror/editorThemes.json'; const editorThemeFile = './themes/codeMirror/editorThemes.json';
if(fs.existsSync(editorThemeFile)) fs.rmSync(editorThemeFile); if(fs.existsSync(editorThemeFile)) fs.rmSync(editorThemeFile);
@@ -114,7 +117,7 @@ fs.emptyDirSync('./build');
stream.write('\n]\n'); stream.write('\n]\n');
stream.end(); stream.end();
await fs.copy('./node_modules/codemirror/theme', './build/homebrew/cm-themes');
await fs.copy('./themes/codeMirror', './build/homebrew/codeMirror'); await fs.copy('./themes/codeMirror', './build/homebrew/codeMirror');
//v==----------------------------- BUNDLE PACKAGES --------------------------------==v// //v==----------------------------- BUNDLE PACKAGES --------------------------------==v//
@@ -151,14 +154,14 @@ fs.emptyDirSync('./build');
// build(bundles); // build(bundles);
// //
})().catch(console.error); //In development, set up LiveReload (refreshes browser), and Nodemon (restarts server)
if(isDev){
livereload('./build'); // Install the Chrome extension LiveReload to automatically refresh the browser
watchFile('./server.js', { // Restart server when change detected to this file or any nested directory from here
ignore : ['./build', './client', './themes'], // Ignore folders that are not running server code / avoids unneeded restarts
ext : 'js json' // Extensions to watch (only .js/.json by default)
//watch : ['./server', './themes'], // Watch additional folders if needed
});
}
//In development, set up LiveReload (refreshes browser), and Nodemon (restarts server) })().catch(console.error);
if(isDev){
livereload('./build'); // Install the Chrome extension LiveReload to automatically refresh the browser
watchFile('./server.js', { // Restart server when change detected to this file or any nested directory from here
ignore : ['./build', './client', './themes'], // Ignore folders that are not running server code / avoids unneeded restarts
ext : 'js json' // Extensions to watch (only .js/.json by default)
//watch : ['./server', './themes'], // Watch additional folders if needed
});
}

View File

@@ -79,7 +79,7 @@ const api = {
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) { if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
const accessError = { name: 'Access Error', status: 401 }; const accessError = { name: 'Access Error', status: 401 };
if(req.account){ if(req.account){
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title }; throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
} }
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title }; throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title };
} }

View File

@@ -112,6 +112,10 @@ const CodeEditor = createClass({
'Shift-Tab' : this.dedent, 'Shift-Tab' : this.dedent,
'Ctrl-B' : this.makeBold, 'Ctrl-B' : this.makeBold,
'Cmd-B' : this.makeBold, 'Cmd-B' : this.makeBold,
'Shift-Ctrl-=' : this.makeSuper,
'Shift-Cmd-=' : this.makeSuper,
'Ctrl-=' : this.makeSub,
'Cmd-=' : this.makeSub,
'Ctrl-I' : this.makeItalic, 'Ctrl-I' : this.makeItalic,
'Cmd-I' : this.makeItalic, 'Cmd-I' : this.makeItalic,
'Ctrl-U' : this.makeUnderline, 'Ctrl-U' : this.makeUnderline,
@@ -219,6 +223,25 @@ const CodeEditor = createClass({
} }
}, },
makeSuper : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 1) === '^' && selection.slice(-1) === '^';
this.codeMirror.replaceSelection(t ? selection.slice(1, -1) : `^${selection}^`, 'around');
if(selection.length === 0){
const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 1 });
}
},
makeSub : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '^^' && selection.slice(-2) === '^^';
this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `^^${selection}^^`, 'around');
if(selection.length === 0){
const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 2 });
}
},
makeNbsp : function() { makeNbsp : function() {
this.codeMirror.replaceSelection('&nbsp;', 'end'); this.codeMirror.replaceSelection('&nbsp;', 'end');
}, },

View File

@@ -206,6 +206,34 @@ const mustacheInjectBlock = {
} }
}; };
const superSubScripts = {
name : 'superSubScript',
level : 'inline',
start(src) { return src.match(/\^/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const superRegex = /^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/m;
const subRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m;
let isSuper = false;
let match = subRegex.exec(src);
if(!match){
match = superRegex.exec(src);
if(match)
isSuper = true;
}
if(match?.length) {
return {
type : 'superSubScript', // Should match "name" above
raw : match[0], // Text to consume from the source
tag : isSuper ? 'sup' : 'sub',
tokens : this.lexer.inlineTokens(match[1])
};
}
},
renderer(token) {
return `<${token.tag}>${this.parser.parseInline(token.tokens)}</${token.tag}>`;
}
};
const definitionLists = { const definitionLists = {
name : 'definitionLists', name : 'definitionLists',
level : 'block', level : 'block',
@@ -238,7 +266,7 @@ const definitionLists = {
} }
}; };
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] }); Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
Marked.use(mustacheInjectBlock); Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, mangle: false }); Marked.use({ renderer: renderer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite()); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());

View File

@@ -40,7 +40,7 @@ body {
-webkit-column-gap : 1cm; -webkit-column-gap : 1cm;
-moz-column-gap : 1cm; -moz-column-gap : 1cm;
} }
.phb{ .phb, .page{
.useColumns(); .useColumns();
counter-increment : phb-page-numbers; counter-increment : phb-page-numbers;
position : relative; position : relative;
@@ -59,6 +59,9 @@ body {
page-break-before : always; page-break-before : always;
page-break-after : always; page-break-after : always;
contain : size; contain : size;
}
.phb{
//***************************** //*****************************
// * BASE // * BASE
// *****************************/ // *****************************/

View File

@@ -18,7 +18,7 @@
body { counter-reset : phb-page-numbers; } body { counter-reset : phb-page-numbers; }
* { -webkit-print-color-adjust : exact; } * { -webkit-print-color-adjust : exact; }
.useSansSerif() { .useSansSerif() {
font-family : "ScalySansRemake"; font-family : 'ScalySansRemake';
font-size : 0.318cm; font-size : 0.318cm;
line-height : 1.2em; line-height : 1.2em;
p,dl,ul,ol { line-height : 1.2em; } p,dl,ul,ol { line-height : 1.2em; }
@@ -57,14 +57,12 @@ body { counter-reset : phb-page-numbers; }
height : 279.4mm; height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm; padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden; overflow : hidden;
font-family : "BookInsanityRemake"; font-family : 'BookInsanityRemake';
font-size : 0.34cm; font-size : 0.34cm;
counter-increment : phb-page-numbers; counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background); background-color : var(--HB_Color_Background);
background-image : @backgroundImage; background-image : @backgroundImage;
text-rendering : optimizeLegibility; text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
} }
//***************************** //*****************************
// * BASE // * BASE
@@ -114,7 +112,7 @@ body { counter-reset : phb-page-numbers; }
// * HEADERS // * HEADERS
// *****************************/ // *****************************/
h1,h2,h3,h4 { h1,h2,h3,h4 {
font-family : "MrEavesRemake"; font-family : 'MrEavesRemake';
font-weight : 800; font-weight : 800;
color : var(--HB_Color_HeaderText); color : var(--HB_Color_HeaderText);
} }
@@ -132,7 +130,7 @@ body { counter-reset : phb-page-numbers; }
margin-top : -0.3cm; margin-top : -0.3cm;
margin-bottom : -20px; margin-bottom : -20px;
margin-left : -40px; margin-left : -40px;
font-family : "SolberaImitationRemake"; font-family : 'SolberaImitationRemake';
font-size : 3.5cm; font-size : 3.5cm;
line-height : 1em; line-height : 1em;
color : rgba(0, 0, 0, 0); color : rgba(0, 0, 0, 0);
@@ -172,7 +170,7 @@ body { counter-reset : phb-page-numbers; }
h5 { h5 {
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly //margin-top : -0.02cm; //Font is misaligned. Shift up slightly
//margin-bottom : 0.02cm; //margin-bottom : 0.02cm;
font-family : "ScalySansSmallCapsRemake"; font-family : 'ScalySansSmallCapsRemake';
font-size : 0.423cm; font-size : 0.423cm;
font-weight : 900; font-weight : 900;
line-height : 0.951em; //Font is misaligned. Shift up slightly line-height : 0.951em; //Font is misaligned. Shift up slightly
@@ -299,7 +297,7 @@ body { counter-reset : phb-page-numbers; }
.artist { .artist {
position : absolute; position : absolute;
width : auto; width : auto;
font-family : "WalterTurncoat"; font-family : 'WalterTurncoat';
font-size : 0.27cm; font-size : 0.27cm;
color : var(--HB_Color_CaptionText); color : var(--HB_Color_CaptionText);
text-align : center; text-align : center;
@@ -309,7 +307,7 @@ body { counter-reset : phb-page-numbers; }
text-indent : unset; text-indent : unset;
} }
h5 { h5 {
font-family : "WalterTurncoat"; font-family : 'WalterTurncoat';
font-size : 1.3em; font-size : 1.3em;
} }
a { a {
@@ -405,12 +403,9 @@ body { counter-reset : phb-page-numbers; }
} }
} }
h3 { h3 {
// margin-top : 0.05cm; //Font is misaligned. Shift up slightly font-family : 'ScalySansSmallCapsRemake';
padding-bottom : 0.05cm; font-size : 0.45cm;
font-family : "ScalySansRemake"; border-bottom : 1.5px solid var(--HB_Color_HeaderText);
font-weight : 800;
font-variant : small-caps;
border-bottom : 2px solid var(--HB_Color_HeaderText);
} }
//Triangle dividers //Triangle dividers
@@ -446,6 +441,8 @@ body { counter-reset : phb-page-numbers; }
tr { background-color : transparent; } tr { background-color : transparent; }
td,th { padding : 0px; } td,th { padding : 0px; }
} }
//indent fix after bulleted lists
:is(ul,ol) + p { text-indent : 0; }
:last-child { margin-bottom : 0; } :last-child { margin-bottom : 0; }
} }
@@ -503,7 +500,7 @@ body { counter-reset : phb-page-numbers; }
// ************************************/ // ************************************/
code { code {
padding : 0px 4px; padding : 0px 4px;
font-family : 'Courier New', "Courier", monospace; font-family : 'Courier New', 'Courier', monospace;
font-size : 0.325; font-size : 0.325;
color : #58180D; color : #58180D;
overflow-wrap : break-word; overflow-wrap : break-word;
@@ -641,7 +638,7 @@ body { counter-reset : phb-page-numbers; }
h1 { h1 {
margin-top : 1.2cm; margin-top : 1.2cm;
margin-bottom : 0; margin-bottom : 0;
font-family : "NodestoCapsCondensed"; font-family : 'NodestoCapsCondensed';
font-size : 2.245cm; font-size : 2.245cm;
font-weight : normal; font-weight : normal;
line-height : 0.85em; line-height : 0.85em;
@@ -654,7 +651,7 @@ body { counter-reset : phb-page-numbers; }
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black); drop-shadow(0 0 0 black) drop-shadow(0 0 0 black);
} }
h2 { h2 {
font-family : "NodestoCapsCondensed"; font-family : 'NodestoCapsCondensed';
font-size : 0.85cm; font-size : 0.85cm;
font-weight : normal; font-weight : normal;
color : white; color : white;
@@ -687,7 +684,7 @@ body { counter-reset : phb-page-numbers; }
height : 1.7cm; height : 1.7cm;
padding-top : 0.1cm; padding-top : 0.1cm;
padding-left : 1cm; padding-left : 1cm;
font-family : "NodestoCapsCondensed"; font-family : 'NodestoCapsCondensed';
font-size : 1cm; font-size : 1cm;
font-weight : normal; font-weight : normal;
color : white; color : white;
@@ -704,7 +701,7 @@ body { counter-reset : phb-page-numbers; }
width : 70%; width : 70%;
margin-right : auto; margin-right : auto;
margin-left : auto; margin-left : auto;
font-family : "Overpass"; font-family : 'Overpass';
font-size : 0.496cm; font-size : 0.496cm;
color : white; color : white;
text-align : center; text-align : center;
@@ -735,14 +732,14 @@ body { counter-reset : phb-page-numbers; }
h1 { h1 {
margin-top : 1.2cm; margin-top : 1.2cm;
margin-bottom : 0; margin-bottom : 0;
font-family : "NodestoCapsCondensed"; font-family : 'NodestoCapsCondensed';
font-size : 2.1cm; font-size : 2.1cm;
font-weight : normal; font-weight : normal;
line-height : 0.85em; line-height : 0.85em;
text-transform : uppercase; text-transform : uppercase;
} }
h2 { h2 {
font-family : "NodestoCapsCondensed"; font-family : 'NodestoCapsCondensed';
font-size : 0.85cm; font-size : 0.85cm;
font-weight : normal; font-weight : normal;
letter-spacing : 0.5cm; letter-spacing : 0.5cm;
@@ -791,7 +788,7 @@ body { counter-reset : phb-page-numbers; }
.blank { height : 1.4em; } .blank { height : 1.4em; }
h1 { h1 {
margin-bottom : 0.3cm; margin-bottom : 0.3cm;
font-family : "NodestoCapsCondensed"; font-family : 'NodestoCapsCondensed';
font-size : 1.35cm; font-size : 1.35cm;
line-height : 0.95em; line-height : 0.95em;
color : #ED1C24; color : #ED1C24;
@@ -817,7 +814,7 @@ body { counter-reset : phb-page-numbers; }
border : none; border : none;
} }
p { p {
font-family : "Overpass"; font-family : 'Overpass';
font-size : 0.332cm; font-size : 0.332cm;
line-height : 1.5em; line-height : 1.5em;
} }
@@ -841,7 +838,7 @@ body { counter-reset : phb-page-numbers; }
p { p {
position : relative; position : relative;
width : 100%; width : 100%;
font-family : "NodestoCapsWide"; font-family : 'NodestoCapsWide';
font-size : 0.4cm; font-size : 0.4cm;
line-height : 1em; line-height : 1em;
color : #FFFFFF; color : #FFFFFF;
@@ -874,7 +871,7 @@ body { counter-reset : phb-page-numbers; }
h1 { h1 {
position : relative; position : relative;
margin-top : 0.4cm; margin-top : 0.4cm;
font-family : "NodestoCapsCondensed"; font-family : 'NodestoCapsCondensed';
font-size : 2.3cm; font-size : 2.3cm;
text-align : center; text-align : center;
text-transform : uppercase; text-transform : uppercase;
@@ -885,7 +882,7 @@ body { counter-reset : phb-page-numbers; }
margin-top : -0.7em; margin-top : -0.7em;
margin-right : auto; margin-right : auto;
margin-left : auto; margin-left : auto;
font-family : "Overpass"; font-family : 'Overpass';
font-size : 0.45cm; font-size : 0.45cm;
line-height : 1.1em; line-height : 1.1em;
} }
@@ -968,9 +965,8 @@ body { counter-reset : phb-page-numbers; }
padding-left : 1em; padding-left : 1em;
line-height : 1.25em; line-height : 1.25em;
white-space : pre-line; white-space : pre-line;
& + * { margin-top : 0.28cm; } & + * { margin-top : 0.17cm; }
} }
dl + * { margin-top : 0.17cm; }
p + dl { margin-top : 0.17cm; } p + dl { margin-top : 0.17cm; }
dt { dt {
display : inline; display : inline;
@@ -1009,7 +1005,7 @@ body { counter-reset : phb-page-numbers; }
outline : 1px solid #000000; outline : 1px solid #000000;
} }
th { th {
font-family : "BookInsanityRemake"; font-family : 'BookInsanityRemake';
font-size : 0.45cm; font-size : 0.45cm;
} }
td { font-size : 0.7cm; } td { font-size : 0.7cm; }

View File

@@ -56,8 +56,6 @@ body {
box-sizing : border-box; box-sizing : border-box;
overflow : hidden; overflow : hidden;
text-rendering : optimizeLegibility; text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size; contain : size;
} }
//***************************** //*****************************

View File

@@ -0,0 +1,129 @@
/* Main BG color and normal text color */
.CodeMirror {
background: #293134;
color: #91A6AA;
}
/* Brew BG */
.brewRenderer {
background-color: #293134;
}
/* Blinking cursor */
.CodeMirror-cursor {
border-left: 1px solid #e0e2e4;
}
/* HB DARK NAV START*/
/* Bars at the top */
.snippetBar {
background-color: #2F393C;
color: white;
}
nav {
background-color: #293134;
}
nav .navItem {
background-color: #293134;
}
/* Fix for Homebrewery custom Snippet icons */
.snippetBar .fac {
filter: invert(1);
}
.snippetBar .snippetGroup .dropdown {
background-color: #2F393C;
}
/* HB DARK NAV END */
/* Line number stuff */
.CodeMirror-gutter-elt {
color: #81969A;
}
.CodeMirror-linenumber {
background-color: #293134;
}
.CodeMirror-gutter {
background-color: #293134;
}
/* column splits */
.editor .codeEditor .columnSplit {
font-style: italic;
color: inherit;
background-color:#1f5763;
border-bottom: #299 solid 1px;
}
/* Colors for headings and such */
/* ###Headings */
.cm-s-default .cm-header {
color: #c51b1b;
-webkit-text-stroke-width: 0.1px;
-webkit-text-stroke-color: #000;
}
/* bold points */
.cm-header, .cm-strong {
font-weight: bold;
color: #309dd2;
}
/* Link headings */
.cm-s-default .cm-link {
color: #dd6300;
}
/* links */
.cm-s-default .cm-string {
color: #aa8261;
}
/*@import*/
.cm-s-default .cm-def {
color:#2986cc;
}
/* Bullets and such */
.cm-s-default .cm-variable-2 {
color: #3cbf30;
}
/* blocks */
.editor .codeEditor .block:not(.cm-comment) {
color: #e3e3e3;
}
/* inline blocks */
.editor .codeEditor .inline-block {
color: #e3e3e3;
}
/* Tags (divs) */
.cm-s-default .cm-tag {
color: #e3ff00;
}
.cm-s-default .cm-attribute {
color: #e3ff00;
}
.cm-s-default .cm-atom {
color:#000;
}
.cm-s-default .cm-qualifier{
color:#ee1919;
}
.cm-s-default .cm-comment{
color:#bbc700;
}
.cm-s-default .cm-keyword {
color:#c302df;
background-color:#b1b1b1;
}
.cm-s-default .cm-property.cm-error {
color:#c50202;
}
.CodeMirror-foldmarker {
color:#f0ff00;
}
/* New page */
.editor .codeEditor .pageLine {
background: #000;
color:#000;
border-bottom: 1px solid #fff;
}
.cm-s-default .cm-builtin {
color:#fff;
}

View File

@@ -15,6 +15,7 @@
"cobalt", "cobalt",
"colorforth", "colorforth",
"darcula", "darcula",
"darkbrewery-v301",
"dracula", "dracula",
"duotone-dark", "duotone-dark",
"duotone-light", "duotone-light",