Skip to content

Commit

Permalink
Merge pull request #4348 from WordPress/add/syntax-highlighting-to-ht…
Browse files Browse the repository at this point in the history
…ml-block

Add CodeMirror to the HTML block
  • Loading branch information
noisysocks authored Feb 27, 2018
2 parents f3c1e22 + 30ade66 commit aaf6060
Show file tree
Hide file tree
Showing 11 changed files with 404 additions and 50 deletions.
32 changes: 24 additions & 8 deletions blocks/library/html/editor.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
.wp-block-html.blocks-plain-text {
font-family: $editor-html-font;
font-size: $text-editor-font-size;
color: $dark-gray-800;
padding: .8em 1.6em;
overflow-x: auto !important;
border: 1px solid $light-gray-500;
border-radius: 4px;
.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;
}
}
64 changes: 33 additions & 31 deletions blocks/library/html/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
*/
import { RawHTML } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { withState } from '@wordpress/components';
import { withState, SandBox, CodeEditor } from '@wordpress/components';

/**
* Internal dependencies
*/
import './editor.scss';
import BlockControls from '../../block-controls';
import PlainText from '../../plain-text';

export const name = 'core/html';

Expand Down Expand Up @@ -49,35 +48,38 @@ export const settings = {

edit: withState( {
preview: false,
} )( ( { attributes, setAttributes, setState, isSelected, preview } ) => [
isSelected && (
<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 } } /> :
<PlainText
className="wp-block-html"
key="editor"
value={ attributes.content }
onChange={ ( content ) => setAttributes( { content } ) }
aria-label={ __( 'HTML' ) }
/>,
] ),
} )( ( { attributes, setAttributes, setState, isSelected, toggleSelection, preview } ) => (
<div className="wp-block-html">
{ isSelected && (
<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={ isSelected }
onFocus={ toggleSelection }
onChange={ content => setAttributes( { content } ) }
/>
) }
</div>
) ),

save( { attributes } ) {
return <RawHTML>{ attributes.content }</RawHTML>;
Expand Down
23 changes: 18 additions & 5 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"
class="blocks-plain-text wp-block-html"
rows="1"
/>
<div
class="wp-block-html"
>
<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 highlighting 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
105 changes: 105 additions & 0 deletions components/code-editor/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { keycodes } from '@wordpress/utils';

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

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 CodeEditor;
Loading

0 comments on commit aaf6060

Please sign in to comment.