diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 25bf4f88e..5ac254132 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -43,9 +43,9 @@ const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery }; const themeCompartment = 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 { customHighlightPlugin, customHighlightStyle } from './extensions/customHighlight.js'; +import { customHighlightStyle , customHighlightPlugin } from './extensions/customHighlight.js'; import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js'; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; @@ -186,7 +186,7 @@ const CodeEditor = forwardRef( //keyboard shortcut keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]), generalKeymap, - ...(tab !== 'brewStyles' ? [markdownKeymap] : []), + ...(tab === 'brewStyles' ? [cssKeymap] : [markdownKeymap]), //multiple cursors and selections drawSelection(), @@ -356,6 +356,8 @@ const CodeEditor = forwardRef( }, 400); }, + formatCode : ()=>formatCSS(viewRef.current), + undo : ()=>undo(viewRef.current), redo : ()=>redo(viewRef.current), diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index ca77f29a8..1d1e1ae96 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -15,6 +15,18 @@ } } +@keyframes slideacross { + 0% { + bottom: -200px; + left: -200px; + } + + 100% { + bottom:100%; + left:100%; + } +} + :where(.codeEditor) { width : 100%; height : calc(100% - 25px); @@ -23,6 +35,27 @@ .cm-editor { height : 100%; 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, diff --git a/client/components/codeEditor/extensions/customHighlight.js b/client/components/codeEditor/extensions/customHighlight.js index 33c68da7a..cb44d3fd9 100644 --- a/client/components/codeEditor/extensions/customHighlight.js +++ b/client/components/codeEditor/extensions/customHighlight.js @@ -251,13 +251,13 @@ function tokenizeCustomMarkdown(text) { ); if(match) endCh = match.index + match[0].length; const closingMatch = lineText.match(/ *(}})/d); - + if(closingMatch) { tokens.push({ line: lineNumber, from: closingMatch.indices[1][0], to: closingMatch.indices[1][1], type: customTags.block }); } else { tokens.push({ line: lineNumber, type: customTags.block }); } - + } }); @@ -350,10 +350,9 @@ class ImageWidget extends WidgetType { toDOM() { const img = document.createElement('img'); - img.loading = "lazy"; + img.loading = 'lazy'; img.className = 'cm-preview'; img.src = this.url; - img.onerror = ()=>{ img.src = 'client/icons/broken-image.jpg'; @@ -403,8 +402,8 @@ export function customHighlightPlugin(renderer, tab) { const url = getUrl(node, view.state.doc); const widgetPosition = node.node.lastChild.from; - //this is not exactly standard, but should hold, - //and is the shortest way i could find of positioning + //this is not exactly standard, but should hold, + //and is the shortest way i could find of positioning //the image inside the cm-image node if(!url) return; diff --git a/client/components/codeEditor/extensions/customKeyMaps.js b/client/components/codeEditor/extensions/customKeyMaps.js index 581268222..974b0b1e2 100644 --- a/client/components/codeEditor/extensions/customKeyMaps.js +++ b/client/components/codeEditor/extensions/customKeyMaps.js @@ -3,7 +3,60 @@ import { keymap } from '@codemirror/view'; import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands'; import { EditorSelection } 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)=>{ // If any selection spans multiple lines, delegates to CodeMirror's indentMore // 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 ])); +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([ { key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 05ca73bea..65de45177 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -6,7 +6,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react'; import _ from 'lodash'; import MarkdownLegacy from '@shared/markdownLegacy.js'; -import Markdown from '@shared/markdown.js'; +import { hbfm } from 'hbmarkedwrapper'; import ErrorBar from './errorBar/errorBar.jsx'; import ToolBar from './toolBar/toolBar.jsx'; @@ -203,7 +203,7 @@ const BrewRenderer = (props)=>{ return ; } else { 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; if(injectedTags) { styles = { ...styles, ...injectedTags.styles }; @@ -231,7 +231,7 @@ const BrewRenderer = (props)=>{ // 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) - const html = Markdown.render(pageText, index); + const html = hbfm.render(pageText, index); return ; } diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx index 9ef30917d..c012a167b 100644 --- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx +++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx @@ -1,7 +1,7 @@ import './notificationPopup.less'; import React, { useEffect, useState } from 'react'; import request from '../../utils/request-middleware.js'; -import Markdown from '@shared/markdown.js'; +import { hbfm } from 'hbmarkedwrapper'; import Dialog from '@components/dialog.jsx'; @@ -44,7 +44,7 @@ const NotificationPopup = ()=>{ return notifications.map((notification)=>(
  • {notification.title}
    -

    +

  • )); }; diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index f500bebeb..70db74b21 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,303 @@ 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 handleFormatCode = () => { + codeEditor.current?.formatCode(); + }; - editor.scrollToPage(targetPage); - setTimeout(()=>{ - jumpSource = null; - }, 200); - }, + 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(); + } + }; - //Called when there are changes to the editor's dimensions - update : function(){}, + const updateCurrentCursorPage = (pageNumber)=>{ + onCursorPageChange(pageNumber); + }; - updateEditorTheme : function(newTheme){ - window.localStorage.setItem(EDITOR_THEME_KEY, newTheme); - this.setState({ - editorTheme : newTheme - }); - }, + const updateCurrentViewPage = (pageNumber)=>{ + onViewPageChange(pageNumber); + }; - //Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory - rerenderParent : function (){ - this.forceUpdate(); - }, + const handleInject = (injectText)=>{ + codeEditor.current?.injectText(injectText); + }; - 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 handleViewChange = (newView)=>{ + setMoveArrows(newView === 'text'); + setView(newView); + }; + useEffect(()=>{ + codeEditor.current?.focus(); + }, [view]); - redo : function(){ - return this.codeEditor.current?.redo(); - }, + const brewJump = (targetPage = currentEditorCursorPageNum, smooth = true)=>{ + if(!window || !isText() || isJumping || jumpSource === 'source') return; - historySize : function(){ - return this.codeEditor.current?.historySize(); - }, + const brewRenderer = + window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; - undo : function(){ - return this.codeEditor.current?.undo(); - }, + const currentPos = brewRenderer.scrollTop; - foldCode : function() { - return this.codeEditor.current?.foldAll(); - }, + 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 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/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 14ea97305..9377aa41c 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -157,11 +157,11 @@ const MetadataEditor = createReactClass({ renderPublish : function(){ if(this.props.metadata.published){ return