diff --git a/changelog.md b/changelog.md index 1b22c7d8d..b44eae35e 100644 --- a/changelog.md +++ b/changelog.md @@ -3,11 +3,12 @@ h5 { font-size: .35cm !important; } -.taskList li { - list-style-type : none; +.page ul ul { + margin-left: 0px; } .taskList li input { + list-style-type : none; margin-left : -0.52cm; transform: translateY(.05cm); filter: brightness(1.1) drop-shadow(1px 2px 1px #222); @@ -33,6 +34,34 @@ pre { ## changelog For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). +### Tuesday 07/12/2021 - v3.0.5 +{{taskList +* [x] Fixed paragraph spacing for **note** and **descriptive** boxes in V3. + + Fixes issues: [#1836](https://github.com/naturalcrit/homebrewery/issues/1836) + +* [x] Added a whole bunch of hotkeys: + + * Page Break `CTRL + ENTER` + * Column Break `CTRL + SHIFT + ENTER` + * Bulleted Lists `CTRL + L` + * Numbered Lists `CTRL + SHIFT + L` + * Headers `CTRL + SHIFT + (1-6)` + * Underline `CTRL + U` + * Link `CTRL + K` + * Non-breaking space (\ ) `CTRL + .` + * Add Horizontal Space `CTRL + SHIFT + .` + * Remove Horizontal Space `CTRL + SHIFT + ,` + * Curly Span `CTRL + M` + * Curly Div `CTRL + SHIFT + M` + +* [x] Fixed page numbers in the editor panel getting scrambled when scrolling up and down. + +* [x] Faster swapping between tabs on long brews. + +* [x] Better error messages for common issue with Google Drive credentials expiring. +}} + ### Wednesday 17/11/2021 - v3.0.4 {{taskList * [x] Fixed incorrect sorting of Google brews by page count and views on the user page. diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 14038f740..6eefc183f 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -107,67 +107,69 @@ const Editor = createClass({ if(this.state.view === 'text') { const codeMirror = this.refs.codeEditor.codeMirror; - //reset custom text styles - const customHighlights = codeMirror.getAllMarks().filter((mark)=>!mark.__isFold); //Don't undo code folding - for (let i=0;i{ // Batch CodeMirror styling + //reset custom text styles + const customHighlights = codeMirror.getAllMarks().filter((mark)=>!mark.__isFold); //Don't undo code folding + for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear(); - const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{ + let editorPageCount = 2; // start page count from page 2 - //reset custom line styles - codeMirror.removeLineClass(lineNumber, 'background'); - codeMirror.removeLineClass(lineNumber, 'text'); + _.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{ - // Legacy Codemirror styling - if(this.props.renderer == 'legacy') { - if(line.includes('\\page')){ + //reset custom line styles + codeMirror.removeLineClass(lineNumber, 'background', 'pageLine'); + codeMirror.removeLineClass(lineNumber, 'text'); + + // Styling for \page breaks + if((this.props.renderer == 'legacy' && line.includes('\\page')) || + (this.props.renderer == 'V3' && line.match(/^\\page$/))) { + + // add back the original class 'background' but also add the new class '.pageline' codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); - r.push(lineNumber); - } - } + const pageCountElement = Object.assign(document.createElement('span'), { + className : 'editor-page-count', + textContent : editorPageCount + }); + codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement); - // New Codemirror styling for V3 renderer - if(this.props.renderer == 'V3') { - if(line.match(/^\\page$/)){ - codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); - r.push(lineNumber); - } + editorPageCount += 1; + }; - if(line.match(/^\\column$/)){ - codeMirror.addLineClass(lineNumber, 'text', 'columnSplit'); - r.push(lineNumber); - } - - // Highlight inline spans {{content}} - if(line.includes('{{') && line.includes('}}')){ - const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g; - let match; - let blockCount = 0; - while ((match = regex.exec(line)) != null) { - if(match[0].startsWith('{')) { - blockCount += 1; - } else { - blockCount -= 1; - } - if(blockCount < 0) { - blockCount = 0; - continue; - } - codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' }); + // New Codemirror styling for V3 renderer + if(this.props.renderer == 'V3') { + if(line.match(/^\\column$/)){ + codeMirror.addLineClass(lineNumber, 'text', 'columnSplit'); } - } else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){ - // Highlight block divs {{\n Content \n}} - let endCh = line.length+1; - const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/); - if(match) - endCh = match.index+match[0].length; - codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); + // Highlight inline spans {{content}} + if(line.includes('{{') && line.includes('}}')){ + const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g; + let match; + let blockCount = 0; + while ((match = regex.exec(line)) != null) { + if(match[0].startsWith('{')) { + blockCount += 1; + } else { + blockCount -= 1; + } + if(blockCount < 0) { + blockCount = 0; + continue; + } + codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' }); + } + } else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){ + // Highlight block divs {{\n Content \n}} + let endCh = line.length+1; + + const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/); + if(match) + endCh = match.index+match[0].length; + codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); + } } - } - - return r; - }, []); - return lineNumbers; + }); + }); } }, diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 8633e4eb3..810ee1710 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -5,17 +5,13 @@ .codeEditor{ height : 100%; - counter-reset : page; - counter-increment : page; .pageLine{ background : #33333328; border-top : #339 solid 1px; - &:after{ - content : counter(page); - counter-increment : page; - float : right; - color : gray; - } + } + .editor-page-count{ + color : grey; + float : right; } .columnSplit{ font-style : italic; diff --git a/client/homebrew/editor/snippetbar/snippets/coverpage.gen.js b/client/homebrew/editor/snippetbar/snippets/coverpage.gen.js index 7fbdba0fc..0fb8ba7a4 100644 --- a/client/homebrew/editor/snippetbar/snippets/coverpage.gen.js +++ b/client/homebrew/editor/snippetbar/snippets/coverpage.gen.js @@ -100,25 +100,25 @@ const subtitles = [ module.exports = ()=>{ return ` -
+{{margin-top:225px}} # ${_.sample(titles)} -
-
+{{margin-top:25px}} + +{{wide ##### ${_.sample(subtitles)} -
+}} \\page`; }; \ No newline at end of file diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 9b2a48aae..22db79ff7 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -350,14 +350,14 @@ const EditPage = createClass({ ; } - if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ + if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){ return Oops!
Looks like your Google credentials have - expired! Visit the log in page to sign out - and sign back in with Google - to save this to Google Drive! + expired! Visit our log in page to sign out + and sign back in with Google, + then try saving again!
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 18a090b3d..b4529f873 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -227,14 +227,14 @@ const NewPage = createClass({ ; } - if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ + if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){ return Oops!
Looks like your Google credentials have - expired! Visit the log in page to sign out - and sign back in with Google - to save this to Google Drive! + expired! Visit our log in page to sign out + and sign back in with Google, + then try saving again!
diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index fdfc81809..42ead0693 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -41,7 +41,7 @@ const SharePage = createClass({ if(!(e.ctrlKey || e.metaKey)) return; const P_KEY = 80; if(e.keyCode == P_KEY){ - window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus(); + window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); e.stopPropagation(); e.preventDefault(); } diff --git a/client/homebrew/pages/userPage/brewItem/brewItem.jsx b/client/homebrew/pages/userPage/brewItem/brewItem.jsx index e2ea1d019..497cef7a3 100644 --- a/client/homebrew/pages/userPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/userPage/brewItem/brewItem.jsx @@ -48,7 +48,7 @@ const BrewItem = createClass({ renderDeleteBrewLink : function(){ if(!this.props.brew.editId) return; - return + return ; }, @@ -61,7 +61,7 @@ const BrewItem = createClass({ editLink = this.props.brew.googleId + editLink; } - return + return ; }, @@ -74,7 +74,7 @@ const BrewItem = createClass({ shareLink = this.props.brew.googleId + shareLink; } - return + return ; }, @@ -87,7 +87,7 @@ const BrewItem = createClass({ shareLink = this.props.brew.googleId + shareLink; } - return + return ; }, diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index bd84ce6b5..3ef0d0340 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -86,7 +86,7 @@ const UserPage = createClass({ @@ -102,7 +102,7 @@ const UserPage = createClass({ renderFilterOption : function(){ return -
')); - return `${openTag} ${Markdown(html)}
`; + return `${openTag} ${Marked.parse(html)}
`; } return html; }; @@ -235,200 +236,10 @@ const definitionLists = { } }; -const spanTable = { - name : 'spanTable', - level : 'block', // Is this a block-level or inline-level tokenizer? - start(src) { return src.match(/^\n *([^\n ].*\|.*)\n/)?.index; }, // Hint to Marked.js to stop and check for a match - tokenizer(src, tokens) { - //const regex = this.tokenizer.rules.block.table; - const regex = new RegExp('^ *([^\\n ].*\\|.*\\n(?: *[^\\s].*\\n)*?)' // Header - + ' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)\\|?' // Align - + '(?:\\n *((?:(?!\\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})' // Cells - + '(?:\\n+|$)| {0,3}#{1,6} | {0,3}>| {4}[^\\n]| {0,3}(?:`{3,}' - + '(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n| {0,3}(?:[*+-]|1[.)]) |' - + '<\\/?(?:address|article|aside|base|basefont|blockquote|body|' - + 'caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\\n|\\/?>)|<(?:script|pre|style|textarea|!--)).*(?:\\n|$))*)\\n*|$)'); // Cells - const cap = regex.exec(src); - - if(cap) { - const item = { - type : 'spanTable', - header : cap[1].replace(/\n$/, '').split('\n'), - align : cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - rows : cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - // Get first header row to determine how many columns - item.header[0] = splitCells(item.header[0]); - - const colCount = item.header[0].reduce((length, header)=>{ - return length + header.colspan; - }, 0); - - if(colCount === item.align.length) { - item.raw = cap[0]; - - let i, j, k, row; - - // Get alignment row (:---:) - let l = item.align.length; - - for (i = 0; i < l; i++) { - if(/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if(/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if(/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - // Get any remaining header rows - l = item.header.length; - for (i = 1; i < l; i++) { - item.header[i] = splitCells(item.header[i], colCount, item.header[i-1]); - } - - // Get main table cells - l = item.rows.length; - for (i = 0; i < l; i++) { - item.rows[i] = splitCells(item.rows[i], colCount, item.rows[i-1]); - } - - // header child tokens - l = item.header.length; - for (j = 0; j < l; j++) { - row = item.header[j]; - for (k = 0; k < row.length; k++) { - row[k].tokens = []; - this.lexer.inlineTokens(row[k].text, row[k].tokens); - } - } - - // cell child tokens - l = item.rows.length; - for (j = 0; j < l; j++) { - row = item.rows[j]; - for (k = 0; k < row.length; k++) { - row[k].tokens = []; - this.lexer.inlineTokens(row[k].text, row[k].tokens); - } - } - return item; - } - } - }, - renderer(token) { - let i, j, row, cell, col, text; - let output = ``; - output += ``; - for (i = 0; i < token.header.length; i++) { - row = token.header[i]; - let col = 0; - output += ``; - for (j = 0; j < row.length; j++) { - cell = row[j]; - text = this.parser.parseInline(cell.tokens); - output += getTableCell(text, cell, 'th', token.align[col]); - col += cell.colspan; - } - output += ``; - } - output += ``; - if(token.rows.length) { - output += ``; - for (i = 0; i < token.rows.length; i++) { - row = token.rows[i]; - col = 0; - output += ``; - for (j = 0; j < row.length; j++) { - cell = row[j]; - text = this.parser.parseInline(cell.tokens); - output += getTableCell(text, cell, 'td', token.align[col]); - col += cell.colspan; - } - output += ``; - } - output += ``; - } - output += `
`; - return output; - } -}; - -const getTableCell = (text, cell, type, align)=>{ - if(!cell.rowspan) { - return ''; - } - const tag = `<${type}` - + `${cell.colspan > 1 ? ` colspan=${cell.colspan}` : ''}` - + `${cell.rowspan > 1 ? ` rowspan=${cell.rowspan}` : ''}` - + `${align ? ` align=${align}` : ''}>`; - return `${tag + text}\n`; -}; - -const splitCells = (tableRow, count, prevRow = [])=>{ - const cells = [...tableRow.matchAll(/(?:[^|\\]|\\.?)+(?:\|+|$)/g)].map((x)=>x[0]); - - // Remove first/last cell in a row if whitespace only and no leading/trailing pipe - if(!cells[0]?.trim()) { cells.shift(); } - if(!cells[cells.length - 1]?.trim()) { cells.pop(); } - - let numCols = 0; - let i, j, trimmedCell, prevCell, prevCols; - - for (i = 0; i < cells.length; i++) { - trimmedCell = cells[i].split(/\|+$/)[0]; - cells[i] = { - rowspan : 1, - colspan : Math.max(cells[i].length - trimmedCell.length, 1), - text : trimmedCell.trim().replace(/\\\|/g, '|') - // display escaped pipes as normal character - }; - - // Handle Rowspan - if(trimmedCell.slice(-1) == '^' && prevRow.length) { - // Find matching cell in previous row - prevCols = 0; - for (j = 0; j < prevRow.length; j++) { - prevCell = prevRow[j]; - if((prevCols == numCols) && (prevCell.colspan == cells[i].colspan)) { - // merge into matching cell in previous row (the "target") - cells[i].rowSpanTarget = prevCell.rowSpanTarget ?? prevCell; - cells[i].rowSpanTarget.text += ` ${cells[i].text.slice(0, -1)}`; - cells[i].rowSpanTarget.rowspan += 1; - cells[i].rowspan = 0; - break; - } - prevCols += prevCell.colspan; - if(prevCols > numCols) - break; - } - } - - numCols += cells[i].colspan; - } - - // Force main cell rows to match header column count - if(numCols > count) { - cells.splice(count); - } else { - while (numCols < count) { - cells.push({ - colspan : 1, - text : '' - }); - numCols += 1; - } - } - return cells; -}; - -Markdown.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, spanTable] }); -Markdown.use(mustacheInjectBlock); -Markdown.use({ smartypants: true }); +Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] }); +Marked.use(MarkedExtendedTables()); +Marked.use(mustacheInjectBlock); +Marked.use({ smartypants: true }); //Fix local links in the Preview iFrame to link inside the frame renderer.link = function (href, title, text) { @@ -532,11 +343,11 @@ const processStyleTags = (string)=>{ }; module.exports = { - marked : Markdown, + marked : Marked, render : (rawBrewText)=>{ rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n
\n`) - .replace(/^(:+)$/gm, (match)=>`${`
`.repeat(match.length)}\n`); - return Markdown( + .replace(/^(:+)$/gm, (match)=>`${`
`.repeat(match.length)}\n`); + return Marked.parse( sanatizeScriptTags(rawBrewText), { renderer: renderer } ); diff --git a/themes/5ePhb.style.less b/themes/5ePhb.style.less index 8991e51e7..63af5e972 100644 --- a/themes/5ePhb.style.less +++ b/themes/5ePhb.style.less @@ -511,7 +511,8 @@ body { color : #58180d; background-color : #faf7ea; border-radius : 4px; - white-space : pre-wrap + white-space : pre-wrap; + overflow-wrap : break-word; } pre code{ @@ -652,7 +653,7 @@ body { break-inside : avoid; h1 { text-align : center; - margin-bottom : 0cm; + margin-bottom : 0.3cm; } a{ display : table; @@ -663,14 +664,12 @@ body { } } h4 { - margin-top : 0.14cm; + margin-top : 0.2cm; + line-height : 0.4cm; & + ul li { line-height: 1.2em; } } - & > ul { - margin-top: 0.52cm; - } ul{ padding-left : 0; list-style-type : none;