Created components folder

This commit is contained in:
cyril lopez 2018-03-26 11:14:07 +02:00
parent 8be6df24b3
commit aed37546ff
12 changed files with 396 additions and 265 deletions

View File

@ -1,192 +0,0 @@
/**
*
* Utils components for the WYSIWYG
* It includes decorators toggle buttons...
*
*/
import React from 'react';
import { CompositeDecorator, ContentState, convertFromHTML, EditorState } from 'draft-js';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { isEmpty } from 'lodash';
import Select from 'components/InputSelect';
import WysiwygEditor from 'components/WysiwygEditor';
import { SELECT_OPTIONS } from './constants';
import converter from './converter';
import { getBlockStyle } from './helpers';
import { findLinkEntities, findImageEntities } from './strategies';
import styles from './styles.scss';
/* eslint-disable react/no-multi-comp */
class CustomSelect extends React.Component {
render() {
const { isPreviewMode, headerValue, isFullscreen, handleChangeSelect } = this.context;
const selectClassName = isFullscreen ? styles.selectFullscreen : styles.editorSelect;
return (
<div className={selectClassName}>
<Select
disabled={isPreviewMode}
name="headerSelect"
onChange={handleChangeSelect}
value={headerValue}
selectOptions={SELECT_OPTIONS}
/>
</div>
);
}
}
CustomSelect.contextTypes = {
handleChangeSelect: PropTypes.func,
headerValue: PropTypes.string,
isPreviewMode: PropTypes.bool,
isFullscreen: PropTypes.bool,
};
const Image = props => {
const { alt, height, src, width } = props.contentState.getEntity(props.entityKey).getData();
return <img alt={alt} src={src} height={height} width={width} style={{ maxWidth: '100%' }} />;
};
Image.propTypes = {
contentState: PropTypes.object.isRequired,
entityKey: PropTypes.string.isRequired,
};
const Link = props => {
const { url } = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url} style={styles.link}>
{props.children}
</a>
);
};
Link.defaultProps = {
children: '',
};
Link.propTypes = {
children: PropTypes.node,
contentState: PropTypes.object.isRequired,
entityKey: PropTypes.string.isRequired,
};
const PreviewControl = ({ characters, onClick }) => (
<div className={styles.previewControlsWrapper} onClick={onClick}>
<div>
<span>{characters}&nbsp;</span>
<FormattedMessage
id="components.WysiwygBottomControls.charactersIndicators"
/>
</div>
<div className={styles.wysiwygCollapse}>
<FormattedMessage id="components.Wysiwyg.collapse" />
</div>
</div>
);
PreviewControl.defaultProps = {
characters: 0,
onClick: () => {},
};
PreviewControl.propTypes = {
characters: PropTypes.number,
onClick: PropTypes.func,
};
class PreviewWysiwyg extends React.Component {
getClassName = () => {
if (this.context.isFullscreen) {
return cn(styles.editor, styles.editorFullScreen, styles.fullscreenPreviewEditor);
}
return styles.editor;
};
previewHTML = () => {
const initHtml = isEmpty(this.context.html) ? '<p></p>' : this.context.html;
const html = converter.makeHtml(initHtml);
console.log('h', html);
const decorator = new CompositeDecorator([
{
strategy: findLinkEntities,
component: Link,
},
{
strategy: findImageEntities,
component: Image,
},
]);
const blocksFromHTML = convertFromHTML(html);
// Make sure blocksFromHTML.contentBlocks !== null
if (blocksFromHTML.contentBlocks) {
const contentState = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap,
);
return EditorState.createWithContent(contentState, decorator);
}
// Prevent errors if value is empty
return EditorState.createEmpty();
};
render() {
const { placeholder } = this.context;
return (
<div className={this.getClassName()}>
<WysiwygEditor
blockStyleFn={getBlockStyle}
editorState={this.previewHTML()}
onChange={() => {}}
placeholder={placeholder}
spellCheck
/>
<input className={styles.editorInput} value="" tabIndex="-1" />
</div>
);
}
}
PreviewWysiwyg.contextTypes = {
editorState: PropTypes.func,
html: PropTypes.string,
isFullscreen: PropTypes.bool,
placeholder: PropTypes.string,
};
const ToggleMode = props => {
const label = props.isPreviewMode
? 'components.Wysiwyg.ToggleMode.markdown'
: 'components.Wysiwyg.ToggleMode.preview';
return (
<div className={styles.toggleModeWrapper}>
<button type="button" className={styles.toggleModeButton} onClick={props.onClick}>
<FormattedMessage id={label} />
</button>
</div>
);
};
ToggleMode.defaultProps = {
isPreviewMode: false,
onClick: () => {},
};
ToggleMode.propTypes = {
isPreviewMode: PropTypes.bool,
onClick: PropTypes.func,
};
export { CustomSelect, Image, Link, PreviewControl, PreviewWysiwyg, ToggleMode };

