Skip to content
Closed
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
35 changes: 24 additions & 11 deletions blocks/library/html/editor.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
.gutenberg textarea.wp-block-html {
box-shadow: none;
font-family: $editor-html-font;
font-size: $text-editor-font-size;
color: $dark-gray-800;
border: 1px solid $light-gray-500;
border-radius: 4px;
padding: .8em 1.6em;
margin: 0;
overflow-x: auto;
width: 100%;
.gutenberg .wp-block-html {
iframe {
display: block;

// Disable pointer events so that we can click on the block to select it
pointer-events: none;
}

.CodeMirror {
border-radius: 4px;
border: 1px solid $light-gray-500;
font-family: $editor-html-font;
font-size: $text-editor-font-size;
height: auto;
}

.CodeMirror-gutters {
background: $white;
border-right: none;
}

.CodeMirror-lines {
padding: 8px 8px 8px 0;
}
}
68 changes: 33 additions & 35 deletions blocks/library/html/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
/**
* External dependencies
*/
import TextareaAutosize from 'react-autosize-textarea';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { withState } from '@wordpress/components';
import { withState, SandBox, CodeEditor } from '@wordpress/components';

/**
* Internal dependencies
Expand Down Expand Up @@ -42,35 +37,38 @@ registerBlockType( 'core/html', {

edit: withState( {
preview: false,
} )( ( { attributes, setAttributes, setState, focus, preview } ) => [
focus && (
<BlockControls key="controls">
<div className="components-toolbar">
<button
className={ `components-tab-button ${ ! preview ? 'is-active' : '' }` }
onClick={ () => setState( { preview: false } ) }>
<span>HTML</span>
</button>
<button
className={ `components-tab-button ${ preview ? 'is-active' : '' }` }
onClick={ () => setState( { preview: true } ) }>
<span>{ __( 'Preview' ) }</span>
</button>
</div>
</BlockControls>
),
preview ?
<div
key="preview"
dangerouslySetInnerHTML={ { __html: attributes.content } } /> :
<TextareaAutosize
className="wp-block-html"
key="editor"
value={ attributes.content }
onChange={ ( event ) => setAttributes( { content: event.target.value } ) }
aria-label={ __( 'HTML' ) }
/>,
] ),
} )( ( { attributes, setAttributes, setState, focus, setFocus, preview } ) => (
<div className="wp-block-html">
{ focus && (
<BlockControls>
<div className="components-toolbar">
<button
className={ `components-tab-button ${ ! preview ? 'is-active' : '' }` }
onClick={ () => setState( { preview: false } ) }
>
<span>HTML</span>
</button>
<button
className={ `components-tab-button ${ preview ? 'is-active' : '' }` }
onClick={ () => setState( { preview: true } ) }
>
<span>{ __( 'Preview' ) }</span>
</button>
</div>
</BlockControls>
) }
{ preview ? (
<SandBox html={ attributes.content } />
) : (
<CodeEditor
value={ attributes.content }
focus={ !! focus }
onFocus={ setFocus }
onChange={ content => setAttributes( { content } ) }
/>
) }
</div>
) ),

save( { attributes } ) {
return attributes.content;
Expand Down
21 changes: 17 additions & 4 deletions blocks/library/html/test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`core/html block edit matches snapshot 1`] = `
<textarea
aria-label="HTML"
<div
class="wp-block-html"
rows="1"
/>
>
<div
class="components-placeholder"
>
<div
class="components-placeholder__label"
/>
<div
class="components-placeholder__fieldset"
>
<span
class="spinner is-active"
/>
</div>
</div>
</div>
`;
56 changes: 56 additions & 0 deletions components/code-editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
CodeEditor
=======

CodeEditor is a React component that provides the user with a code editor
that has syntax highliting and linting.

The components acts as a drop-in replacement for a <textarea>, and uses the
CodeMirror library that is provided as part of WordPress Core.

## Usage

```jsx
import { CodeEditor } from '@wordpress/components';

function editCode() {
return (
<CodeEditor
value={ '<p>This is some <b>HTML</b> code that will have syntax highlighting!</p>' }
onChange={ value => console.log( value ) }
/>
);
}
```

## Props

The component accepts the following props:

### value

The source code to load into the code editor.

- Type: `string`
- Required: Yes

### focus

Whether or not the code editor should be focused.

- Type: `boolean`
- Required: No

### onFocus

The function called when the editor is focused.

- Type: `Function`
- Required: No

### onChange

The function called when the user has modified the source code via the
editor. It is passed the new value as an argument.

- Type: `Function`
- Required: No
129 changes: 129 additions & 0 deletions components/code-editor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { keycodes } from '@wordpress/utils';

/**
* Internal dependencies
*/
import withLazyDependencies from '../higher-order/with-lazy-dependencies';

/**
* Module constants
*/
const { UP, DOWN } = keycodes;

export class CodeEditor extends Component {
constructor() {
super( ...arguments );

this.onFocus = this.onFocus.bind( this );
this.onBlur = this.onBlur.bind( this );
this.onCursorActivity = this.onCursorActivity.bind( this );
this.onKeyHandled = this.onKeyHandled.bind( this );
}

componentDidMount() {
const instance = wp.codeEditor.initialize( this.textarea, window._wpGutenbergCodeEditorSettings );
this.editor = instance.codemirror;

this.editor.on( 'focus', this.onFocus );
this.editor.on( 'blur', this.onBlur );
this.editor.on( 'cursorActivity', this.onCursorActivity );
this.editor.on( 'keyHandled', this.onKeyHandled );

this.updateFocus();
}

componentDidUpdate( prevProps ) {
if ( this.props.value !== prevProps.value && this.editor.getValue() !== this.props.value ) {
this.editor.setValue( this.props.value );
}

if ( this.props.focus !== prevProps.focus ) {
this.updateFocus();
}
}

componentWillUnmount() {
this.editor.on( 'focus', this.onFocus );
this.editor.off( 'blur', this.onBlur );
this.editor.off( 'cursorActivity', this.onCursorActivity );
this.editor.off( 'keyHandled', this.onKeyHandled );

this.editor.toTextArea();
this.editor = null;
}

onFocus() {
if ( this.props.onFocus ) {
this.props.onFocus();
}
}

onBlur( editor ) {
if ( this.props.onChange ) {
this.props.onChange( editor.getValue() );
}
}

onCursorActivity( editor ) {
this.lastCursor = editor.getCursor();
}

onKeyHandled( editor, name, event ) {
/*
* Pressing UP/DOWN should only move focus to another block if the cursor is
* at the start or end of the editor.
*
* We do this by stopping UP/DOWN from propagating if:
* - We know what the cursor was before this event; AND
* - This event caused the cursor to move
*/
if ( event.keyCode === UP || event.keyCode === DOWN ) {
const areCursorsEqual = ( a, b ) => a.line === b.line && a.ch === b.ch;
if ( this.lastCursor && ! areCursorsEqual( editor.getCursor(), this.lastCursor ) ) {
event.stopImmediatePropagation();
}
}
}

updateFocus() {
if ( this.props.focus && ! this.editor.hasFocus() ) {
// Need to wait for the next frame to be painted before we can focus the editor
window.requestAnimationFrame( () => {
this.editor.focus();
} );
}

if ( ! this.props.focus && this.editor.hasFocus() ) {
document.activeElement.blur();
}
}

render() {
return <textarea ref={ ref => ( this.textarea = ref ) } value={ this.props.value } />;
}
}

export default withLazyDependencies( {
scripts() {
const scripts = [
'wp-codemirror',
'code-editor',
'htmlhint',
'csslint',
'jshint',
];

// Don't load htmlhint-kses unless we need it
if ( window._wpGutenbergCodeEditorSettings.htmlhint.kses ) {
scripts.push( 'htmlhint-kses' );
}

return scripts;
},

styles: [ 'wp-codemirror', 'code-editor' ],
} )( CodeEditor );
7 changes: 7 additions & 0 deletions components/code-editor/test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CodeEditor should render without an error 1`] = `
<textarea
value="<b>wowee</b>"
/>
`;
24 changes: 24 additions & 0 deletions components/code-editor/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { shallow } from 'enzyme';
import { set, noop } from 'lodash';

/**
* Internal dependencies
*/
import { CodeEditor } from '../';

describe( 'CodeEditor', () => {
it( 'should render without an error', () => {
set( global, 'wp.codeEditor.initialize', () => ( {
codemirror: {
on: noop,
hasFocus: () => false,
},
} ) );

const wrapper = shallow( <CodeEditor value={ '<b>wowee</b>' } /> );
expect( wrapper ).toMatchSnapshot();
} );
} );
Loading