Skip to content

Instantly share code, notes, and snippets.

@SamyPesse
Created May 24, 2016 22:34
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save SamyPesse/0690602631c19aedcfa0a28feabb9d2b to your computer and use it in GitHub Desktop.
Save SamyPesse/0690602631c19aedcfa0a28feabb9d2b to your computer and use it in GitHub Desktop.
Complete example of code highlight for Draft.js with Prism

How to test it?

Copy the prism.js file under examples/prism/ in Draft.js repository. Run npm install prismjs Then open it in your browser.

<!--
Copyright (c) 2013-present, Facebook, Inc. All rights reserved.
This file provided by Facebook is for non-commercial testing and evaluation
purposes only. Facebook reserves all rights not expressly granted.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Draft • Code Highlighting</title>
<link rel="stylesheet" href="../../dist/Draft.css" />
<link rel="stylesheet" href="../../node_modules/prismjs/themes/prism.css" />
<link rel="stylesheet" href="RichEditor.css" />
<style>
#target { width: 600px; }
</style>
</head>
<body>
<div id="target"></div>
<script src="../../node_modules/react/dist/react.min.js"></script>
<script src="../../node_modules/react-dom/dist/react-dom.js"></script>
<script src="../../node_modules/immutable/dist/immutable.js"></script>
<script src="../../node_modules/prismjs/prism.js"></script>
<script src="../../node_modules/es6-shim/es6-shim.js"></script>
<script src="../../node_modules/babel-core/browser.js"></script>
<script src="../../dist/Draft.js"></script>
<script type="text/babel">
'use strict';
const {
Editor,
EditorState,
RichUtils,
DefaultDraftBlockRenderMap,
Decorator
} = Draft;
const {Map, List} = Immutable;
class PrismDraftDecorator {
constructor(grammar) {
this.grammar = grammar;
this.highlighted = {};
}
getDecorations(block) {
var blockType = block.getType();
var blockKey = block.getKey();
var blockText = block.getText();
var decorations = Array(blockText.length).fill(null);
this.highlighted[blockKey] = {};
if (blockType !== 'code-block') {
return List(decorations);
}
var tokens = Prism.tokenize(blockText, this.grammar);
var offset = 0;
var that = this;
tokens.forEach(function(tok) {
if (typeof tok === 'string') {
offset += tok.length;
} else {
var tokId = 'tok'+offset;
var completeId = blockKey + '-' + tokId;
that.highlighted[blockKey][tokId] = tok;
occupySlice(decorations, offset, offset + tok.content.length, completeId);
offset += tok.content.length;
}
});
return List(decorations);
}
getComponentForKey(key) {
return function(props) {
return <span {...props} className={'token ' + props.tokType}>{props.children}</span>;
}
}
getPropsForKey(key) {
var parts = key.split('-');
var blockKey = parts[0];
var tokId = parts[1];
var token = this.highlighted[blockKey][tokId];
return {
tokType: token.type
};
}
}
function occupySlice(targetArr, start, end, componentKey) {
for (var ii = start; ii < end; ii++) {
targetArr[ii] = componentKey;
}
}
class PrismEditorExample extends React.Component {
constructor(props) {
super(props);
var decorator = new PrismDraftDecorator(Prism.languages.javascript);
this.state = {
editorState: EditorState.createEmpty(decorator),
};
this.focus = () => this.refs.editor.focus();
this.onChange = (editorState) => this.setState({editorState});
this.handleKeyCommand = (command) => this._handleKeyCommand(command);
this.toggleBlockType = (type) => this._toggleBlockType(type);
this.toggleInlineStyle = (style) => this._toggleInlineStyle(style);
}
_handleKeyCommand(command) {
const {editorState} = this.state;
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}
_toggleBlockType(blockType) {
this.onChange(
RichUtils.toggleBlockType(
this.state.editorState,
blockType
)
);
}
_toggleInlineStyle(inlineStyle) {
this.onChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
inlineStyle
)
);
}
render() {
const {editorState} = this.state;
// If the user changes block type before entering any text, we can
// either style the placeholder or hide it. Let's just hide it now.
let className = 'RichEditor-editor';
var contentState = editorState.getCurrentContent();
if (!contentState.hasText()) {
if (contentState.getBlockMap().first().getType() !== 'unstyled') {
className += ' RichEditor-hidePlaceholder';
}
}
return (
<div className="RichEditor-root">
<BlockStyleControls
editorState={editorState}
onToggle={this.toggleBlockType}
/>
<InlineStyleControls
editorState={editorState}
onToggle={this.toggleInlineStyle}
/>
<div className={className} onClick={this.focus}>
<Editor
blockStyleFn={getBlockStyle}
customStyleMap={styleMap}
editorState={editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
placeholder="Tell a story..."
ref="editor"
spellCheck={true}
/>
</div>
</div>
);
}
}
// Custom overrides for "code" style.
const styleMap = {
CODE: {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
fontSize: 16,
padding: 2,
},
};
function getBlockStyle(block) {
switch (block.getType()) {
case 'blockquote': return 'RichEditor-blockquote';
default: return null;
}
}
class StyleButton extends React.Component {
constructor() {
super();
this.onToggle = (e) => {
e.preventDefault();
this.props.onToggle(this.props.style);
};
}
render() {
let className = 'RichEditor-styleButton';
if (this.props.active) {
className += ' RichEditor-activeButton';
}
return (
<span className={className} onMouseDown={this.onToggle}>
{this.props.label}
</span>
);
}
}
const BLOCK_TYPES = [
{label: 'H1', style: 'header-one'},
{label: 'H2', style: 'header-two'},
{label: 'H3', style: 'header-three'},
{label: 'H4', style: 'header-four'},
{label: 'H5', style: 'header-five'},
{label: 'H6', style: 'header-six'},
{label: 'Blockquote', style: 'blockquote'},
{label: 'UL', style: 'unordered-list-item'},
{label: 'OL', style: 'ordered-list-item'},
{label: 'Code Block', style: 'code-block'},
];
const BlockStyleControls = (props) => {
const {editorState} = props;
const selection = editorState.getSelection();
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();
return (
<div className="RichEditor-controls">
{BLOCK_TYPES.map((type) =>
<StyleButton
key={type.label}
active={type.style === blockType}
label={type.label}
onToggle={props.onToggle}
style={type.style}
/>
)}
</div>
);
};
var INLINE_STYLES = [
{label: 'Bold', style: 'BOLD'},
{label: 'Italic', style: 'ITALIC'},
{label: 'Underline', style: 'UNDERLINE'},
{label: 'Monospace', style: 'CODE'},
];
const InlineStyleControls = (props) => {
var currentStyle = props.editorState.getCurrentInlineStyle();
return (
<div className="RichEditor-controls">
{INLINE_STYLES.map(type =>
<StyleButton
key={type.label}
active={currentStyle.has(type.style)}
label={type.label}
onToggle={props.onToggle}
style={type.style}
/>
)}
</div>
);
};
ReactDOM.render(
<PrismEditorExample />,
document.getElementById('target')
);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment