diff --git a/.eslintrc b/.eslintrc index a3519dc365..fc89a45049 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { "parser": "babel-eslint", - "plugins": [ "mocha" ], + "plugins": ["mocha"], "env": { "browser": true, "mocha": true, @@ -24,7 +24,7 @@ "import/no-webpack-loader-syntax": 0, "import/extensions": 0, "jsx-a11y/img-has-alt": 0, - "react/require-default-props": 0, + "react/require-default-props": 0 }, "settings": { "import/core-modules": [ @@ -69,7 +69,9 @@ "draft-js-undo-plugin", "draft-js-undo-plugin/lib/plugin.css", "draft-js-video-plugin", - "draft-js-video-plugin/lib/plugin.css" + "draft-js-video-plugin/lib/plugin.css", + "draft-js-divider-plugin", + "draft-js-divider-plugin/lib/plugin.css" ] } } diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index eae5cb888b..7866013910 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -41,6 +41,7 @@ module.exports = { path.join(__dirname, '..', 'draft-js-resizeable-plugin', 'src'), path.join(__dirname, '..', 'draft-js-buttons', 'src'), path.join(__dirname, '..', 'draft-js-video-plugin', 'src'), + path.join(__dirname, '..', 'draft-js-divider-plugin', 'src'), ], }, { @@ -71,6 +72,7 @@ module.exports = { path.join(__dirname, '..', 'draft-js-resizeable-plugin', 'src'), path.join(__dirname, '..', 'draft-js-buttons', 'src'), path.join(__dirname, '..', 'draft-js-video-plugin', 'src'), + path.join(__dirname, '..', 'draft-js-divider-plugin', 'src'), ], }, { test: /\.(png|jpg|gif|ico)$/, @@ -103,6 +105,7 @@ module.exports = { 'draft-js-resizeable-plugin': path.join(__dirname, '..', 'draft-js-resizeable-plugin', 'src'), 'draft-js-buttons': path.join(__dirname, '..', 'draft-js-buttons', 'src'), 'draft-js-video-plugin': path.join(__dirname, '..', 'draft-js-video-plugin', 'src'), + 'draft-js-divider-plugin': path.join(__dirname, '..', 'draft-js-divider-plugin', 'src'), react: path.join(__dirname, '..', 'node_modules', 'react'), }, } diff --git a/docs/client/components/pages/Divider/DividerWithSideToolbarEditor/editorStyles.css b/docs/client/components/pages/Divider/DividerWithSideToolbarEditor/editorStyles.css new file mode 100644 index 0000000000..7dbeb8bcfc --- /dev/null +++ b/docs/client/components/pages/Divider/DividerWithSideToolbarEditor/editorStyles.css @@ -0,0 +1,14 @@ +.editor { + box-sizing: border-box; + border: 1px solid #ddd; + cursor: text; + padding: 16px; + border-radius: 2px; + margin-bottom: 2em; + box-shadow: inset 0px 1px 8px -3px #ABABAB; + background: #fefefe; +} + +.editor :global(.public-DraftEditor-content) { + min-height: 140px; +} diff --git a/docs/client/components/pages/Divider/DividerWithSideToolbarEditor/index.js b/docs/client/components/pages/Divider/DividerWithSideToolbarEditor/index.js new file mode 100644 index 0000000000..def98711b4 --- /dev/null +++ b/docs/client/components/pages/Divider/DividerWithSideToolbarEditor/index.js @@ -0,0 +1,105 @@ +import React, { Component } from 'react'; +import { convertFromRaw, EditorState } from 'draft-js'; + +import Editor, { composeDecorators } from 'draft-js-plugins-editor'; +import createFocusPlugin from 'draft-js-focus-plugin'; +import createSideToolbarPlugin from 'draft-js-side-toolbar-plugin'; +import createDividerPlugin from 'draft-js-divider-plugin'; +// eslint-disable-next-line +import BlockTypeSelect from 'draft-js-side-toolbar-plugin/components/BlockTypeSelect'; +import editorStyles from './editorStyles.css'; + +const focusPlugin = createFocusPlugin(); + +const decorator = composeDecorators(focusPlugin.decorator); + +const dividerPlugin = createDividerPlugin({ decorator }); + +const DefaultBlockTypeSelect = ({ getEditorState, setEditorState, theme }) => ( + +); + +const sideToolbarPlugin = createSideToolbarPlugin({ + structure: [DefaultBlockTypeSelect] +}); + +const { SideToolbar } = sideToolbarPlugin; + +const plugins = [focusPlugin, dividerPlugin, sideToolbarPlugin]; + +/* eslint-disable */ +const initialState = { + entityMap: { + '0': { + type: 'divider', + mutability: 'IMMUTABLE', + data: {} + } + }, + blocks: [ + { + key: '9gm3s', + text: + 'This is a simple example for divider plugin. Click side toolbar divider button.', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {} + }, + { + key: 'ov7r', + text: ' ', + type: 'atomic', + depth: 0, + inlineStyleRanges: [], + entityRanges: [ + { + offset: 0, + length: 1, + key: 0 + } + ], + data: {} + } + ] +}; +/* eslint-enable */ + +export default class CustomImageEditor extends Component { + state = { + editorState: EditorState.createWithContent(convertFromRaw(initialState)) + }; + + onChange = (editorState) => { + this.setState({ + editorState + }); + }; + + focus = () => { + this.editor.focus(); + }; + + render() { + return ( +
+ { + this.editor = element; + }} + /> + + +
+ ); + } +} diff --git a/docs/client/components/pages/Divider/gettingStarted.js b/docs/client/components/pages/Divider/gettingStarted.js new file mode 100644 index 0000000000..431558ef8c --- /dev/null +++ b/docs/client/components/pages/Divider/gettingStarted.js @@ -0,0 +1,20 @@ +// It is important to import the Editor which accepts plugins. + +import Editor from 'draft-js-plugins-editor'; + +import createDividerPlugin from 'draft-js-divider-plugin'; +import React from 'react'; + +const dividerPlugin = createDividerPlugin(); + +// The Editor accepts an array of plugins. In this case, only the dividerPlugin +// is passed in, although it is possible to pass in multiple plugins. +const MyEditor = ({ editorState, onChange }) => ( + +); + +export default MyEditor; diff --git a/docs/client/components/pages/Divider/index.js b/docs/client/components/pages/Divider/index.js new file mode 100644 index 0000000000..d9fe3db703 --- /dev/null +++ b/docs/client/components/pages/Divider/index.js @@ -0,0 +1,116 @@ +import React, { Component } from 'react'; + +// eslint-disable-next-line import/no-unresolved +import simpleExampleCode from '!!../../../loaders/prism-loader?language=javascript!./DividerWithSideToolbarEditor'; +// eslint-disable-next-line import/no-unresolved +import simpleExampleEditorStylesCode from '!!../../../loaders/prism-loader?language=css!./DividerWithSideToolbarEditor/editorStyles.css'; + +// eslint-disable-next-line import/no-unresolved +import gettingStarted from '!!../../../loaders/prism-loader?language=javascript!./gettingStarted'; +// eslint-disable-next-line import/no-unresolved +import webpackConfig from '!!../../../loaders/prism-loader?language=javascript!./webpackConfig'; +// eslint-disable-next-line import/no-unresolved +import webpackImport from '!!../../../loaders/prism-loader?language=javascript!./webpackImport'; + +import Container from '../../shared/Container'; +import AlternateContainer from '../../shared/AlternateContainer'; +import Heading from '../../shared/Heading'; +import styles from './styles.css'; +import Code from '../../shared/Code'; +import DividerWithSideToolbarEditor from './DividerWithSideToolbarEditor'; +import ExternalLink from '../../shared/Link'; +import InlineCode from '../../shared/InlineCode'; +import SocialBar from '../../shared/SocialBar'; +import NavBar from '../../shared/NavBar'; +import Separator from '../../shared/Separator'; + +export default class App extends Component { + render() { + return ( +
+ + + + Divider + Supported Environment +
    +
  • Desktop: Yes
  • +
  • Mobile: Yes
  • +
  • Screen-reader: Yes
  • +
