Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions app/containers/appContainer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { connect } from 'react-redux'
import electronBridge from '../../utilities/electronBridge'
import { updateUpdateAvailableBarStatus } from '../../actions/index'
import AboutPage from '../aboutPage'
import Dashboard from '../dashboard'
import FindInPage, { isFindInPageAvailable } from '../findInPage'
import LoginPage from '../loginPage'
import NavigationPanel from '../navigationPanel'
import NavigationPanelDetails from '../navigationPanelDetails'
import React, { Component } from 'react'
import React, { Component, lazy, Suspense } from 'react'
import SearchPage from '../searchPage'
import SnippetPanel from '../snippetPanel'
import { Pane, SplitPane } from 'react-split-pane'
Expand All @@ -20,6 +19,7 @@ import './index.scss'
import './scrollbar.scss'

const conf = electronBridge.config
const Dashboard = lazy(() => import(/* webpackChunkName: "dashboard" */ '../dashboard'))
const themeManager = new ThemeManager()
themeManager.setTheme(conf.get('theme'))

Expand Down Expand Up @@ -129,9 +129,12 @@ export class AppContainer extends Component {
}

renderDashboard () {
const { updateDashboardStatus } = this.props
const { dashboardModalStatus, updateDashboardStatus } = this.props
if (dashboardModalStatus !== 'ON') return null
return (
<Dashboard updateDashboardStatus = { updateDashboardStatus }/>
<Suspense fallback={ null }>
<Dashboard updateDashboardStatus = { updateDashboardStatus }/>
</Suspense>
)
}

