Skip to content

Commit

Permalink
Replace brace with react-ace (#1366)
Browse files Browse the repository at this point in the history
Replaces the brace dependency, which hasn't received any updates for 5+ years,
with react-ace which works with the most recent releases of the ACE Editor.
Also takes advantage of the new useStrictCSP option in the ACE Editor so 
Fauxton can work without the "style-src: 'unsafe-inline'" Content-Security Policy.
  • Loading branch information
Antonio-Maranhao committed Sep 20, 2022
1 parent 15ffec6 commit 25ad6d5
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 82 deletions.
1 change: 1 addition & 0 deletions app/addons/components/assets/less/polling.less
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
border: 1px solid @sliderBackgroundHighlight;
border-radius: 8px;
background-color: @sliderBackgroundHighlight;
opacity: 1;
}
}

Expand Down
16 changes: 16 additions & 0 deletions app/addons/components/components/ace-webpack-resolvers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ace from 'ace-builds';

// The entries below must match the ACE editor modes, themes and extensions used by Fauxton.

// Enables dynamically loading modes, which is required for syntax checking.
// The method below was based on node_modules/ace-builds/webpack-resolver.js but with the following changes:
// - trimmed down to import only the necessary modes, themes and extensions
// - sets "outputPath=dashboard.assets" which is required to match how Fauxton loads CSS / JS assets
ace.config.setModuleUrl('ace/theme/idle_fingers', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/theme-idle_fingers.js'));
ace.config.setModuleUrl('ace/theme/dawn', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/theme-dawn.js'));
ace.config.setModuleUrl('ace/mode/json', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/mode-json.js'));
ace.config.setModuleUrl('ace/mode/json_worker', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/worker-json.js'));
ace.config.setModuleUrl('ace/mode/javascript', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/mode-javascript.js'));
ace.config.setModuleUrl('ace/mode/javascript_worker', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/worker-javascript.js'));
ace.config.setModuleUrl('ace/ext/static_highlight', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/ext-static_highlight.js'));
ace.config.setModuleUrl('ace/ext/searchbox', require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/ext-searchbox.js'));
64 changes: 35 additions & 29 deletions app/addons/components/components/codeeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
// the License.
import React from "react";
import FauxtonAPI from "../../../core/api";
import ace from "brace";
import "brace/ext/searchbox";
import AceEditor from "react-ace";
import ace from 'ace-builds';
ace.config.set("useStrictCSP", true);
import './ace-webpack-resolvers';
import {StringEditModal} from './stringeditmodal';

require('brace/mode/javascript');
require('brace/mode/json');
require('brace/theme/idle_fingers');
import 'ace-builds/css/ace.css';
import 'ace-builds/css/theme/idle_fingers.css';
import 'ace-builds/css/theme/dawn.css';



export class CodeEditor extends React.Component {
static defaultProps = {
Expand Down Expand Up @@ -88,18 +92,12 @@ export class CodeEditor extends React.Component {
}
);

// suppresses an Ace editor error
this.editor.$blockScrolling = Infinity;

if (shouldUpdateCode) {
this.setValue(props.defaultCode);
}

this.editor.setShowPrintMargin(props.showPrintMargin);
this.editor.autoScrollEditorIntoView = props.autoScrollEditorIntoView;

this.editor.setOption('highlightActiveLine', this.props.highlightActiveLine);

if (this.props.setHeightToLineCount) {
this.setHeightToLineCount();
}
Expand All @@ -109,16 +107,6 @@ export class CodeEditor extends React.Component {
}

this.addCommands();
this.editor.getSession().setMode('ace/mode/' + props.mode);
this.editor.setTheme('ace/theme/' + props.theme);
this.editor.setFontSize(props.fontSize);
this.editor.getSession().setTabSize(2);
this.editor.getSession().setUseSoftTabs(true);

if (this.props.autoFocus) {
this.editor.focus();
}
this.editor.setReadOnly(props.disabled);
};

addCommands = () => {
Expand All @@ -128,9 +116,6 @@ export class CodeEditor extends React.Component {
};

setupEvents = () => {
this.editor.on('blur', _.bind(this.onBlur, this));
this.editor.on('change', _.bind(this.onContentChange, this));

if (this.props.stringEditModalEnabled) {
this.editor.on('changeSelection', _.bind(this.showHideEditStringGutterIcon, this));
this.editor.getSession().on('changeBackMarker', _.bind(this.showHideEditStringGutterIcon, this));
Expand Down Expand Up @@ -179,10 +164,6 @@ export class CodeEditor extends React.Component {
componentDidMount() {
this.setupAce(this.props, true);
this.setupEvents();

if (this.props.autoFocus) {
this.editor.focus();
}
}

componentWillUnmount() {
Expand Down Expand Up @@ -357,10 +338,35 @@ export class CodeEditor extends React.Component {
this.editor.getSelection().moveCursorUp();
};

onAceLoad = (ace) => {
this.ace = ace;
};

render() {
return (
<div>
<div ref={node => this.ace = node} className="js-editor" id={this.props.id}></div>
<AceEditor
name={this.props.id}
className="js-editor"
mode={this.props.mode}
theme={this.props.theme}
onLoad={_.bind(this.onAceLoad, this)}
onBlur={_.bind(this.onBlur, this)}
onChange={_.bind(this.onContentChange, this)}
editorProps={{
$blockScrolling: Infinity,
useSoftTabs: true
}}
readOnly={this.props.disabled}
showPrintMargin={this.props.showPrintMargin}
highlightActiveLine={this.props.highlightActiveLine}
width="100%"
height="100%"
tabSize={2}
fontSize={this.props.fontSize}
focus={this.props.autoFocus}
setOptions={{
}}/>
<button ref={node => this.stringEditIcon = node}
className="btn string-edit"
title="Edit string"
Expand Down
8 changes: 1 addition & 7 deletions app/addons/components/components/stringeditmodal.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,10 @@
// the License.

import PropTypes from 'prop-types';

import React from "react";
import ReactDOM from "react-dom";
import {Modal} from "react-bootstrap";
import ace from "brace";
import ace from "ace-builds";
import Helpers from "../../documents/helpers";
require('brace/mode/javascript');
require('brace/mode/json');
require('brace/theme/idle_fingers');

// this appears when the cursor is over a string. It shows an icon in the gutter that opens the modal.
export class StringEditModal extends React.Component {
Expand All @@ -42,7 +37,6 @@ export class StringEditModal extends React.Component {

initAceEditor = (dom_node) => {
this.editor = ace.edit(dom_node);
this.editor.$blockScrolling = Infinity; // suppresses an Ace editor error
this.editor.setShowPrintMargin(false);
this.editor.setOption('highlightActiveLine', true);
this.editor.setTheme('ace/theme/idle_fingers');
Expand Down
2 changes: 0 additions & 2 deletions app/addons/components/components/zenmodeoverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
// License for the specific language governing permissions and limitations under
// the License.
import React from "react";
import ReactDOM from "react-dom";
import app from "../../../app";
import {CodeEditor} from './codeeditor';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';

require('brace/theme/dawn');

const themes = {
dark: 'idle_fingers',
Expand Down
27 changes: 12 additions & 15 deletions app/addons/documents/rev-browser/components/splitscreenarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,10 @@
// the License.

import React from 'react';
import ReactDOM from "react-dom";
import ace from "brace";

require('brace/ext/static_highlight');
const highlight = ace.acequire('ace/ext/static_highlight');

require('brace/mode/json');
const JavaScriptMode = ace.acequire('ace/mode/json').Mode;

require('brace/theme/idle_fingers');
const theme = ace.acequire('ace/theme/idle_fingers');
const highlight = require('ace-builds/src-min-noconflict/ext-static_highlight');
const JSONMode = require('ace-builds/src-min-noconflict/mode-json').Mode;
const theme = require('ace-builds/src-noconflict/theme-idle_fingers');

export default class SplitScreenArea extends React.Component {

Expand All @@ -40,14 +33,18 @@ export default class SplitScreenArea extends React.Component {
hightlightAfterRender () {
const format = (input) => { return JSON.stringify(input, null, ' '); };

const jsmode = new JavaScriptMode();
const jsonMode = new JSONMode();
const left = this.revLeftOurs;
const right = this.revRightTheirs;

const leftRes = highlight.render(format(this.props.ours), jsmode, theme, 0, true);
left.innerHTML = leftRes.html;
const rightRes = highlight.render(format(this.props.theirs), jsmode, theme, 0, true);
right.innerHTML = rightRes.html;
if (left) {
const leftRes = highlight.render(format(this.props.ours), jsonMode, theme, 0, true);
left.innerHTML = leftRes.html;
}
if (right) {
const rightRes = highlight.render(format(this.props.theirs), jsonMode, theme, 0, true);
right.innerHTML = rightRes.html;
}
}

render () {
Expand Down
13 changes: 0 additions & 13 deletions assets/index.underscore
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,6 @@
<meta http-equiv="Content-Language" content="en" />
<link rel="shortcut icon" type="image/png" href="dashboard.assets/img/couchdb-logo.png"/>
<title><%= htmlWebpackPlugin.options.title %></title>

<!-- Application styles. -->
<style>
.noscript-warning {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 1px 30px 10px 30px;
color: #fff;
background: @brandHighlight;
margin: 100px;
box-shadow: 2px 2px 5px #989898;
}
</style>

</head>

<body id="home">
Expand Down
9 changes: 9 additions & 0 deletions assets/less/fauxton.less
Original file line number Diff line number Diff line change
Expand Up @@ -675,3 +675,12 @@ body .control-toggle-include-docs span {
margin-right: 0;
}
}

.noscript-warning {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 1px 30px 10px 30px;
color: #fff;
background: @brandHighlight;
margin: 100px;
box-shadow: 2px 2px 5px #989898;
}
2 changes: 1 addition & 1 deletion devserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const devSetup = function (cb) {
};

const defaultHeaderValue = "default-src 'self'; child-src 'self' blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; " +
"script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';";
"script-src 'self'; style-src 'self'; object-src 'none';";
function getCspHeaders () {
if (!settings.contentSecurityPolicy) {
return;
Expand Down
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ module.exports = function (options) {
accept = req.headers.accept.split(',');
}
if (setContentSecurityPolicy) {
var headerValue = "default-src 'self'; child-src 'self' data: blob:; img-src 'self' data:; font-src 'self'; " +
"script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';";
var headerValue = "default-src 'self'; child-src 'self' data: blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; " +
"script-src 'self'; style-src 'self'; object-src 'none';";
res.setHeader('Content-Security-Policy', headerValue);
}

Expand Down
1 change: 1 addition & 0 deletions jest-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

"moduleNameMapper": {
"underscore": "lodash",
"ace-builds": "<rootDir>/node_modules/ace-builds",

"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|swf|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
Expand Down
57 changes: 48 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 25ad6d5

Please sign in to comment.