├── .npmignore
├── .gitignore
├── example
├── assets
│ ├── whoa.jpg
│ ├── css
│ │ ├── bundle.css.map
│ │ ├── bundle.css
│ │ └── example.css
│ ├── app.js.map
│ └── index.html
├── browser.js
├── config.js
├── draft
│ ├── unstyled.js
│ ├── header.js
│ ├── index.js
│ ├── resizeable-div2.js
│ ├── resizeable-div.js
│ ├── youtube.js
│ └── data.js
├── index.js
└── container.js
├── src
├── index.js
├── block-wrapper.js
├── editor.js
└── create-plugins.js
├── gulpfile.js
├── .eslintrc
├── README.md
├── LICENSE
└── package.json
/.npmignore:
--------------------------------------------------------------------------------
1 | /dist/
2 | /node_modules/
3 | npm-debug.log
4 | /.idea
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /lib/
2 | /node_modules/
3 | npm-debug.log
4 | /.idea
5 | node_modules
6 |
--------------------------------------------------------------------------------
/example/assets/whoa.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bkniffler/draft-wysiwyg/HEAD/example/assets/whoa.jpg
--------------------------------------------------------------------------------
/example/assets/css/bundle.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"css/bundle.css","sources":[],"mappings":"","sourceRoot":""}
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export EditorBlock from 'draft-js/lib/DraftEditorBlock.react';
2 | export BlockWrapper from './block-wrapper';
3 | export default from './editor';
4 |
--------------------------------------------------------------------------------
/example/assets/app.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"app.js","sources":["webpack:///app.js","webpack:///"],"mappings":"AAAA;ACyuJA;AA2vBA;AAsuHA;AA41GA;AAy3IA;AA+uIA;AAsqFA;AAg2FA;AAghEA;AAwlEA;AAg7GA;AA4nJA;AAyrIA;AAu3GA","sourceRoot":""}
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 |
3 | gulp.task("build", function (callback) {
4 | global.DEBUG = false;
5 |
6 | require('wrappack/gulpfile')(
7 | require('./example/config.js')(), callback
8 | );
9 | });
10 |
--------------------------------------------------------------------------------
/example/browser.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import Example from './container';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 |
6 | ReactDOM.render(
7 | ,
8 | document.getElementById('target')
9 | );
10 |
--------------------------------------------------------------------------------
/example/config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = function(){
4 | return {
5 | root: path.resolve(__dirname, '..'),
6 | app: path.resolve(__dirname, 'app.js'),
7 | cssModules: false,
8 | alias: {
9 | 'draft-wysiwyg': path.resolve(__dirname, '..', 'src'),
10 | }
11 | };
12 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": [ "mocha" ],
4 | "env": {
5 | "browser": true,
6 | "mocha": true,
7 | "node": true
8 | },
9 | "extends": "airbnb",
10 | "rules": {
11 | "max-len": 0,
12 | "comma-dangle": 0,
13 | "new-cap": 0,
14 | "react/prop-types": 0,
15 | "react/prefer-stateless-function": 0
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example/draft/unstyled.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import DraftEditorBlock from 'draft-js/lib/DraftEditorBlock.react';
3 |
4 | export default class Paragraph extends Component {
5 | render(){
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 | }
--------------------------------------------------------------------------------
/example/draft/header.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import DraftEditorBlock from 'draft-js/lib/DraftEditorBlock.react';
3 |
4 | export default function(size){
5 | return class Header extends Component {
6 | render(){
7 | return React.createElement('h'+size, { className: 'header' }, )
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Draft
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/draft/index.js:
--------------------------------------------------------------------------------
1 | export Data from './data';
2 |
3 | import Header from './header';
4 | import Youtube from './youtube';
5 | import ResizeableDiv from './resizeable-div';
6 | import ResizeableDiv2 from './resizeable-div2';
7 | /*import Columns2 from './columns2';
8 | import Image from './image';
9 | import Unstyled from './unstyled';*/
10 |
11 | var blocks = {
12 | 'header-1': Header(1),
13 | 'header-2': Header(2),
14 | 'header-3': Header(3),
15 | 'header-4': Header(4),
16 | 'header-5': Header(5),
17 | youtube: Youtube,
18 | 'resizeable-div': ResizeableDiv,
19 | 'resizeable-div2': ResizeableDiv2,
20 | /*
21 | columns2: Columns2,
22 | image: Image,
23 | unstyled: Unstyled,
24 | */
25 | }
26 |
27 | export const Blocks = blocks;
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # draft-wysiwyg
2 | A wysiwyg editor that mimics medium, build on top of https://github.com/facebook/draft-js and https://github.com/draft-js-plugins/draft-js-plugins. All the relevant behaviour comes from plugins, so this may serve as an example of using the plugin architecture and the different plugins. You can also install and use draft-wysiwyg right away.
3 |
4 | ## Demo
5 | https://draft-wysiwyg.herokuapp.com/
6 |
7 | ## Features
8 | - Drag & Drop uploading
9 | - Inline toolbar for text
10 | - Block drag/drop
11 | - Block resizing (horizontal/vertical with absolute/relative sizes and aspect ratios)
12 | - Block toolbars
13 | - Block keydown handling to remove blocks (backspace) or move cursor to next/previous block
14 | - Tables (nested draft-js)
15 | - Links
16 | - Some more things
17 |
18 | ## Installation
19 | ```
20 | npm install draft-wysiwyg
21 | or
22 | sudo npm install draft-wysiwyg
23 | ```
24 |
25 | ## Usage
26 | WIP
27 |
28 | ## Contributing
29 | Pull requests are very welcome, feel free to commit your ideas!
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Benjamin Kniffler
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/example/draft/resizeable-div2.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import { FocusDecorator } from 'draft-js-focus-plugin';
3 | import { DraggableDecorator } from 'draft-js-dnd-plugin';
4 | import { ToolbarDecorator } from 'draft-js-toolbar-plugin';
5 | import { AlignmentDecorator } from 'draft-js-alignment-plugin';
6 | import { ResizeableDecorator } from 'draft-js-resizeable-plugin';
7 |
8 | class Div extends Component {
9 | render(){
10 | const { style, className } = this.props;
11 | var styles = {
12 | backgroundColor: 'rgba(100, 100, 100, 1.0)',
13 | width: '100%',
14 | height: '100%',
15 | textAlign: 'center',
16 | color: 'white',
17 | zIndex: 1,
18 | position: 'relative',
19 | ...style
20 | };
21 | return (
22 |
23 | Horizontal only
24 | {/**/}
25 |
26 | );
27 | }
28 | }
29 | export default ResizeableDecorator({
30 | handles: true
31 | })(
32 | DraggableDecorator(
33 | FocusDecorator(
34 | AlignmentDecorator(
35 | ToolbarDecorator()(
36 | Div
37 | )
38 | )
39 | )
40 | )
41 | );
42 |
--------------------------------------------------------------------------------
/example/draft/resizeable-div.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import { FocusDecorator } from 'draft-js-focus-plugin';
3 | import { DraggableDecorator } from 'draft-js-dnd-plugin';
4 | import { ToolbarDecorator } from 'draft-js-toolbar-plugin';
5 | import { AlignmentDecorator } from 'draft-js-alignment-plugin';
6 | import { ResizeableDecorator } from 'draft-js-resizeable-plugin';
7 |
8 | class Div extends Component {
9 | render(){
10 | const { style, className } = this.props;
11 | var styles = {
12 | backgroundColor: 'rgba(98, 177, 254, 1.0)',
13 | width: '100%',
14 | height: '100%',
15 | textAlign: 'center',
16 | color: 'white',
17 | zIndex: 1,
18 | position: 'relative',
19 | ...style
20 | };
21 | return (
22 |
23 | Horizontal+Vertical
24 | {/**/}
25 |
26 | );
27 | }
28 | }
29 |
30 | export default ResizeableDecorator({
31 | resizeSteps: 10,
32 | caption: true,
33 | vertical: 'absolute'
34 | })(
35 | DraggableDecorator(
36 | FocusDecorator(
37 | AlignmentDecorator(
38 | ToolbarDecorator()(
39 | Div
40 | )
41 | )
42 | )
43 | )
44 | );
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "draft-wysiwyg",
3 | "version": "0.2.4",
4 | "author": "Benjamin Kniffler",
5 | "license": "MIT",
6 | "repository": "https://github.com/bkniffler/draft-wysiwyg",
7 | "main": "./lib",
8 | "engines": {
9 | "node": "5.x.x",
10 | "npm": "3.x.x"
11 | },
12 | "keywords": [
13 | "draftjs",
14 | "draft-js",
15 | "editor",
16 | "wysiwyg",
17 | "drag",
18 | "drop",
19 | "react",
20 | "richtext"
21 | ],
22 | "scripts": {
23 | "start": "node ./example",
24 | "build": "babel --presets es2015,stage-0,react src/ --out-dir lib/",
25 | "heroku": "git push heroku master",
26 | "start:production": "NODE_ENV=production node ./example",
27 | "build:example": "gulp build",
28 | "patch": "npm run build; npm version patch --force; npm publish"
29 | },
30 | "dependencies": {
31 | "draft-js": "^0.7.0",
32 | "draft-js-alignment-plugin": "^1.0.3",
33 | "draft-js-cleanup-empty-plugin": "^1.0.2",
34 | "draft-js-dnd-plugin": "^1.0.7",
35 | "draft-js-entity-props-plugin": "^1.0.2",
36 | "draft-js-focus-plugin": "^1.0.12",
37 | "draft-js-image-plugin": "^1.0.3",
38 | "draft-js-plugins-editor-wysiwyg": "^1.0.3",
39 | "draft-js-resizeable-plugin": "^1.0.8",
40 | "draft-js-table-plugin": "^1.0.4",
41 | "draft-js-toolbar-plugin": "^1.0.5",
42 | "immutable": "^3.7.4"
43 | },
44 | "devDependencies": {
45 | "express": "^4.13.1",
46 | "gulp": "^3.9.0",
47 | "multer": "^1.1.0",
48 | "react": "^15.0.2",
49 | "react-dom": "^15.0.2",
50 | "superagent": "^1.8.0",
51 | "wrappack": "^0.3.10"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/example/draft/youtube.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 |
3 | import { FocusDecorator } from 'draft-js-focus-plugin';
4 | import { DraggableDecorator } from 'draft-js-dnd-plugin';
5 | import { ToolbarDecorator } from 'draft-js-toolbar-plugin';
6 | import { AlignmentDecorator } from 'draft-js-alignment-plugin';
7 | import { ResizeableDecorator } from 'draft-js-resizeable-plugin';
8 |
9 | var defaultVideo = 'https://www.youtube.com/embed/zalYJacOhpo';
10 | class Div extends Component {
11 | setUrl(){
12 | var url = window.prompt("URL", this.props.blockProps.url||defaultVideo);
13 | if(url){
14 | const {setEntityData} = this.props.blockProps;
15 | setEntityData(this.props.block, {url});
16 | }
17 | }
18 | render(){
19 | const { style, className, ratioContentStyle, ratioContainerStyle, createRatioPlaceholder } = this.props;
20 | var action = {
21 | active: false,
22 | button: URL ,
23 | toggle: ()=>this.setUrl(),
24 | label: 'URL'
25 | };
26 | var styles = {
27 | width: '100%',
28 | height: '100%',
29 | position: 'relative',
30 | zIndex: 1,
31 | margin: '3px',
32 | ...style,
33 | ...ratioContainerStyle
34 | };
35 | return (
36 |
37 | {createRatioPlaceholder()}
38 |
39 | {/* */}
40 |
41 | );
42 | }
43 | }
44 |
45 | export default ResizeableDecorator({
46 | resizeSteps: 10,
47 | ratio: 2/3,
48 | vertical: 'auto',
49 | handles: true,
50 | caption: true
51 | })(
52 | DraggableDecorator(
53 | FocusDecorator(
54 | AlignmentDecorator(
55 | ToolbarDecorator()(
56 | Div
57 | )
58 | )
59 | )
60 | )
61 | );
--------------------------------------------------------------------------------
/src/block-wrapper.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { FocusDecorator } from 'draft-js-focus-plugin';
4 | import { DraggableDecorator } from 'draft-js-dnd-plugin';
5 | import { ToolbarDecorator } from 'draft-js-toolbar-plugin';
6 | import { AlignmentDecorator } from 'draft-js-alignment-plugin';
7 | import { ResizeableDecorator } from 'draft-js-resizeable-plugin';
8 |
9 | const getDisplayName = WrappedComponent => {
10 | const component = WrappedComponent.WrappedComponent || WrappedComponent;
11 | return component.displayName || component.name || 'Component';
12 | };
13 |
14 | const getComponent = WrappedComponent => class BlockWrapper extends Component {
15 | static displayName = `BlockWrapper(${getDisplayName(WrappedComponent)})`;
16 | static WrappedComponent = WrappedComponent.WrappedComponent || WrappedComponent;
17 | constructor(props) {
18 | super(props);
19 | this.state = {};
20 | }
21 | setEntityData = patch => {
22 | const { blockProps, block } = this.props;
23 | const {setEntityData} = blockProps;
24 | setEntityData(patch);
25 | this.setState({
26 | ...patch
27 | });
28 | }
29 | render() {
30 | const {blockProps} = this.props;
31 | const readOnly = blockProps.pluginEditor.getReadOnly();
32 | return
33 | }
34 | }
35 |
36 | export default options => WrappedComponent => {
37 | const {resizeable, draggable, focus, alignment, toolbar} = options || {};
38 | let component = getComponent(WrappedComponent);
39 | if (toolbar !== false) {
40 | component = ToolbarDecorator(toolbar || {})(component);
41 | }
42 | if (alignment !== false) {
43 | component = AlignmentDecorator(component);
44 | }
45 | if (focus !== false) {
46 | component = FocusDecorator(component);
47 | }
48 | if (draggable !== false) {
49 | component = DraggableDecorator(component);
50 | }
51 | if (resizeable !== false) {
52 | component = ResizeableDecorator(resizeable || {})(component);
53 | }
54 | return component;
55 | }
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 | var fs = require('fs');
4 |
5 | var multer = require('multer');
6 | var storage = multer.diskStorage({
7 | destination: function (req, file, callback) {
8 | callback(null, path.resolve(__dirname, 'assets'));
9 | },
10 | filename: function (req, file, callback) {
11 | callback(null, file.originalname);
12 | //callback(null, file.fieldname + '-' + Date.now());
13 | }
14 | });
15 | var upload = multer({
16 | storage : storage,
17 | limits: {
18 | fields: 10,
19 | files: 3,
20 | fileSize: 1000000
21 | }
22 | }).array('files', 3);
23 |
24 | var APP_PORT = process.env.PORT||3030;
25 |
26 | var app = express();
27 | if(process.env.NODE_ENV !== 'production'){
28 | process.env.NODE_ENV = 'development';
29 | var wrappack = require('wrappack');
30 | var config = require('./config');
31 | wrappack(app, config());
32 | /*var webpack = require('webpack');
33 | var webpackMiddleware = require("webpack-dev-middleware");
34 | var config = require('./webpack');
35 |
36 | var compiler = webpack(config);
37 | app.use(webpackMiddleware(compiler, {
38 | contentBase: '/public/',
39 | publicPath: '/js/',
40 | stats: {colors: true}
41 | }));*/
42 | }
43 |
44 | app.post('/upload', upload, function(req, res) {
45 | var file = req.files;
46 |
47 | setTimeout(function(){
48 | req.files.forEach(function(file){
49 | fs.unlink(file.path, function(err) {});
50 | })
51 | }, 1*60000);
52 |
53 | res.json({
54 | success: true,
55 | files: file.map(function(file){
56 | return {
57 | encoding: file.encoding,
58 | filename: file.filename,
59 | mimetype: file.mimetype,
60 | originalname: file.originalname,
61 | size: file.originalname,
62 | url: '/'+file.originalname
63 | }
64 | })
65 | });
66 | });
67 |
68 | // Serve static resources
69 | app.use('/', express.static(path.resolve(__dirname, 'assets')));
70 | app.use('/node_modules', express.static(path.resolve(__dirname, '..', 'node_modules')));
71 | app.use(function(err, req, res, next) {
72 | res.status(500).json(err);
73 | });
74 | app.listen(APP_PORT, function(){
75 | console.log('Server is now running on http://localhost:'+APP_PORT);
76 | });
77 |
--------------------------------------------------------------------------------
/src/editor.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {EditorState, convertToRaw, convertFromRaw} from 'draft-js';
3 | import Editor from 'draft-js-plugins-editor-wysiwyg';
4 | import {DefaultDraftBlockRenderMap} from 'draft-js';
5 | import createPlugins from './create-plugins';
6 | import {Map} from 'immutable';
7 |
8 | class WysiwygEditor extends Component {
9 | constructor(props) {
10 | super(props);
11 | this.batch = batch(200);
12 | this.plugins = createPlugins(props);
13 | this.editorState = props.value
14 | ? EditorState.push(EditorState.createEmpty(), convertFromRaw(props.value))
15 | : EditorState.createEmpty();
16 |
17 | this.blockRenderMap = DefaultDraftBlockRenderMap.merge(
18 | this.customBlockRendering(props)
19 | );
20 |
21 | this.state = {};
22 | }
23 |
24 | componentWillUnmount(){
25 | this.unmounted = true;
26 | }
27 |
28 | shouldComponentUpdate(props, state) {
29 | if (this.props.value !== props.value && this._raw !== props.value) {
30 | this.editorState = !props.value
31 | ? EditorState.createEmpty()
32 | : EditorState.push(this.editorState, convertFromRaw(props.value));
33 | return true;
34 | } else if (this.state.active !== state.active
35 | || this.state.readOnly !== state.readOnly
36 | || this.state.editorState !== state.editorState) {
37 | return true;
38 | } else if (this.props.readOnly !== props.readOnly
39 | || this.props.fileDrag !== props.fileDrag
40 | || this.props.uploading !== props.uploading
41 | || this.props.percent !== props.percent) {
42 | return true;
43 | }
44 | return false;
45 | }
46 |
47 | onChange = (editorState) => {
48 | if (this.unmounted) return;
49 | this.editorState = editorState;
50 | this.setState({editorState: Date.now()});
51 |
52 | if (this.props.onChange) {
53 | this.batch(() => {
54 | this._raw = convertToRaw(editorState.getCurrentContent());
55 | this.props.onChange(this._raw, editorState);
56 | });
57 | }
58 | };
59 |
60 | focus = () => {
61 | this.refs.editor.focus();
62 | };
63 |
64 | blockRendererFn = contentBlock => {
65 | const {blockTypes} = this.props;
66 | const type = contentBlock.getType();
67 | return blockTypes && blockTypes[type] ? {
68 | component: blockTypes[type]
69 | } : undefined;
70 | }
71 |
72 | customBlockRendering = props => {
73 | const {blockTypes} = props;
74 | var newObj = {
75 | 'paragraph': {
76 | element: 'div',
77 | },
78 | 'unstyled': {
79 | element: 'div',
80 | },
81 | 'block-image': {
82 | element: 'div',
83 | },
84 | 'block-table': {
85 | element: 'div',
86 | }
87 | };
88 | for (var key in blockTypes) {
89 | newObj[key] = {
90 | element: 'div'
91 | };
92 | }
93 | return Map(newObj);
94 | }
95 |
96 | render() {
97 | const {editorState} = this;
98 | const {isDragging, progress, readOnly} = this.props;
99 |
100 | return (
101 |
108 | );
109 | }
110 | }
111 |
112 | export default WysiwygEditor;
113 |
114 | const batch = (limit=500) => {
115 | var _callback = null;
116 | return (callback) => {
117 | _callback = callback;
118 | setTimeout(() => {
119 | if (_callback === callback) {
120 | callback();
121 | }
122 | }, limit);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/create-plugins.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Editor from 'draft-js-plugins-editor-wysiwyg';
3 | import createCleanupEmptyPlugin from 'draft-js-cleanup-empty-plugin';
4 | import createEntityPropsPlugin from 'draft-js-entity-props-plugin';
5 | import createFocusPlugin, { FocusDecorator } from 'draft-js-focus-plugin';
6 | import createDndPlugin, { DraggableDecorator } from 'draft-js-dnd-plugin';
7 | import createToolbarPlugin, { ToolbarDecorator } from 'draft-js-toolbar-plugin';
8 | import createAlignmentPlugin, { AlignmentDecorator } from 'draft-js-alignment-plugin';
9 | import createResizeablePlugin, { ResizeableDecorator } from 'draft-js-resizeable-plugin';
10 | // Blocks
11 | import createImagePlugin, { imageCreator, imageStyles } from 'draft-js-image-plugin';
12 | import createTablePlugin, { tableCreator, tableStyles } from 'draft-js-table-plugin';
13 |
14 | // Styles
15 | import 'draft-js-alignment-plugin/lib/plugin.css';
16 | import 'draft-js-focus-plugin/lib/plugin.css';
17 | import 'draft-js-image-plugin/lib/plugin.css';
18 | import 'draft-js-table-plugin/lib/plugin.css';
19 | import 'draft-js-toolbar-plugin/lib/plugin.css';
20 |
21 | // Utils
22 | import addBlock from 'draft-js-dnd-plugin/lib/modifiers/addBlock';
23 | import { RichUtils } from 'draft-js';
24 |
25 | const image = ResizeableDecorator({
26 | resizeSteps: 10,
27 | handles: true,
28 | vertical: 'auto'
29 | })(
30 | DraggableDecorator(
31 | FocusDecorator(
32 | AlignmentDecorator(
33 | ToolbarDecorator()(
34 | imageCreator({ theme: imageStyles })
35 | )
36 | )
37 | )
38 | )
39 | );
40 | const table = FocusDecorator(
41 | DraggableDecorator(
42 | ToolbarDecorator()(
43 | tableCreator({ theme: tableStyles, Editor })
44 | )
45 | )
46 | );
47 |
48 | // Init Plugins
49 | export default ({ handleUpload, handleDefaultData, plugins = ()=>{}, toolbar = { disableItems: [], textActions: []}}) => [
50 | plugins,
51 | createCleanupEmptyPlugin({
52 | types: ['block-image', 'block-table']
53 | }),
54 | createEntityPropsPlugin({ }),
55 | createToolbarPlugin({
56 | __toolbarHandler: {
57 | add: props => console.log('Add toolbar', props),
58 | remove: props => console.log('Remove toolbar', props),
59 | }, textActions: [...[{
60 | button: H1 ,
61 | key: 'H1',
62 | label: 'Header 1',
63 | active: (block, editorState) => block.get('type') === 'header-1',
64 | toggle: (block, action, editorState, setEditorState) => setEditorState(RichUtils.toggleBlockType(
65 | editorState,
66 | 'header-1'
67 | )),
68 | }, {
69 | button: H2 ,
70 | key: 'H2',
71 | label: 'Header 2',
72 | active: (block, editorState) => block.get('type') === 'header-2',
73 | toggle: (block, action, editorState, setEditorState) => setEditorState(RichUtils.toggleBlockType(
74 | editorState,
75 | 'header-2'
76 | )),
77 | }, {
78 | button: H3 ,
79 | key: 'H3',
80 | label: 'Header 3',
81 | active: (block, editorState) => block.get('type') === 'header-3',
82 | toggle: (block, action, editorState, setEditorState) => setEditorState(RichUtils.toggleBlockType(
83 | editorState,
84 | 'header-3'
85 | )),
86 | }, {
87 | button: H4 ,
88 | key: 'H4',
89 | label: 'Header 4',
90 | active: (block, editorState) => block.get('type') === 'header-4',
91 | toggle: (block, action, editorState, setEditorState) => setEditorState(RichUtils.toggleBlockType(
92 | editorState,
93 | 'header-4'
94 | )),
95 | }, {
96 | button: H5 ,
97 | key: 'H5',
98 | label: 'Header 5',
99 | active: (block, editorState) => block.get('type') === 'header-4',
100 | toggle: (block, action, editorState, setEditorState) => setEditorState(RichUtils.toggleBlockType(
101 | editorState,
102 | 'header-5'
103 | )),
104 | }].filter(toolbarItem => !toolbar.disableItems.includes(toolbarItem.key)), ...toolbar.textActions]
105 | }),
106 | createFocusPlugin({}),
107 | createAlignmentPlugin({}),
108 | createDndPlugin({
109 | allowDrop: true,
110 | handleUpload,
111 | handleDefaultData,
112 | handlePlaceholder: (state, selection, data) => {
113 | const { type } = data;
114 | if (type.indexOf('image/') === 0) {
115 | return 'block-image';
116 | } else if (type.indexOf('text/') === 0 || type === 'application/json') {
117 | return 'placeholder-github';
118 | } return undefined;
119 | }, handleBlock: (state, selection, data) => {
120 | const { type } = data;
121 | if (type.indexOf('image/') === 0) {
122 | return 'block-image';
123 | } else if (type.indexOf('text/') === 0 || type === 'application/json') {
124 | return 'block-text';
125 | } return undefined;
126 | },
127 | }),
128 | createResizeablePlugin({}),
129 | // Blocks
130 | createImagePlugin({ component: image }),
131 | createTablePlugin({ component: table, Editor }),
132 | ];
133 |
--------------------------------------------------------------------------------
/example/assets/css/bundle.css:
--------------------------------------------------------------------------------
1 | .draftJsEmojiPlugin__centerWrapper2__4EfaH>.draftJsEmojiPlugin__center__2hzUI>*{display:block;float:left;list-style:none;margin:0;padding:0;position:relative;right:50%}.draftJsEmojiPlugin__centerWrapper__l1cWY{text-align:center;width:100%}.draftJsEmojiPlugin__centerWrapper__l1cWY>.draftJsEmojiPlugin__center__2hzUI{display:inline-block;width:100%}.draftJsEmojiPlugin__center__2hzUI{display:block;height:auto;margin:0 auto!important}.draftJsEmojiPlugin__left__23qnn{float:left;margin-right:10px}.draftJsEmojiPlugin__right__1o-p7{float:right;margin-left:10px}.draftJsEmojiPlugin__focused__3Mksn{outline:3px solid #000}.draftJsEmojiPlugin__imageWrapper__o63QA{height:auto;position:relative;display:block;z-index:1}.draftJsEmojiPlugin__imageButton__IvvDa{background:#d9d9d9;color:#fff;margin:0;padding:.5em;border:none;border-radius:50%;line-height:80%;position:absolute;font-size:.62em;margin-left:-.825em;cursor:pointer;z-index:1000}.draftJsEmojiPlugin__imageButton__IvvDa:hover{background:#e4e4e4}.draftJsEmojiPlugin__imageButton__IvvDa:active{background:#cecece;color:#efefef}.draftJsEmojiPlugin__imageLoader__1rOmo{position:absolute;right:0;top:0;height:100%;background-color:#fff;-webkit-transition:width .5s ease 0s;transition:width .5s ease 0s}.draftJsEmojiPlugin__table__39DpF a:link{color:#666;font-weight:700;text-decoration:none}.draftJsEmojiPlugin__table__39DpF a:visited{color:#999;font-weight:700;text-decoration:none}.draftJsEmojiPlugin__table__39DpF a:active,.draftJsEmojiPlugin__table__39DpF a:hover{color:#bd5a35;text-decoration:underline}.draftJsEmojiPlugin__table__39DpF{text-shadow:1px 1px 0 #fff;background:#eaebec;border:1px solid #ccc;width:100%;border-radius:3px;box-shadow:0 1px 2px #d1d1d1}.draftJsEmojiPlugin__table__39DpF th{padding:21px 25px 22px;border-top:1px solid #fafafa;border-bottom:1px solid #e0e0e0;background:#ededed;background:-webkit-gradient(linear,left top,left bottom,from(#ededed),to(#ebebeb));background:-moz-linear-gradient(top,#ededed,#ebebeb)}.draftJsEmojiPlugin__table__39DpF th:first-child{text-align:left;padding-left:20px}.draftJsEmojiPlugin__table__39DpF tr:first-child th:first-child{border-top-left-radius:3px}.draftJsEmojiPlugin__table__39DpF tr:first-child th:last-child{border-top-right-radius:3px}.draftJsEmojiPlugin__table__39DpF tr{text-align:center;padding-left:20px}.draftJsEmojiPlugin__table__39DpF td:first-child{text-align:left;padding-left:20px;border-left:0}.draftJsEmojiPlugin__table__39DpF td{padding:18px;border-top:1px solid #fff;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;background:#fafafa;background:-webkit-gradient(linear,left top,left bottom,from(#fbfbfb),to(#fafafa));background:-moz-linear-gradient(top,#fbfbfb,#fafafa)}.draftJsEmojiPlugin__table__39DpF tr.draftJsEmojiPlugin__even__Hc165 td{background:#f6f6f6;background:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#f6f6f6));background:-moz-linear-gradient(top,#f8f8f8,#f6f6f6)}.draftJsEmojiPlugin__table__39DpF tr:last-child td{border-bottom:0}.draftJsEmojiPlugin__table__39DpF tr:last-child td:first-child{border-bottom-left-radius:3px}.draftJsEmojiPlugin__table__39DpF tr:last-child td:last-child{border-bottom-right-radius:3px}.draftJsEmojiPlugin__table__39DpF tr:hover td{background:#f2f2f2;background:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#f0f0f0));background:-moz-linear-gradient(top,#f2f2f2,#f0f0f0)}[data-tooltip]{position:relative;text-decoration:none}[data-tooltip]:after{content:attr(data-tooltip);position:absolute;bottom:130%;left:0;background:#000;padding:5px 15px;color:#fff;border-radius:10px;white-space:nowrap;opacity:0;-webkit-transition:all .4s ease;transition:all .4s ease;pointer-events:none}[data-tooltip]:hover:after{bottom:110%}[data-tooltip]:hover:after,a:hover:before{opacity:1}.draftJsToolbar__draft-sidebar__iGLU3{background-color:transparent;border:none;border-radius:50px;font-size:16px;width:32px}.draftJsToolbar__draft-sidebar__iGLU3 .draftJsToolbar__item__16yus button{background-color:transparent;cursor:pointer;border:1px solid rgba(0,0,0,.6);border-radius:50px;font-size:16px;color:rgba(0,0,0,.6);line-height:31px;padding:0 14px;font-size:12px;width:32px;height:32px;padding:0}.draftJsToolbar__draft-sidebar__iGLU3 .draftJsToolbar__item__16yus .draftJsToolbar__menu__2kTke{display:none}.draftJsToolbar__draft-sidebar__iGLU3 .draftJsToolbar__item__16yus:hover .draftJsToolbar__menu__2kTke{display:block}.draftJsToolbar__toolbar__2NV21{background-color:#000;border:none;border-radius:50px;font-size:16px}.draftJsToolbar__toolbar__2NV21 .draftJsToolbar__toolbar-item__ZQoML{float:left;list-style:none;margin:0;padding:0;background-color:#000}.draftJsToolbar__toolbar__2NV21 .draftJsToolbar__toolbar-item__ZQoML:first-child{border-bottom-left-radius:50px;border-top-left-radius:50px;padding-left:6px}.draftJsToolbar__toolbar__2NV21 .draftJsToolbar__toolbar-item__ZQoML:last-child{border-bottom-right-radius:50px;border-top-right-radius:50px;padding-right:6px}.draftJsToolbar__toolbar__2NV21 .draftJsToolbar__toolbar-item__ZQoML.draftJsToolbar__toolbar-item-active__1MJoi,.draftJsToolbar__toolbar__2NV21 .draftJsToolbar__toolbar-item__ZQoML:hover{background-color:#2f2f2f}.draftJsToolbar__toolbar__2NV21 .draftJsToolbar__toolbar-item__ZQoML button{background-color:transparent;border:0;color:#fff;box-sizing:border-box;cursor:pointer;display:block;font-size:14px;line-height:1.33;margin:0;padding:15px;text-decoration:none}
2 | /*# sourceMappingURL=bundle.css.map*/
--------------------------------------------------------------------------------
/example/assets/css/example.css:
--------------------------------------------------------------------------------
1 | body{
2 | font-family: "Georgia","Cambria","Times New Roman","Times","serif";
3 | line-height: initial;
4 | margin: 0;
5 | background: #F7F7F7;
6 | }
7 |
8 | .public-DraftEditor-content > div > *:first-child{
9 | margin-top: 1px;
10 | }
11 | .public-DraftEditor-content > div > *:not(:first-child){
12 | margin-top: 39px;
13 | }
14 |
15 | .header {
16 | font-family: "Lucida Grande";
17 | font-weight: 700;
18 | line-height: 1.15;
19 | margin-top: 39px;
20 | margin-bottom: 0;
21 | }
22 | .h1{
23 | font-size: 52px;
24 | }
25 | .h2{
26 | font-size: 44px;
27 | }
28 | .h3{
29 | font-size: 36px;
30 | }
31 | .h4{
32 | font-size: 28px;
33 | }
34 | .header + .header, .header + * + .header {
35 | margin-top: 1px!important;
36 | font-weight: initial;
37 | color: rgba(0,0,0,.44);
38 | }
39 | .header + .paragraph, .header + * + .paragraph {
40 | margin-top: 10px!important;
41 | }
42 |
43 | .TeXEditor-container {
44 | width: 100%;
45 | height: 100%;
46 | }
47 |
48 | .TeXEditor-root {
49 | background: white;
50 | -webkit-font-smoothing: antialiased;
51 | margin: 40px auto;
52 | width: 700px;
53 | }
54 |
55 | .TeXEditor-editor {
56 | cursor: text;
57 | font-size: 21px;
58 | line-height: 33px;
59 | min-height: 40px;
60 | padding: 30px;
61 | }
62 |
63 | .TeXEditor-button {
64 | margin-top: 10px;
65 | text-align: center;
66 | }
67 |
68 | .TeXEditor-handle {
69 | color: rgba(98, 177, 254, 1.0);
70 | direction: ltr;
71 | unicode-bidi: bidi-override;
72 | }
73 |
74 | .TeXEditor-hashtag {
75 | color: rgba(95, 184, 138, 1.0);
76 | }
77 |
78 | .TeXEditor-tex {
79 | background-color: #fff;
80 | cursor: pointer;
81 | margin: 20px auto;
82 | padding: 20px;
83 | -webkit-transition: background-color 0.2s fade-in-out;
84 | user-select: none;
85 | -webkit-user-select: none;
86 | }
87 |
88 | .TeXEditor-activeTeX {
89 | color: #888;
90 | }
91 |
92 | .TeXEditor-panel {
93 | font-family: 'Helvetica', sans-serif;
94 | font-weight: 200;
95 | }
96 |
97 | .TeXEditor-panel .TeXEditor-texValue {
98 | border: 1px solid #e1e1e1;
99 | display: block;
100 | font-family: 'Inconsolata', 'Menlo', monospace;
101 | font-size: 14px;
102 | height: 110px;
103 | margin: 20px auto 10px;
104 | outline: none;
105 | padding: 14px;
106 | resize: none;
107 | -webkit-box-sizing: border-box;
108 | width: 500px;
109 | }
110 |
111 | .TeXEditor-buttons {
112 | text-align: center;
113 | }
114 |
115 | .TeXEditor-saveButton,
116 | .TeXEditor-removeButton {
117 | background-color: #fff;
118 | border: 1px solid #0a0;
119 | cursor: pointer;
120 | font-family: 'Helvetica', 'Arial', sans-serif;
121 | font-size: 16px;
122 | font-weight: 200;
123 | margin: 10px auto;
124 | padding: 6px;
125 | -webkit-border-radius: 3px;
126 | width: 100px;
127 | }
128 |
129 | .TeXEditor-removeButton {
130 | border-color: #aaa;
131 | color: #999;
132 | margin-left: 8px;
133 | }
134 |
135 | .TeXEditor-invalidButton {
136 | background-color: #eee;
137 | border-color: #a00;
138 | color: #666;
139 | }
140 |
141 | .TeXEditor-insert {
142 | background-color: #f1f1f1;
143 | border: 1px solid #ccc;
144 | border-radius: 3px;
145 | bottom: 30px;
146 | position: fixed;
147 | right: 30px;
148 | }
149 | .TeXEditor-insert2 {
150 | background-color: #f1f1f1;
151 | border: 1px solid #ccc;
152 | border-radius: 3px;
153 | bottom: 30px;
154 | position: fixed;
155 | right: 160px;
156 | }
157 |
158 | .TeXEditor-insert3 {
159 | background-color: #f1f1f1;
160 | border: 1px solid #ccc;
161 | border-radius: 3px;
162 | bottom: 30px;
163 | position: fixed;
164 | right: 300px;
165 | }
166 |
167 | .logo{
168 | float: left;
169 | font-size: 30px;
170 | color: white;
171 | margin: 15px;
172 | }
173 |
174 | .github-button{
175 | float: left;
176 | margin: 15px;
177 | margin-top: 26px;
178 | color: white;
179 | }
180 |
181 | .flex-container {
182 | margin-top: 100px;
183 | }
184 |
185 | .head {
186 | /*background: linear-gradient(150deg, #ffb347, #ffcc33);*/
187 | background: linear-gradient(150deg, #485563, #29323c);
188 |
189 | height: 65px;
190 | top:0;
191 | width: 100%;
192 | z-index: 10;
193 | position: fixed;
194 | }
195 |
196 | .container-content {
197 | -webkit-order: 0;
198 | -ms-flex-order: 0;
199 | order: 0;
200 | -webkit-flex: 1 1 auto;
201 | -ms-flex: 1 1 auto;
202 | flex: 1 1 auto;
203 | -webkit-align-self: stretch;
204 | -ms-flex-item-align: stretch;
205 | align-self: stretch;
206 | }
207 |
208 | .button{
209 | outline: 0;
210 | cursor: pointer;
211 | float: right;
212 | margin: 12px;
213 | background: transparent;
214 | border-radius: 30px;
215 | border: 1px solid transparent;
216 | /* font-weight: bold; */
217 | font-size: 15px;
218 | color: rgb(255, 255, 255);
219 | padding: 10px;
220 | }
221 | .button:hover{
222 | border: 1px solid rgba(255, 255, 255, 0.51);
223 | }
224 | .button.active{
225 | border: 1px solid rgba(255, 255, 255, 0.51);
226 | }
227 |
228 | .sidepanel{
229 | position: fixed;
230 | left: 30px;
231 | top: 200px;
232 | background: white;
233 | padding: 10px 30px 10px 30px;
234 | }
235 | .sidepanel .info{
236 | font-size: 10px;
237 | color: #666;
238 | margin: 10px 0 10px 0;
239 | text-transform: uppercase;
240 | }
241 |
242 | .sidepanel .item{
243 | margin-bottom: 5px;
244 | }
--------------------------------------------------------------------------------
/example/container.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { RichUtils } from 'draft-js';
3 | import Editor from '../src';
4 | import { Blocks, Data } from './draft';
5 | import request from 'superagent';
6 | import createToolbarPlugin from 'draft-js-toolbar-plugin';
7 | export default class Example extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | var data = localStorage.getItem("data");
12 | var oldHash = localStorage.getItem("hash");
13 | var hash = this.hash = function(s){
14 | return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
15 | }(JSON.stringify(Data))+'';
16 |
17 | if(data && oldHash === hash){
18 | try{
19 | data = JSON.parse(data);
20 | }
21 | catch(err){
22 | data = null;
23 | console.error(err);
24 | }
25 | }
26 | else{
27 | data = null;
28 | }
29 | this.state = {
30 | data: data || Data,
31 | view: 'edit',
32 | saved: false
33 | }
34 | }
35 |
36 | save(){
37 | localStorage.setItem("data", JSON.stringify(this.state.data));
38 | localStorage.setItem("hash", this.hash);
39 |
40 | this.setState({
41 | saved: true
42 | });
43 | setTimeout(()=>{
44 | this.setState({
45 | saved: false
46 | });
47 | }, 1500)
48 | }
49 |
50 | upload = (data, success, failed, progress) => {
51 | console.log(data.formData);
52 | request.post('/upload')
53 | .accept('application/json')
54 | .send(data.formData)
55 | .on('progress', ({ percent }) => {
56 | progress(percent);
57 | })
58 | .end((err, res) => {
59 | if (err) {
60 | return failed(err);
61 | }
62 | success(res.body.files, 'image');
63 | });
64 | }
65 |
66 | defaultData = (blockType) => {
67 | if (blockType === 'block-image') {
68 | return {
69 | url: '/whoa.jpg',
70 | }
71 | }
72 | return {};
73 | }
74 |
75 | renderSide(){
76 | return (
77 |
78 |
Drag & Drop one of these
79 | {Object.keys(Blocks).filter(key=>key.indexOf('header-')!==0&&key!=='unstyled').concat(['block-image', 'block-table']).map(key=> {
80 | var startDrag = (e)=>{
81 | e.dataTransfer.dropEffect = 'move';
82 | e.dataTransfer.setData("text", 'DRAFTJS_BLOCK_TYPE:'+key);
83 | }
84 | return (
85 |
86 | {key}
87 |
88 | )
89 | })}
90 |
91 | )
92 | }
93 | render() {
94 | const {data, view, saved} = this.state;
95 |
96 | return (
97 |
98 |
99 |
Draft-Wysiwyg
100 |
101 | View on Github
102 |
103 |
this.setState({view: 'json'})}>
104 | See JSON
105 |
106 |
this.setState({view: 'edit'})}>
107 | See Editor
108 |
109 |
110 | {saved ? 'Saved!' : 'Save to localstorage'}
111 |
112 |
this.setState({data:null})}>
113 | Clear
114 |
115 | {/*
this.setState({data: Draft.AddBlock(data, 'end', 'div', {}, true)})}>
116 | Horizontal+Vertical
117 |
118 |
this.setState({data: Draft.AddBlock(data, 'start', 'div2', {}, true)})}>Add
119 | Horizontal only
120 |
121 |
this.setState({data: Draft.AddBlock(data, 'start', 'youtube', {}, true)})}>Add
122 | Youtube
123 | */}
124 |
125 | {this.renderSide()}
126 |
127 |
{JSON.stringify(data, null, 3)}
128 |
129 |
130 |
131 |
132 | this.setState({data})}
133 | value={data}
134 | blockTypes={Blocks}
135 | cleanupTypes="*"
136 | sidebar={0}
137 | handleDefaultData={this.defaultData}
138 | handleUpload={this.upload}
139 | toolbar={{
140 | disableItems: ['H5'],
141 | textActions: [
142 | {
143 | button: Quote ,
144 | label: 'Quote',
145 | active: (block, editorState) => block.get('type') === 'blockquote',
146 | toggle: (block, action, editorState, setEditorState) => setEditorState(RichUtils.toggleBlockType(
147 | editorState,
148 | 'blockquote'
149 | )),
150 | }]
151 | }}/>
152 |
153 |
154 |
155 |
156 | );
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/example/draft/data.js:
--------------------------------------------------------------------------------
1 | var rawContent = {
2 | "entityMap": {
3 | "0": {
4 | "type": "link",
5 | "mutability": "MUTABLE",
6 | "data": {
7 | "href": "http://google.de"
8 | }
9 | },
10 | "1": {
11 | "type": "TOKEN",
12 | "mutability": "MUTABLE",
13 | "data": {
14 | "width": 50,
15 | "height": null,
16 | "align": "center",
17 | "caption": {
18 | "entityMap": {},
19 | "blocks": [
20 | {
21 | "key": "djcej",
22 | "text": "Resizeable block, click circle in top right corner for toolbar",
23 | "type": "unstyled",
24 | "depth": 0,
25 | "inlineStyleRanges": [
26 | {
27 | "offset": 0,
28 | "length": 62,
29 | "style": "ITALIC"
30 | }
31 | ],
32 | "entityRanges": []
33 | }
34 | ]
35 | }
36 | }
37 | },
38 | "2": {
39 | "type": "link",
40 | "mutability": "MUTABLE",
41 | "data": {
42 | "href": "http://google.de"
43 | }
44 | },
45 | "3": {
46 | "type": "block-table",
47 | "mutability": "IMMUTABLE",
48 | "data": {
49 | "rows": [
50 | [
51 | {
52 | "entityMap": {},
53 | "blocks": [
54 | {
55 | "key": "ba4l0",
56 | "text": "Insert text ...",
57 | "type": "unstyled",
58 | "depth": 0,
59 | "inlineStyleRanges": [],
60 | "entityRanges": []
61 | }
62 | ]
63 | }
64 | ],
65 | []
66 | ],
67 | "numberOfColumns": 3
68 | }
69 | },
70 | "4": {
71 | "type": "TOKEN",
72 | "mutability": "MUTABLE",
73 | "data": {
74 | "encoding": "7bit",
75 | "filename": "whoa.jpg",
76 | "mimetype": "image/jpeg",
77 | "originalname": "whoa.jpg",
78 | "size": "whoa.jpg",
79 | "url": "/whoa.jpg",
80 | "width": 30,
81 | "align": "right",
82 | "caption": {
83 | "entityMap": {},
84 | "blocks": [
85 | {
86 | "key": "6jtmg",
87 | "text": "Whoa.. you can even drop files!",
88 | "type": "unstyled",
89 | "depth": 0,
90 | "inlineStyleRanges": [],
91 | "entityRanges": []
92 | }
93 | ]
94 | }
95 | }
96 | },
97 | "5": {
98 | "type": "TOKEN",
99 | "mutability": "MUTABLE",
100 | "data": {
101 | "width": 60,
102 | "height": 190,
103 | "caption": {
104 | "entityMap": {},
105 | "blocks": [
106 | {
107 | "key": "1uac4",
108 | "text": "qwdqwdqw",
109 | "type": "unstyled",
110 | "depth": 0,
111 | "inlineStyleRanges": [
112 | {
113 | "offset": 3,
114 | "length": 2,
115 | "style": "BOLD"
116 | }
117 | ],
118 | "entityRanges": []
119 | }
120 | ]
121 | }
122 | }
123 | }
124 | },
125 | "blocks": [
126 | {
127 | "key": "1ahm2",
128 | "text": "You can edit this document",
129 | "type": "header-1",
130 | "depth": 0,
131 | "inlineStyleRanges": [],
132 | "entityRanges": [
133 | {
134 | "offset": 4,
135 | "length": 3,
136 | "key": 0
137 | }
138 | ]
139 | },
140 | {
141 | "key": "1ahm3",
142 | "text": "Try drag & drop (from desktop, sidebar or editor), resizing, toolbars, captions, whatever. WIP, buggy, tested with chrome.",
143 | "type": "header-4",
144 | "depth": 0,
145 | "inlineStyleRanges": [],
146 | "entityRanges": []
147 | },
148 | {
149 | "key": "bm3ko",
150 | "text": "",
151 | "type": "header-4",
152 | "depth": 0,
153 | "inlineStyleRanges": [],
154 | "entityRanges": []
155 | },
156 | {
157 | "key": "50cnm",
158 | "text": " ",
159 | "type": "youtube",
160 | "depth": 0,
161 | "inlineStyleRanges": [],
162 | "entityRanges": [
163 | {
164 | "offset": 0,
165 | "length": 1,
166 | "key": 1
167 | }
168 | ]
169 | },
170 | {
171 | "key": "c65i7",
172 | "text": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
173 | "type": "unstyled",
174 | "depth": 0,
175 | "inlineStyleRanges": [
176 | {
177 | "offset": 539,
178 | "length": 3,
179 | "style": "BOLD"
180 | }
181 | ],
182 | "entityRanges": [
183 | {
184 | "offset": 586,
185 | "length": 4,
186 | "key": 2
187 | }
188 | ]
189 | },
190 | {
191 | "key": "c3l3o",
192 | "text": " ",
193 | "type": "block-table",
194 | "depth": 0,
195 | "inlineStyleRanges": [],
196 | "entityRanges": [
197 | {
198 | "offset": 0,
199 | "length": 1,
200 | "key": 3
201 | }
202 | ]
203 | },
204 | {
205 | "key": "1fe43",
206 | "text": "Drop a file here, or a block, anything!",
207 | "type": "header-3",
208 | "depth": 0,
209 | "inlineStyleRanges": [],
210 | "entityRanges": []
211 | },
212 | {
213 | "key": "dhdth",
214 | "text": "And change the image captions text and styles.",
215 | "type": "header-4",
216 | "depth": 0,
217 | "inlineStyleRanges": [],
218 | "entityRanges": []
219 | },
220 | {
221 | "key": "dls1m",
222 | "text": " ",
223 | "type": "block-image",
224 | "depth": 0,
225 | "inlineStyleRanges": [],
226 | "entityRanges": [
227 | {
228 | "offset": 0,
229 | "length": 1,
230 | "key": 4
231 | }
232 | ]
233 | },
234 | {
235 | "key": "30nh",
236 | "text": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea",
237 | "type": "unstyled",
238 | "depth": 0,
239 | "inlineStyleRanges": [],
240 | "entityRanges": []
241 | },
242 | {
243 | "key": "9u0bg",
244 | "text": "Toolbars. Toolbars everywhere!",
245 | "type": "header-3",
246 | "depth": 0,
247 | "inlineStyleRanges": [],
248 | "entityRanges": []
249 | },
250 | {
251 | "key": "8vni4",
252 | "text": "Easy to implement for your custom blocks.",
253 | "type": "header-4",
254 | "depth": 0,
255 | "inlineStyleRanges": [],
256 | "entityRanges": []
257 | },
258 | {
259 | "key": "f39g7",
260 | "text": "",
261 | "type": "header-4",
262 | "depth": 0,
263 | "inlineStyleRanges": [],
264 | "entityRanges": []
265 | },
266 | {
267 | "key": "8m92g",
268 | "text": " ",
269 | "type": "resizeable-div",
270 | "depth": 0,
271 | "inlineStyleRanges": [],
272 | "entityRanges": [
273 | {
274 | "offset": 0,
275 | "length": 1,
276 | "key": 5
277 | }
278 | ]
279 | },
280 | {
281 | "key": "crak8",
282 | "text": "takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
283 | "type": "unstyled",
284 | "depth": 0,
285 | "inlineStyleRanges": [],
286 | "entityRanges": []
287 | }
288 | ]
289 | };
290 |
291 | module.exports = rawContent;
292 |
--------------------------------------------------------------------------------