diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index f500bebeb..92ce0b78c 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -1,8 +1,6 @@ /*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ import './editor.less'; -import React from 'react'; -import createReactClass from 'create-react-class'; -import _ from 'lodash'; +import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react'; import dedent from 'dedent'; import CodeEditor from '@components/codeEditor/codeEditor.jsx'; @@ -18,12 +16,8 @@ import cm5Themes from 'codemirror-5-themes'; const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery }; const EditorThemes = Object.entries(themes) - .filter(([name, value])=>Array.isArray(value) && - !name.endsWith('Init') && - !name.endsWith('Style') - ) - .map(([name])=>name); - + .filter(([name, value])=>Array.isArray(value) && !name.endsWith('Init') && !name.endsWith('Style')) + .map(([name])=>name); //const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; //const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; @@ -45,306 +39,298 @@ const DEFAULT_SNIPPET_TEXT = dedent` let isJumping = false; let jumpSource = null; -const Editor = createReactClass({ - displayName : 'Editor', - getDefaultProps : function() { - return { - brew : { - text : '', - style : '' - }, +const Editor = forwardRef( + ( + { + brew = {}, - onBrewChange : ()=>{}, - reportError : ()=>{}, + onBrewChange = ()=>{}, + reportError = ()=>{}, - onCursorPageChange : ()=>{}, - onViewPageChange : ()=>{}, + onCursorPageChange = ()=>{}, + onViewPageChange = ()=>{}, - editorTheme : 'default', - renderer : 'legacy', + editorTheme = 'default', + renderer = 'legacy', - currentEditorCursorPageNum : 1, - currentEditorViewPageNum : 1, - currentBrewRendererPageNum : 1, - }; - }, - getInitialState : function() { - return { - editorTheme : this.props.editorTheme, - view : 'text', //'text', 'style', 'meta', 'snippet' - snippetBarHeight : 26, - }; - }, + moveBrew, + moveSource, + liveScroll, - editor : React.createRef(null), - codeEditor : React.createRef(null), + setMoveArrows, + updateBrew, + showEditButtons, + themeBundle, + userThemes, - 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';}, + 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); - componentDidMount : function() { + const editor = useRef(null); + const codeEditor = useRef(null); + const throttleBrewMove = useRef(null); - const brewRenderer = document.getElementById('BrewRenderer'); - brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', this.handleControlKeys); - document.addEventListener('keydown', this.handleControlKeys); + const isText = ()=>isView('text'); + const isStyle = ()=>isView('style'); + const isMeta = ()=>isView('meta'); + const isSnip = ()=>isView('snippet'); - const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY); - if(editorTheme && EditorThemes.includes(editorTheme)) { - this.setState({ editorTheme }); - } else { - this.setState({ editorTheme: 'default' }); - } - const snippetBar = document.querySelector('.editor > .snippetBar'); - if(!snippetBar) return; + const isView = (name)=>view === name; - this.resizeObserver = new ResizeObserver((entries)=>{ - const height = document.querySelector('.editor > .snippetBar').offsetHeight; - this.setState({ snippetBarHeight: height }); - }); + useEffect(()=>{ + const brewRenderer = document.getElementById('BrewRenderer'); + brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', handleControlKeys); + document.addEventListener('keydown', handleControlKeys); - this.resizeObserver.observe(snippetBar); - }, + const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY); + if(editorTheme && EditorThemes.includes(editorTheme)) setEditorTheme(editorTheme); else setEditorTheme('default'); + const snippetBar = document.querySelector('.editor > .snippetBar'); + if(!snippetBar) return; - componentDidUpdate : function(prevProps, prevState, snapshot) { + const resizeObserver = new ResizeObserver((entries)=>{ + const height = document.querySelector('.editor > .snippetBar').offsetHeight; + setSnippetBarHeight(height); + }); + resizeObserver.observe(snippetBar); - if(prevProps.moveBrew !== this.props.moveBrew) - this.brewJump(); - - if(prevProps.moveSource !== this.props.moveSource) - 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; - const LEFTARROW_KEY = 37; - const RIGHTARROW_KEY = 39; - if(e.keyCode == RIGHTARROW_KEY) this.brewJump(); - if(e.keyCode == LEFTARROW_KEY) this.sourceJump(); - if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) { - e.stopPropagation(); - e.preventDefault(); - } - }, - - updateCurrentCursorPage : function(pageNumber) { - this.props.onCursorPageChange(pageNumber); - }, - - updateCurrentViewPage : function(pageNumber) { - this.props.onViewPageChange(pageNumber); - }, - - handleInject : function(injectText){ - this.codeEditor.current?.injectText(injectText); - }, - - handleViewChange : function(newView){ - this.props.setMoveArrows(newView === 'text'); - - this.setState({ - view : newView - }, ()=>{ - this.codeEditor.current?.focus(); - }); - }, - - brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){ - if(!window || !this.isText() || isJumping || jumpSource === 'source') - return; - - // Get current brewRenderer scroll position and calculate target position - const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; - const currentPos = brewRenderer.scrollTop; - const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top; - - 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 - scrollingTimeout = setTimeout(()=>{ - isJumping = false; - jumpSource = null; - brewRenderer.removeEventListener('scroll', checkIfScrollComplete); - }, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done - }; - - isJumping = true; - jumpSource = 'brew'; - checkIfScrollComplete(); - brewRenderer.addEventListener('scroll', checkIfScrollComplete); - - if(smooth) { - const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling - const bounceDelay = 100; - const scrollDelay = 500; - - if(!this.throttleBrewMove) { - this.throttleBrewMove = _.throttle((currentPos, bouncePos, targetPos)=>{ - brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' }); - setTimeout(()=>{ - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); - }, bounceDelay); - }, scrollDelay, { leading: true, trailing: false }); + return ()=>{ + if(resizeObserver) resizeObserver.disconnect(); }; - this.throttleBrewMove(currentPos, bouncePos, targetPos); - } else { - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' }); - } - }, + }, []); - sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){ - if(!this.isText() || isJumping || jumpSource === 'brew') - return; + 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]); - const editor = this.codeEditor.current; - if(!editor) return; - jumpSource = 'source'; + const handleControlKeys = (e)=>{ + if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return; + const LEFTARROW_KEY = 37; + const RIGHTARROW_KEY = 39; + if(e.keyCode == RIGHTARROW_KEY) brewJump(); + if(e.keyCode == LEFTARROW_KEY) sourceJump(); + if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) { + e.stopPropagation(); + e.preventDefault(); + } + }; - editor.scrollToPage(targetPage); - setTimeout(()=>{ - jumpSource = null; - }, 200); - }, + const updateCurrentCursorPage = (pageNumber)=>{ + onCursorPageChange(pageNumber); + }; - //Called when there are changes to the editor's dimensions - update : function(){}, + const updateCurrentViewPage = (pageNumber)=>{ + onViewPageChange(pageNumber); + }; - updateEditorTheme : function(newTheme){ - window.localStorage.setItem(EDITOR_THEME_KEY, newTheme); - this.setState({ - editorTheme : newTheme - }); - }, + const handleInject = (injectText)=>{ + codeEditor.current?.injectText(injectText); + }; - //Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory - rerenderParent : function (){ - this.forceUpdate(); - }, + const handleViewChange = (newView)=>{ + setMoveArrows(newView === 'text'); + setView(newView); + }; + useEffect(()=>{ + codeEditor.current?.focus(); + }, [view]); - renderEditor : function(){ - if(this.isText()){ - return <> - this.updateCurrentCursorPage(page)} - onViewChange={(page)=>this.updateCurrentViewPage(page)} - editorTheme={this.state.editorTheme} - renderer={this.props.brew.renderer} - style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}/> - ; - } - if(this.isStyle()){ - return <> - - ; - } - if(this.isMeta()){ - return <> - - - ; - } - if(this.isSnip()){ - if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; } - return <> - - ; - } - }, + const brewJump = (targetPage = currentEditorCursorPageNum, smooth = true)=>{ + if(!window || !isText() || isJumping || jumpSource === 'source') return; - redo : function(){ - return this.codeEditor.current?.redo(); - }, + const brewRenderer = + window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; - historySize : function(){ - return this.codeEditor.current?.historySize(); - }, + const currentPos = brewRenderer.scrollTop; - undo : function(){ - return this.codeEditor.current?.undo(); - }, + const targetPos = window.frames['BrewRenderer'].contentDocument + .getElementById(`p${targetPage}`) + .getBoundingClientRect().top; - foldCode : function() { - return this.codeEditor.current?.foldAll(); - }, + 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 + + scrollingTimeout = setTimeout(()=>{ + isJumping = false; + jumpSource = null; + + brewRenderer.removeEventListener('scroll', checkIfScrollComplete); + }, 150);// If 150 ms pass without a brewRenderer scroll event, assume scrolling is done + }; + + isJumping = true; + jumpSource = 'brew'; + + checkIfScrollComplete(); + brewRenderer.addEventListener('scroll', checkIfScrollComplete); + + if(smooth) { + const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling + const now = Date.now(); + + if(now - throttleBrewMove.current >= 500) { + throttleBrewMove.current = now; + + brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' }); + + setTimeout(()=>{ + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); + }, 100); + } + } else { + brewRenderer.scrollTo({ top : currentPos + targetPos, behavior : 'instant', block : 'start', + }); + } + }; + + const sourceJump = (targetPage = currentBrewRendererPageNum, smooth = true)=>{ + if(!isText() || isJumping || jumpSource === 'brew') return; + + if(!codeEditor.current) return; + jumpSource = 'source'; + + codeEditor.current.scrollToPage(targetPage); + setTimeout(()=>{ + jumpSource = null; + }, 200); + }; + + const updateEditorTheme = (newTheme)=>{ + window.localStorage.setItem(EDITOR_THEME_KEY, newTheme); + setEditorTheme(newTheme); + }; + + const renderEditor = ()=>{ + if(isText()) { + return ( + <> + updateCurrentCursorPage(page)} + onViewChange={(page)=>updateCurrentViewPage(page)} + editorTheme={currentEditorTheme} + renderer={brew.renderer} + style={{ height: `calc(100% - ${snippetBarHeight}px)` }} + /> + + ); + } + if(isStyle()) { + return ( + <> + + + ); + } + if(isSnip()) { + if(!brew.snippets) { + brew.snippets = DEFAULT_SNIPPET_TEXT; + } + return ( + <> + + + ); + } + if(isMeta()) { + return ( + <> + + + + ); + } + }; + + 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, + })); - unfoldCode : function() { - return this.codeEditor.current?.unfoldAll(); - }, - render : function(){ return ( -
+
- {this.renderEditor()} + {renderEditor()}
); } -}); +); -export default Editor; \ No newline at end of file +export default Editor; diff --git a/package-lock.json b/package-lock.json index 0c808689c..a693595cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@babel/preset-react": "^8.0.1", "@babel/runtime": "^8.0.0", "@codemirror/autocomplete": "^6.20.3", - "@codemirror/commands": "^6.10.3", + "@codemirror/commands": "^6.11.0", "@codemirror/highlight": "^0.19.8", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-javascript": "^6.2.5", @@ -25,11 +25,11 @@ "@codemirror/language-data": "^6.5.2", "@codemirror/search": "^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", - "@googleapis/drive": "^20.2.0", + "@googleapis/drive": "^21.0.0", "@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", "@vitejs/plugin-react": "^6.0.5", "body-parser": "^2.3.0", @@ -65,7 +65,7 @@ "marked-variables": "^1.0.5", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^9.9.2", + "mongoose": "^9.9.3", "nanoid": "6.0.1", "nconf": "^0.13.0", "node": "^26.7.0", @@ -2633,7 +2633,9 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.10.4", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.0.tgz", + "integrity": "sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -3024,7 +3026,9 @@ "license": "MIT" }, "node_modules/@codemirror/view": { - "version": "6.43.8", + "version": "6.43.9", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.9.tgz", + "integrity": "sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.7.0", @@ -3420,7 +3424,9 @@ "license": "MIT" }, "node_modules/@googleapis/drive": { - "version": "20.2.0", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-21.0.0.tgz", + "integrity": "sha512-vebQpAqn+FZcmDWh1TEQTcYdkKftcSaHc7kO2ZxrINBb+ZMcFu1UybD3+maeqdLs60HrRDYwyzJ6TgZ7/Vn2Rg==", "license": "Apache-2.0", "dependencies": { "googleapis-common": "^8.0.0" @@ -4273,29 +4279,15 @@ } }, "node_modules/@oddbird/css-anchor-positioning": { - "version": "0.10.1", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@oddbird/css-anchor-positioning/-/css-anchor-positioning-0.10.2.tgz", + "integrity": "sha512-sk+1BpF5V+CfFm0CUbyTRxtwsUjcuP6Nz5tOzC6Bh500gBRdJJZJAeQ/3DYKPd9hGge5cqyVfUNl8zpWJVma/Q==", "license": "BSD-3-Clause", "dependencies": { - "@floating-ui/dom": "^1.7.6", - "@types/css-tree": "^2.3.11", + "@floating-ui/dom": "^1.8.0", + "@types/css-tree": "^3.2.0", "css-tree": "^3.2.1", - "nanoid": "^5.1.16" - } - }, - "node_modules/@oddbird/css-anchor-positioning/node_modules/nanoid": { - "version": "5.1.16", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" + "nanoid": "^6.0.1" } }, "node_modules/@oxc-project/types": { @@ -4666,7 +4658,9 @@ } }, "node_modules/@types/css-tree": { - "version": "2.3.11", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-3.2.0.tgz", + "integrity": "sha512-J5KXmk6BFIepOT7280FdFyNs4c5fh0Uee0otjKEvzchrRs38Ii9qminqc4ds0L19X8Zd/rT+brR9jkBthygjWw==", "license": "MIT" }, "node_modules/@types/gensync": { @@ -10867,7 +10861,9 @@ } }, "node_modules/mongoose": { - "version": "9.9.2", + "version": "9.9.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.9.4.tgz", + "integrity": "sha512-Gc7buf0ExrOZ3t8MD8tVzTek7Qr5d+tEwj4GRFmHCtTZvpaDb38EVsXgCkYacPL9ICAftgS+FGe+dQHLRLHhcw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", diff --git a/package.json b/package.json index bd11b5e8d..cbe8a127b 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "@babel/preset-react": "^8.0.1", "@babel/runtime": "^8.0.0", "@codemirror/autocomplete": "^6.20.3", - "@codemirror/commands": "^6.10.3", + "@codemirror/commands": "^6.11.0", "@codemirror/highlight": "^0.19.8", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-javascript": "^6.2.5", @@ -101,11 +101,11 @@ "@codemirror/language-data": "^6.5.2", "@codemirror/search": "^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", - "@googleapis/drive": "^20.2.0", + "@googleapis/drive": "^21.0.0", "@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", "@vitejs/plugin-react": "^6.0.5", "body-parser": "^2.3.0", @@ -141,7 +141,7 @@ "marked-variables": "^1.0.5", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^9.9.2", + "mongoose": "^9.9.3", "nanoid": "6.0.1", "nconf": "^0.13.0", "node": "^26.7.0", diff --git a/server/brewDefaults.js b/server/brewDefaults.js index 501914735..cb3114467 100644 --- a/server/brewDefaults.js +++ b/server/brewDefaults.js @@ -2,26 +2,32 @@ import _ from 'lodash'; // Default properties for newly-created brews const DEFAULT_BREW = { - title : '', - text : '', - style : undefined, - description : '', - editId : undefined, - shareId : undefined, - createdAt : undefined, - updatedAt : undefined, - renderer : 'V3', - theme : '5ePHB', - authors : [], - tags : [], - lang : 'en', - thumbnail : '', - views : 0, - published : false, - pageCount : 1, - gDrive : false, - trashed : false - + title : '', + text : '', + style : undefined, + description : '', + editId : undefined, + shareId : undefined, + createdAt : undefined, + updatedAt : undefined, + renderer : 'V3', + theme : '5ePHB', + authors : [], + tags : [], + lang : 'en', + thumbnail : '', + views : 0, + published : false, + pageCount : 1, + gDrive : 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 // e.g., missing "renderer" is assumed to be "legacy" diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 51bcf08c9..4cca76193 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -218,6 +218,14 @@ const api = { const metadata = _.pick(brew, ['title', 'description', 'tags', 'renderer', 'theme']); const snippetsArray = brewSnippetsToJSON('brew_snippets', brew.snippets, null, false).snippets; 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` + `${yaml.dump(metadata)}\n` + `\`\`\`\n\n` + diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js index a8500d8f4..0e09cc044 100644 --- a/server/homebrew.api.spec.js +++ b/server/homebrew.api.spec.js @@ -359,7 +359,27 @@ describe('Tests for api', ()=>{ style : undefined, trashed : false, 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(api.getId).toHaveBeenCalledWith(req); @@ -450,7 +470,27 @@ describe('Tests for api', ()=>{ tags : ['something', 'fun'], renderer : 'v3', 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 @@ -461,6 +501,23 @@ tags: - fun renderer: v3 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'], renderer : 'v3', 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 @@ -487,6 +564,23 @@ tags: - fun renderer: v3 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: '' \`\`\` @@ -593,16 +687,16 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ - _id : '1', - authors : ['test user'], - createdAt : undefined, - description : '', - editId : expect.any(String), - gDrive : false, - pageCount : 1, - published : false, - renderer : 'V3', - lang : 'en', + _id : '1', + authors : ['test user'], + createdAt : undefined, + description : '', + editId : expect.any(String), + gDrive : false, + pageCount : 1, + published : false, + renderer : 'V3', + lang : 'en', shareId : expect.any(String), style : undefined, tags : [], @@ -613,7 +707,27 @@ brew`); title : 'asdf', trashed : false, 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', + }, }); }); @@ -651,28 +765,48 @@ brew`); expect(google.newGoogleBrew).toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ - _id : '1', - authors : ['test user'], - createdAt : undefined, - description : '', - editId : expect.any(String), - gDrive : false, - pageCount : 1, - published : false, - renderer : 'V3', - lang : 'en', - shareId : expect.any(String), - googleId : expect.any(String), - style : undefined, - tags : [], - text : undefined, - textBin : undefined, - theme : '5ePHB', - thumbnail : '', - title : 'asdf', - trashed : false, - updatedAt : undefined, - views : 0 + _id : '1', + authors : ['test user'], + bleedSize: { + top: '.125in', + bottom: '.125in', + inner: '.125in', + outer: '.125in', + }, + columns: '2', + columnGutter: '.125in', + createdAt : undefined, + description : '', + editId : expect.any(String), + gDrive : false, + pageCount : 1, + published : false, + renderer : 'V3', + lang : 'en', + shareId : expect.any(String), + legalAuthors: '', + license: 'None', + safetySpace: { + top: '.25in', + bottom: '.25in', + outer: '.25in', + inner: '.5in', + }, + googleId : expect.any(String), + style : undefined, + tags : [], + text : undefined, + textBin : undefined, + theme : '5ePHB', + thumbnail : '', + title : 'asdf', + trashed : false, + trimSize: { + width: '8.5in', + height: '11in', + }, + updatedAt : undefined, + views : 0 }); }); }); @@ -1164,6 +1298,79 @@ brew`); // Text 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'); + }); }); }); diff --git a/shared/helpers.js b/shared/helpers.js index 3165d31eb..667d7b6b1 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -93,6 +93,15 @@ const splitTextStyleAndMetadata = (brew)=>{ const metadata = yaml.load(metadataSection); Object.assign(brew, _.pick(metadata, ['title', 'description', 'renderer', 'theme', 'lang'])); 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); } if(brew.text.startsWith('```css')) {