View File

@ -0,0 +1,42 @@
/**
*
*
* CustomSelect
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'components/InputSelect';
import { SELECT_OPTIONS } from '../constants';
import styles from './styles.scss';
class CustomSelect extends React.Component {
render() {
const { isPreviewMode, headerValue, isFullscreen, handleChangeSelect } = this.context;
const selectClassName = isFullscreen ? styles.selectFullscreen : styles.editorSelect;
return (
<div className={selectClassName}>
<Select
disabled={isPreviewMode}
name="headerSelect"
onChange={handleChangeSelect}
value={headerValue}
selectOptions={SELECT_OPTIONS}
/>
</div>
);
}
}
CustomSelect.contextTypes = {
handleChangeSelect: PropTypes.func,
headerValue: PropTypes.string,
isPreviewMode: PropTypes.bool,
isFullscreen: PropTypes.bool,
};
export default CustomSelect;

View File

@ -0,0 +1,22 @@
/**
*
* Image
*
*/
import React from 'react';
import PropTypes from 'prop-types';
const Image = props => {
const { alt, height, src, width } = props.contentState.getEntity(props.entityKey).getData();
return <img alt={alt} src={src} height={height} width={width} style={{ maxWidth: '100%' }} />;
};
Image.propTypes = {
contentState: PropTypes.object.isRequired,
entityKey: PropTypes.string.isRequired,
};
export default Image;

View File

@ -0,0 +1,21 @@
/**
*
* Wysiwyg components' index
*
*/
import CustomSelect from './customSelect';
import Image from './image';
import Link from './link';
import PreviewControl from './previewControl';
import PreviewWysiwyg from './previewWysiwyg';
import ToggleMode from './toggleMode';
export {
CustomSelect,
Image,
Link,
PreviewControl,
PreviewWysiwyg,
ToggleMode,
};

View File

@ -0,0 +1,30 @@
/**
*
* Link
*
*/
import React from 'react';
import PropTypes from 'prop-types';
const Link = props => {
const { url } = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url}>
{props.children}
</a>
);
};
Link.defaultProps = {
children: '',
};
Link.propTypes = {
children: PropTypes.node,
contentState: PropTypes.object.isRequired,
entityKey: PropTypes.string.isRequired,
};
export default Link;

View File

