mirror of
https://github.com/strapi/strapi.git
synced 2025-09-18 13:02:18 +00:00
Created components folder
This commit is contained in:
parent
8be6df24b3
commit
aed37546ff
@ -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} </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 };
|
@ -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;
|
@ -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;
|
@ -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,
|
||||
};
|
@ -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;
|
@ -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} </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;
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -12,6 +12,15 @@ export function getBlockStyle(block) {
|
||||
return styles.editorBlockquote;
|
||||
case 'code-block':
|
||||
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:
|
||||
return null;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import Drop from 'components/WysiwygDropUpload';
|
||||
import WysiwygBottomControls from 'components/WysiwygBottomControls';
|
||||
import WysiwygEditor from 'components/WysiwygEditor';
|
||||
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 { getBlockContent, getBlockStyle, getDefaultSelectionOffsets, getOffSets } from './helpers';
|
||||
import styles from './styles.scss';
|
||||
|
@ -64,24 +64,6 @@
|
||||
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 {
|
||||
height: 0;
|
||||
width: 0;
|
||||
@ -145,57 +127,3 @@
|
||||
margin-bottom: 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user