diff --git a/README.md b/README.md index d1a30ab4..7f4e6b25 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ draftail [![Build Status](https://travis-ci.org/springload/draftail.svg?branch=master)](https://travis-ci.org/springload/draftail) [![Dependency Status](https://david-dm.org/springload/draftail.svg?style=flat-square)](https://david-dm.org/springload/draftail) [![devDependency Status](https://david-dm.org/springload/draftail/dev-status.svg?style=flat-square)](https://david-dm.org/springload/draftail#info=devDependencies) ========= -> A batteries-excluded rich text editor based on [Draft.js](https://facebook.github.io/draft-js/) :memo::cocktail: +> A batteries-excluded rich text editor based on [Draft.js](https://facebook.github.io/draft-js/). :memo::cocktail: This is a work in progress. It is intended to be integrated into [Wagtail](https://wagtail.io/). @@ -43,5 +43,7 @@ npm run ```sh git release x.y.z npm run dist +# Use irish-pub to check the package content. Install w/ npm install -g first. +irish-pub npm publish ``` diff --git a/lib/components/BlockControls.js b/lib/components/BlockControls.js index 4a4ecf85..af9a4201 100644 --- a/lib/components/BlockControls.js +++ b/lib/components/BlockControls.js @@ -1,6 +1,6 @@ import React from 'react'; import StyleButton from './StyleButton'; -import * as DraftUtils from '../utils/DraftUtils'; +import DraftUtils from '../utils/DraftUtils'; const BlockControls = ({ editorState, styles, onToggle }) => (
diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js index a7f11bb0..55b188dc 100644 --- a/lib/components/DraftailEditor.js +++ b/lib/components/DraftailEditor.js @@ -12,7 +12,7 @@ import { import { Map } from 'immutable'; -import * as DraftUtils from '../utils/DraftUtils'; +import DraftUtils from '../utils/DraftUtils'; // ============================================================================= // Config @@ -108,10 +108,10 @@ class DraftailEditor extends Component { } handleFocus() { - // if (this.refs.wrapper.contains(e.target)) { + // if (this.wrapperRef.contains(e.target)) { // this.setState({readOnly: false}, () => { // global.setTimeout(() => { - // this.refs.editor.focus(); + // this.editorRef.focus(); // }, 0) // }) // } else { @@ -121,9 +121,8 @@ class DraftailEditor extends Component { saveRawState() { const { editorState } = this.state; - const input = this.refs.input; - input.value = conversion.serialiseEditorState(editorState); + this.inputElt.value = conversion.serialiseEditorState(editorState); } // Sets a selection to encompass the containing entity. @@ -165,14 +164,14 @@ class DraftailEditor extends Component { const nextState = EditorState.acceptSelection(this.state.editorState, updatedSelection); this.onChange(nextState); - global.setTimeout(() => this.refs.editor.focus(), 0); + global.setTimeout(() => this.editorRef.focus(), 0); } } updateState(state) { this.onChange(state); // not sure we need this. - // setTimeout(() => this.refs.editor.focus(), 0); + // setTimeout(() => this.editorRef.focus(), 0); return true; } @@ -347,7 +346,7 @@ class DraftailEditor extends Component { global.setTimeout(() => { this.setState({ readOnly: false }, () => { global.setTimeout(() => { - this.refs.editor.focus(); + this.editorRef.focus(); }, 0); }); }, 0); @@ -499,7 +498,7 @@ class DraftailEditor extends Component { return (
{ this.wrapperRef = ref; }} className="json-text" onBlur={this.saveRawState} onClick={this.handleFocus} @@ -509,7 +508,7 @@ class DraftailEditor extends Component { {this.renderControls()} { this.editorRef = ref; }} editorState={editorState} onChange={this.onChange} readOnly={readOnly} @@ -520,7 +519,15 @@ class DraftailEditor extends Component { blockRenderMap={blockRenderMap} /> - + {this.renderTooltip()} + + {this.renderDialog()} + + { this.inputElt = ref; }} + type="hidden" + name={name} + />
); } diff --git a/lib/index.js b/lib/index.js index 6c8415ed..4b2164ff 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,3 +1,6 @@ import DraftailEditor from './components/DraftailEditor'; +import DraftUtils from './utils/DraftUtils'; export default DraftailEditor; + +export { DraftUtils }; diff --git a/lib/utils/DraftUtils.js b/lib/utils/DraftUtils.js index 49f95e28..4e840cef 100644 --- a/lib/utils/DraftUtils.js +++ b/lib/utils/DraftUtils.js @@ -15,109 +15,110 @@ import DraftUtils from 'draftjs-utils'; /** * Wrapper around draftjs-utils, with our custom functions. - * import * as DraftUtils from '../utils/DraftUtils'; + * import DraftUtils from '../utils/DraftUtils'; */ - -export const getSelectionEntity = DraftUtils.getSelectionEntity.bind(DraftUtils); - -export const getEntityRange = DraftUtils.getEntityRange.bind(DraftUtils); - -export const getEntityData = (entityKey) => { - const entity = Entity.get(entityKey); - return entity.getData(); -}; - -/** - * Returns the type of the block the current selection starts in. - */ -export const getSelectedBlockType = (editorState) => { - const selectionState = editorState.getSelection(); - const contentState = editorState.getCurrentContent(); - const startKey = selectionState.getStartKey(); - const block = contentState.getBlockForKey(startKey); - - return block.type; -}; - -/** - * Creates a selection for the entirety of an entity that can be partially selected. - */ -export const getSelectedEntitySelection = (editorState) => { - const selectionState = editorState.getSelection(); - const contentState = editorState.getCurrentContent(); - const entityKey = getSelectionEntity(editorState); - const entityRange = getEntityRange(editorState, entityKey); - const anchorKey = selectionState.getAnchorKey(); - const block = contentState.getBlockForKey(anchorKey); - const blockKey = block.getKey(); - - return new SelectionState({ - anchorOffset: entityRange.start, - anchorKey: blockKey, - focusOffset: entityRange.end, - focusKey: blockKey, - isBackward: false, - hasFocus: selectionState.getHasFocus(), - }); -}; - -// TODO Document. -export const insertBlock = (editorState, entityKey, character, blockType) => { - const contentState = editorState.getCurrentContent(); - const selectionState = editorState.getSelection(); - - const afterRemoval = Modifier.removeRange(contentState, selectionState, 'backward'); - - const targetSelection = afterRemoval.getSelectionAfter(); - const afterSplit = Modifier.splitBlock(afterRemoval, targetSelection); - const insertionTarget = afterSplit.getSelectionAfter(); - - const asAtomicBlock = Modifier.setBlockType(afterSplit, insertionTarget, blockType); - - const charData = CharacterMetadata.create({ entity: entityKey }); - - const fragmentArray = [ - new ContentBlock({ - key: genKey(), - type: blockType, - text: character, - characterList: List(Repeat(charData, character.length)), - }), - new ContentBlock({ - key: genKey(), - type: 'unstyled', - text: '', - characterList: List(), - }), - ]; - - const fragment = BlockMapBuilder.createFromArray(fragmentArray); - - const withBlock = Modifier.replaceWithFragment(asAtomicBlock, insertionTarget, fragment); - - const newContent = withBlock.merge({ - selectionBefore: selectionState, - selectionAfter: withBlock.getSelectionAfter().set('hasFocus', true), - }); - - return EditorState.push(editorState, newContent, 'insert-fragment'); -}; - -// TODO Document. -export const createEntity = (editorState, entityType, entityData, entityText, entityMutability = 'IMMUTABLE') => { - const entityKey = Entity.create(entityType, entityMutability, entityData); - - const contentState = editorState.getCurrentContent(); - const selection = editorState.getSelection(); - - let nextContentState; - - if (selection.isCollapsed()) { - nextContentState = Modifier.insertText(contentState, editorState.getSelection(), entityText, null, entityKey); - } else { - nextContentState = Modifier.replaceText(contentState, editorState.getSelection(), entityText, null, entityKey); - } - - const nextState = EditorState.push(editorState, nextContentState, 'insert'); - return nextState; +export default { + getSelectionEntity: DraftUtils.getSelectionEntity.bind(DraftUtils), + + getEntityRange: DraftUtils.getEntityRange.bind(DraftUtils), + + getEntityData: (entityKey) => { + const entity = Entity.get(entityKey); + return entity.getData(); + }, + + /** + * Returns the type of the block the current selection starts in. + */ + getSelectedBlockType: (editorState) => { + const selectionState = editorState.getSelection(); + const contentState = editorState.getCurrentContent(); + const startKey = selectionState.getStartKey(); + const block = contentState.getBlockForKey(startKey); + + return block.type; + }, + + /** + * Creates a selection for the entirety of an entity that can be partially selected. + */ + getSelectedEntitySelection: (editorState) => { + const selectionState = editorState.getSelection(); + const contentState = editorState.getCurrentContent(); + const entityKey = getSelectionEntity(editorState); + const entityRange = getEntityRange(editorState, entityKey); + const anchorKey = selectionState.getAnchorKey(); + const block = contentState.getBlockForKey(anchorKey); + const blockKey = block.getKey(); + + return new SelectionState({ + anchorOffset: entityRange.start, + anchorKey: blockKey, + focusOffset: entityRange.end, + focusKey: blockKey, + isBackward: false, + hasFocus: selectionState.getHasFocus(), + }); + }, + + // TODO Document. + insertBlock: (editorState, entityKey, character, blockType) => { + const contentState = editorState.getCurrentContent(); + const selectionState = editorState.getSelection(); + + const afterRemoval = Modifier.removeRange(contentState, selectionState, 'backward'); + + const targetSelection = afterRemoval.getSelectionAfter(); + const afterSplit = Modifier.splitBlock(afterRemoval, targetSelection); + const insertionTarget = afterSplit.getSelectionAfter(); + + const asAtomicBlock = Modifier.setBlockType(afterSplit, insertionTarget, blockType); + + const charData = CharacterMetadata.create({ entity: entityKey }); + + const fragmentArray = [ + new ContentBlock({ + key: genKey(), + type: blockType, + text: character, + characterList: List(Repeat(charData, character.length)), + }), + new ContentBlock({ + key: genKey(), + type: 'unstyled', + text: '', + characterList: List(), + }), + ]; + + const fragment = BlockMapBuilder.createFromArray(fragmentArray); + + const withBlock = Modifier.replaceWithFragment(asAtomicBlock, insertionTarget, fragment); + + const newContent = withBlock.merge({ + selectionBefore: selectionState, + selectionAfter: withBlock.getSelectionAfter().set('hasFocus', true), + }); + + return EditorState.push(editorState, newContent, 'insert-fragment'); + }, + + // TODO Document. + createEntity: (editorState, entityType, entityData, entityText, entityMutability = 'IMMUTABLE') => { + const entityKey = Entity.create(entityType, entityMutability, entityData); + + const contentState = editorState.getCurrentContent(); + const selection = editorState.getSelection(); + + let nextContentState; + + if (selection.isCollapsed()) { + nextContentState = Modifier.insertText(contentState, editorState.getSelection(), entityText, null, entityKey); + } else { + nextContentState = Modifier.replaceText(contentState, editorState.getSelection(), entityText, null, entityKey); + } + + const nextState = EditorState.push(editorState, nextContentState, 'insert'); + return nextState; + }, }; diff --git a/lib/utils/DraftUtils.test.js b/lib/utils/DraftUtils.test.js index cf6a743d..96e1959d 100644 --- a/lib/utils/DraftUtils.test.js +++ b/lib/utils/DraftUtils.test.js @@ -6,7 +6,7 @@ import { ContentState, } from 'draft-js'; -import * as DraftUtils from '../utils/DraftUtils'; +import DraftUtils from '../utils/DraftUtils'; describe('DraftUtils', () => { describe('#getEntityData', () => { diff --git a/package.json b/package.json index 99492190..1495e1e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "draftail", "version": "0.1.0", - "description": "", + "description": "A batteries-excluded rich text editor based on Draft.js", "main": "dist/index.js", "keywords": [ "draftjs", diff --git a/server/index.js b/server/index.js index d6d28cad..f6ec00dc 100644 --- a/server/index.js +++ b/server/index.js @@ -12,7 +12,7 @@ const compiler = webpack(config); const PORT = process.env.PORT || 3000; -app.use('/', express.static(path.join(__dirname, '..', 'docs'))); +app.use('/', express.static(path.join(__dirname, '..', 'examples'))); app.use(webpackDev(compiler, { noInfo: true, @@ -21,8 +21,8 @@ app.use(webpackDev(compiler, { app.use(webpackHot(compiler)); -app.get('*', (req, res) => { - res.sendFile(path.join(__dirname, '..', 'docs/index.html')); +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, '..', 'examples/index.html')); }); app.listen(PORT, 'localhost', () => { diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 0f3bd27b..bef5629b 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -7,19 +7,21 @@ const ProgressBarPlugin = require('progress-bar-webpack-plugin'); module.exports = { // See http://webpack.github.io/docs/configuration.html#devtool devtool: 'inline-source-map', - entry: [ - 'webpack-hot-middleware/client', - './examples/index', - ], + entry: { + hmr: 'webpack-hot-middleware/client', + basic: './examples/basic', + entities: './examples/entities', + }, output: { path: path.join(__dirname, 'build'), - filename: 'examples.bundle.js', + filename: '[name].bundle.js', publicPath: '/', }, plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), new ProgressBarPlugin(), + new webpack.optimize.CommonsChunkPlugin('common', 'common.bundle.js', Infinity), ], module: { loaders: [ diff --git a/webpack.config.prod.js b/webpack.config.prod.js index dcec2320..93ee6187 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -5,9 +5,9 @@ const webpack = require('webpack'); const config = require('./webpack.config.dev'); // Lose the webpack middleware -config.entry.shift(); +delete config.entry.hmr; config.watch = false; -config.devtool = ''; +config.devtool = false; config.output.path = path.join(__dirname, 'docs'); config.plugins = [