Expand Down
44 changes: 41 additions & 3 deletions app/containers/codeArea/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import HighlightJS from 'highlight.js'
import hljsDefineSolidity from 'highlightjs-solidity'
import hljsDefineGraphQL from 'highlightjs-graphql'
import AsciiDoc from '../../utilities/asciidoc'
import Markdown from '../../utilities/markdown'
import React, { Component } from 'react'
import electronBridge from '../../utilities/electronBridge'
Expand Down Expand Up @@ -63,27 +62,48 @@ resolveHighlightLanguage(hljsDefineGraphQL)(HighlightJS) // register graphql to
export default class CodeArea extends Component {
constructor (props) {
super(props)
this.state = { asciiDoc: null }
this.isMountedForRendering = false
this.codeAreaNode = null
this.handleMarkdownImageError = this.handleMarkdownImageError.bind(this)
}

componentDidMount () {
this.isMountedForRendering = true
this.loadAsciiDocIfNeeded()
if (this.codeAreaNode) {
this.codeAreaNode.addEventListener('error', this.handleMarkdownImageError, true)
}
this.initializeMarkdownImageFallbacks()
}

componentDidUpdate () {
this.loadAsciiDocIfNeeded()
this.initializeMarkdownImageFallbacks()
}

componentWillUnmount () {
this.isMountedForRendering = false
if (this.codeAreaNode) {
this.codeAreaNode.removeEventListener('error', this.handleMarkdownImageError, true)
}
}

loadAsciiDocIfNeeded () {
const { filename, language } = this.props
if (adaptedLanguage(filename, language) !== 'AsciiDoc' || this.asciiDocLoad) return this.asciiDocLoad

// The converter is large; ordinary snippets should not initialize it.
// Render the current props after loading, so switching snippets cannot
// display an earlier snippet's asynchronously generated HTML.
this.asciiDocLoad = import(/* webpackChunkName: "asciidoc" */ '../../utilities/asciidoc')
.then(module => {
if (this.isMountedForRendering) this.setState({ asciiDoc: module.default })
})
.catch(err => logger.error(`Failed to load AsciiDoc renderer with err ${err}`))
return this.asciiDocLoad
}

initializeMarkdownImageFallbacks () {
if (!this.codeAreaNode) return

Expand Down Expand Up @@ -230,6 +250,7 @@ export default class CodeArea extends Component {
}
return `<div class='jupyterNotebook-section'>${result.html}</div>`
} catch (err) {
this.renderFailed = true
logger.error(`Failed to render Jupyter Notebook content with err ${err}`)
return this.createHighlightedCodeBlock(content, language, kTabLength)
}
Expand All @@ -240,9 +261,11 @@ export default class CodeArea extends Component {
}

createAsciiDocCodeBlock (content, language, kTabLength) {
if (!this.state.asciiDoc) return this.createHighlightedCodeBlock(content, language, kTabLength)
try {
return `<div class='markdown-section asciidoc-section'>${AsciiDoc.render(content)}</div>`
return `<div class='markdown-section asciidoc-section'>${this.state.asciiDoc.render(content)}</div>`
} catch (err) {
this.renderFailed = true
logger.error(`Failed to render AsciiDoc content with err ${err}`)
return this.createHighlightedCodeBlock(content, language, kTabLength)
}
Expand Down Expand Up @@ -277,8 +300,16 @@ export default class CodeArea extends Component {
return `<pre><code><table class='code-table' style='tab-size: ${kTabLength};'>${contentTable}</table></code></pre>`
}

renderCodeArea (filename, content, lang, kTabLength, siblingFiles) {
getRenderedContent (filename, content, lang, kTabLength, siblingFiles) {
// Keep only this component's most recent result. Callback changes and
// unrelated UI updates must not reparse Markdown, highlight code, or make
// synchronous notebook IPC calls for unchanged content.
const inputs = [filename, content, lang, kTabLength, siblingFiles, this.state.asciiDoc]
if (this.renderedContent && inputs.every((value, index) => value === this.renderedContent.inputs[index])) {
return this.renderedContent.html
}
const language = adaptedLanguage(filename, lang)
this.renderFailed = false
let htmlContent = ''
switch (language) {
case 'Jupyter Notebook':
Expand All @@ -293,6 +324,13 @@ export default class CodeArea extends Component {
default:
htmlContent = this.createHighlightedCodeBlock(content, language, kTabLength)
}
// Preserve retry behavior for transient renderer/IPC failures.
this.renderedContent = this.renderFailed ? null : { inputs, html: htmlContent }
return htmlContent
}

renderCodeArea (filename, content, lang, kTabLength, siblingFiles) {
const htmlContent = this.getRenderedContent(filename, content, lang, kTabLength, siblingFiles)
return (
<div className='code-area'
ref={ node => { this.codeAreaNode = node } }
Expand Down
15 changes: 12 additions & 3 deletions app/containers/gistEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,14 @@ import './index.scss'

import electronBridge from '../../utilities/electronBridge'
import { getEditorTheme } from '../../utilities/themeManager'
import { needsBoundedViewport } from './viewport'

const { createMarkdownImageReference, getFilesFromTransfer } = require('../../utilities/imageAttachments')

const conf = electronBridge.config

const highlightTheme = getEditorTheme(conf.get('theme'))
const configuredEditorOptions = conf.get('editor') || {}
const defaultOptions = Object.assign({}, {
theme: highlightTheme,
lineNumbers: true,
Expand All @@ -164,7 +166,7 @@ const defaultOptions = Object.assign({}, {
viewportMargin: Infinity,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
}, conf.get('editor'))
}, configuredEditorOptions)

class CodeMirrorEditor extends Component {
constructor (props) {
Expand Down Expand Up @@ -194,6 +196,8 @@ class CodeMirrorEditor extends Component {
this.codeMirror.setOption(key, options[key])
}
})

if (this.props.className !== prevProps.className) this.codeMirror.refresh()
}

componentWillUnmount () {
Expand Down Expand Up @@ -226,7 +230,7 @@ class CodeMirrorEditor extends Component {
}

render () {
return <div ref={ this.setEditorNode } />
return <div className={ this.props.className } ref={ this.setEditorNode } />
}
}

Expand Down Expand Up @@ -307,13 +311,18 @@ class GistEditor extends Component {

render () {
const { value, placeholder, readOnly } = this.props
const boundedViewport = needsBoundedViewport(value || '')
const options = Object.assign({}, defaultOptions, {
placeholder,
readOnly: Boolean(readOnly)
readOnly: Boolean(readOnly),
viewportMargin: boundedViewport && configuredEditorOptions.viewportMargin === undefined
? 10
: defaultOptions.viewportMargin
})

return (
<CodeMirrorEditor
className={ boundedViewport ? 'gist-editor-viewport' : undefined }
ref={ this.setEditorRef.bind(this) }
value={ value }
options={ options }
Expand Down
11 changes: 11 additions & 0 deletions app/containers/gistEditor/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
height: auto;
}

.gist-editor-viewport {
.CodeMirror {
height: min(60vh, 600px);
min-height: 400px;
}

.CodeMirror-scroll {
min-height: 0;
}
}

.CodeMirror-gutters {
border-right: 1px solid var(--border-color);
background-color: var(--bg-secondary);
Expand Down
11 changes: 11 additions & 0 deletions app/containers/gistEditor/viewport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Small snippets can grow with their content. Large documents need a bounded
// viewport so CodeMirror only creates DOM for the visible lines.
export function needsBoundedViewport (value = '') {
if (value.length > 20000) return true

let lines = 1
for (let index = 0; index < value.length; index++) {
if (value.charCodeAt(index) === 10 && ++lines > 200) return true
}
return false
}
5 changes: 4 additions & 1 deletion app/containers/gistEditorForm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ class GistEditorForm extends Component {
})
}

componentDidUpdate () {
componentDidUpdate (prevProps) {
// Local typing does not change initialData. Only compare original content
// when new input arrives, rather than serializing it on every keystroke.
if (prevProps.initialData === this.props.initialData) return
const nextInitialValuesKey = getInitialValuesKey(this.props.initialData)

if (nextInitialValuesKey !== this.state.initialValuesKey) {
Expand Down
Loading
Loading