mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-09-20 16:42:58 +00:00
Merge branch 'master' into meta_a11y
This commit is contained in:
@@ -43,9 +43,9 @@ const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
|||||||
const themeCompartment = new Compartment();
|
const themeCompartment = new Compartment();
|
||||||
const highlightCompartment = new Compartment();
|
const highlightCompartment = new Compartment();
|
||||||
|
|
||||||
import { generalKeymap, markdownKeymap } from './extensions/customKeyMaps.js';
|
import { generalKeymap, markdownKeymap, cssKeymap, formatCSS } from './extensions/customKeyMaps.js';
|
||||||
import foldOnPages from './extensions/customFolding.js';
|
import foldOnPages from './extensions/customFolding.js';
|
||||||
import { customHighlightPlugin, customHighlightStyle } from './extensions/customHighlight.js';
|
import { customHighlightStyle , customHighlightPlugin } from './extensions/customHighlight.js';
|
||||||
import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js';
|
import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js';
|
||||||
|
|
||||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
@@ -186,7 +186,7 @@ const CodeEditor = forwardRef(
|
|||||||
//keyboard shortcut
|
//keyboard shortcut
|
||||||
keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]),
|
keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]),
|
||||||
generalKeymap,
|
generalKeymap,
|
||||||
...(tab !== 'brewStyles' ? [markdownKeymap] : []),
|
...(tab === 'brewStyles' ? [cssKeymap] : [markdownKeymap]),
|
||||||
|
|
||||||
//multiple cursors and selections
|
//multiple cursors and selections
|
||||||
drawSelection(),
|
drawSelection(),
|
||||||
@@ -356,6 +356,8 @@ const CodeEditor = forwardRef(
|
|||||||
}, 400);
|
}, 400);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
formatCode : ()=>formatCSS(viewRef.current),
|
||||||
|
|
||||||
undo : ()=>undo(viewRef.current),
|
undo : ()=>undo(viewRef.current),
|
||||||
redo : ()=>redo(viewRef.current),
|
redo : ()=>redo(viewRef.current),
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes slideacross {
|
||||||
|
0% {
|
||||||
|
bottom: -200px;
|
||||||
|
left: -200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
bottom:100%;
|
||||||
|
left:100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:where(.codeEditor) {
|
:where(.codeEditor) {
|
||||||
width : 100%;
|
width : 100%;
|
||||||
height : calc(100% - 25px);
|
height : calc(100% - 25px);
|
||||||
@@ -23,6 +35,27 @@
|
|||||||
.cm-editor {
|
.cm-editor {
|
||||||
height : 100%;
|
height : 100%;
|
||||||
outline : none !important;
|
outline : none !important;
|
||||||
|
|
||||||
|
&.cm-flash {
|
||||||
|
position:relative;
|
||||||
|
&::after {
|
||||||
|
position:absolute;
|
||||||
|
content:'';
|
||||||
|
bottom:-200px;
|
||||||
|
left:-200px;
|
||||||
|
translate:-50%;
|
||||||
|
width:200%;
|
||||||
|
height:100px;
|
||||||
|
background:linear-gradient(0deg, #89eafc00 0px, #89ebfc8e 50px, #89eafc00 100px, transparent);
|
||||||
|
display:block;
|
||||||
|
|
||||||
|
rotate:30deg;
|
||||||
|
|
||||||
|
@media screen and (prefers-reduced-motion: no-preference) {
|
||||||
|
animation: .5s linear 1 slideacross ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.brewSnippets .cm-snippetLine,
|
&.brewSnippets .cm-snippetLine,
|
||||||
|
|||||||
@@ -350,11 +350,10 @@ class ImageWidget extends WidgetType {
|
|||||||
|
|
||||||
toDOM() {
|
toDOM() {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.loading = "lazy";
|
img.loading = 'lazy';
|
||||||
img.className = 'cm-preview';
|
img.className = 'cm-preview';
|
||||||
img.src = this.url;
|
img.src = this.url;
|
||||||
|
|
||||||
|
|
||||||
img.onerror = ()=>{
|
img.onerror = ()=>{
|
||||||
img.src = 'client/icons/broken-image.jpg';
|
img.src = 'client/icons/broken-image.jpg';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,60 @@ import { keymap } from '@codemirror/view';
|
|||||||
import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands';
|
import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands';
|
||||||
import { EditorSelection } from '@codemirror/state';
|
import { EditorSelection } from '@codemirror/state';
|
||||||
import { Prec } from '@codemirror/state';
|
import { Prec } from '@codemirror/state';
|
||||||
|
import * as prettier from 'prettier/standalone';
|
||||||
|
import * as postcssPlugin from 'prettier/plugins/postcss';
|
||||||
|
|
||||||
|
export async function formatCSS(view) {
|
||||||
|
try {
|
||||||
|
const { from, to, empty } = view.state.selection.main;
|
||||||
|
const fullDoc = view.state.doc.toString();
|
||||||
|
const selection = view.state.doc.sliceString(from, to);
|
||||||
|
const code = empty ? fullDoc : selection;
|
||||||
|
|
||||||
|
let formatted = await prettier.format(code, {
|
||||||
|
parser: 'css',
|
||||||
|
plugins: [postcssPlugin],
|
||||||
|
|
||||||
|
// formatting options
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
printWidth: 100,
|
||||||
|
singleQuote: false,
|
||||||
|
trailingComma: 'all',
|
||||||
|
bracketSpacing: true,
|
||||||
|
endOfLine: 'lf'
|
||||||
|
});
|
||||||
|
|
||||||
|
//format manually single declaration rules to span one line.
|
||||||
|
//Prettier can't do it by default, this is crude but it works
|
||||||
|
formatted = formatted.replace(
|
||||||
|
/([^{]+)\{\s*\n\s*([^;\n]+:[^;\n]+;)\s*\n\s*\}(\s*)/g,
|
||||||
|
(_, selector, decl, whitespace) =>
|
||||||
|
`${selector} { ${decl.trim()} }${whitespace}`
|
||||||
|
);
|
||||||
|
if(formatted === code) return true;
|
||||||
|
|
||||||
|
const dom = view.dom;
|
||||||
|
dom.classList.add('cm-flash');
|
||||||
|
|
||||||
|
setTimeout(()=>{
|
||||||
|
dom.classList.remove('cm-flash');
|
||||||
|
|
||||||
|
view.dispatch({
|
||||||
|
changes : {
|
||||||
|
from : empty ? 0 : from,
|
||||||
|
to : empty ? view.state.doc.length : to,
|
||||||
|
insert : formatted
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}, 500);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error formatting css: ', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const insertTab = (view)=>{
|
const insertTab = (view)=>{
|
||||||
// If any selection spans multiple lines, delegates to CodeMirror's indentMore
|
// If any selection spans multiple lines, delegates to CodeMirror's indentMore
|
||||||
// Otherwise inserts two spaces at each cursor/selection
|
// Otherwise inserts two spaces at each cursor/selection
|
||||||
@@ -188,6 +241,11 @@ export const generalKeymap = Prec.high(keymap.of([
|
|||||||
{ key: 'Mod-d', run: deleteLine }, //annoyingly overrides "selectNextOccurrence" because users asked
|
{ key: 'Mod-d', run: deleteLine }, //annoyingly overrides "selectNextOccurrence" because users asked
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
export const cssKeymap = Prec.highest(keymap.of([
|
||||||
|
{ key: 'Mod-Shift-f', run: formatCSS },
|
||||||
|
{ key: 'Alt-Shift-f', run: formatCSS },
|
||||||
|
]));
|
||||||
|
|
||||||
export const markdownKeymap = Prec.highest(keymap.of([
|
export const markdownKeymap = Prec.highest(keymap.of([
|
||||||
|
|
||||||
{ key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold
|
{ key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
||||||
import Markdown from '@shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
import ErrorBar from './errorBar/errorBar.jsx';
|
import ErrorBar from './errorBar/errorBar.jsx';
|
||||||
import ToolBar from './toolBar/toolBar.jsx';
|
import ToolBar from './toolBar/toolBar.jsx';
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ const BrewRenderer = (props)=>{
|
|||||||
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
|
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
|
||||||
} else {
|
} else {
|
||||||
if(pageText.startsWith('\\page')) {
|
if(pageText.startsWith('\\page')) {
|
||||||
const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
|
const firstLineTokens = hbfm.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
|
||||||
const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
|
const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
|
||||||
if(injectedTags) {
|
if(injectedTags) {
|
||||||
styles = { ...styles, ...injectedTags.styles };
|
styles = { ...styles, ...injectedTags.styles };
|
||||||
@@ -231,7 +231,7 @@ const BrewRenderer = (props)=>{
|
|||||||
// DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME.
|
// DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME.
|
||||||
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||||
|
|
||||||
const html = Markdown.render(pageText, index);
|
const html = hbfm.render(pageText, index);
|
||||||
|
|
||||||
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />;
|
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import './notificationPopup.less';
|
import './notificationPopup.less';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import Markdown from '@shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
|
|
||||||
import Dialog from '@components/dialog.jsx';
|
import Dialog from '@components/dialog.jsx';
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ const NotificationPopup = ()=>{
|
|||||||
return notifications.map((notification)=>(
|
return notifications.map((notification)=>(
|
||||||
<li key={notification.dismissKey} >
|
<li key={notification.dismissKey} >
|
||||||
<em>{notification.title}</em><br />
|
<em>{notification.title}</em><br />
|
||||||
<p dangerouslySetInnerHTML={{ __html: Markdown.render(notification.text) }}></p>
|
<p dangerouslySetInnerHTML={{ __html: hbfm.render(notification.text) }}></p>
|
||||||
</li>
|
</li>
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|||||||
+220
-229
@@ -1,8 +1,6 @@
|
|||||||
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
|
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
|
||||||
import './editor.less';
|
import './editor.less';
|
||||||
import React from 'react';
|
import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
|
|
||||||
import CodeEditor from '@components/codeEditor/codeEditor.jsx';
|
import CodeEditor from '@components/codeEditor/codeEditor.jsx';
|
||||||
@@ -18,13 +16,9 @@ import cm5Themes from 'codemirror-5-themes';
|
|||||||
const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
||||||
|
|
||||||
const EditorThemes = Object.entries(themes)
|
const EditorThemes = Object.entries(themes)
|
||||||
.filter(([name, value])=>Array.isArray(value) &&
|
.filter(([name, value])=>Array.isArray(value) && !name.endsWith('Init') && !name.endsWith('Style'))
|
||||||
!name.endsWith('Init') &&
|
|
||||||
!name.endsWith('Style')
|
|
||||||
)
|
|
||||||
.map(([name])=>name);
|
.map(([name])=>name);
|
||||||
|
|
||||||
|
|
||||||
//const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
//const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
//const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
//const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
||||||
const DEFAULT_STYLE_TEXT = dedent`
|
const DEFAULT_STYLE_TEXT = dedent`
|
||||||
@@ -45,306 +39,303 @@ const DEFAULT_SNIPPET_TEXT = dedent`
|
|||||||
let isJumping = false;
|
let isJumping = false;
|
||||||
let jumpSource = null;
|
let jumpSource = null;
|
||||||
|
|
||||||
const Editor = createReactClass({
|
const Editor = forwardRef(
|
||||||
displayName : 'Editor',
|
(
|
||||||
getDefaultProps : function() {
|
{
|
||||||
return {
|
brew = {},
|
||||||
brew : {
|
|
||||||
text : '',
|
onBrewChange = ()=>{},
|
||||||
style : ''
|
reportError = ()=>{},
|
||||||
|
|
||||||
|
onCursorPageChange = ()=>{},
|
||||||
|
onViewPageChange = ()=>{},
|
||||||
|
|
||||||
|
editorTheme = 'default',
|
||||||
|
renderer = 'legacy',
|
||||||
|
|
||||||
|
moveBrew,
|
||||||
|
moveSource,
|
||||||
|
liveScroll,
|
||||||
|
|
||||||
|
setMoveArrows,
|
||||||
|
updateBrew,
|
||||||
|
showEditButtons,
|
||||||
|
themeBundle,
|
||||||
|
userThemes,
|
||||||
|
|
||||||
|
currentEditorCursorPageNum = 1,
|
||||||
|
currentEditorViewPageNum = 1,
|
||||||
|
currentBrewRendererPageNum = 1,
|
||||||
},
|
},
|
||||||
|
ref,
|
||||||
|
)=>{
|
||||||
|
const [currentEditorTheme, setEditorTheme] = useState(editorTheme);
|
||||||
|
const [view, setView] = useState('text'); // 'text', 'style', 'meta', 'snippet'
|
||||||
|
const [snippetBarHeight, setSnippetBarHeight] = useState(26);
|
||||||
|
|
||||||
onBrewChange : ()=>{},
|
const editor = useRef(null);
|
||||||
reportError : ()=>{},
|
const codeEditor = useRef(null);
|
||||||
|
const throttleBrewMove = useRef(null);
|
||||||
|
|
||||||
onCursorPageChange : ()=>{},
|
const isText = ()=>isView('text');
|
||||||
onViewPageChange : ()=>{},
|
const isStyle = ()=>isView('style');
|
||||||
|
const isMeta = ()=>isView('meta');
|
||||||
|
const isSnip = ()=>isView('snippet');
|
||||||
|
|
||||||
editorTheme : 'default',
|
const isView = (name)=>view === name;
|
||||||
renderer : 'legacy',
|
|
||||||
|
|
||||||
currentEditorCursorPageNum : 1,
|
|
||||||
currentEditorViewPageNum : 1,
|
|
||||||
currentBrewRendererPageNum : 1,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState : function() {
|
|
||||||
return {
|
|
||||||
editorTheme : this.props.editorTheme,
|
|
||||||
view : 'text', //'text', 'style', 'meta', 'snippet'
|
|
||||||
snippetBarHeight : 26,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
editor : React.createRef(null),
|
|
||||||
codeEditor : React.createRef(null),
|
|
||||||
|
|
||||||
isText : function() {return this.state.view == 'text';},
|
|
||||||
isStyle : function() {return this.state.view == 'style';},
|
|
||||||
isMeta : function() {return this.state.view == 'meta';},
|
|
||||||
isSnip : function() {return this.state.view == 'snippet';},
|
|
||||||
|
|
||||||
componentDidMount : function() {
|
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
const brewRenderer = document.getElementById('BrewRenderer');
|
const brewRenderer = document.getElementById('BrewRenderer');
|
||||||
brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', this.handleControlKeys);
|
brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', handleControlKeys);
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
document.addEventListener('keydown', handleControlKeys);
|
||||||
|
|
||||||
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
||||||
if(editorTheme && EditorThemes.includes(editorTheme)) {
|
if(editorTheme && EditorThemes.includes(editorTheme)) setEditorTheme(editorTheme); else setEditorTheme('default');
|
||||||
this.setState({ editorTheme });
|
|
||||||
} else {
|
|
||||||
this.setState({ editorTheme: 'default' });
|
|
||||||
}
|
|
||||||
const snippetBar = document.querySelector('.editor > .snippetBar');
|
const snippetBar = document.querySelector('.editor > .snippetBar');
|
||||||
if(!snippetBar) return;
|
if(!snippetBar) return;
|
||||||
|
|
||||||
this.resizeObserver = new ResizeObserver((entries)=>{
|
const resizeObserver = new ResizeObserver((entries)=>{
|
||||||
const height = document.querySelector('.editor > .snippetBar').offsetHeight;
|
const height = document.querySelector('.editor > .snippetBar').offsetHeight;
|
||||||
this.setState({ snippetBarHeight: height });
|
setSnippetBarHeight(height);
|
||||||
});
|
});
|
||||||
|
resizeObserver.observe(snippetBar);
|
||||||
|
|
||||||
this.resizeObserver.observe(snippetBar);
|
return ()=>{
|
||||||
},
|
if(resizeObserver) resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
componentDidUpdate : function(prevProps, prevState, snapshot) {
|
useEffect(()=>{ if(moveBrew) brewJump(); }, [moveBrew]);
|
||||||
|
useEffect(()=>{ if(moveSource) sourceJump(); }, [moveSource]);
|
||||||
|
useEffect(()=>{ if(liveScroll) sourceJump(currentBrewRendererPageNum, false); }, [currentBrewRendererPageNum, liveScroll]);
|
||||||
|
useEffect(()=>{ if(liveScroll) brewJump(currentEditorViewPageNum, false); }, [currentEditorViewPageNum, liveScroll]);
|
||||||
|
useEffect(()=>{ if(liveScroll) brewJump(currentEditorCursorPageNum, false); }, [currentEditorCursorPageNum, liveScroll]);
|
||||||
|
|
||||||
if(prevProps.moveBrew !== this.props.moveBrew)
|
const handleFormatCode = () => {
|
||||||
this.brewJump();
|
codeEditor.current?.formatCode();
|
||||||
|
};
|
||||||
|
|
||||||
if(prevProps.moveSource !== this.props.moveSource)
|
const handleControlKeys = (e)=>{
|
||||||
this.sourceJump();
|
|
||||||
|
|
||||||
if(this.props.liveScroll) {
|
|
||||||
if(prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) {
|
|
||||||
this.sourceJump(this.props.currentBrewRendererPageNum, false);
|
|
||||||
} else if(prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) {
|
|
||||||
this.brewJump(this.props.currentEditorViewPageNum, false);
|
|
||||||
} else if(prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) {
|
|
||||||
this.brewJump(this.props.currentEditorCursorPageNum, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if(this.resizeObserver) this.resizeObserver.disconnect();
|
|
||||||
},
|
|
||||||
|
|
||||||
handleControlKeys : function(e){
|
|
||||||
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
|
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
|
||||||
const LEFTARROW_KEY = 37;
|
const LEFTARROW_KEY = 37;
|
||||||
const RIGHTARROW_KEY = 39;
|
const RIGHTARROW_KEY = 39;
|
||||||
if(e.keyCode == RIGHTARROW_KEY) this.brewJump();
|
if(e.keyCode == RIGHTARROW_KEY) brewJump();
|
||||||
if(e.keyCode == LEFTARROW_KEY) this.sourceJump();
|
if(e.keyCode == LEFTARROW_KEY) sourceJump();
|
||||||
if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) {
|
if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
updateCurrentCursorPage : function(pageNumber) {
|
const updateCurrentCursorPage = (pageNumber)=>{
|
||||||
this.props.onCursorPageChange(pageNumber);
|
onCursorPageChange(pageNumber);
|
||||||
},
|
};
|
||||||
|
|
||||||
updateCurrentViewPage : function(pageNumber) {
|
const updateCurrentViewPage = (pageNumber)=>{
|
||||||
this.props.onViewPageChange(pageNumber);
|
onViewPageChange(pageNumber);
|
||||||
},
|
};
|
||||||
|
|
||||||
handleInject : function(injectText){
|
const handleInject = (injectText)=>{
|
||||||
this.codeEditor.current?.injectText(injectText);
|
codeEditor.current?.injectText(injectText);
|
||||||
},
|
};
|
||||||
|
|
||||||
handleViewChange : function(newView){
|
const handleViewChange = (newView)=>{
|
||||||
this.props.setMoveArrows(newView === 'text');
|
setMoveArrows(newView === 'text');
|
||||||
|
setView(newView);
|
||||||
|
};
|
||||||
|
useEffect(()=>{
|
||||||
|
codeEditor.current?.focus();
|
||||||
|
}, [view]);
|
||||||
|
|
||||||
this.setState({
|
const brewJump = (targetPage = currentEditorCursorPageNum, smooth = true)=>{
|
||||||
view : newView
|
if(!window || !isText() || isJumping || jumpSource === 'source') return;
|
||||||
}, ()=>{
|
|
||||||
this.codeEditor.current?.focus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){
|
const brewRenderer =
|
||||||
if(!window || !this.isText() || isJumping || jumpSource === 'source')
|
window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0];
|
||||||
return;
|
|
||||||
|
|
||||||
// Get current brewRenderer scroll position and calculate target position
|
|
||||||
const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0];
|
|
||||||
const currentPos = brewRenderer.scrollTop;
|
const currentPos = brewRenderer.scrollTop;
|
||||||
const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top;
|
|
||||||
|
const targetPos = window.frames['BrewRenderer'].contentDocument
|
||||||
|
.getElementById(`p${targetPage}`)
|
||||||
|
.getBoundingClientRect().top;
|
||||||
|
|
||||||
let scrollingTimeout;
|
let scrollingTimeout;
|
||||||
const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times
|
|
||||||
clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs
|
const checkIfScrollComplete = ()=>{// Prevent interrupting a scroll in progress if user clicks multiple times
|
||||||
|
clearTimeout(scrollingTimeout);// Reset the timer every time a scroll event occurs
|
||||||
|
|
||||||
scrollingTimeout = setTimeout(()=>{
|
scrollingTimeout = setTimeout(()=>{
|
||||||
isJumping = false;
|
isJumping = false;
|
||||||
jumpSource = null;
|
jumpSource = null;
|
||||||
|
|
||||||
brewRenderer.removeEventListener('scroll', checkIfScrollComplete);
|
brewRenderer.removeEventListener('scroll', checkIfScrollComplete);
|
||||||
}, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done
|
}, 150);// If 150 ms pass without a brewRenderer scroll event, assume scrolling is done
|
||||||
};
|
};
|
||||||
|
|
||||||
isJumping = true;
|
isJumping = true;
|
||||||
jumpSource = 'brew';
|
jumpSource = 'brew';
|
||||||
|
|
||||||
checkIfScrollComplete();
|
checkIfScrollComplete();
|
||||||
brewRenderer.addEventListener('scroll', checkIfScrollComplete);
|
brewRenderer.addEventListener('scroll', checkIfScrollComplete);
|
||||||
|
|
||||||
if(smooth) {
|
if(smooth) {
|
||||||
const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling
|
const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling
|
||||||
const bounceDelay = 100;
|
const now = Date.now();
|
||||||
const scrollDelay = 500;
|
|
||||||
|
if(now - throttleBrewMove.current >= 500) {
|
||||||
|
throttleBrewMove.current = now;
|
||||||
|
|
||||||
if(!this.throttleBrewMove) {
|
|
||||||
this.throttleBrewMove = _.throttle((currentPos, bouncePos, targetPos)=>{
|
|
||||||
brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' });
|
brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' });
|
||||||
|
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' });
|
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' });
|
||||||
}, bounceDelay);
|
}, 100);
|
||||||
}, scrollDelay, { leading: true, trailing: false });
|
|
||||||
};
|
|
||||||
this.throttleBrewMove(currentPos, bouncePos, targetPos);
|
|
||||||
} else {
|
|
||||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' });
|
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
|
brewRenderer.scrollTo({ top : currentPos + targetPos, behavior : 'instant', block : 'start',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){
|
const sourceJump = (targetPage = currentBrewRendererPageNum, smooth = true)=>{
|
||||||
if(!this.isText() || isJumping || jumpSource === 'brew')
|
if(!isText() || isJumping || jumpSource === 'brew') return;
|
||||||
return;
|
|
||||||
|
|
||||||
const editor = this.codeEditor.current;
|
if(!codeEditor.current) return;
|
||||||
if(!editor) return;
|
|
||||||
jumpSource = 'source';
|
jumpSource = 'source';
|
||||||
|
|
||||||
editor.scrollToPage(targetPage);
|
codeEditor.current.scrollToPage(targetPage);
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
jumpSource = null;
|
jumpSource = null;
|
||||||
}, 200);
|
}, 200);
|
||||||
},
|
};
|
||||||
|
|
||||||
//Called when there are changes to the editor's dimensions
|
const updateEditorTheme = (newTheme)=>{
|
||||||
update : function(){},
|
|
||||||
|
|
||||||
updateEditorTheme : function(newTheme){
|
|
||||||
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
||||||
this.setState({
|
setEditorTheme(newTheme);
|
||||||
editorTheme : newTheme
|
};
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
//Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory
|
const renderEditor = ()=>{
|
||||||
rerenderParent : function (){
|
if(isText()) {
|
||||||
this.forceUpdate();
|
return (
|
||||||
},
|
<>
|
||||||
|
<CodeEditor
|
||||||
renderEditor : function(){
|
key='codeEditor'
|
||||||
if(this.isText()){
|
ref={codeEditor}
|
||||||
return <>
|
|
||||||
<CodeEditor key='codeEditor'
|
|
||||||
ref={this.codeEditor}
|
|
||||||
language='gfm'
|
language='gfm'
|
||||||
tab='brewText'
|
tab='brewText'
|
||||||
view={this.state.view}
|
view={view}
|
||||||
value={this.props.brew.text}
|
value={brew.text}
|
||||||
onChange={this.props.onBrewChange('text')}
|
onChange={onBrewChange('text')}
|
||||||
onCursorChange={(page)=>this.updateCurrentCursorPage(page)}
|
onCursorChange={(page)=>updateCurrentCursorPage(page)}
|
||||||
onViewChange={(page)=>this.updateCurrentViewPage(page)}
|
onViewChange={(page)=>updateCurrentViewPage(page)}
|
||||||
editorTheme={this.state.editorTheme}
|
editorTheme={currentEditorTheme}
|
||||||
renderer={this.props.brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}/>
|
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||||
</>;
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if(this.isStyle()){
|
if(isStyle()) {
|
||||||
return <>
|
return (
|
||||||
<CodeEditor key='codeEditor'
|
<>
|
||||||
ref={this.codeEditor}
|
<CodeEditor
|
||||||
|
key='codeEditor'
|
||||||
|
ref={codeEditor}
|
||||||
language='css'
|
language='css'
|
||||||
tab='brewStyles'
|
tab='brewStyles'
|
||||||
view={this.state.view}
|
view={view}
|
||||||
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
|
value={brew.style ?? DEFAULT_STYLE_TEXT}
|
||||||
onChange={this.props.onBrewChange('style')}
|
onChange={onBrewChange('style')}
|
||||||
editorTheme={this.state.editorTheme}
|
editorTheme={currentEditorTheme}
|
||||||
renderer={this.props.brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}/>
|
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||||
</>;
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if(this.isMeta()){
|
if(isSnip()) {
|
||||||
return <>
|
if(!brew.snippets) {
|
||||||
<CodeEditor key='codeEditor'
|
brew.snippets = DEFAULT_SNIPPET_TEXT;
|
||||||
view={this.state.view}
|
|
||||||
style={{ display: 'none' }}/>
|
|
||||||
<MetadataEditor
|
|
||||||
metadata={this.props.brew}
|
|
||||||
themeBundle={this.props.themeBundle}
|
|
||||||
onChange={this.props.onBrewChange('metadata')}
|
|
||||||
reportError={this.props.reportError}
|
|
||||||
userThemes={this.props.userThemes}/>
|
|
||||||
</>;
|
|
||||||
}
|
}
|
||||||
if(this.isSnip()){
|
return (
|
||||||
if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; }
|
<>
|
||||||
return <>
|
<CodeEditor
|
||||||
<CodeEditor key='codeEditor'
|
key='codeEditor'
|
||||||
ref={this.codeEditor}
|
ref={codeEditor}
|
||||||
language='gfm'
|
language='gfm'
|
||||||
tab='brewSnippets'
|
tab='brewSnippets'
|
||||||
view={this.state.view}
|
view={view}
|
||||||
value={this.props.brew.snippets}
|
value={brew.snippets}
|
||||||
onChange={this.props.onBrewChange('snippets')}
|
onChange={onBrewChange('snippets')}
|
||||||
enableFolding={true}
|
enableFolding={true}
|
||||||
editorTheme={this.state.editorTheme}
|
editorTheme={currentEditorTheme}
|
||||||
renderer={this.props.brew.renderer}
|
renderer={brew.renderer}
|
||||||
rerenderParent={this.rerenderParent}
|
style={{ height: `calc(100% - 25px)` }}
|
||||||
style={{ height: `calc(100% - 25px)` }}/>
|
/>
|
||||||
</>;
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
if(isMeta()) {
|
||||||
|
|
||||||
redo : function(){
|
|
||||||
return this.codeEditor.current?.redo();
|
|
||||||
},
|
|
||||||
|
|
||||||
historySize : function(){
|
|
||||||
return this.codeEditor.current?.historySize();
|
|
||||||
},
|
|
||||||
|
|
||||||
undo : function(){
|
|
||||||
return this.codeEditor.current?.undo();
|
|
||||||
},
|
|
||||||
|
|
||||||
foldCode : function() {
|
|
||||||
return this.codeEditor.current?.foldAll();
|
|
||||||
},
|
|
||||||
|
|
||||||
unfoldCode : function() {
|
|
||||||
return this.codeEditor.current?.unfoldAll();
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
return (
|
return (
|
||||||
<div className='editor' ref={this.editor}>
|
<>
|
||||||
|
<CodeEditor key='codeEditor' view={view} style={{ display: 'none' }} />
|
||||||
|
<MetadataEditor
|
||||||
|
metadata={brew}
|
||||||
|
themeBundle={themeBundle}
|
||||||
|
onChange={onBrewChange('metadata')}
|
||||||
|
reportError={reportError}
|
||||||
|
userThemes={userThemes}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const redo = ()=>codeEditor.current?.redo();
|
||||||
|
const historySize = ()=>codeEditor.current?.historySize();
|
||||||
|
const undo = ()=>codeEditor.current?.undo();
|
||||||
|
const foldCode = ()=>codeEditor.current?.foldAll();
|
||||||
|
const unfoldCode = ()=>codeEditor.current?.unfoldAll();
|
||||||
|
|
||||||
|
//Called when there are changes to the editor's dimensions
|
||||||
|
const update = ()=>{};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, ()=>({
|
||||||
|
update,
|
||||||
|
undo,
|
||||||
|
redo,
|
||||||
|
foldCode,
|
||||||
|
unfoldCode,
|
||||||
|
historySize,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='editor' ref={editor}>
|
||||||
<SnippetBar
|
<SnippetBar
|
||||||
brew={this.props.brew}
|
brew={brew}
|
||||||
view={this.state.view}
|
view={view}
|
||||||
onViewChange={this.handleViewChange}
|
onViewChange={handleViewChange}
|
||||||
onInject={this.handleInject}
|
onInject={handleInject}
|
||||||
showEditButtons={this.props.showEditButtons}
|
showEditButtons={showEditButtons}
|
||||||
renderer={this.props.renderer}
|
renderer={renderer}
|
||||||
theme={this.props.brew.theme}
|
theme={brew.theme}
|
||||||
undo={this.undo}
|
undo={undo}
|
||||||
redo={this.redo}
|
redo={redo}
|
||||||
foldCode={this.foldCode}
|
foldCode={foldCode}
|
||||||
unfoldCode={this.unfoldCode}
|
unfoldCode={unfoldCode}
|
||||||
historySize={this.historySize()}
|
formatCode={isStyle() ? handleFormatCode : null}
|
||||||
currentEditorTheme={this.state.editorTheme}
|
historySize={historySize()}
|
||||||
updateEditorTheme={this.updateEditorTheme}
|
currentEditorTheme={currentEditorTheme}
|
||||||
themeBundle={this.props.themeBundle}
|
updateEditorTheme={updateEditorTheme}
|
||||||
cursorPos={this.codeEditor.current?.getCursorPosition() || {}}
|
themeBundle={themeBundle}
|
||||||
updateBrew={this.props.updateBrew}
|
cursorPos={codeEditor.current?.getCursorPosition() || {}}
|
||||||
|
updateBrew={updateBrew}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{this.renderEditor()}
|
{renderEditor()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default Editor;
|
export default Editor;
|
||||||
@@ -65,6 +65,7 @@ const Snippetbar = createReactClass({
|
|||||||
historySize : ()=>{},
|
historySize : ()=>{},
|
||||||
foldCode : ()=>{},
|
foldCode : ()=>{},
|
||||||
unfoldCode : ()=>{},
|
unfoldCode : ()=>{},
|
||||||
|
formatCode : ()=>{},
|
||||||
updateEditorTheme : ()=>{},
|
updateEditorTheme : ()=>{},
|
||||||
cursorPos : {},
|
cursorPos : {},
|
||||||
themeBundle : [],
|
themeBundle : [],
|
||||||
@@ -269,6 +270,10 @@ const Snippetbar = createReactClass({
|
|||||||
onClick={this.props.unfoldCode} >
|
onClick={this.props.unfoldCode} >
|
||||||
<i className='fas fa-expand-alt' />
|
<i className='fas fa-expand-alt' />
|
||||||
</div>
|
</div>
|
||||||
|
<div className={`editorTool formatCode ${this.props.formatCode ? 'active' : ''}`}
|
||||||
|
onClick={this.props.formatCode} >
|
||||||
|
<i className='fas fa-wand-magic-sparkles' />
|
||||||
|
</div>
|
||||||
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
|
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
|
||||||
onClick={this.toggleThemeSelector} >
|
onClick={this.toggleThemeSelector} >
|
||||||
<i className='fas fa-palette' />
|
<i className='fas fa-palette' />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
.editors {
|
.editors {
|
||||||
display : flex;
|
display : flex;
|
||||||
justify-content : flex-end;
|
justify-content : flex-end;
|
||||||
min-width : 250px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
|
min-width : 275px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
|
||||||
font-size: .85rem;
|
font-size: .85rem;
|
||||||
&:only-child {min-width : unset; margin-left : auto;}
|
&:only-child {min-width : unset; margin-left : auto;}
|
||||||
|
|
||||||
@@ -75,6 +75,11 @@
|
|||||||
color : grey;
|
color : grey;
|
||||||
&.active { color : inherit; }
|
&.active { color : inherit; }
|
||||||
}
|
}
|
||||||
|
&.formatCode {
|
||||||
|
.tooltipLeft('Clean your Code');
|
||||||
|
color : grey;
|
||||||
|
&.active { color : inherit; }
|
||||||
|
}
|
||||||
&.history {
|
&.history {
|
||||||
.tooltipLeft('History');
|
.tooltipLeft('History');
|
||||||
position : relative;
|
position : relative;
|
||||||
@@ -208,7 +213,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@container editor (width < 816px) {
|
@container editor (width < 841px) {
|
||||||
.snippetBar {
|
.snippetBar {
|
||||||
.editors {
|
.editors {
|
||||||
flex : 1;
|
flex : 1;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './editPage.less';
|
|||||||
// Common imports
|
// Common imports
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import Markdown from '@shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||||
@@ -62,7 +62,7 @@ const EditPage = (props)=>{
|
|||||||
const [lastSavedTime, setLastSavedTime] = useState(new Date());
|
const [lastSavedTime, setLastSavedTime] = useState(new Date());
|
||||||
const [saveGoogle, setSaveGoogle] = useState(!!props.brew.googleId);
|
const [saveGoogle, setSaveGoogle] = useState(!!props.brew.googleId);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||||
@@ -85,7 +85,7 @@ const EditPage = (props)=>{
|
|||||||
const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
|
const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
|
||||||
setAutoSaveEnabled(autoSavePref);
|
setAutoSaveEnabled(autoSavePref);
|
||||||
setWarnUnsavedChanges(!autoSavePref);
|
setWarnUnsavedChanges(!autoSavePref);
|
||||||
setHTMLErrors(Markdown.validate(currentBrew.text));
|
setHTMLErrors(hbfm.validate(currentBrew.text));
|
||||||
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
|
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
|
||||||
|
|
||||||
const handleControlKeys = (e)=>{
|
const handleControlKeys = (e)=>{
|
||||||
@@ -131,7 +131,7 @@ const EditPage = (props)=>{
|
|||||||
|
|
||||||
//If there are HTML errors, run the validator on every change to give quick feedback
|
//If there are HTML errors, run the validator on every change to give quick feedback
|
||||||
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
||||||
setHTMLErrors(Markdown.validate(value));
|
setHTMLErrors(hbfm.validate(value));
|
||||||
|
|
||||||
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
||||||
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
||||||
@@ -205,7 +205,7 @@ const EditPage = (props)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const save = async (brew, saveToGoogle)=>{
|
const save = async (brew, saveToGoogle)=>{
|
||||||
setHTMLErrors(Markdown.validate(brew.text));
|
setHTMLErrors(hbfm.validate(brew.text));
|
||||||
|
|
||||||
await updateHistory(brew).catch(console.error);
|
await updateHistory(brew).catch(console.error);
|
||||||
await versionHistoryGarbageCollection().catch(console.error);
|
await versionHistoryGarbageCollection().catch(console.error);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import './errorPage.less';
|
import './errorPage.less';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import UIPage from '../basePages/uiPage/uiPage.jsx';
|
import UIPage from '../basePages/uiPage/uiPage.jsx';
|
||||||
import Markdown from '@shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
import ErrorIndex from './errors/errorIndex.js';
|
import ErrorIndex from './errors/errorIndex.js';
|
||||||
|
|
||||||
const ErrorPage = ({ brew })=>{
|
const ErrorPage = ({ brew })=>{
|
||||||
@@ -16,7 +16,7 @@ const ErrorPage = ({ brew })=>{
|
|||||||
<h4>{brew?.text || 'No error text'}</h4>
|
<h4>{brew?.text || 'No error text'}</h4>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div dangerouslySetInnerHTML={{ __html: Markdown.render(errorText) }} />
|
<div dangerouslySetInnerHTML={{ __html: hbfm.render(errorText) }} />
|
||||||
</div>
|
</div>
|
||||||
</UIPage>
|
</UIPage>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './homePage.less';
|
|||||||
// Common imports
|
// Common imports
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import Markdown from '@shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||||
@@ -47,7 +47,7 @@ const HomePage =(props)=>{
|
|||||||
|
|
||||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||||
const [error, setError] = useState(undefined);
|
const [error, setError] = useState(undefined);
|
||||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||||
@@ -118,7 +118,7 @@ const HomePage =(props)=>{
|
|||||||
|
|
||||||
//If there are HTML errors, run the validator on every change to give quick feedback
|
//If there are HTML errors, run the validator on every change to give quick feedback
|
||||||
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
||||||
setHTMLErrors(Markdown.validate(value));
|
setHTMLErrors(hbfm.validate(value));
|
||||||
|
|
||||||
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
||||||
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './newPage.less';
|
|||||||
// Common imports
|
// Common imports
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import Markdown from '@shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||||
@@ -46,7 +46,7 @@ const NewPage = (props)=>{
|
|||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false);
|
const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||||
@@ -133,7 +133,7 @@ const NewPage = (props)=>{
|
|||||||
|
|
||||||
//If there are HTML errors, run the validator on every change to give quick feedback
|
//If there are HTML errors, run the validator on every change to give quick feedback
|
||||||
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
||||||
setHTMLErrors(Markdown.validate(value));
|
setHTMLErrors(hbfm.validate(value));
|
||||||
|
|
||||||
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
||||||
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
||||||
|
|||||||
Generated
+3913
-2110
File diff suppressed because it is too large
Load Diff
+11
-9
@@ -69,10 +69,10 @@
|
|||||||
],
|
],
|
||||||
"coverageThreshold": {
|
"coverageThreshold": {
|
||||||
"global": {
|
"global": {
|
||||||
"statements": 50,
|
"statements": 40,
|
||||||
"branches": 40,
|
"branches": 25,
|
||||||
"functions": 40,
|
"functions": 30,
|
||||||
"lines": 50
|
"lines": 40
|
||||||
},
|
},
|
||||||
"server/homebrew.api.js": {
|
"server/homebrew.api.js": {
|
||||||
"statements": 60,
|
"statements": 60,
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"@babel/preset-react": "^8.0.1",
|
"@babel/preset-react": "^8.0.1",
|
||||||
"@babel/runtime": "^8.0.0",
|
"@babel/runtime": "^8.0.0",
|
||||||
"@codemirror/autocomplete": "^6.20.3",
|
"@codemirror/autocomplete": "^6.20.3",
|
||||||
"@codemirror/commands": "^6.10.3",
|
"@codemirror/commands": "^6.11.0",
|
||||||
"@codemirror/highlight": "^0.19.8",
|
"@codemirror/highlight": "^0.19.8",
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
"@codemirror/lang-javascript": "^6.2.5",
|
"@codemirror/lang-javascript": "^6.2.5",
|
||||||
@@ -101,11 +101,11 @@
|
|||||||
"@codemirror/language-data": "^6.5.2",
|
"@codemirror/language-data": "^6.5.2",
|
||||||
"@codemirror/search": "^6.6.0",
|
"@codemirror/search": "^6.6.0",
|
||||||
"@codemirror/state": "^6.6.0",
|
"@codemirror/state": "^6.6.0",
|
||||||
"@codemirror/view": "^6.43.8",
|
"@codemirror/view": "^6.43.9",
|
||||||
"@dmsnell/diff-match-patch": "^1.1.0",
|
"@dmsnell/diff-match-patch": "^1.1.0",
|
||||||
"@googleapis/drive": "^20.2.0",
|
"@googleapis/drive": "^21.0.0",
|
||||||
"@lezer/highlight": "^1.2.3",
|
"@lezer/highlight": "^1.2.3",
|
||||||
"@oddbird/css-anchor-positioning": "^0.10.1",
|
"@oddbird/css-anchor-positioning": "^0.10.2",
|
||||||
"@sanity/diff-match-patch": "^3.2.0",
|
"@sanity/diff-match-patch": "^3.2.0",
|
||||||
"@vitejs/plugin-react": "^6.0.5",
|
"@vitejs/plugin-react": "^6.0.5",
|
||||||
"body-parser": "^2.3.0",
|
"body-parser": "^2.3.0",
|
||||||
@@ -122,6 +122,7 @@
|
|||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
"fs-extra": "^11.3.5",
|
"fs-extra": "^11.3.5",
|
||||||
"hash-wasm": "^4.12.0",
|
"hash-wasm": "^4.12.0",
|
||||||
|
"hbmarkedwrapper": "^1.0.0",
|
||||||
"idb-keyval": "^6.2.5",
|
"idb-keyval": "^6.2.5",
|
||||||
"js-yaml": "^5.3.0",
|
"js-yaml": "^5.3.0",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
@@ -140,10 +141,11 @@
|
|||||||
"marked-variables": "^1.0.5",
|
"marked-variables": "^1.0.5",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^9.9.2",
|
"mongoose": "^9.9.3",
|
||||||
"nanoid": "6.0.1",
|
"nanoid": "6.0.1",
|
||||||
"nconf": "^0.13.0",
|
"nconf": "^0.13.0",
|
||||||
"node": "^26.7.0",
|
"node": "^26.7.0",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
"react-frame-component": "^5.3.2",
|
"react-frame-component": "^5.3.2",
|
||||||
|
|||||||
@@ -20,8 +20,14 @@ const DEFAULT_BREW = {
|
|||||||
published : false,
|
published : false,
|
||||||
pageCount : 1,
|
pageCount : 1,
|
||||||
gDrive : false,
|
gDrive : false,
|
||||||
trashed : false
|
trashed : false,
|
||||||
|
bleedSize : { top: '.125in', bottom: '.125in', inner: '.125in', outer: '.125in' },
|
||||||
|
safetySpace : { top: '.25in', bottom: '.25in', outer: '.25in', inner: '.5in' },
|
||||||
|
trimSize : { width: '8.5in', height: '11in' },
|
||||||
|
columns : '2',
|
||||||
|
columnGutter : '.125in',
|
||||||
|
license : 'None',
|
||||||
|
legalAuthors : ''
|
||||||
};
|
};
|
||||||
// Default values for older brews with missing properties
|
// Default values for older brews with missing properties
|
||||||
// e.g., missing "renderer" is assumed to be "legacy"
|
// e.g., missing "renderer" is assumed to be "legacy"
|
||||||
|
|||||||
+10
-2
@@ -4,7 +4,7 @@ import { model as HomebrewModel } from './homebrew.model.js';
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import zlib from 'zlib';
|
import zlib from 'zlib';
|
||||||
import GoogleActions from './googleActions.js';
|
import GoogleActions from './googleActions.js';
|
||||||
import Markdown from '../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import asyncHandler from 'express-async-handler';
|
import asyncHandler from 'express-async-handler';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@@ -218,6 +218,14 @@ const api = {
|
|||||||
const metadata = _.pick(brew, ['title', 'description', 'tags', 'renderer', 'theme']);
|
const metadata = _.pick(brew, ['title', 'description', 'tags', 'renderer', 'theme']);
|
||||||
const snippetsArray = brewSnippetsToJSON('brew_snippets', brew.snippets, null, false).snippets;
|
const snippetsArray = brewSnippetsToJSON('brew_snippets', brew.snippets, null, false).snippets;
|
||||||
metadata.snippets = snippetsArray.length > 0 ? snippetsArray : undefined;
|
metadata.snippets = snippetsArray.length > 0 ? snippetsArray : undefined;
|
||||||
|
metadata.bleedSize = { top: brew?.bleedSize?.top, bottom: brew?.bleedSize?.bottom, inner: brew?.bleedSize?.inner, outer: brew?.bleedSize?.outer };
|
||||||
|
metadata.safetySpace = { top: brew?.safetySpace?.top, bottom: brew?.safetySpace?.bottom, outer: brew?.safetySpace?.outer, inner: brew?.safetySpace?.inner };
|
||||||
|
metadata.trimSize = { width: brew?.trimSize?.width, height: brew?.trimSize?.height };
|
||||||
|
metadata.columns = brew?.columns;
|
||||||
|
metadata.columnGutter = brew?.columnGutter;
|
||||||
|
metadata.license = brew?.license;
|
||||||
|
metadata.legalAuthors = brew?.legalAuthors;
|
||||||
|
|
||||||
text = `\`\`\`metadata\n` +
|
text = `\`\`\`metadata\n` +
|
||||||
`${yaml.dump(metadata)}\n` +
|
`${yaml.dump(metadata)}\n` +
|
||||||
`\`\`\`\n\n` +
|
`\`\`\`\n\n` +
|
||||||
@@ -225,7 +233,7 @@ const api = {
|
|||||||
return text;
|
return text;
|
||||||
},
|
},
|
||||||
getGoodBrewTitle : (text)=>{
|
getGoodBrewTitle : (text)=>{
|
||||||
const tokens = Markdown.marked.lexer(text);
|
const tokens = hbfm.marked.lexer(text);
|
||||||
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
|
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
|
||||||
.slice(0, MAX_TITLE_LENGTH);
|
.slice(0, MAX_TITLE_LENGTH);
|
||||||
},
|
},
|
||||||
|
|||||||
+211
-4
@@ -359,7 +359,27 @@ describe('Tests for api', ()=>{
|
|||||||
style : undefined,
|
style : undefined,
|
||||||
trashed : false,
|
trashed : false,
|
||||||
updatedAt : undefined,
|
updatedAt : undefined,
|
||||||
views : 0
|
views : 0,
|
||||||
|
bleedSize: {
|
||||||
|
top: '.125in',
|
||||||
|
bottom: '.125in',
|
||||||
|
inner: '.125in',
|
||||||
|
outer: '.125in',
|
||||||
|
},
|
||||||
|
columns: '2',
|
||||||
|
columnGutter: '.125in',
|
||||||
|
legalAuthors: '',
|
||||||
|
license: 'None',
|
||||||
|
safetySpace: {
|
||||||
|
top: '.25in',
|
||||||
|
bottom: '.25in',
|
||||||
|
outer: '.25in',
|
||||||
|
inner: '.5in',
|
||||||
|
},
|
||||||
|
trimSize: {
|
||||||
|
width: '8.5in',
|
||||||
|
height: '11in',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
expect(api.getId).toHaveBeenCalledWith(req);
|
expect(api.getId).toHaveBeenCalledWith(req);
|
||||||
@@ -450,7 +470,27 @@ describe('Tests for api', ()=>{
|
|||||||
tags : ['something', 'fun'],
|
tags : ['something', 'fun'],
|
||||||
renderer : 'v3',
|
renderer : 'v3',
|
||||||
theme : 'phb',
|
theme : 'phb',
|
||||||
googleId : '12345'
|
googleId : '12345',
|
||||||
|
bleedSize: {
|
||||||
|
top: '.125in',
|
||||||
|
bottom: '.125in',
|
||||||
|
inner: '.125in',
|
||||||
|
outer: '.125in',
|
||||||
|
},
|
||||||
|
columns: '2',
|
||||||
|
columnGutter: '.125in',
|
||||||
|
legalAuthors: '',
|
||||||
|
license: 'None',
|
||||||
|
safetySpace: {
|
||||||
|
top: '.25in',
|
||||||
|
bottom: '.25in',
|
||||||
|
outer: '.25in',
|
||||||
|
inner: '.5in',
|
||||||
|
},
|
||||||
|
trimSize: {
|
||||||
|
width: '8.5in',
|
||||||
|
height: '11in',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(`\`\`\`metadata
|
expect(result).toEqual(`\`\`\`metadata
|
||||||
@@ -461,6 +501,23 @@ tags:
|
|||||||
- fun
|
- fun
|
||||||
renderer: v3
|
renderer: v3
|
||||||
theme: phb
|
theme: phb
|
||||||
|
bleedSize:
|
||||||
|
top: .125in
|
||||||
|
bottom: .125in
|
||||||
|
inner: .125in
|
||||||
|
outer: .125in
|
||||||
|
safetySpace:
|
||||||
|
top: .25in
|
||||||
|
bottom: .25in
|
||||||
|
outer: .25in
|
||||||
|
inner: .5in
|
||||||
|
trimSize:
|
||||||
|
width: 8.5in
|
||||||
|
height: 11in
|
||||||
|
columns: '2'
|
||||||
|
columnGutter: .125in
|
||||||
|
license: None
|
||||||
|
legalAuthors: ''
|
||||||
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
@@ -476,7 +533,27 @@ brew`);
|
|||||||
tags : ['something', 'fun'],
|
tags : ['something', 'fun'],
|
||||||
renderer : 'v3',
|
renderer : 'v3',
|
||||||
theme : 'phb',
|
theme : 'phb',
|
||||||
googleId : '12345'
|
googleId : '12345',
|
||||||
|
bleedSize: {
|
||||||
|
top: '.125in',
|
||||||
|
bottom: '.125in',
|
||||||
|
inner: '.125in',
|
||||||
|
outer: '.125in',
|
||||||
|
},
|
||||||
|
columns: '2',
|
||||||
|
columnGutter: '.125in',
|
||||||
|
legalAuthors: '',
|
||||||
|
license: 'None',
|
||||||
|
safetySpace: {
|
||||||
|
top: '.25in',
|
||||||
|
bottom: '.25in',
|
||||||
|
outer: '.25in',
|
||||||
|
inner: '.5in',
|
||||||
|
},
|
||||||
|
trimSize: {
|
||||||
|
width: '8.5in',
|
||||||
|
height: '11in',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(`\`\`\`metadata
|
expect(result).toEqual(`\`\`\`metadata
|
||||||
@@ -487,6 +564,23 @@ tags:
|
|||||||
- fun
|
- fun
|
||||||
renderer: v3
|
renderer: v3
|
||||||
theme: phb
|
theme: phb
|
||||||
|
bleedSize:
|
||||||
|
top: .125in
|
||||||
|
bottom: .125in
|
||||||
|
inner: .125in
|
||||||
|
outer: .125in
|
||||||
|
safetySpace:
|
||||||
|
top: .25in
|
||||||
|
bottom: .25in
|
||||||
|
outer: .25in
|
||||||
|
inner: .5in
|
||||||
|
trimSize:
|
||||||
|
width: 8.5in
|
||||||
|
height: 11in
|
||||||
|
columns: '2'
|
||||||
|
columnGutter: .125in
|
||||||
|
license: None
|
||||||
|
legalAuthors: ''
|
||||||
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
@@ -613,7 +707,27 @@ brew`);
|
|||||||
title : 'asdf',
|
title : 'asdf',
|
||||||
trashed : false,
|
trashed : false,
|
||||||
updatedAt : undefined,
|
updatedAt : undefined,
|
||||||
views : 0
|
views : 0,
|
||||||
|
bleedSize: {
|
||||||
|
top: '.125in',
|
||||||
|
bottom: '.125in',
|
||||||
|
inner: '.125in',
|
||||||
|
outer: '.125in',
|
||||||
|
},
|
||||||
|
columns: '2',
|
||||||
|
columnGutter: '.125in',
|
||||||
|
legalAuthors: '',
|
||||||
|
license: 'None',
|
||||||
|
safetySpace: {
|
||||||
|
top: '.25in',
|
||||||
|
bottom: '.25in',
|
||||||
|
outer: '.25in',
|
||||||
|
inner: '.5in',
|
||||||
|
},
|
||||||
|
trimSize: {
|
||||||
|
width: '8.5in',
|
||||||
|
height: '11in',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -653,6 +767,14 @@ brew`);
|
|||||||
expect(res.send).toHaveBeenCalledWith({
|
expect(res.send).toHaveBeenCalledWith({
|
||||||
_id : '1',
|
_id : '1',
|
||||||
authors : ['test user'],
|
authors : ['test user'],
|
||||||
|
bleedSize: {
|
||||||
|
top: '.125in',
|
||||||
|
bottom: '.125in',
|
||||||
|
inner: '.125in',
|
||||||
|
outer: '.125in',
|
||||||
|
},
|
||||||
|
columns: '2',
|
||||||
|
columnGutter: '.125in',
|
||||||
createdAt : undefined,
|
createdAt : undefined,
|
||||||
description : '',
|
description : '',
|
||||||
editId : expect.any(String),
|
editId : expect.any(String),
|
||||||
@@ -662,6 +784,14 @@ brew`);
|
|||||||
renderer : 'V3',
|
renderer : 'V3',
|
||||||
lang : 'en',
|
lang : 'en',
|
||||||
shareId : expect.any(String),
|
shareId : expect.any(String),
|
||||||
|
legalAuthors: '',
|
||||||
|
license: 'None',
|
||||||
|
safetySpace: {
|
||||||
|
top: '.25in',
|
||||||
|
bottom: '.25in',
|
||||||
|
outer: '.25in',
|
||||||
|
inner: '.5in',
|
||||||
|
},
|
||||||
googleId : expect.any(String),
|
googleId : expect.any(String),
|
||||||
style : undefined,
|
style : undefined,
|
||||||
tags : [],
|
tags : [],
|
||||||
@@ -671,6 +801,10 @@ brew`);
|
|||||||
thumbnail : '',
|
thumbnail : '',
|
||||||
title : 'asdf',
|
title : 'asdf',
|
||||||
trashed : false,
|
trashed : false,
|
||||||
|
trimSize: {
|
||||||
|
width: '8.5in',
|
||||||
|
height: '11in',
|
||||||
|
},
|
||||||
updatedAt : undefined,
|
updatedAt : undefined,
|
||||||
views : 0
|
views : 0
|
||||||
});
|
});
|
||||||
@@ -1164,6 +1298,79 @@ brew`);
|
|||||||
// Text
|
// Text
|
||||||
expect(testBrew.text).toEqual('text\n');
|
expect(testBrew.text).toEqual('text\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('extended metadata', async ()=>{
|
||||||
|
const testBrew = {
|
||||||
|
text : '```metadata\n' +
|
||||||
|
'title: title\n' +
|
||||||
|
'description: description\n' +
|
||||||
|
'tags: [ \'tag a\' , \'tag b\' ]\n' +
|
||||||
|
'renderer: legacy\n' +
|
||||||
|
'theme: 5ePHB\n' +
|
||||||
|
'lang: en\n' +
|
||||||
|
'bleedSize:\n' +
|
||||||
|
' top: 1.5in\n' +
|
||||||
|
' bottom: 1.5in\n' +
|
||||||
|
' outer: 1.5in\n' +
|
||||||
|
' inner: 1.5in\n' +
|
||||||
|
'safetySpace:\n' +
|
||||||
|
' top: 1.25in\n' +
|
||||||
|
' bottom: 1.25in\n' +
|
||||||
|
' outer: 1.25in\n' +
|
||||||
|
' inner: 1.5in\n' +
|
||||||
|
'trimSize:\n' +
|
||||||
|
' width: 18.5in\n' +
|
||||||
|
' height: 111in\n' +
|
||||||
|
'columns: 12\n' +
|
||||||
|
'columnGutter: 1.125in\n' +
|
||||||
|
'license: AELF\n' +
|
||||||
|
'legalAuthors: Tom Bombadil\n' +
|
||||||
|
'\n' +
|
||||||
|
'```\n' +
|
||||||
|
'\n' +
|
||||||
|
'```css\n' +
|
||||||
|
'style\n' +
|
||||||
|
'style\n' +
|
||||||
|
'style\n' +
|
||||||
|
'```\n' +
|
||||||
|
'\n' +
|
||||||
|
'text\n'
|
||||||
|
};
|
||||||
|
|
||||||
|
splitTextStyleAndMetadata(testBrew);
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
expect(testBrew.title).toEqual('title');
|
||||||
|
expect(testBrew.description).toEqual('description');
|
||||||
|
expect(testBrew.renderer).toEqual('legacy');
|
||||||
|
expect(testBrew.theme).toEqual('5ePHB');
|
||||||
|
expect(testBrew.lang).toEqual('en');
|
||||||
|
// Paper Specfications
|
||||||
|
expect(testBrew.bleedSize.top).toEqual('1.5in');
|
||||||
|
expect(testBrew.bleedSize.bottom).toEqual('1.5in');
|
||||||
|
expect(testBrew.bleedSize.inner).toEqual('1.5in');
|
||||||
|
expect(testBrew.bleedSize.outer).toEqual('1.5in');
|
||||||
|
|
||||||
|
expect(testBrew.safetySpace.top).toEqual('1.25in');
|
||||||
|
expect(testBrew.safetySpace.bottom).toEqual('1.25in');
|
||||||
|
expect(testBrew.safetySpace.inner).toEqual('1.5in');
|
||||||
|
expect(testBrew.safetySpace.outer).toEqual('1.25in');
|
||||||
|
|
||||||
|
expect(testBrew.trimSize.width).toEqual('18.5in');
|
||||||
|
expect(testBrew.trimSize.height).toEqual('111in');
|
||||||
|
|
||||||
|
expect(testBrew.columns).toEqual(12);
|
||||||
|
expect(testBrew.columnGutter).toEqual('1.125in');
|
||||||
|
|
||||||
|
// Extended Metadata
|
||||||
|
expect(testBrew.license).toEqual('AELF');
|
||||||
|
expect(testBrew.legalAuthors).toEqual('Tom Bombadil');
|
||||||
|
|
||||||
|
// Style
|
||||||
|
expect(testBrew.style).toEqual('style\nstyle\nstyle\n');
|
||||||
|
// Text
|
||||||
|
expect(testBrew.text).toEqual('text\n');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,6 +93,15 @@ const splitTextStyleAndMetadata = (brew)=>{
|
|||||||
const metadata = yaml.load(metadataSection);
|
const metadata = yaml.load(metadataSection);
|
||||||
Object.assign(brew, _.pick(metadata, ['title', 'description', 'renderer', 'theme', 'lang']));
|
Object.assign(brew, _.pick(metadata, ['title', 'description', 'renderer', 'theme', 'lang']));
|
||||||
brew.snippets = yamlSnippetsToText(_.pick(metadata, ['snippets']).snippets || '');
|
brew.snippets = yamlSnippetsToText(_.pick(metadata, ['snippets']).snippets || '');
|
||||||
|
|
||||||
|
brew.bleedSize = { ...metadata.bleedSize };
|
||||||
|
brew.safetySpace = { ...metadata.safetySpace };
|
||||||
|
brew.trimSize = { ...metadata.trimSize };
|
||||||
|
brew.columns = metadata?.columns;
|
||||||
|
brew.columnGutter = metadata?.columnGutter;
|
||||||
|
brew.license = metadata?.license;
|
||||||
|
brew.legalAuthors = metadata.legalAuthors;
|
||||||
|
|
||||||
brew.text = brew.text.slice(index + 6);
|
brew.text = brew.text.slice(index + 6);
|
||||||
}
|
}
|
||||||
if(brew.text.startsWith('```css')) {
|
if(brew.text.startsWith('```css')) {
|
||||||
|
|||||||
@@ -1,574 +0,0 @@
|
|||||||
|
|
||||||
/* eslint-disable max-lines */
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { marked as Marked } from 'marked';
|
|
||||||
import MarkedExtendedTables from 'marked-extended-tables';
|
|
||||||
import MarkedDefinitionLists from 'marked-definition-lists';
|
|
||||||
import MarkedAlignedParagraphs from 'marked-alignment-paragraphs';
|
|
||||||
import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces';
|
|
||||||
import MarkedSubSuperText from 'marked-subsuper-text';
|
|
||||||
import { markedVariables,
|
|
||||||
setMarkedVariablePage,
|
|
||||||
setMarkedVariable,
|
|
||||||
getMarkedVariable } from 'marked-variables';
|
|
||||||
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
|
|
||||||
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
|
|
||||||
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
|
|
||||||
import MarkedDiagramsMarkdeep from 'marked-diagrams-markdeep';
|
|
||||||
|
|
||||||
|
|
||||||
//Icon fonts included so they can appear in emoji autosuggest dropdown
|
|
||||||
import diceFont from '../themes/fonts/iconFonts/diceFont.js';
|
|
||||||
import elderberryInn from '../themes/fonts/iconFonts/elderberryInn.js';
|
|
||||||
import gameIcons from '../themes/fonts/iconFonts/gameIcons.js';
|
|
||||||
import fontAwesome from '../themes/fonts/iconFonts/fontAwesome.js';
|
|
||||||
|
|
||||||
const renderer = new Marked.Renderer();
|
|
||||||
const tokenizer = new Marked.Tokenizer();
|
|
||||||
|
|
||||||
//Processes the markdown within an HTML block if it's just a class-wrapper
|
|
||||||
renderer.html = function (token) {
|
|
||||||
let html = token.text;
|
|
||||||
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
|
|
||||||
const openTag = html.substring(0, html.indexOf('>')+1);
|
|
||||||
html = html.substring(html.indexOf('>')+1);
|
|
||||||
html = html.substring(0, html.lastIndexOf('</div>'));
|
|
||||||
|
|
||||||
// Repeat the markdown processing for content inside the div, minus the preprocessing and postprocessing hooks which should only run once globally
|
|
||||||
const opts = Marked.defaults;
|
|
||||||
const tokens = Marked.lexer(html, opts);
|
|
||||||
Marked.walkTokens(tokens, opts.walkTokens);
|
|
||||||
return `${openTag} ${Marked.parser(tokens, opts)} </div>`;
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Don't wrap {{ Spans alone on a line, or {{ Divs in <p> tags
|
|
||||||
renderer.paragraph = function(token){
|
|
||||||
let match;
|
|
||||||
const text = this.parser.parseInline(token.tokens);
|
|
||||||
if(text.startsWith('<div') || text.startsWith('</div'))
|
|
||||||
return `${text}`;
|
|
||||||
else if(match = text.match(/(^|^.*?\n)<span class="inline-block(.*?<\/span>)$/))
|
|
||||||
return `${match[1].trim() ? `<p>${match[1]}</p>` : ''}<span class="inline-block${match[2]}`;
|
|
||||||
else
|
|
||||||
return `<p>${text}</p>\n`;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Fix local links in the Preview iFrame to link inside the frame
|
|
||||||
renderer.link = function (token) {
|
|
||||||
let { href, title, tokens } = token;
|
|
||||||
const text = this.parser.parseInline(tokens);
|
|
||||||
let self = false;
|
|
||||||
if(href[0] == '#') {
|
|
||||||
self = true;
|
|
||||||
}
|
|
||||||
href = cleanUrl(href);
|
|
||||||
|
|
||||||
if(href === null) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
let out = `<a href="${escape(href)}"`;
|
|
||||||
if(title) {
|
|
||||||
out += ` title="${escape(title)}"`;
|
|
||||||
}
|
|
||||||
// if(self) {
|
|
||||||
// out += ' target="_self"';
|
|
||||||
// }
|
|
||||||
out += `>${text}</a>`;
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS
|
|
||||||
renderer.image = function (token) {
|
|
||||||
const { href, title, text } = token;
|
|
||||||
if(href === null)
|
|
||||||
return text;
|
|
||||||
|
|
||||||
let out = `<img loading="lazy" src="${href}" alt="${text}" style="--HB_src:url(${href});"`;
|
|
||||||
if(title)
|
|
||||||
out += ` title="${title}"`;
|
|
||||||
|
|
||||||
out += '>';
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Disable default reflink behavior, as it steps on our variables extension
|
|
||||||
tokenizer.def = function () {
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mustacheSpans = {
|
|
||||||
name : 'mustacheSpans',
|
|
||||||
level : 'inline', // Is this a block-level or inline-level tokenizer?
|
|
||||||
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 match = completeSpan.exec(src);
|
|
||||||
if(match) {
|
|
||||||
//Find closing delimiter
|
|
||||||
let blockCount = 0;
|
|
||||||
let tags = {};
|
|
||||||
let endTags = 0;
|
|
||||||
let endToken = 0;
|
|
||||||
let delim;
|
|
||||||
while (delim = inlineRegex.exec(match[0])) {
|
|
||||||
if(_.isEmpty(tags)) {
|
|
||||||
tags = processStyleTags(delim[0].substring(2));
|
|
||||||
endTags = delim[0].length;
|
|
||||||
}
|
|
||||||
if(delim[0].startsWith('{{')) {
|
|
||||||
blockCount++;
|
|
||||||
} else if(delim[0] == '}}' && blockCount !== 0) {
|
|
||||||
blockCount--;
|
|
||||||
if(blockCount == 0) {
|
|
||||||
endToken = inlineRegex.lastIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(endToken) {
|
|
||||||
const raw = src.slice(0, endToken);
|
|
||||||
const text = raw.slice(endTags || -2, -2);
|
|
||||||
|
|
||||||
return { // Token to generate
|
|
||||||
type : 'mustacheSpans', // Should match "name" above
|
|
||||||
raw : raw, // Text to consume from the source
|
|
||||||
text : text, // Additional custom properties
|
|
||||||
tags : tags,
|
|
||||||
tokens : this.lexer.inlineTokens(text) // inlineTokens to process **bold**, *italics*, etc.
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
const tags = token.tags;
|
|
||||||
tags.classes = ['inline-block', tags.classes].join(' ').trim();
|
|
||||||
return `<span` +
|
|
||||||
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
|
|
||||||
`${tags.id ? ` id="${tags.id}"` : ''}` +
|
|
||||||
`${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
|
|
||||||
`${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
|
|
||||||
`>${this.parser.parseInline(token.tokens)}</span>`; // parseInline to turn child tokens into HTML
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mustacheDivs = {
|
|
||||||
name : 'mustacheDivs',
|
|
||||||
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 completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
|
|
||||||
const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
|
|
||||||
const match = completeBlock.exec(src);
|
|
||||||
if(match) {
|
|
||||||
//Find closing delimiter
|
|
||||||
let blockCount = 0;
|
|
||||||
let tags = {};
|
|
||||||
let endTags = 0;
|
|
||||||
let endToken = 0;
|
|
||||||
let delim;
|
|
||||||
while (delim = blockRegex.exec(match[0])?.[0].trim()) {
|
|
||||||
if(_.isEmpty(tags)) {
|
|
||||||
tags = processStyleTags(delim.substring(2));
|
|
||||||
endTags = delim.length + src.indexOf(delim);
|
|
||||||
}
|
|
||||||
if(delim.startsWith('{{')) {
|
|
||||||
blockCount++;
|
|
||||||
} else if(delim == '}}' && blockCount !== 0) {
|
|
||||||
blockCount--;
|
|
||||||
if(blockCount == 0) {
|
|
||||||
endToken = blockRegex.lastIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(endToken) {
|
|
||||||
const raw = src.slice(0, endToken);
|
|
||||||
const text = raw.slice(endTags || -2, -2);
|
|
||||||
return { // Token to generate
|
|
||||||
type : 'mustacheDivs', // Should match "name" above
|
|
||||||
raw : raw, // Text to consume from the source
|
|
||||||
text : text, // Additional custom properties
|
|
||||||
tags : tags,
|
|
||||||
tokens : this.lexer.blockTokens(text)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
const tags = token.tags;
|
|
||||||
tags.classes = ['block', tags.classes].join(' ').trim();
|
|
||||||
return `<div` +
|
|
||||||
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
|
|
||||||
`${tags.id ? ` id="${tags.id}"` : ''}` +
|
|
||||||
`${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
|
|
||||||
`${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
|
|
||||||
`>${this.parser.parse(token.tokens)}</div>`; // parse to turn child tokens into HTML
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/g;
|
|
||||||
const match = inlineRegex.exec(src);
|
|
||||||
if(match) {
|
|
||||||
const lastToken = tokens[tokens.length - 1];
|
|
||||||
if(!lastToken || lastToken.type == 'mustacheInjectInline')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const tags = processStyleTags(match[1]);
|
|
||||||
lastToken.originalType = lastToken.type;
|
|
||||||
lastToken.type = 'mustacheInjectInline';
|
|
||||||
lastToken.injectedTags = tags;
|
|
||||||
return {
|
|
||||||
type : 'mustacheInjectInline', // Should match "name" above
|
|
||||||
raw : match[0], // Text to consume from the source
|
|
||||||
text : ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
if(!token.originalType){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
token.type = token.originalType;
|
|
||||||
const text = this.parser.parseInline([token]);
|
|
||||||
const originalTags = extractHTMLStyleTags(text);
|
|
||||||
const injectedTags = token.injectedTags;
|
|
||||||
const tags = mergeHTMLTags(originalTags, injectedTags);
|
|
||||||
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
|
|
||||||
if(openingTag) {
|
|
||||||
return `${openingTag[1]}` +
|
|
||||||
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
|
|
||||||
`${tags.id ? ` id="${tags.id}"` : ''}` +
|
|
||||||
`${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
|
|
||||||
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
|
|
||||||
`${openingTag[2]}`; // parse to turn child tokens into HTML
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mustacheInjectBlock = {
|
|
||||||
extensions : [{
|
|
||||||
name : '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 match = inlineRegex.exec(src);
|
|
||||||
if(match) {
|
|
||||||
const lastToken = tokens[tokens.length - 1];
|
|
||||||
if(!lastToken || lastToken.type == 'mustacheInjectBlock')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
lastToken.originalType = 'mustacheInjectBlock';
|
|
||||||
lastToken.injectedTags = processStyleTags(match[1]);
|
|
||||||
return {
|
|
||||||
type : 'mustacheInjectBlock', // Should match "name" above
|
|
||||||
raw : match[0], // Text to consume from the source
|
|
||||||
text : ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
if(!token.originalType){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
token.type = token.originalType;
|
|
||||||
const text = this.parser.parse([token]);
|
|
||||||
const originalTags = extractHTMLStyleTags(text);
|
|
||||||
const injectedTags = token.injectedTags;
|
|
||||||
const tags = mergeHTMLTags(originalTags, injectedTags);
|
|
||||||
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
|
|
||||||
if(openingTag) {
|
|
||||||
return `${openingTag[1]}` +
|
|
||||||
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
|
|
||||||
`${tags.id ? ` id="${tags.id}"` : ''}` +
|
|
||||||
`${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
|
|
||||||
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
|
|
||||||
`${openingTag[2]}`; // parse to turn child tokens into HTML
|
|
||||||
}
|
|
||||||
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 forcedParagraphBreaks = {
|
|
||||||
name : 'hardBreaks',
|
|
||||||
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 regex = /^(:+)(?:\n|$)/ym;
|
|
||||||
const match = regex.exec(src);
|
|
||||||
if(match?.length) {
|
|
||||||
return {
|
|
||||||
type : 'hardBreaks', // Should match "name" above
|
|
||||||
raw : match[0], // Text to consume from the source
|
|
||||||
length : match[1].length,
|
|
||||||
text : ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
return `<div class='blank'></div>\n`.repeat(token.length);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Emoji options
|
|
||||||
// To add more icon fonts, need to do these things
|
|
||||||
// 1) Add the font file as .woff2 to themes/fonts/iconFonts folder
|
|
||||||
// 2) Create a .less file mapping CSS class names to the font character
|
|
||||||
// 3) Create a .js file mapping Autosuggest names to CSS class names
|
|
||||||
// 4) Import the .less file into shared/naturalcrit/codeEditor/codeEditor.less
|
|
||||||
// 5) Import the .less file into themes/V3/blank.style.less
|
|
||||||
// 6) Import the .js file to shared/naturalcrit/codeEditor/autocompleteEmoji.js and add to `emojis` object
|
|
||||||
// 7) Import the .js file here to markdown.js, and add to `emojis` object below
|
|
||||||
const MarkedEmojiOptions = {
|
|
||||||
emojis : {
|
|
||||||
...diceFont,
|
|
||||||
...elderberryInn,
|
|
||||||
...fontAwesome,
|
|
||||||
...gameIcons,
|
|
||||||
},
|
|
||||||
renderer : (token)=>`<i class="${token.emoji}"></i>`
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableTerminators = [
|
|
||||||
`:+\\n`, // hardBreak
|
|
||||||
` *{[^\n]+}`, // blockInjector
|
|
||||||
` *{{[^{\n]*\n.*?\n}}` // mustacheDiv
|
|
||||||
];
|
|
||||||
|
|
||||||
const markdeepOptions = { langs: ['asciiArt'] };
|
|
||||||
|
|
||||||
Marked.use(markedVariables());
|
|
||||||
Marked.use(MarkedDiagramsMarkdeep(markdeepOptions));
|
|
||||||
Marked.use(MarkedDefinitionLists());
|
|
||||||
Marked.use({ extensions: [forcedParagraphBreaks, mustacheSpans, mustacheDivs, mustacheInjectInline] });
|
|
||||||
Marked.use(mustacheInjectBlock);
|
|
||||||
Marked.use(MarkedAlignedParagraphs());
|
|
||||||
Marked.use(MarkedSubSuperText());
|
|
||||||
Marked.use(MarkedNonbreakingSpaces());
|
|
||||||
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
|
|
||||||
Marked.use(MarkedExtendedTables({ interruptPatterns: tableTerminators }), MarkedGFMHeadingId({ globalSlugs: true }),
|
|
||||||
MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
|
|
||||||
|
|
||||||
function cleanUrl(href) {
|
|
||||||
try {
|
|
||||||
href = encodeURI(href).replace(/%25/g, '%');
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return href;
|
|
||||||
}
|
|
||||||
|
|
||||||
const escapeTest = /[&<>"']/;
|
|
||||||
const escapeReplace = /[&<>"']/g;
|
|
||||||
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
|
|
||||||
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
|
|
||||||
const escapeReplacements = {
|
|
||||||
'&' : '&',
|
|
||||||
'<' : '<',
|
|
||||||
'>' : '>',
|
|
||||||
'"' : '"',
|
|
||||||
'\'' : '''
|
|
||||||
};
|
|
||||||
const getEscapeReplacement = (ch)=>escapeReplacements[ch];
|
|
||||||
const escape = function (html, encode) {
|
|
||||||
if(encode) {
|
|
||||||
if(escapeTest.test(html)) {
|
|
||||||
return html.replace(escapeReplace, getEscapeReplacement);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(escapeTestNoEncode.test(html)) {
|
|
||||||
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tagTypes = ['div', 'span', 'a'];
|
|
||||||
const tagRegex = new RegExp(`(${
|
|
||||||
_.map(tagTypes, (type)=>{
|
|
||||||
return `\\<${type}\\b|\\</${type}>`;
|
|
||||||
}).join('|')})`, 'g');
|
|
||||||
|
|
||||||
// Special "void" tags that can be self-closed but don't need to be.
|
|
||||||
const voidTags = new Set([
|
|
||||||
'area', 'base', 'br', 'col', 'command', 'hr', 'img',
|
|
||||||
'input', 'keygen', 'link', 'meta', 'param', 'source'
|
|
||||||
]);
|
|
||||||
|
|
||||||
const processStyleTags = (string)=>{
|
|
||||||
//split tags up. quotes can only occur right after : or =.
|
|
||||||
//TODO: can we simplify to just split on commas?
|
|
||||||
const tags = string.match(/(?:[^, ":=]+|[:=](?:"[^"]*"|))+/g);
|
|
||||||
|
|
||||||
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0] || null;
|
|
||||||
const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))).join(' ') || null;
|
|
||||||
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)=>{
|
|
||||||
const index = attr.indexOf('=');
|
|
||||||
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
|
|
||||||
value = value.replace(/"/g, '');
|
|
||||||
obj[key.trim()] = value.trim();
|
|
||||||
return obj;
|
|
||||||
}, {}) || null;
|
|
||||||
const styles = tags?.length ? tags.reduce((styleObj, style)=>{
|
|
||||||
const index = style.indexOf(':');
|
|
||||||
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
|
|
||||||
styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim();
|
|
||||||
return styleObj;
|
|
||||||
}, {}) : null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id : id,
|
|
||||||
classes : classes,
|
|
||||||
styles : _.isEmpty(styles) ? null : styles,
|
|
||||||
attributes : _.isEmpty(attributes) ? null : attributes
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//Given a string representing an HTML element, extract all of its properties (id, class, style, and other attributes)
|
|
||||||
const extractHTMLStyleTags = (htmlString)=>{
|
|
||||||
const firstElementOnly = htmlString.split('>')[0];
|
|
||||||
const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null;
|
|
||||||
const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null;
|
|
||||||
const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1]
|
|
||||||
?.split(';').reduce((styleObj, style)=>{
|
|
||||||
if(style.trim() === '') return styleObj;
|
|
||||||
const index = style.indexOf(':');
|
|
||||||
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
|
|
||||||
styleObj[key.trim()] = value.trim();
|
|
||||||
return styleObj;
|
|
||||||
}, {}) || null;
|
|
||||||
const attributes = firstElementOnly.match(/[a-zA-Z]+="[^"]*"/g)
|
|
||||||
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
|
|
||||||
.reduce((obj, attr)=>{
|
|
||||||
const index = attr.indexOf('=');
|
|
||||||
const [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
|
|
||||||
obj[key.trim()] = value.replace(/"/g, '');
|
|
||||||
return obj;
|
|
||||||
}, {}) || null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id : id,
|
|
||||||
classes : classes,
|
|
||||||
styles : _.isEmpty(styles) ? null : styles,
|
|
||||||
attributes : _.isEmpty(attributes) ? null : attributes
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mergeHTMLTags = (originalTags, newTags)=>{
|
|
||||||
return {
|
|
||||||
id : newTags.id || originalTags.id || null,
|
|
||||||
classes : [originalTags.classes, newTags.classes].join(' ').trim() || null,
|
|
||||||
styles : Object.assign(originalTags.styles ?? {}, newTags.styles ?? {}),
|
|
||||||
attributes : Object.assign(originalTags.attributes ?? {}, newTags.attributes ?? {})
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Markdown = {
|
|
||||||
marked : Marked,
|
|
||||||
render : (rawBrewText, pageNumber=0)=>{
|
|
||||||
setMarkedVariablePage(pageNumber);
|
|
||||||
|
|
||||||
const lastPageNumber = pageNumber > 0 ? getMarkedVariable('HB_pageNumber', pageNumber - 1) : 0;
|
|
||||||
setMarkedVariable('HB_pageNumber', //Add document variables for this page
|
|
||||||
!isNaN(Number(lastPageNumber)) ? Number(lastPageNumber) + 1 : lastPageNumber,
|
|
||||||
pageNumber);
|
|
||||||
|
|
||||||
if(pageNumber==0) MarkedGFMResetHeadingIDs();
|
|
||||||
|
|
||||||
rawBrewText = rawBrewText.replace(/^\\column(?:break)?$/gm, `\n<div class='columnSplit'></div>\n`);
|
|
||||||
|
|
||||||
const opts = Marked.defaults;
|
|
||||||
|
|
||||||
rawBrewText = opts.hooks.preprocess(rawBrewText);
|
|
||||||
const tokens = Marked.lexer(rawBrewText, opts);
|
|
||||||
|
|
||||||
Marked.walkTokens(tokens, opts.walkTokens);
|
|
||||||
|
|
||||||
const html = Marked.parser(tokens, opts);
|
|
||||||
return opts.hooks.postprocess(html);
|
|
||||||
},
|
|
||||||
|
|
||||||
validate : (rawBrewText)=>{
|
|
||||||
const errors = [];
|
|
||||||
const leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber)=>{
|
|
||||||
const lineNumber = _lineNumber + 1;
|
|
||||||
const matches = line.match(tagRegex);
|
|
||||||
if(!matches || !matches.length) return acc;
|
|
||||||
|
|
||||||
_.each(matches, (match)=>{
|
|
||||||
_.each(tagTypes, (type)=>{
|
|
||||||
if(match == `<${type}`){
|
|
||||||
acc.push({
|
|
||||||
type : type,
|
|
||||||
line : lineNumber
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if(match === `</${type}>`){
|
|
||||||
// Closing tag: Check we expect it to be closed.
|
|
||||||
// The accumulator may contain a sequence of voidable opening tags,
|
|
||||||
// over which we skip before checking validity of the close.
|
|
||||||
while (acc.length && voidTags.has(_.last(acc).type) && _.last(acc).type != type) {
|
|
||||||
acc.pop();
|
|
||||||
}
|
|
||||||
// Now check that what remains in the accumulator is valid.
|
|
||||||
if(!acc.length){
|
|
||||||
errors.push({
|
|
||||||
line : lineNumber,
|
|
||||||
type : type,
|
|
||||||
text : 'Unmatched closing tag',
|
|
||||||
id : 'CLOSE'
|
|
||||||
});
|
|
||||||
} else if(_.last(acc).type == type){
|
|
||||||
acc.pop();
|
|
||||||
} else {
|
|
||||||
errors.push({
|
|
||||||
line : `${_.last(acc).line} to ${lineNumber}`,
|
|
||||||
type : type,
|
|
||||||
text : 'Type mismatch on closing tag',
|
|
||||||
id : 'MISMATCH'
|
|
||||||
});
|
|
||||||
acc.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
_.each(leftovers, (unmatched)=>{
|
|
||||||
errors.push({
|
|
||||||
line : unmatched.line,
|
|
||||||
type : unmatched.type,
|
|
||||||
text : 'Unmatched opening tag',
|
|
||||||
id : 'OPEN'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Markdown;
|
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
import Markdown from '../../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
|
|
||||||
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
|
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
|
||||||
const source = '<div>*Bold text*</div>';
|
const source = '<div>*Bold text*</div>';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered).toBe('<div> <p><em>Bold text</em></p>\n </div>');
|
expect(rendered).toBe('<div> <p><em>Bold text</em></p>\n </div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -12,6 +12,6 @@ test('Processes the markdown within an HTML block if its just a class wrapper',
|
|||||||
//
|
//
|
||||||
// test('Check markdown is using the custom renderer; specifically that it adds target=_self attribute to internal links in HTML blocks', function() {
|
// test('Check markdown is using the custom renderer; specifically that it adds target=_self attribute to internal links in HTML blocks', function() {
|
||||||
// const source = '<div>[Has _self Attribute?](#p1)</div>';
|
// const source = '<div>[Has _self Attribute?](#p1)</div>';
|
||||||
// const rendered = Markdown.render(source);
|
// const rendered = hbfm.render(source);
|
||||||
// expect(rendered).toBe('<div> <p><a href="#p1" target="_self">Has _self Attribute?</a></p>\n </div>');
|
// expect(rendered).toBe('<div> <p><a href="#p1" target="_self">Has _self Attribute?</a></p>\n </div>');
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
import Markdown from '../../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
|
|
||||||
describe('Inline Definition Lists', ()=>{
|
describe('Inline Definition Lists', ()=>{
|
||||||
test('No Term 1 Definition', function() {
|
test('No Term 1 Definition', function() {
|
||||||
const source = ':: My First Definition\n\n';
|
const source = ':: My First Definition\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt></dt><dd>My First Definition</dd>\n</dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt></dt><dd>My First Definition</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Single Definition Term', function() {
|
test('Single Definition Term', function() {
|
||||||
const source = 'My term :: My First Definition\n\n';
|
const source = 'My term :: My First Definition\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>My term</dt><dd>My First Definition</dd>\n</dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>My term</dt><dd>My First Definition</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Definition Terms', function() {
|
test('Multiple Definition Terms', function() {
|
||||||
const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n';
|
const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Definition of Term 1</dd>\n<dt>Term 2</dt><dd>Definition of Term 2</dd>\n</dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Definition of Term 1</dd>\n<dt>Term 2</dt><dd>Definition of Term 2</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -25,79 +25,79 @@ describe('Inline Definition Lists', ()=>{
|
|||||||
describe('Multiline Definition Lists', ()=>{
|
describe('Multiline Definition Lists', ()=>{
|
||||||
test('Single Term, Single Definition', function() {
|
test('Single Term, Single Definition', function() {
|
||||||
const source = 'Term 1\n::Definition 1\n\n';
|
const source = 'Term 1\n::Definition 1\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd></dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Single Term, Plural Definitions', function() {
|
test('Single Term, Plural Definitions', function() {
|
||||||
const source = 'Term 1\n::Definition 1\n::Definition 2\n\n';
|
const source = 'Term 1\n::Definition 1\n::Definition 2\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Term, Single Definitions', function() {
|
test('Multiple Term, Single Definitions', function() {
|
||||||
const source = 'Term 1\n::Definition 1\n\nTerm 2\n::Definition 1\n\n';
|
const source = 'Term 1\n::Definition 1\n\nTerm 2\n::Definition 1\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd></dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Term, Plural Definitions', function() {
|
test('Multiple Term, Plural Definitions', function() {
|
||||||
const source = 'Term 1\n::Definition 1\n::Definition 2\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
|
const source = 'Term 1\n::Definition 1\n::Definition 2\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Single Term, Single multi-line definition', function() {
|
test('Single Term, Single multi-line definition', function() {
|
||||||
const source = 'Term 1\n::Definition 1\nand more and\nmore and more\n\n';
|
const source = 'Term 1\n::Definition 1\nand more and\nmore and more\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more and more</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more and more</dd></dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Single Term, Plural multi-line definitions', function() {
|
test('Single Term, Plural multi-line definitions', function() {
|
||||||
const source = 'Term 1\n::Definition 1\nand more and more\n::Definition 2\nand more\nand more\n::Definition 3\n\n';
|
const source = 'Term 1\n::Definition 1\nand more and more\n::Definition 2\nand more\nand more\n::Definition 3\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dd>Definition 2 and more and more</dd>\n<dd>Definition 3</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dd>Definition 2 and more and more</dd>\n<dd>Definition 3</dd></dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Term, Single multi-line definition', function() {
|
test('Multiple Term, Single multi-line definition', function() {
|
||||||
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
|
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Term, Single multi-line definition, followed by an inline dl', function() {
|
test('Multiple Term, Single multi-line definition, followed by an inline dl', function() {
|
||||||
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)';
|
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><dl><dt></dt><dd>Inline Definition (no term)</dd>\n</dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><dl><dt></dt><dd>Inline Definition (no term)</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Term, Single multi-line definition, followed by paragraph', function() {
|
test('Multiple Term, Single multi-line definition, followed by paragraph', function() {
|
||||||
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\nParagraph';
|
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\nParagraph';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><p>Paragraph</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><p>Paragraph</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Block Token cannot be the Term of a multi-line definition', function() {
|
test('Block Token cannot be the Term of a multi-line definition', function() {
|
||||||
const source = '## Header\n::Definition 1 of a single-line DL\n::Definition 1 of another single-line DL';
|
const source = '## Header\n::Definition 1 of a single-line DL\n::Definition 1 of another single-line DL';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 id="header">Header</h2>\n<dl><dt></dt><dd>Definition 1 of a single-line DL</dd>\n<dt></dt><dd>Definition 1 of another single-line DL</dd>\n</dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 id="header">Header</h2>\n<dl><dt></dt><dd>Definition 1 of a single-line DL</dd>\n<dt></dt><dd>Definition 1 of another single-line DL</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Inline DL has priority over Multiline', function() {
|
test('Inline DL has priority over Multiline', function() {
|
||||||
const source = 'Term 1 :: Inline definition 1\n:: Inline definition 2 (no DT)';
|
const source = 'Term 1 :: Inline definition 1\n:: Inline definition 2 (no DT)';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Inline definition 1</dd>\n<dt></dt><dd>Inline definition 2 (no DT)</dd>\n</dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Inline definition 1</dd>\n<dt></dt><dd>Inline definition 2 (no DT)</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiline Definition Term must have at least one non-empty Definition', function() {
|
test('Multiline Definition Term must have at least one non-empty Definition', function() {
|
||||||
const source = 'Term 1\n::';
|
const source = 'Term 1\n::';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiline Definition List must have at least one non-newline character after ::', function() {
|
test('Multiline Definition List must have at least one non-newline character after ::', function() {
|
||||||
const source = 'Term 1\n::\nDefinition 1\n\n';
|
const source = 'Term 1\n::\nDefinition 1\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Definition 1</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Definition 1</p>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Markdown from '../../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
|
|
||||||
// Marked.js adds line returns after closing tags on some default tokens.
|
// Marked.js adds line returns after closing tags on some default tokens.
|
||||||
@@ -12,37 +12,37 @@ const emoji = 'df_d12_2';
|
|||||||
describe(`When emojis/icons are active`, ()=>{
|
describe(`When emojis/icons are active`, ()=>{
|
||||||
it('when a word is between two colons (:word:), and a matching emoji exists, it is rendered as an emoji', function() {
|
it('when a word is between two colons (:word:), and a matching emoji exists, it is rendered as an emoji', function() {
|
||||||
const source = `:${emoji}:`;
|
const source = `:${emoji}:`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i></p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i></p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when a word is between two colons (:word:), and no matching emoji exists, it is not parsed', function() {
|
it('when a word is between two colons (:word:), and no matching emoji exists, it is not parsed', function() {
|
||||||
const source = `:invalid:`;
|
const source = `:invalid:`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>:invalid:</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>:invalid:</p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('two valid emojis with no whitespace are prioritized over definition lists', function() {
|
it('two valid emojis with no whitespace are prioritized over definition lists', function() {
|
||||||
const source = `:${emoji}::${emoji}:`;
|
const source = `:${emoji}::${emoji}:`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i><i class="df d12-2"></i></p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i><i class="df d12-2"></i></p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('definition lists that are not also part of an emoji can coexist with normal emojis', function() {
|
it('definition lists that are not also part of an emoji can coexist with normal emojis', function() {
|
||||||
const source = `definition :: term ${emoji}::${emoji}:`;
|
const source = `definition :: term ${emoji}::${emoji}:`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>definition</dt><dd>term df_d12_2:<i class="df d12-2"></i></dd></dl>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>definition</dt><dd>term df_d12_2:<i class="df d12-2"></i></dd></dl>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('A valid emoji is compatible with curly injectors', function() {
|
it('A valid emoji is compatible with curly injectors', function() {
|
||||||
const source = `:${emoji}:{color:blue,myClass}`;
|
const source = `:${emoji}:{color:blue,myClass}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2 myClass" style="color:blue;"></i></p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2 myClass" style="color:blue;"></i></p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Emojis are not parsed inside of curly span CSS blocks', function() {
|
it('Emojis are not parsed inside of curly span CSS blocks', function() {
|
||||||
const source = `{{color:${emoji} text}}`;
|
const source = `{{color:${emoji} text}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="color:df_d12_2;">text</span>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="color:df_d12_2;">text</span>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ describe(`When emojis/icons are active`, ()=>{
|
|||||||
const source = dedent`{{color:${emoji}
|
const source = dedent`{{color:${emoji}
|
||||||
text
|
text
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:df_d12_2;"><p>text</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:df_d12_2;"><p>text</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
|
|
||||||
|
|
||||||
import Markdown from '../../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
|
|
||||||
describe('Hard Breaks', ()=>{
|
describe('Hard Breaks', ()=>{
|
||||||
test('Single Break', function() {
|
test('Single Break', function() {
|
||||||
const source = ':\n\n';
|
const source = ':\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Double Break', function() {
|
test('Double Break', function() {
|
||||||
const source = '::\n\n';
|
const source = '::\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Triple Break', function() {
|
test('Triple Break', function() {
|
||||||
const source = ':::\n\n';
|
const source = ':::\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Many Break', function() {
|
test('Many Break', function() {
|
||||||
const source = '::::::::::\n\n';
|
const source = '::::::::::\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple sets of Breaks', function() {
|
test('Multiple sets of Breaks', function() {
|
||||||
const source = ':::\n:::\n:::';
|
const source = ':::\n:::\n:::';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Break directly between two paragraphs', function() {
|
test('Break directly between two paragraphs', function() {
|
||||||
const source = 'Line 1\n::\nLine 2';
|
const source = 'Line 1\n::\nLine 2';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Line 2</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Line 2</p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Ignored inside a code block', function() {
|
test('Ignored inside a code block', function() {
|
||||||
const source = '```\n\n:\n\n```\n';
|
const source = '```\n\n:\n\n```\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:\n</code></pre>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:\n</code></pre>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
|
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import Markdown from '../../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
|
|
||||||
// Marked.js adds line returns after closing tags on some default tokens.
|
// Marked.js adds line returns after closing tags on some default tokens.
|
||||||
// This removes those line returns for comparison sake.
|
// This removes those line returns for comparison sake.
|
||||||
@@ -15,112 +15,112 @@ String.prototype.trimReturns = function(){
|
|||||||
describe('Inline: When using the Inline syntax {{ }}', ()=>{
|
describe('Inline: When using the Inline syntax {{ }}', ()=>{
|
||||||
it('Renders a mustache span with text only', function() {
|
it('Renders a mustache span with text only', function() {
|
||||||
const source = '{{ text}}';
|
const source = '{{ text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text only, but with spaces', function() {
|
it('Renders a mustache span with text only, but with spaces', function() {
|
||||||
const source = '{{ this is a text}}';
|
const source = '{{ this is a text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">this is a text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">this is a text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an empty mustache span', function() {
|
it('Renders an empty mustache span', function() {
|
||||||
const source = '{{}}';
|
const source = '{{}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with just a space', function() {
|
it('Renders a mustache span with just a space', function() {
|
||||||
const source = '{{ }}';
|
const source = '{{ }}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with a few spaces only', function() {
|
it('Renders a mustache span with a few spaces only', function() {
|
||||||
const source = '{{ }}';
|
const source = '{{ }}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and class', function() {
|
it('Renders a mustache span with text and class', function() {
|
||||||
const source = '{{my-class text}}';
|
const source = '{{my-class text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and two classes', function() {
|
it('Renders a mustache span with text and two classes', function() {
|
||||||
const source = '{{my-class,my-class2 text}}';
|
const source = '{{my-class,my-class2 text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class my-class2">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class my-class2">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text with spaces and class', function() {
|
it('Renders a mustache span with text with spaces and class', function() {
|
||||||
const source = '{{my-class this is a text}}';
|
const source = '{{my-class this is a text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">this is a text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">this is a text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and id', function() {
|
it('Renders a mustache span with text and id', function() {
|
||||||
const source = '{{#my-span text}}';
|
const source = '{{#my-span text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and two ids', function() {
|
it('Renders a mustache span with text and two ids', function() {
|
||||||
const source = '{{#my-span,#my-favorite-span text}}';
|
const source = '{{#my-span,#my-favorite-span text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and css property', function() {
|
it('Renders a mustache span with text and css property', function() {
|
||||||
const source = '{{color:red text}}';
|
const source = '{{color:red text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and two css properties', function() {
|
it('Renders a mustache span with text and two css properties', function() {
|
||||||
const source = '{{color:red,padding:5px text}}';
|
const source = '{{color:red,padding:5px text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; padding:5px;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; padding:5px;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and css property which contains quotes', function() {
|
it('Renders a mustache span with text and css property which contains quotes', function() {
|
||||||
const source = '{{font-family:"trebuchet ms" text}}';
|
const source = '{{font-family:"trebuchet ms" text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a mustache span with text and two css properties which contains quotes', function() {
|
it('Renders a mustache span with text and two css properties which contains quotes', function() {
|
||||||
const source = '{{font-family:"trebuchet ms",padding:"5px 10px" text}}';
|
const source = '{{font-family:"trebuchet ms",padding:"5px 10px" text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms; padding:5px 10px;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms; padding:5px 10px;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('Renders a mustache span with text with quotes and css property which contains double quotes', function() {
|
it('Renders a mustache span with text with quotes and css property which contains double quotes', function() {
|
||||||
const source = '{{font-family:"trebuchet ms" text "with quotes"}}';
|
const source = '{{font-family:"trebuchet ms" text "with quotes"}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('Renders a mustache span with text with quotes and css property which contains double and simple quotes', function() {
|
it('Renders a mustache span with text with quotes and css property which contains double and simple quotes', function() {
|
||||||
const source = `{{--stringVariable:"'string'" text "with quotes"}}`;
|
const source = `{{--stringVariable:"'string'" text "with quotes"}}`;
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text “with quotes”</span>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text “with quotes”</span>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('Renders a mustache span with text, id, class and a couple of css properties', function() {
|
it('Renders a mustache span with text, id, class and a couple of css properties', function() {
|
||||||
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}';
|
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span with added attributes', function() {
|
it('Renders a span with added attributes', function() {
|
||||||
const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!';
|
const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Text and <span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e">text</span> and more text!</p>\n');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Text and <span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e">text</span> and more text!</p>\n');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -132,7 +132,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{
|
const source = dedent`{{
|
||||||
text
|
text
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"><p>text</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"><p>text</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,14 +140,14 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{
|
const source = dedent`{{
|
||||||
|
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a single paragraph with opening and closing brackets', function() {
|
it('Renders a single paragraph with opening and closing brackets', function() {
|
||||||
const source = dedent`{{
|
const source = dedent`{{
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{cat
|
const source = dedent`{{cat
|
||||||
|
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{cat
|
const source = dedent`{{cat
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{cat,dog
|
const source = dedent`{{cat,dog
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat dog"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat dog"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{color:red
|
const source = dedent`{{color:red
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red;"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red;"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{--stringVariable:"'string'"
|
const source = dedent`{{--stringVariable:"'string'"
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{--stringVariable:"'string'"
|
const source = dedent`{{--stringVariable:"'string'"
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{cat,color:red
|
const source = dedent`{{cat,color:red
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" style="color:red;"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" style="color:red;"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{color:red,cat,#dog
|
const source = dedent`{{color:red,cat,#dog
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" id="dog" style="color:red;"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" id="dog" style="color:red;"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{#cat,#dog
|
const source = dedent`{{#cat,#dog
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" id="cat"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" id="cat"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,13 +227,13 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
|||||||
const source = dedent`{{color:red,cat,#dog,a="b and c",d="e"
|
const source = dedent`{{color:red,cat,#dog,a="b and c",d="e"
|
||||||
Sample text.
|
Sample text.
|
||||||
}}`;
|
}}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class=\"block cat\" id=\"dog\" style=\"color:red;\" a=\"b and c\" d=\"e\"><p>Sample text.</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class=\"block cat\" id=\"dog\" style=\"color:red;\" a=\"b and c\" d=\"e\"><p>Sample text.</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a div with added attributes', function() {
|
it('Renders a div with added attributes', function() {
|
||||||
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n';
|
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e"><p>Text and text and more text!</p>\n</div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e"><p>Text and text and more text!</p>\n</div>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -245,116 +245,116 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
describe('and that element is an inline-block', ()=>{
|
describe('and that element is an inline-block', ()=>{
|
||||||
it('Renders a span "text" with no injection', function() {
|
it('Renders a span "text" with no injection', function() {
|
||||||
const source = '{{ text}}{}';
|
const source = '{{ text}}{}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with injected Class name', function() {
|
it('Renders a span "text" with injected Class name', function() {
|
||||||
const source = '{{ text}}{ClassName}';
|
const source = '{{ text}}{ClassName}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with injected attribute', function() {
|
it('Renders a span "text" with injected attribute', function() {
|
||||||
const source = '{{ text}}{a="b and c"}';
|
const source = '{{ text}}{a="b and c"}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" a="b and c">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" a="b and c">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with injected style', function() {
|
it('Renders a span "text" with injected style', function() {
|
||||||
const source = '{{ text}}{color:red}';
|
const source = '{{ text}}{color:red}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with injected style using a string variable', function() {
|
it('Renders a span "text" with injected style using a string variable', function() {
|
||||||
const source = `{{ text}}{--stringVariable:"'string'"}`;
|
const source = `{{ text}}{--stringVariable:"'string'"}`;
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text</span>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text</span>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with two injected styles', function() {
|
it('Renders a span "text" with two injected styles', function() {
|
||||||
const source = '{{ text}}{color:red,background:blue}';
|
const source = '{{ text}}{color:red,background:blue}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; background:blue;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; background:blue;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with its own ID, overwritten with an injected ID', function() {
|
it('Renders a span "text" with its own ID, overwritten with an injected ID', function() {
|
||||||
const source = '{{#oldId text}}{#newId}';
|
const source = '{{#oldId text}}{#newId}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="newId">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="newId">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with its own attributes, overwritten with an injected attribute, plus a new one', function() {
|
it('Renders a span "text" with its own attributes, overwritten with an injected attribute, plus a new one', function() {
|
||||||
const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new"}';
|
const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new"}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with its own attributes, overwritten with an injected attribute, ignoring "class", "style", and "id"', function() {
|
it('Renders a span "text" with its own attributes, overwritten with an injected attribute, ignoring "class", "style", and "id"', function() {
|
||||||
const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new",class="new",style="new",id="new"}';
|
const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new",class="new",style="new",id="new"}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with its own styles, appended with injected styles', function() {
|
it('Renders a span "text" with its own styles, appended with injected styles', function() {
|
||||||
const source = '{{color:blue,height:10px text}}{width:10px,color:red}';
|
const source = '{{color:blue,height:10px text}}{width:10px,color:red}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; height:10px; width:10px;">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; height:10px; width:10px;">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a span "text" with its own classes, appended with injected classes', function() {
|
it('Renders a span "text" with its own classes, appended with injected classes', function() {
|
||||||
const source = '{{classA,classB text}}{classA,classC}';
|
const source = '{{classA,classB text}}{classA,classC}';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block classA classB classA classC">text</span>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block classA classB classA classC">text</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an emphasis element with injected Class name', function() {
|
it('Renders an emphasis element with injected Class name', function() {
|
||||||
const source = '*emphasis*{big}';
|
const source = '*emphasis*{big}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><em class="big">emphasis</em></p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><em class="big">emphasis</em></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders a code element with injected style', function() {
|
it('Renders a code element with injected style', function() {
|
||||||
const source = '`code`{background:gray}';
|
const source = '`code`{background:gray}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><code style="background:gray;">code</code></p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><code style="background:gray;">code</code></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an image element with injected style', function() {
|
it('Renders an image element with injected style', function() {
|
||||||
const source = '{position:absolute}';
|
const source = '{position:absolute}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="alt text"></p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="alt text"></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an element modified by only the first of two consecutive injections', function() {
|
it('Renders an element modified by only the first of two consecutive injections', function() {
|
||||||
const source = '{{ text}}{color:red}{background:blue}';
|
const source = '{{ text}}{color:red}{background:blue}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><span class="inline-block" style="color:red;">text</span>{background:blue}</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><span class="inline-block" style="color:red;">text</span>{background:blue}</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an parent and child element, each modified by an injector', function() {
|
it('Renders an parent and child element, each modified by an injector', function() {
|
||||||
const source = dedent`**bolded text**{color:red}
|
const source = dedent`**bolded text**{color:red}
|
||||||
{color:blue}`;
|
{color:blue}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p style="color:blue;"><strong style="color:red;">bolded text</strong></p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p style="color:blue;"><strong style="color:red;">bolded text</strong></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an image with added attributes', function() {
|
it('Renders an image with added attributes', function() {
|
||||||
const source = ` {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`;
|
const source = ` {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e"></p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e"></p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an image with "=" in the url, and added attributes', function() {
|
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 source = ` {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png?auth=12345&height=1024); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png?auth=12345&height=1024" alt="homebrew mug" a="b and c" d="e"></p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png?auth=12345&height=1024); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png?auth=12345&height=1024" alt="homebrew mug" a="b and c" d="e"></p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders an image and added attributes with "=" in the value, ', function() {
|
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 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();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e" otherUrl="url?auth=12345"></p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e" otherUrl="url?auth=12345"></p>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -362,19 +362,19 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
describe('and that element is a block', ()=>{
|
describe('and that element is a block', ()=>{
|
||||||
it('renders a div "text" with no injection', function() {
|
it('renders a div "text" with no injection', function() {
|
||||||
const source = '{{\ntext\n}}\n{}';
|
const source = '{{\ntext\n}}\n{}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a div "text" with injected Class name', function() {
|
it('renders a div "text" with injected Class name', function() {
|
||||||
const source = '{{\ntext\n}}\n{ClassName}';
|
const source = '{{\ntext\n}}\n{ClassName}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block ClassName"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block ClassName"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a div "text" with injected style', function() {
|
it('renders a div "text" with injected style', function() {
|
||||||
const source = '{{\ntext\n}}\n{color:red}';
|
const source = '{{\ntext\n}}\n{color:red}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red;"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red;"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -383,7 +383,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
text
|
text
|
||||||
}}
|
}}
|
||||||
{color:red,background:blue}`;
|
{color:red,background:blue}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red; background:blue;"><p>text</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red; background:blue;"><p>text</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -392,7 +392,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
text
|
text
|
||||||
}}
|
}}
|
||||||
{--stringVariable:"'string'"}`;
|
{--stringVariable:"'string'"}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>text</p></div>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>text</p></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
text
|
text
|
||||||
}}
|
}}
|
||||||
{#newId}`;
|
{#newId}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" id="newId"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" id="newId"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
text
|
text
|
||||||
}}
|
}}
|
||||||
{attrA="new",attrC="new"}`;
|
{attrA="new",attrC="new"}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -419,7 +419,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
text
|
text
|
||||||
}}
|
}}
|
||||||
{attrA="new",attrC="new",class="new",style="new",id="new"}`;
|
{attrA="new",attrC="new",class="new",style="new",id="new"}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -428,7 +428,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
text
|
text
|
||||||
}}
|
}}
|
||||||
{width:10px,color:red}`;
|
{width:10px,color:red}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; height:10px; width:10px;"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; height:10px; width:10px;"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -437,14 +437,14 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
text
|
text
|
||||||
}}
|
}}
|
||||||
{classA,classC}`;
|
{classA,classC}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block classA classB classA classC"><p>text</p></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block classA classB classA classC"><p>text</p></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an h2 header "text" with injected class name', function() {
|
it('renders an h2 header "text" with injected class name', function() {
|
||||||
const source = dedent`## text
|
const source = dedent`## text
|
||||||
{ClassName}`;
|
{ClassName}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
| 300 | 2 |
|
| 300 | 2 |
|
||||||
|
|
||||||
{ClassName}`;
|
{ClassName}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<table class="ClassName"><thead><tr><th align=left>Experience Points</th><th align=center>Level</th></tr></thead><tbody><tr><td align=left>0</td><td align=center>1</td></tr><tr><td align=left>300</td><td align=center>2</td></tr></tbody></table>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<table class="ClassName"><thead><tr><th align=left>Experience Points</th><th align=center>Level</th></tr></thead><tbody><tr><td align=left>0</td><td align=center>1</td></tr><tr><td align=left>300</td><td align=center>2</td></tr></tbody></table>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -466,7 +466,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
// - Dark Chant of the Dentists
|
// - Dark Chant of the Dentists
|
||||||
// - Divine Spell of Crossdressing
|
// - Divine Spell of Crossdressing
|
||||||
// {color:red}`;
|
// {color:red}`;
|
||||||
// const rendered = Markdown.render(source).trimReturns();
|
// const rendered = hbfm.render(source).trimReturns();
|
||||||
// expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`...`); // FIXME: expect this to be injected into <ul>? Currently injects into last <li>
|
// expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`...`); // FIXME: expect this to be injected into <ul>? Currently injects into last <li>
|
||||||
// });
|
// });
|
||||||
|
|
||||||
@@ -474,7 +474,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
const source = dedent`## text
|
const source = dedent`## text
|
||||||
{ClassName}
|
{ClassName}
|
||||||
{secondInjection}`;
|
{secondInjection}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2><p>{secondInjection}</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2><p>{secondInjection}</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
|
|||||||
{innerDiv}
|
{innerDiv}
|
||||||
}}
|
}}
|
||||||
{outerDiv}`;
|
{outerDiv}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block outerDiv"><p>outer text</p><div class="block innerDiv"><p>inner text</p></div></div>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block outerDiv"><p>outer text</p><div class="block innerDiv"><p>inner text</p></div></div>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
import Markdown from '../../shared/markdown.js';
|
import {hbfm} from 'hbmarkedwrapper';
|
||||||
|
|
||||||
describe('Non-Breaking Spaces Interactions', ()=>{
|
describe('Non-Breaking Spaces Interactions', ()=>{
|
||||||
test('I am actually a single-line definition list!', function() {
|
test('I am actually a single-line definition list!', function() {
|
||||||
const source = 'Term ::> Definition 1\n';
|
const source = 'Term ::> Definition 1\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt><dd>> Definition 1</dd>\n</dl>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt><dd>> Definition 1</dd>\n</dl>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('I am actually a definition list!', function() {
|
test('I am actually a definition list!', function() {
|
||||||
const source = 'Term\n::> Definition 1\n';
|
const source = 'Term\n::> Definition 1\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>> Definition 1</dd></dl>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>> Definition 1</dd></dl>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('I am actually a two-term definition list!', function() {
|
test('I am actually a two-term definition list!', function() {
|
||||||
const source = 'Term\n::> Definition 1\n::>> Definition 2';
|
const source = 'Term\n::> Definition 1\n::>> Definition 2';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = hbfm.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>> Definition 1</dd>\n<dd>>> Definition 2</dd></dl>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>> Definition 1</dd>\n<dd>>> Definition 2</dd></dl>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
|
|
||||||
|
|
||||||
import Markdown from '../../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
|
|
||||||
describe('Justification', ()=>{
|
describe('Justification', ()=>{
|
||||||
test('Left Justify', function() {
|
test('Left Justify', function() {
|
||||||
const source = ':- Hello';
|
const source = ':- Hello';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Left\">Hello</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Left\">Hello</p>`);
|
||||||
});
|
});
|
||||||
test('Right Justify', function() {
|
test('Right Justify', function() {
|
||||||
const source = '-: Hello';
|
const source = '-: Hello';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Right\">Hello</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Right\">Hello</p>`);
|
||||||
});
|
});
|
||||||
test('Center Justify', function() {
|
test('Center Justify', function() {
|
||||||
const source = ':-: Hello';
|
const source = ':-: Hello';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Center\">Hello</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Center\">Hello</p>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Ignored inside a code block', function() {
|
test('Ignored inside a code block', function() {
|
||||||
const source = '```\n\n:- Hello\n\n```\n';
|
const source = '```\n\n:- Hello\n\n```\n';
|
||||||
const rendered = Markdown.render(source);
|
const rendered = hbfm.render(source);
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:- Hello\n</code></pre>\n`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:- Hello\n</code></pre>\n`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
|
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import Markdown from '../../shared/markdown.js';
|
import { hbfm } from 'hbmarkedwrapper';
|
||||||
|
|
||||||
// Marked.js adds line returns after closing tags on some default tokens.
|
// Marked.js adds line returns after closing tags on some default tokens.
|
||||||
// This removes those line returns for comparison sake.
|
// This removes those line returns for comparison sake.
|
||||||
@@ -12,7 +12,7 @@ String.prototype.trimReturns = function(){
|
|||||||
const renderAllPages = function(pages){
|
const renderAllPages = function(pages){
|
||||||
const outputs = [];
|
const outputs = [];
|
||||||
pages.forEach((page, index)=>{
|
pages.forEach((page, index)=>{
|
||||||
const output = Markdown.render(page, index);
|
const output = hbfm.render(page, index);
|
||||||
outputs.push(output);
|
outputs.push(output);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ describe('Block-level variables', ()=>{
|
|||||||
|
|
||||||
$[var]
|
$[var]
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ describe('Block-level variables', ()=>{
|
|||||||
lines
|
lines
|
||||||
|
|
||||||
$[var]`;
|
$[var]`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string across multiple lines</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string across multiple lines</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ describe('Block-level variables', ()=>{
|
|||||||
| C | D |
|
| C | D |
|
||||||
|
|
||||||
$[var]`;
|
$[var]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<h5 id="title">Title</h5>
|
<h5 id="title">Title</h5>
|
||||||
<table><thead><tr><th align=left>H1</th>
|
<table><thead><tr><th align=left>H1</th>
|
||||||
@@ -71,7 +71,7 @@ describe('Block-level variables', ()=>{
|
|||||||
$[var]
|
$[var]
|
||||||
|
|
||||||
[var]: string`;
|
[var]: string`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ describe('Block-level variables', ()=>{
|
|||||||
[var]: string
|
[var]: string
|
||||||
|
|
||||||
[var]: new string`;
|
[var]: new string`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>new string</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>new string</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ describe('Block-level variables', ()=>{
|
|||||||
|
|
||||||
[lastName]: $[lastName]son
|
[lastName]: $[lastName]son
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Welcome, Mr. Bob Jacobson!</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Welcome, Mr. Bob Jacobson!</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ describe('Block-level variables', ()=>{
|
|||||||
|
|
||||||
$[var]
|
$[var]
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>one</p><p>two</p>'.trimReturns());
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>one</p><p>two</p>'.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ describe('Block-level variables', ()=>{
|
|||||||
|
|
||||||
$[var]
|
$[var]
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>two</p><p>one</p><p>two</p>'.trimReturns());
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>two</p><p>one</p><p>two</p>'.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ describe('Block-level variables', ()=>{
|
|||||||
|
|
||||||
$[last]: Jones
|
$[last]: Jones
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is $[first] Jones</p>`.trimReturns());
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is $[first] Jones</p>`.trimReturns());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -154,7 +154,7 @@ describe('Inline-level variables', ()=>{
|
|||||||
|
|
||||||
$[var]
|
$[var]
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p><p>string</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p><p>string</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ describe('Inline-level variables', ()=>{
|
|||||||
$[var](My name is $[name] Jones)
|
$[var](My name is $[name] Jones)
|
||||||
|
|
||||||
[name]: Bob`;
|
[name]: Bob`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>My name is Bob Jones</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>My name is Bob Jones</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ describe('Inline-level variables', ()=>{
|
|||||||
$[name](Bob)
|
$[name](Bob)
|
||||||
|
|
||||||
[name]: Bill`;
|
[name]: Bill`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is Bill Jones</p> <p>Bob</p>`.trimReturns());
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is Bill Jones</p> <p>Bob</p>`.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ describe('Inline-level variables', ()=>{
|
|||||||
$[var2](A variable ) with unbalanced parens)
|
$[var2](A variable ) with unbalanced parens)
|
||||||
|
|
||||||
$[var2]`;
|
$[var2]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p>A variable (with nested parens) inside</p>
|
<p>A variable (with nested parens) inside</p>
|
||||||
<p>A variable (with nested parens) inside</p>
|
<p>A variable (with nested parens) inside</p>
|
||||||
@@ -202,35 +202,35 @@ describe('Math', ()=>{
|
|||||||
const source = dedent`
|
const source = dedent`
|
||||||
$[1 + 3 * 5 - (1 / 4)]
|
$[1 + 3 * 5 - (1 / 4)]
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>15.75</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>15.75</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handles round function', function() {
|
it('Handles round function', function() {
|
||||||
const source = dedent`
|
const source = dedent`
|
||||||
$[round(1/4)]`;
|
$[round(1/4)]`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handles floor function', function() {
|
it('Handles floor function', function() {
|
||||||
const source = dedent`
|
const source = dedent`
|
||||||
$[floor(0.6)]`;
|
$[floor(0.6)]`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handles ceil function', function() {
|
it('Handles ceil function', function() {
|
||||||
const source = dedent`
|
const source = dedent`
|
||||||
$[ceil(0.2)]`;
|
$[ceil(0.2)]`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handles nested functions', function() {
|
it('Handles nested functions', function() {
|
||||||
const source = dedent`
|
const source = dedent`
|
||||||
$[ceil(floor(round(0.6)))]`;
|
$[ceil(floor(round(0.6)))]`;
|
||||||
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
|
const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ describe('Math', ()=>{
|
|||||||
|
|
||||||
Answer is $[answer]($[1 + 3 * num1 - (1 / num2)]).
|
Answer is $[answer]($[1 + 3 * num1 - (1 / num2)]).
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Answer is 15.75.</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Answer is 15.75.</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ describe('Math', ()=>{
|
|||||||
|
|
||||||
Increment num1 to get $[num1]($[num1 + 1]) and again to $[num1]($[num1 + 1]).
|
Increment num1 to get $[num1]($[num1 + 1]) and again to $[num1]($[num1 + 1]).
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Increment num1 to get 6 and again to 7.</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Increment num1 to get 6 and again to 7.</p>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -268,7 +268,7 @@ describe('Code blocks', ()=>{
|
|||||||
$[var](new string)
|
$[var](new string)
|
||||||
\`\`\`
|
\`\`\`
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<pre><code>
|
<pre><code>
|
||||||
[var]: string
|
[var]: string
|
||||||
@@ -289,7 +289,7 @@ describe('Code blocks', ()=>{
|
|||||||
|
|
||||||
$[var](new string)
|
$[var](new string)
|
||||||
`;
|
`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p>test</p>
|
<p>test</p>
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ describe('Code blocks', ()=>{
|
|||||||
|
|
||||||
it('Ignores all variables in inline code blocks', function() {
|
it('Ignores all variables in inline code blocks', function() {
|
||||||
const source = '[var](Hello) `[link](url)`. This `[var] does not work`';
|
const source = '[var](Hello) `[link](url)`. This `[var] does not work`';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p><a href="Hello">var</a> <code>[link](url)</code>. This <code>[var] does not work</code></p>`.trimReturns());
|
<p><a href="Hello">var</a> <code>[link](url)</code>. This <code>[var] does not work</code></p>`.trimReturns());
|
||||||
});
|
});
|
||||||
@@ -313,35 +313,35 @@ describe('Code blocks', ()=>{
|
|||||||
describe('Normal Links and Images', ()=>{
|
describe('Normal Links and Images', ()=>{
|
||||||
it('Renders normal images', function() {
|
it('Renders normal images', function() {
|
||||||
const source = ``;
|
const source = ``;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p><img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);"></p>`.trimReturns());
|
<p><img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);"></p>`.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders normal images with a title', function() {
|
it('Renders normal images with a title', function() {
|
||||||
const source = 'An image !';
|
const source = 'An image !';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p>An image <img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);" title="and title">!</p>`.trimReturns());
|
<p>An image <img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);" title="and title">!</p>`.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Applies curly injectors to images', function() {
|
it('Applies curly injectors to images', function() {
|
||||||
const source = `{width:100px}`;
|
const source = `{width:100px}`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p><img style="--HB_src:url(url); width:100px;" loading="lazy" src="url" alt="alt text"></p>`.trimReturns());
|
<p><img style="--HB_src:url(url); width:100px;" loading="lazy" src="url" alt="alt text"></p>`.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders normal links', function() {
|
it('Renders normal links', function() {
|
||||||
const source = 'A Link to my [website](url)!';
|
const source = 'A Link to my [website](url)!';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p>A Link to my <a href="url">website</a>!</p>`.trimReturns());
|
<p>A Link to my <a href="url">website</a>!</p>`.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders normal links with a title', function() {
|
it('Renders normal links with a title', function() {
|
||||||
const source = 'A Link to my [website](url "and title")!';
|
const source = 'A Link to my [website](url "and title")!';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
|
||||||
<p>A Link to my <a href="url" title="and title">website</a>!</p>`.trimReturns());
|
<p>A Link to my <a href="url" title="and title">website</a>!</p>`.trimReturns());
|
||||||
});
|
});
|
||||||
@@ -399,17 +399,17 @@ describe('Cross-page variables', ()=>{
|
|||||||
describe('Math function parameter handling', ()=>{
|
describe('Math function parameter handling', ()=>{
|
||||||
it('allows variables in single-parameter functions', function() {
|
it('allows variables in single-parameter functions', function() {
|
||||||
const source = '[var]:4.1\n\n$[floor(var)]';
|
const source = '[var]:4.1\n\n$[floor(var)]';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`);
|
||||||
});
|
});
|
||||||
it('allows one variable and a number in two-parameter functions', function() {
|
it('allows one variable and a number in two-parameter functions', function() {
|
||||||
const source = '[var]:4\n\n$[min(1,var)]';
|
const source = '[var]:4\n\n$[min(1,var)]';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>1</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>1</p>`);
|
||||||
});
|
});
|
||||||
it('allows two variables in two-parameter functions', function() {
|
it('allows two variables in two-parameter functions', function() {
|
||||||
const source = '[var1]:4\n\n[var2]:8\n\n$[min(var1,var2)]';
|
const source = '[var1]:4\n\n[var2]:8\n\n$[min(var1,var2)]';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`);
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -417,13 +417,13 @@ describe('Math function parameter handling', ()=>{
|
|||||||
describe('Variable names that are subsets of other names', ()=>{
|
describe('Variable names that are subsets of other names', ()=>{
|
||||||
it('do not conflict with function names', function() {
|
it('do not conflict with function names', function() {
|
||||||
const source = `[a]: -1\n\n$[abs(a)]`;
|
const source = `[a]: -1\n\n$[abs(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>1</p>');
|
expect(rendered).toBe('<p>1</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('do not conflict with other variable names', function() {
|
it('do not conflict with other variable names', function() {
|
||||||
const source = `[ab]: 2\n\n[aba]: 8\n\n[ba]: 4\n\n$[ab + aba + ba]`;
|
const source = `[ab]: 2\n\n[aba]: 8\n\n[ba]: 4\n\n$[ab + aba + ba]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>14</p>');
|
expect(rendered).toBe('<p>14</p>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -431,31 +431,31 @@ describe('Variable names that are subsets of other names', ()=>{
|
|||||||
describe('Regression Tests', ()=>{
|
describe('Regression Tests', ()=>{
|
||||||
it('Don\'t Eat all the parentheticals!', function() {
|
it('Don\'t Eat all the parentheticals!', function() {
|
||||||
const source='\n| title 1 | title 2 | title 3 | title 4|\n|-----------|---------|---------|--------|\n|[foo](bar) | Ipsum | ) | ) |\n';
|
const source='\n| title 1 | title 2 | title 3 | title 4|\n|-----------|---------|---------|--------|\n|[foo](bar) | Ipsum | ) | ) |\n';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>');
|
expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handle Extra spaces in image alt-text 1', function(){
|
it('Handle Extra spaces in image alt-text 1', function(){
|
||||||
const source='';
|
const source='';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
|
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handle Extra spaces in image alt-text 2', function(){
|
it('Handle Extra spaces in image alt-text 2', function(){
|
||||||
const source='';
|
const source='';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
|
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handle Extra spaces in image alt-text 3', function(){
|
it('Handle Extra spaces in image alt-text 3', function(){
|
||||||
const source='';
|
const source='';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
|
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Handle Extra spaces in image alt-text 4', function(){
|
it('Handle Extra spaces in image alt-text 4', function(){
|
||||||
const source='{height=20%,width=20%}';
|
const source='{height=20%,width=20%}';
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p><img style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\" loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" height=\"20%\" width=\"20%\"></p>');
|
expect(rendered).toBe('<p><img style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\" loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" height=\"20%\" width=\"20%\"></p>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -463,73 +463,73 @@ describe('Regression Tests', ()=>{
|
|||||||
describe('Custom Math Function Tests', ()=>{
|
describe('Custom Math Function Tests', ()=>{
|
||||||
it('Sign Test', function() {
|
it('Sign Test', function() {
|
||||||
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[sign(a)]\n\nNegative: $[sign(b)]`;
|
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[sign(a)]\n\nNegative: $[sign(b)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Positive: +</p><p>Negative: -</p>');
|
expect(rendered).toBe('<p>Positive: +</p><p>Negative: -</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Signed Test', function() {
|
it('Signed Test', function() {
|
||||||
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[signed(a)]\n\nNegative: $[signed(b)]`;
|
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[signed(a)]\n\nNegative: $[signed(b)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Positive: +13</p><p>Negative: -11</p>');
|
expect(rendered).toBe('<p>Positive: +13</p><p>Negative: -11</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Roman Numerals Test', function() {
|
it('Roman Numerals Test', function() {
|
||||||
const source = `[a]: 18\n\nRoman Numeral: $[toRomans(a)]`;
|
const source = `[a]: 18\n\nRoman Numeral: $[toRomans(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
|
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Roman Numerals Test - Uppercase', function() {
|
it('Roman Numerals Test - Uppercase', function() {
|
||||||
const source = `[a]: 18\n\nRoman Numeral: $[toRomansUpper(a)]`;
|
const source = `[a]: 18\n\nRoman Numeral: $[toRomansUpper(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
|
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Roman Numerals Test - Lowercase', function() {
|
it('Roman Numerals Test - Lowercase', function() {
|
||||||
const source = `[a]: 18\n\nRoman Numeral: $[toRomansLower(a)]`;
|
const source = `[a]: 18\n\nRoman Numeral: $[toRomansLower(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Roman Numeral: xviii</p>');
|
expect(rendered).toBe('<p>Roman Numeral: xviii</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Number to Characters Test', function() {
|
it('Number to Characters Test', function() {
|
||||||
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toChar(a)] $[toChar(b)]`;
|
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toChar(a)] $[toChar(b)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Characters: R AM</p>');
|
expect(rendered).toBe('<p>Characters: R AM</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Number to Characters Test - Uppercase', function() {
|
it('Number to Characters Test - Uppercase', function() {
|
||||||
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharUpper(a)] $[toCharUpper(b)]`;
|
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharUpper(a)] $[toCharUpper(b)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Characters: R AM</p>');
|
expect(rendered).toBe('<p>Characters: R AM</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Number to Characters Test - Lowercase', function() {
|
it('Number to Characters Test - Lowercase', function() {
|
||||||
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharLower(a)] $[toCharLower(b)]`;
|
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharLower(a)] $[toCharLower(b)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Characters: r am</p>');
|
expect(rendered).toBe('<p>Characters: r am</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Number to Words Test', function() {
|
it('Number to Words Test', function() {
|
||||||
const source = `[a]: 80085\n\nWords: $[toWords(a)]`;
|
const source = `[a]: 80085\n\nWords: $[toWords(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
|
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Number to Words Test - Uppercase', function() {
|
it('Number to Words Test - Uppercase', function() {
|
||||||
const source = `[a]: 80085\n\nWords: $[toWordsUpper(a)]`;
|
const source = `[a]: 80085\n\nWords: $[toWordsUpper(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Words: EIGHTY THOUSAND AND EIGHTY-FIVE</p>');
|
expect(rendered).toBe('<p>Words: EIGHTY THOUSAND AND EIGHTY-FIVE</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Number to Words Test - Lowercase', function() {
|
it('Number to Words Test - Lowercase', function() {
|
||||||
const source = `[a]: 80085\n\nWords: $[toWordsLower(a)]`;
|
const source = `[a]: 80085\n\nWords: $[toWordsLower(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
|
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Number to Words Test - Capitalized', function() {
|
it('Number to Words Test - Capitalized', function() {
|
||||||
const source = `[a]: 80085\n\nWords: $[toWordsCaps(a)]`;
|
const source = `[a]: 80085\n\nWords: $[toWordsCaps(a)]`;
|
||||||
const rendered = Markdown.render(source).trimReturns();
|
const rendered = hbfm.render(source).trimReturns();
|
||||||
expect(rendered).toBe('<p>Words: Eighty Thousand And Eighty-Five</p>');
|
expect(rendered).toBe('<p>Words: Eighty Thousand And Eighty-Five</p>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Markdown from '@shared/markdown.js';
|
import hbfm from 'hbmarkedwrapper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createFooterFunc : function(headerSize=1){
|
createFooterFunc : function(headerSize=1){
|
||||||
|
|||||||
Reference in New Issue
Block a user