@ -0,0 +1,36 @@
/**
*
*
* PreviewControl
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
const PreviewControl = ({ characters, onClick }) => (
<div className={styles.previewControlsWrapper} onClick={onClick}>
<div>
<span>{characters}&nbsp;</span>
<FormattedMessage
id="components.WysiwygBottomControls.charactersIndicators"
/>
</div>
<div className={styles.wysiwygCollapse}>
<FormattedMessage id="components.Wysiwyg.collapse" />
</div>
</div>
);
PreviewControl.defaultProps = {
characters: 0,
onClick: () => {},
};
PreviewControl.propTypes = {
characters: PropTypes.number,
onClick: PropTypes.func,
};
export default PreviewControl;

View File

@ -0,0 +1,82 @@
/**
*
* PreviewWysiwyg
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { CompositeDecorator, ContentState, convertFromHTML, EditorState } from 'draft-js';
import cn from 'classnames';
import { isEmpty } from 'lodash';
import WysiwygEditor from 'components/WysiwygEditor';
import converter from '../converter';
import { getBlockStyle } from '../helpers';
import { findLinkEntities, findImageEntities } from '../strategies';
import styles from './styles.scss';
import { Image, Link } from './index';
class PreviewWysiwyg extends React.Component {
getClassName = () => {
if (this.context.isFullscreen) {
return cn(styles.editor, styles.editorFullScreen, styles.fullscreenPreviewEditor);
}
return styles.editor;
};
previewHTML = () => {
const initHtml = isEmpty(this.context.html) ? '<p></p>' : this.context.html;
const html = converter.makeHtml(initHtml);
const decorator = new CompositeDecorator([
{
strategy: findLinkEntities,
component: Link,
},
{
strategy: findImageEntities,
component: Image,
},
]);
const blocksFromHTML = convertFromHTML(html);
// Make sure blocksFromHTML.contentBlocks !== null
if (blocksFromHTML.contentBlocks) {
const contentState = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap,
);
return EditorState.createWithContent(contentState, decorator);
}
// Prevent errors if value is empty
return EditorState.createEmpty();
};
render() {
const { placeholder } = this.context;
return (
<div className={this.getClassName()}>
<WysiwygEditor
blockStyleFn={getBlockStyle}
editorState={this.previewHTML()}
onChange={() => {}}
placeholder={placeholder}
spellCheck
/>
<input className={styles.editorInput} value="" tabIndex="-1" />
</div>
);
}
}
PreviewWysiwyg.contextTypes = {
editorState: PropTypes.func,
html: PropTypes.string,
isFullscreen: PropTypes.bool,
placeholder: PropTypes.string,
};
export default PreviewWysiwyg;

View File

@ -0,0 +1,117 @@
.editor {
min-height: 303px;
padding: 10px 20px 0 20px;
font-size: 16px;
margin-top: 10px;
background-color: #fff;
line-height: 18px !important;
cursor: text;
// TODO define rules for header's margin
h1, h2, h3, h4, h5, h6 {
margin: 0;
line-height: 18px !important;
}
h1 {
margin-top: 13px !important;
margin-bottom: 22px;
}
> div {
> div {
> div {
margin-bottom: 32px;
}
}
}
li {
margin-top: 0;
}
ul, ol {
margin-bottom: 18px;
}
span {
white-space: pre-line;
}
}
.editorFullScreen {
max-height: calc(100% - 100px) !important;
margin-bottom: 0;
overflow: auto;
}
.editorInput {
height: 0;
width: 0;
}
.editorSelect {
min-width: 161px;
margin-left: 15px;
margin-right: 5px;
> select {
box-shadow: 0 0 0 rgba(0,0,0,0)!important;
}
}
.fullscreenPreviewEditor {
margin-top: 9px;
padding: 10px 20px;
}
.previewControlsWrapper {
display: flex;
height: 49px;
width: 100%;
padding: 0 17px;
justify-content: space-between;
background-color: #FAFAFB;
line-height: 30px;
font-size: 12px;
font-family: Lato;
background-color: #fff;
border-bottom: 1px solid #F3F4F4;
line-height: 49px;
font-size: 13px;
>div:first-child {
>span:last-child {
font-size: 12px;
}
}
}
.selectFullscreen {
min-width: 115px;
margin-left: 15px;
> select {
min-width: 110px !important;
box-shadow: 0 0 0 rgba(0,0,0,0)!important;
}
}
.toggleModeButton {
height: 32px;
min-width: 32px;
padding-left: 10px;
padding-right: 10px;
border: 1px solid rgba(16,22,34,0.10);
border-radius: 3px;
background: #F3F4F4;
font-size: 13px;
font-weight: 500;
text-align: center;
cursor: pointer;
}
.toggleModeWrapper {
margin-left: auto;
margin-right: 15px;
padding-top: 8px;
}
.wysiwygCollapse {
&:after {
content: '\f066';
font-family: FontAwesome;
margin-left: 8px;
}
}

View File

@ -0,0 +1,36 @@
/**
*
*
* ToggleMode
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import styles from './styles.scss';
const ToggleMode = props => {
const label = props.isPreviewMode
? 'components.Wysiwyg.ToggleMode.markdown'
: 'components.Wysiwyg.ToggleMode.preview';
return (
<div className={styles.toggleModeWrapper}>
<button type="button" className={styles.toggleModeButton} onClick={props.onClick}>
<FormattedMessage id={label} />
</button>
</div>
);
};
ToggleMode.defaultProps = {
isPreviewMode: false,
onClick: () => {},
};
ToggleMode.propTypes = {
isPreviewMode: PropTypes.bool,
onClick: PropTypes.func,
};
export default ToggleMode;

View File

@ -12,6 +12,15 @@ export function getBlockStyle(block) {
return styles.editorBlockquote; return styles.editorBlockquote;
case 'code-block': case 'code-block':
return styles.editorCodeBlock; return styles.editorCodeBlock;
case 'paragraph':
case 'unordered-list-item':
case 'ordered-list-item':
case 'header-one':
case 'header-two':
case 'header-three':
case 'header-four':
case 'header-five':
case 'header-six':
default: default:
return null; return null;
} }

View File

@ -24,7 +24,7 @@ import Drop from 'components/WysiwygDropUpload';
import WysiwygBottomControls from 'components/WysiwygBottomControls'; import WysiwygBottomControls from 'components/WysiwygBottomControls';
import WysiwygEditor from 'components/WysiwygEditor'; import WysiwygEditor from 'components/WysiwygEditor';
import request from 'utils/request'; import request from 'utils/request';
import { CustomSelect, PreviewControl, PreviewWysiwyg, ToggleMode } from './components'; import { CustomSelect, PreviewControl, PreviewWysiwyg, ToggleMode } from './components/index';
import { CONTROLS } from './constants'; import { CONTROLS } from './constants';
import { getBlockContent, getBlockStyle, getDefaultSelectionOffsets, getOffSets } from './helpers'; import { getBlockContent, getBlockStyle, getDefaultSelectionOffsets, getOffSets } from './helpers';
import styles from './styles.scss'; import styles from './styles.scss';

View File

@ -64,24 +64,6 @@
border-color: #ff203c !important; border-color: #ff203c !important;
} }
.editorSelect {
min-width: 161px;
margin-left: 15px;
margin-right: 5px;
> select {
box-shadow: 0 0 0 rgba(0,0,0,0)!important;
}
}
.selectFullscreen {
min-width: 115px;
margin-left: 15px;
> select {
min-width: 110px !important;
box-shadow: 0 0 0 rgba(0,0,0,0)!important;
}
}
.editorInput { .editorInput {
height: 0; height: 0;
width: 0; width: 0;
@ -145,57 +127,3 @@
margin-bottom: 0; margin-bottom: 0;
overflow: auto; overflow: auto;
} }
.previewControlsWrapper {
display: flex;
height: 49px;
width: 100%;
padding: 0 17px;
justify-content: space-between;
background-color: #FAFAFB;
line-height: 30px;
font-size: 12px;
font-family: Lato;
background-color: #fff;
border-bottom: 1px solid #F3F4F4;
line-height: 49px;
font-size: 13px;
>div:first-child {
>span:last-child {
font-size: 12px;
}
}
}
.fullscreenPreviewEditor {
margin-top: 9px;
padding: 10px 20px;
}
.toggleModeButton {
height: 32px;
min-width: 32px;
padding-left: 10px;
padding-right: 10px;
border: 1px solid rgba(16,22,34,0.10);
border-radius: 3px;
background: #F3F4F4;
font-size: 13px;
font-weight: 500;
text-align: center;
cursor: pointer;
}
.toggleModeWrapper {
margin-left: auto;
margin-right: 15px;
padding-top: 8px;
}
.wysiwygCollapse {
&:after {
content: '\f066';
font-family: FontAwesome;
margin-left: 8px;
}
}