Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Commit

Permalink
Add a media example
Browse files Browse the repository at this point in the history
Summary:Looks like people were interested in seeing an example using media (#46).

I created an example that shows how to use Custom Block Components to attach audio, image, and video into the editor.

Side note:

The docs (http://facebook.github.io/draft-js/docs/advanced-topics-block-components.html) led me to believe it's much simpler to render custom blocks. However, more steps (see `insertMedia(...)`) were needed to wrap around the media for it to work well. The `tex` example (https://github.com/facebook/draft-js/blob/master/examples/tex/js/modifiers/insertTeXBlock.js) has the same code to handle this. Can we provide this as a utility function?
Closes #67

Reviewed By: hellendag

Differential Revision: D2989709

fb-gh-sync-id: 76c11119760c61d9935e4dd14030fb85485355d2
fbshipit-source-id: 76c11119760c61d9935e4dd14030fb85485355d2
  • Loading branch information
tasti authored and Facebook Github Bot 5 committed Mar 31, 2016
1 parent 67c5e69 commit 226d361
Show file tree
Hide file tree
Showing 10 changed files with 544 additions and 58 deletions.
3 changes: 3 additions & 0 deletions docs/Advanced-Topics-Block-Components.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ in the Draft repository provides a live example of custom block rendering, with
TeX syntax translated on the fly into editable embedded formula rendering via the
[KaTeX library](https://khan.github.io/KaTeX/).

A [media example](https://github.com/facebook/draft-js/tree/master/examples/media) is also
available, which showcases custom block rendering of audio, image, and video.

By using a custom block renderer, it is possible to introduce complex rich
interactions within the frame of your editor.

Expand Down
211 changes: 211 additions & 0 deletions examples/media/media.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<!--
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 • Media Editor</title>
<link rel="stylesheet" href="../../dist/Draft.css" />
</head>
<body>
<div id="target"></div>
<script src="../../node_modules/react/dist/react.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/babel-core/browser.js"></script>
<script src="../../dist/Draft.js"></script>
<script type="text/babel">
'use strict';

const {
Editor,
EditorState,
Entity,
MediaUtils,
RichUtils,
convertToRaw,
} = Draft;

class MediaEditorExample extends React.Component {
constructor(props) {
super(props);

this.state = {
editorState: EditorState.createEmpty(),
};

this.focus = () => this.refs.editor.focus();
this.onChange = (editorState) => this.setState({editorState});
this.logState = () => {
const content = this.state.editorState.getCurrentContent();
console.log(convertToRaw(content));
};

this.handleKeyCommand = this._handleKeyCommand.bind(this);
this.addMedia = this._addMedia.bind(this);
this.addAudio = this._addAudio.bind(this);
this.addImage = this._addImage.bind(this);
this.addVideo = this._addVideo.bind(this);
}

_handleKeyCommand(command) {
const {editorState} = this.state;
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}

_addMedia(type) {
const src = window.prompt('Enter a URL');
if (!src) {
return;
}

const entityKey = Entity.create(type, 'IMMUTABLE', {src});

return MediaUtils.insertMedia(this.state.editorState, entityKey, ' ');
}

_addAudio() {
this.onChange(this._addMedia('audio'));
}

_addImage() {
this.onChange(this._addMedia('image'));
}

_addVideo() {
this.onChange(this._addMedia('video'));
}

render() {
return (
<div style={styles.root}>
<div style={{marginBottom: 10}}>
Use the buttons to add audio, image, or video.
</div>
<div style={{marginBottom: 10}}>
Here are some local examples that can be entered as a URL:
<ul>
<li>media.mp3</li>
<li>media.png</li>
<li>media.mp4</li>
</ul>
</div>
<div style={styles.buttons}>
<button onMouseDown={this.addAudio} style={{marginRight: 10}}>
Add Audio
</button>
<button onMouseDown={this.addImage} style={{marginRight: 10}}>
Add Image
</button>
<button onMouseDown={this.addVideo} style={{marginRight: 10}}>
Add Video
</button>
</div>
<div style={styles.editor} onClick={this.focus}>
<Editor
blockRendererFn={mediaBlockRenderer}
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
placeholder="Enter some text..."
ref="editor"
/>
</div>
<input
onClick={this.logState}
style={styles.button}
type="button"
value="Log State"
/>
</div>
);
}
}

function mediaBlockRenderer(block) {
if (block.getType() === 'media') {
return {
component: Media,
editable: false,
};
}

return null;
}

const Audio = (props) => {
return <audio controls src={props.src} style={styles.media} />;
};

const Image = (props) => {
return <img src={props.src} style={styles.media} />;
};

const Video = (props) => {
return <video controls src={props.src} style={styles.media} />;
};

const Media = (props) => {
const entity = Entity.get(props.block.getEntityAt(0));
const {src} = entity.getData();
const type = entity.getType();

let media;
if (type === 'audio') {
media = <Audio src={src} />;
} else if (type === 'image') {
media = <Image src={src} />;
} else if (type === 'video') {
media = <Video src={src} />;
}

return media;
};

const styles = {
root: {
fontFamily: '\'Georgia\', serif',
padding: 20,
width: 600,
},
buttons: {
marginBottom: 10,
},
editor: {
border: '1px solid #ccc',
cursor: 'text',
minHeight: 80,
padding: 10,
},
button: {
marginTop: 10,
textAlign: 'center',
},
media: {
width: '100%',
},
};

ReactDOM.render(
<MediaEditorExample />,
document.getElementById('target')
);
</script>
</body>
</html>
Binary file added examples/media/media.mp3
Binary file not shown.
Binary file added examples/media/media.mp4
Binary file not shown.
Binary file added examples/media/media.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 6 additions & 57 deletions examples/tex/js/modifiers/insertTeXBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,13 @@

'use strict';

import {List, Repeat} from 'immutable';
import {
BlockMapBuilder,
CharacterMetadata,
ContentBlock,
EditorState,
Entity,
Modifier,
genKey,
MediaUtils,
} from 'draft-js';

var count = 0;
var examples = [
let count = 0;
const examples = [
'\\int_a^bu\\frac{d^2v}{dx^2}\\,dx\n' +
'=\\left.u\\frac{dv}{dx}\\right|_a^b\n' +
'-\\int_a^b\\frac{du}{dx}\\frac{dv}{dx}\\,dx',
Expand All @@ -42,57 +36,12 @@ var examples = [
];

export function insertTeXBlock(editorState) {
var contentState = editorState.getCurrentContent();
var selectionState = editorState.getSelection();

var afterRemoval = Modifier.removeRange(
contentState,
selectionState,
'backward'
);

var targetSelection = afterRemoval.getSelectionAfter();
var afterSplit = Modifier.splitBlock(afterRemoval, targetSelection);
var insertionTarget = afterSplit.getSelectionAfter();

var asMedia = Modifier.setBlockType(afterSplit, insertionTarget, 'media');
var nextFormula = count++ % examples.length;

var entityKey = Entity.create(
const nextFormula = count++ % examples.length;
const entityKey = Entity.create(
'TOKEN',
'IMMUTABLE',
{content: examples[nextFormula]}
);

var charData = CharacterMetadata.create({entity: entityKey});

var fragmentArray = [
new ContentBlock({
key: genKey(),
type: 'media',
text: ' ',
characterList: List(Repeat(charData, 1)),
}),
new ContentBlock({
key: genKey(),
type: 'unstyled',
text: '',
characterList: List(),
}),
];

var fragment = BlockMapBuilder.createFromArray(fragmentArray);

var withMedia = Modifier.replaceWithFragment(
asMedia,
insertionTarget,
fragment
);

var newContent = withMedia.merge({
selectionBefore: selectionState,
selectionAfter: withMedia.getSelectionAfter().set('hasFocus', true),
});

return EditorState.push(editorState, newContent, 'insert-fragment');
return MediaUtils.insertMedia(editorState, entityKey, ' ');
}
2 changes: 2 additions & 0 deletions src/Draft.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const DraftEntity = require('DraftEntity');
const DraftEntityInstance = require('DraftEntityInstance');
const EditorState = require('EditorState');
const KeyBindingUtil = require('KeyBindingUtil');
const MediaUtils = require('MediaUtils');
const RichTextEditorUtil = require('RichTextEditorUtil');
const SelectionState = require('SelectionState');

Expand Down Expand Up @@ -50,6 +51,7 @@ const DraftPublic = {
SelectionState,

KeyBindingUtil,
MediaUtils,
Modifier: DraftModifier,
RichUtils: RichTextEditorUtil,

Expand Down
Loading

0 comments on commit 226d361

Please sign in to comment.