+
+ + Getting Started + + + + Importing the default styles +

+ The plugin ships with a default styling available at this location + in the installed package:   + +

+ Webpack Usage +
    +
  • + 1. Install Webpack loaders:   + +
  • +
  • + 2. Add the below section to Webpack config (if your config already + has a loaders array, simply add the below loader object to your + existing list. + +
  • +
  • + 3. Add the below import line to your component to tell Webpack to + inject the style to your component. + +
  • +
  • 4. Restart Webpack.
  • +
+ Browserify Usage +

+ Please help, by submiting a Pull Request to the{' '} + + documentation + . +

+
+ + Configuration Parameters +
+ theme + Object of CSS classes with the following keys. +
+
+ divider: CSS class + for the divider. +
+
+
+
+ entityType + Entity type for divider. default type is `divider` +
+
+ dividerComponent + Pass in a custom divider component to be rendered. +
+
+ + Divider + SideToolbar + Focus Example + + + + + +
+ ); + } +} diff --git a/docs/client/components/pages/Divider/styles.css b/docs/client/components/pages/Divider/styles.css new file mode 100644 index 0000000000..867d6646a9 --- /dev/null +++ b/docs/client/components/pages/Divider/styles.css @@ -0,0 +1,49 @@ +.root { + text-align: center; + padding-top: 10em; +} + +.param { + margin: 10px 0; +} + +.paramBig { + margin: 10px 0; + display: flex; +} + +.paramName { + font-weight: bold; + width: 175px; + min-width: 175px; + display: inline-block; +} + +.subParams { + margin-left: 175px; + margin-top: 10px; +} + +.subParam { + margin-bottom: 5px; + display: flex; +} + +.subParamName { + font-weight: bold; + margin-right: 5px; +} + +.list { + list-style-type: none; + padding-left: 0; +} + +.listEntry { + margin-top: 1rem; +} + +.guideCodeBlock { + margin-top: 1rem; + margin-bottom: 1rem !important; +} diff --git a/docs/client/components/pages/Divider/webpackConfig.js b/docs/client/components/pages/Divider/webpackConfig.js new file mode 100644 index 0000000000..acdf72c88c --- /dev/null +++ b/docs/client/components/pages/Divider/webpackConfig.js @@ -0,0 +1,12 @@ +module.exports = { + module: { + loaders: [ + { + test: /plugin\.css$/, + loaders: [ + 'style-loader', 'css', + ], + }, + ], + }, +}; diff --git a/docs/client/components/pages/Divider/webpackImport.js b/docs/client/components/pages/Divider/webpackImport.js new file mode 100644 index 0000000000..641db2f638 --- /dev/null +++ b/docs/client/components/pages/Divider/webpackImport.js @@ -0,0 +1 @@ +import 'draft-js-divider-plugin/lib/plugin.css'; diff --git a/docs/client/components/shared/NavBar/index.js b/docs/client/components/shared/NavBar/index.js index ac45421928..b1b91d2d2f 100644 --- a/docs/client/components/shared/NavBar/index.js +++ b/docs/client/components/shared/NavBar/index.js @@ -3,7 +3,6 @@ import { Link } from 'react-router'; import styles from './styles.css'; export default class NavBar extends Component { - render() { return (
@@ -97,7 +96,12 @@ export default class NavBar extends Component {
  • - {'Drag\'n\'Drop'} + {"Drag'n'Drop"} + +
  • +
  • + + Divider
  • diff --git a/docs/client/routes.js b/docs/client/routes.js index 583c250b8b..fb23c73c79 100644 --- a/docs/client/routes.js +++ b/docs/client/routes.js @@ -23,6 +23,7 @@ import Focus from './components/pages/Focus'; import Resizeable from './components/pages/Resizeable'; import Video from './components/pages/Video'; import DragNDrop from './components/pages/DragNDrop'; +import Divider from './components/pages/Divider'; export const routes = ( @@ -45,6 +46,7 @@ export const routes = ( + diff --git a/docs/webpack.config.base.js b/docs/webpack.config.base.js index 59d13beb3c..10be61bd38 100644 --- a/docs/webpack.config.base.js +++ b/docs/webpack.config.base.js @@ -25,9 +25,10 @@ module.exports = { 'draft-js-resizeable-plugin': path.join(__dirname, '..', 'draft-js-resizeable-plugin', 'src'), 'draft-js-buttons': path.join(__dirname, '..', 'draft-js-buttons', 'src'), 'draft-js-video-plugin': path.join(__dirname, '..', 'draft-js-video-plugin', 'src'), - react: path.join(__dirname, 'node_modules', 'react'), + 'draft-js-divider-plugin': path.join(__dirname, '..', 'draft-js-divider-plugin', 'src'), + react: path.join(__dirname, 'node_modules', 'react') }, - extensions: ['.js'], + extensions: ['.js'] }, module: { rules: [ @@ -35,8 +36,9 @@ module.exports = { // match all js files except example.js test: /^(?!.*example\.js$).*\.js$/, use: ['babel-loader'], - exclude: /node_modules\/(?!url-regex)/, - }, { + exclude: /node_modules\/(?!url-regex)/ + }, + { test: /\.js$/, loader: ['babel-loader'], include: [ @@ -61,8 +63,10 @@ module.exports = { path.join(__dirname, '..', 'draft-js-resizeable-plugin', 'src'), path.join(__dirname, '..', 'draft-js-buttons', 'src'), path.join(__dirname, '..', 'draft-js-video-plugin', 'src'), - ], - }, { + path.join(__dirname, '..', 'draft-js-divider-plugin', 'src') + ] + }, + { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]!postcss-loader' }), include: [ @@ -87,29 +91,27 @@ module.exports = { path.join(__dirname, '..', 'draft-js-resizeable-plugin', 'src'), path.join(__dirname, '..', 'draft-js-buttons', 'src'), path.join(__dirname, '..', 'draft-js-video-plugin', 'src'), - path.join(__dirname, 'client/components'), - ], - }, { + path.join(__dirname, '..', 'draft-js-divider-plugin', 'src'), + path.join(__dirname, 'client/components') + ] + }, + { test: /\.(scss|sass)$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', - use: 'css-loader?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]!postcss-loader!sass-loader', + use: 'css-loader?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]!postcss-loader!sass-loader' }), - include: [ - path.join(__dirname, '..', 'draft-js-emoji-plugin', 'src'), - ], - }, { + include: [path.join(__dirname, '..', 'draft-js-emoji-plugin', 'src')] + }, + { test: /prism\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }), - include: [ - path.join(__dirname, 'node_modules/prismjs/themes/'), - ], - }, { - test: /\.(png|jpg|gif|ico)$/, - use: [ - { loader: 'file-loader', options: { name: '[name].[ext]' } }, - ], + include: [path.join(__dirname, 'node_modules/prismjs/themes/')] }, - ], - }, + { + test: /\.(png|jpg|gif|ico)$/, + use: [{ loader: 'file-loader', options: { name: '[name].[ext]' } }] + } + ] + } }; diff --git a/draft-js-divider-plugin/.babelrc b/draft-js-divider-plugin/.babelrc new file mode 100644 index 0000000000..676f718220 --- /dev/null +++ b/draft-js-divider-plugin/.babelrc @@ -0,0 +1,16 @@ +{ + "presets": ["react", "es2015", "stage-0"], + "env": { + "production": { + "plugins": [ + [ + "babel-plugin-webpack-loaders", + { + "config": "${WEBPACK_CONFIG}", + "verbose": true + } + ] + ] + } + } +} diff --git a/draft-js-divider-plugin/.gitignore b/draft-js-divider-plugin/.gitignore new file mode 100644 index 0000000000..cdecab19d6 --- /dev/null +++ b/draft-js-divider-plugin/.gitignore @@ -0,0 +1 @@ +lib/* diff --git a/draft-js-divider-plugin/.npmignore b/draft-js-divider-plugin/.npmignore new file mode 100644 index 0000000000..edb27a0415 --- /dev/null +++ b/draft-js-divider-plugin/.npmignore @@ -0,0 +1,12 @@ +node_modules + +# sources +/src/ +webpack.config.js +.babelrc +.gitignore +README.md +CHANGELOG.md + +# NPM debug +npm-debug.log diff --git a/draft-js-divider-plugin/README.md b/draft-js-divider-plugin/README.md new file mode 100644 index 0000000000..fbfea71ba4 --- /dev/null +++ b/draft-js-divider-plugin/README.md @@ -0,0 +1,95 @@ +# DraftJS Divider Plugin + +*This is a plugin for the `draft-js-plugins-editor`.* + +## Usage + +```js +import createDividerPlugin from 'draft-js-divider-plugin'; + +const dividerPlugin = createDividerPlugin(); +``` + +It exposes `addDivider` function and `DividerButton`. + +You can use this plugin like below. + +```js +/* eslint-disable */ + +import React, { Component } from 'react'; + +import 'draft-js/dist/Draft.css'; + +import Editor, { createEditorStateWithText } from 'draft-js-plugins-editor'; + +import createSideToolbarPlugin from 'draft-js-side-toolbar-plugin'; +import BlockTypeSelect from 'draft-js-side-toolbar-plugin/lib/components/BlockTypeSelect'; +import 'draft-js-side-toolbar-plugin/lib/plugin.css'; + +import createDividerPlugin from 'draft-js-divider-plugin'; +import 'draft-js-divider-plugin/lib/plugin.css'; + +const dividerPlugin = createDividerPlugin(); + +const DefaultBlockTypeSelect = ({ getEditorState, setEditorState, theme }) => ( + +); + +const sideToolbarPlugin = createSideToolbarPlugin({ + structure: [DefaultBlockTypeSelect], +}); + +import './Editor.css'; + +const plugins = [sideToolbarPlugin, dividerPlugin]; +const { SideToolbar } = sideToolbarPlugin; + +class CustomEditor extends Component { + state = { + editorState: createEditorStateWithText(''), + }; + + onChange = editorState => { + this.setState({ + editorState, + }); + }; + + render() { + return ( +
    +
    + { + this.editor = element; + }} + /> + + +
    +
    + ); + } +} + +export default CustomEditor; +``` + +### Importing the default styles + +The plugin ships with a default styling available at this location in the installed package: +`draft-js-divider-plugin/lib/plugin.css`. + +If you want to use the default styles, you can include this stylesheet. +Otherwise you can supply your own styles via the `theme` config option. diff --git a/draft-js-divider-plugin/package.json b/draft-js-divider-plugin/package.json new file mode 100644 index 0000000000..dcb67ccf4b --- /dev/null +++ b/draft-js-divider-plugin/package.json @@ -0,0 +1,45 @@ +{ + "name": "draft-js-divider-plugin", + "version": "2.0.1", + "description": "Divider Plugin for DraftJS", + "author": { + "name": "Ilkwon Sim", + "email": "simsim0709@gmail.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/draft-js-plugins/draft-js-plugins.git" + }, + "main": "lib/index.js", + "keywords": [ + "editor", + "wysiwyg", + "draft", + "react", + "ux", + "components", + "widget", + "link", + "react-component" + ], + "scripts": { + "clean": "../node_modules/.bin/rimraf lib", + "build": "npm run clean && npm run build:js && npm run build:css", + "build:js": + "WEBPACK_CONFIG=$(pwd)/webpack.config.js BABEL_DISABLE_CACHE=1 BABEL_ENV=production NODE_ENV=production ../node_modules/.bin/babel --out-dir='lib' --ignore='__test__/*' src", + "build:css": + "node ../scripts/concatCssFiles $(pwd) && ../node_modules/.bin/rimraf lib-css", + "prepublish": "npm run build" + }, + "license": "MIT", + "dependencies": { + "decorate-component-with-props": "^1.0.2", + "prop-types": "^15.5.8", + "union-class-names": "^1.0.0" + }, + "peerDependencies": { + "draft-js": "^0.10.1", + "react": "^15.5.0 || ^16.0.0-rc", + "react-dom": "^15.5.0 || ^16.0.0-rc" + } +} diff --git a/draft-js-divider-plugin/postcss.config.js b/draft-js-divider-plugin/postcss.config.js new file mode 100644 index 0000000000..a86895c60d --- /dev/null +++ b/draft-js-divider-plugin/postcss.config.js @@ -0,0 +1,7 @@ +const autoprefixer = require('autoprefixer'); + +module.exports = { + plugins: [ + autoprefixer({ browsers: ['> 1%'] }) + ] +}; diff --git a/draft-js-divider-plugin/src/components/DefaultDivider.js b/draft-js-divider-plugin/src/components/DefaultDivider.js new file mode 100644 index 0000000000..7f99472d41 --- /dev/null +++ b/draft-js-divider-plugin/src/components/DefaultDivider.js @@ -0,0 +1,22 @@ +import unionClassNames from 'union-class-names'; +import React from 'react'; + +const Divider = ({ block, className, theme = {}, ...otherProps }) => { + const { + blockProps, // eslint-disable-line no-unused-vars + customStyleMap, // eslint-disable-line no-unused-vars + customStyleFn, // eslint-disable-line no-unused-vars + decorator, // eslint-disable-line no-unused-vars + forceSelection, // eslint-disable-line no-unused-vars + offsetKey, // eslint-disable-line no-unused-vars + selection, // eslint-disable-line no-unused-vars + tree, // eslint-disable-line no-unused-vars + contentState, + blockStyleFn, + ...elementProps + } = otherProps; + const combinedClassName = unionClassNames(theme.divider, className); + return
    ; +}; + +export default Divider; diff --git a/draft-js-divider-plugin/src/components/DividerButton.js b/draft-js-divider-plugin/src/components/DividerButton.js new file mode 100644 index 0000000000..0c880d3dd3 --- /dev/null +++ b/draft-js-divider-plugin/src/components/DividerButton.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import unionClassNames from 'union-class-names'; + +class DividerButton extends Component { + onClick = (event) => { + event.preventDefault(); + + const editorState = this.props.getEditorState(); + const newEditorState = this.props.addDivider(editorState); + + this.props.setEditorState(newEditorState); + }; + + preventBubblingUp = (event) => { + event.preventDefault(); + }; + + blockTypeIsActive = () => { + const editorState = this.props.getEditorState(); + const type = editorState + .getCurrentContent() + .getBlockForKey(editorState.getSelection().getStartKey()) + .getType(); + return type === this.props.blockType; + }; + + render() { + const { theme } = this.props; + const className = this.blockTypeIsActive() + ? unionClassNames(theme.button, theme.active) + : theme.button; + + return ( +
    + +
    + ); + } +} + +DividerButton.propTypes = { + theme: PropTypes.object, + getEditorState: PropTypes.func.isRequired, + setEditorState: PropTypes.func.isRequired, + addDivider: PropTypes.func.isRequired +}; + +DividerButton.defaultProps = { + theme: {} +}; + +export default DividerButton; diff --git a/draft-js-divider-plugin/src/dividerStyles.css b/draft-js-divider-plugin/src/dividerStyles.css new file mode 100644 index 0000000000..857e2aac10 --- /dev/null +++ b/draft-js-divider-plugin/src/dividerStyles.css @@ -0,0 +1,18 @@ +.divider { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + margin: 32px 0; + border: none; /* strip default hr styling */ + text-align: center; +} + +.divider::after { + margin-left: 48px; + color: rgba(0, 0, 0, 0.26); /* pick a color */ + font-size: 2.125rem; + letter-spacing: 48px; /* increase space between dots */ + content: '•••'; +} diff --git a/draft-js-divider-plugin/src/index.js b/draft-js-divider-plugin/src/index.js new file mode 100644 index 0000000000..17e6324dc0 --- /dev/null +++ b/draft-js-divider-plugin/src/index.js @@ -0,0 +1,53 @@ +import decorateComponentWithProps from 'decorate-component-with-props'; + +import DefaultDivider from './components/DefaultDivider'; +import DividerButton from './components/DividerButton'; + +import addDivider from './modifiers/addDivider'; + +import dividerStyles from './dividerStyles.css'; + +const defaultTheme = { + divider: dividerStyles.divider +}; + +const createDividerPlugin = ({ + entityType = 'divider', + dividerComponent = DefaultDivider, + theme = defaultTheme, + decorator +} = {}) => { + let Divider = dividerComponent; + + if (typeof decorator === 'function') { + Divider = decorator(Divider); + } + + const ThemedDivider = decorateComponentWithProps(Divider, { theme }); + + return { + blockRendererFn: (block, { getEditorState }) => { + if (block.getType() === 'atomic') { + const contentState = getEditorState().getCurrentContent(); + const entity = block.getEntityAt(0); + if (!entity) return null; + const type = contentState.getEntity(entity).getType(); + if (type === entityType) { + return { + component: ThemedDivider, + editable: false + }; + } + } + + return null; + }, + DividerButton: decorateComponentWithProps(DividerButton, { + entityType, + addDivider: addDivider(entityType) + }), + addDivider: addDivider(entityType) + }; +}; + +export default createDividerPlugin; diff --git a/draft-js-divider-plugin/src/modifiers/addDivider.js b/draft-js-divider-plugin/src/modifiers/addDivider.js new file mode 100644 index 0000000000..5fbdda6e99 --- /dev/null +++ b/draft-js-divider-plugin/src/modifiers/addDivider.js @@ -0,0 +1,21 @@ +import { EditorState, AtomicBlockUtils } from 'draft-js'; + +export default (entityType) => (editorState, data) => { + const contentState = editorState.getCurrentContent(); + const contentStateWithEntity = contentState.createEntity( + entityType, + 'IMMUTABLE', + data + ); + const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); + const newEditorState = AtomicBlockUtils.insertAtomicBlock( + editorState, + entityKey, + ' ' + ); + + return EditorState.forceSelection( + newEditorState, + newEditorState.getCurrentContent().getSelectionAfter() + ); +}; diff --git a/draft-js-divider-plugin/webpack.config.js b/draft-js-divider-plugin/webpack.config.js new file mode 100644 index 0000000000..3eeb675801 --- /dev/null +++ b/draft-js-divider-plugin/webpack.config.js @@ -0,0 +1,29 @@ +/* eslint-disable no-var */ +var path = require('path'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +module.exports = { + output: { + publicPath: '/', + libraryTarget: 'commonjs2', // necessary for the babel plugin + path: path.join(__dirname, 'lib-css'), // where to place webpack files + }, + module: { + loaders: [ + { + test: /\.css$/, + loader: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: + 'css-loader?modules&importLoaders=1&localIdentName=draftJsDividerPlugin__[local]__[hash:base64:5]!postcss-loader', + }), + }, + ], + }, + + plugins: [ + new ExtractTextPlugin({ + filename: `${path.parse(process.argv[2]).name}.css`, + }), + ], +}; diff --git a/draft-js-divider-plugin/yarn.lock b/draft-js-divider-plugin/yarn.lock new file mode 100644 index 0000000000..3b31991395 --- /dev/null +++ b/draft-js-divider-plugin/yarn.lock @@ -0,0 +1,119 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +decorate-component-with-props@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/decorate-component-with-props/-/decorate-component-with-props-1.0.2.tgz#5764d3cf6a58685a522201bad31bff0cb531e5fe" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +exec-sh@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + dependencies: + merge "^1.1.3" + +fbjs@^0.8.9: + version "0.8.15" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.15.tgz#4f0695fdfcc16c37c0b07facec8cb4c4091685b9" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + +iconv-lite@~0.4.13: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +js-tokens@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +loose-envify@^1.0.0, loose-envify@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.5.8: + version "15.5.10" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +ua-parser-js@^0.7.9: + version "0.7.14" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca" + +union-class-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-class-names/-/union-class-names-1.0.0.tgz#9259608adacc39094a2b0cfe16c78e6200617847" + +watch@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c" + dependencies: + exec-sh "^0.2.0" + minimist "^1.2.0" + +whatwg-fetch@>=0.10.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" diff --git a/publish.sh b/publish.sh index ac4e9267c4..724dc23103 100755 --- a/publish.sh +++ b/publish.sh @@ -73,3 +73,6 @@ cd .. cd draft-js-video-plugin npm publish cd .. +cd draft-js-divider-plugin +npm publish +cd .. diff --git a/scripts/renameBabelConfigs.sh b/scripts/renameBabelConfigs.sh index be88d52096..114196b56b 100755 --- a/scripts/renameBabelConfigs.sh +++ b/scripts/renameBabelConfigs.sh @@ -18,3 +18,5 @@ mv $(pwd)/../draft-js-image-plugin/.babelrc $(pwd)/../draft-js-image-plugin/tmpB mv $(pwd)/../draft-js-buttons/.babelrc $(pwd)/../draft-js-buttons/tmpBabelrc mv $(pwd)/../draft-js-video-plugin/.babelrc $(pwd)/../draft-js-video-plugin/tmpBabelrc mv $(pwd)/../draft-js-anchor-plugin/.babelrc $(pwd)/../draft-js-anchor-plugin/tmpBabelrc +mv $(pwd)/../draft-js-divider-plugin/.babelrc $(pwd)/../draft-js-divider-plugin/tmpBabelrc + diff --git a/scripts/restoreBabelConfigs.sh b/scripts/restoreBabelConfigs.sh index ca353d756a..66eab1e20f 100755 --- a/scripts/restoreBabelConfigs.sh +++ b/scripts/restoreBabelConfigs.sh @@ -19,3 +19,4 @@ mv $(pwd)/../draft-js-image-plugin/tmpBabelrc $(pwd)/../draft-js-image-plugin/.b mv $(pwd)/../draft-js-buttons/tmpBabelrc $(pwd)/../draft-js-buttons/.babelrc mv $(pwd)/../draft-js-video-plugin/tmpBabelrc $(pwd)/../draft-js-video-plugin/.babelrc mv $(pwd)/../draft-js-anchor-plugin/tmpBabelrc $(pwd)/../draft-js-anchor-plugin/.babelrc +mv $(pwd)/../draft-js-divider-plugin/tmpBabelrc $(pwd)/../draft-js-divider-plugin/.babelrc diff --git a/stories/Divider/DividerWithSideToolbarEditor/editorStyles.css b/stories/Divider/DividerWithSideToolbarEditor/editorStyles.css new file mode 100644 index 0000000000..7dbeb8bcfc --- /dev/null +++ b/stories/Divider/DividerWithSideToolbarEditor/editorStyles.css @@ -0,0 +1,14 @@ +.editor { + box-sizing: border-box; + border: 1px solid #ddd; + cursor: text; + padding: 16px; + border-radius: 2px; + margin-bottom: 2em; + box-shadow: inset 0px 1px 8px -3px #ABABAB; + background: #fefefe; +} + +.editor :global(.public-DraftEditor-content) { + min-height: 140px; +} diff --git a/stories/Divider/DividerWithSideToolbarEditor/index.js b/stories/Divider/DividerWithSideToolbarEditor/index.js new file mode 100644 index 0000000000..def98711b4 --- /dev/null +++ b/stories/Divider/DividerWithSideToolbarEditor/index.js @@ -0,0 +1,105 @@ +import React, { Component } from 'react'; +import { convertFromRaw, EditorState } from 'draft-js'; + +import Editor, { composeDecorators } from 'draft-js-plugins-editor'; +import createFocusPlugin from 'draft-js-focus-plugin'; +import createSideToolbarPlugin from 'draft-js-side-toolbar-plugin'; +import createDividerPlugin from 'draft-js-divider-plugin'; +// eslint-disable-next-line +import BlockTypeSelect from 'draft-js-side-toolbar-plugin/components/BlockTypeSelect'; +import editorStyles from './editorStyles.css'; + +const focusPlugin = createFocusPlugin(); + +const decorator = composeDecorators(focusPlugin.decorator); + +const dividerPlugin = createDividerPlugin({ decorator }); + +const DefaultBlockTypeSelect = ({ getEditorState, setEditorState, theme }) => ( + +); + +const sideToolbarPlugin = createSideToolbarPlugin({ + structure: [DefaultBlockTypeSelect] +}); + +const { SideToolbar } = sideToolbarPlugin; + +const plugins = [focusPlugin, dividerPlugin, sideToolbarPlugin]; + +/* eslint-disable */ +const initialState = { + entityMap: { + '0': { + type: 'divider', + mutability: 'IMMUTABLE', + data: {} + } + }, + blocks: [ + { + key: '9gm3s', + text: + 'This is a simple example for divider plugin. Click side toolbar divider button.', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {} + }, + { + key: 'ov7r', + text: ' ', + type: 'atomic', + depth: 0, + inlineStyleRanges: [], + entityRanges: [ + { + offset: 0, + length: 1, + key: 0 + } + ], + data: {} + } + ] +}; +/* eslint-enable */ + +export default class CustomImageEditor extends Component { + state = { + editorState: EditorState.createWithContent(convertFromRaw(initialState)) + }; + + onChange = (editorState) => { + this.setState({ + editorState + }); + }; + + focus = () => { + this.editor.focus(); + }; + + render() { + return ( +
    + { + this.editor = element; + }} + /> + + +
    + ); + } +} diff --git a/stories/index.js b/stories/index.js index 8a781d4c1c..6bd75b8860 100644 --- a/stories/index.js +++ b/stories/index.js @@ -76,6 +76,9 @@ import CustomToolbarEditor from './StaticToolbar/CustomToolbarEditor'; import ThemedToolbarEditor from './StaticToolbar/ThemedToolbarEditor'; import SimpleToolbarEditor from './StaticToolbar/SimpleToolbarEditor'; +// Divider +import DividerWithSideToolbarEditor from './Divider/DividerWithSideToolbarEditor'; + storiesOf('Alignment Plugin', module) .add('Editor with Alignment Plugin', () => ) .add('Editor with custom themed Alignment Plugin', () => ); @@ -146,3 +149,6 @@ storiesOf('StaticToolbar Plugin') .add('CustomToolbarEditor', () => ) .add('Simple toolbar editor', () => ) .add('ThemedToolbarEditor', () => ); + +storiesOf('Divider Plugin') + .add('Divider with SideToolbar', () => );