migrate markdown widget

This commit is contained in:
Shawn Erquhart 2018-07-24 21:46:04 -04:00
parent 3f47fe6dbf
commit f1a2eb33b4
62 changed files with 411 additions and 538 deletions

View File

@ -27,15 +27,12 @@
"gray-matter": "^3.0.6",
"history": "^4.7.2",
"immutable": "^3.7.6",
"is-hotkey": "^0.1.1",
"js-base64": "^2.1.9",
"js-yaml": "^3.10.0",
"jwt-decode": "^2.1.0",
"lib": "^3.0.2",
"localforage": "^1.4.2",
"lodash": "^4.17.10",
"mdast-util-definitions": "^1.2.2",
"mdast-util-to-string": "^1.0.4",
"moment": "^2.11.2",
"netlify-cms-editor-component-image": "2.0.0-alpha.0",
"netlify-cms-lib-auth": "2.0.0-alpha.0",
@ -65,30 +62,14 @@
"react-topbar-progress-indicator": "^2.0.0",
"react-transition-group": "^2.2.1",
"react-waypoint": "^7.1.0",
"recompose": "^0.27.1",
"redux": "^3.3.1",
"redux-notifications": "^4.0.1",
"redux-optimist": "^0.0.2",
"redux-thunk": "^1.0.3",
"rehype-parse": "^3.1.0",
"rehype-remark": "^2.0.0",
"rehype-stringify": "^3.0.0",
"remark-parse": "^3.0.1",
"remark-rehype": "^2.0.0",
"remark-stringify": "^3.0.1",
"sanitize-filename": "^1.6.1",
"semaphore": "^1.0.5",
"slate": "^0.30.0",
"slate-edit-list": "^0.10.1",
"slate-edit-table": "^0.12.0",
"slate-plain-serializer": "^0.4.0",
"slate-react": "0.10.11",
"slate-soft-break": "^0.6.0",
"toml-j0.4": "^1.1.1",
"tomlify-j0.4": "^3.0.0-alpha.0",
"unified": "^6.1.4",
"unist-builder": "^1.0.2",
"unist-util-visit-parents": "^1.1.1",
"url": "^0.11.0",
"uuid": "^3.1.0",
"what-input": "^5.0.3"

View File

@ -10,11 +10,11 @@ import { FileControl, FilePreview } from 'netlify-cms-widget-file';
import { ImageControl, ImagePreview } from 'netlify-cms-widget-image';
import { ListControl, ListPreview } from 'netlify-cms-widget-list';
import { ObjectControl, ObjectPreview } from 'netlify-cms-widget-object';
import { MarkdownControl, MarkdownPreview } from 'netlify-cms-widget-markdown';
import { StringControl, StringPreview } from 'netlify-cms-widget-string';
// import { NumberControl, NumberPreview } from 'netlify-cms-widget-number';
// import { TextControl, TextPreview } from 'netlify-cms-widget-text';
// import { SelectControl, SelectPreview } from 'netlify-cms-widget-select';
// import { MarkdownControl, MarkdownPreview } from 'netlify-cms-widget-markdown';
// import { RelationControl, RelationPreview } from 'netlify-cms-widget-relation';
import image from 'netlify-cms-editor-component-image';
@ -28,11 +28,11 @@ registerWidget('datetime', DateTimeControl, DateTimePreview);
registerWidget('file', FileControl, FilePreview);
registerWidget('image', ImageControl, ImagePreview);
registerWidget('list', ListControl, ListPreview);
registerWidget('markdown', MarkdownControl, MarkdownPreview);
registerWidget('object', ObjectControl, ObjectPreview);
registerWidget('string', StringControl, StringPreview);
// registerWidget('text', TextControl, TextPreview);
// registerWidget('number', NumberControl, NumberPreview);
// registerWidget('markdown', MarkdownControl, MarkdownPreview);
// registerWidget('select', SelectControl, SelectPreview);
// registerWidget('relation', RelationControl, RelationPreview);
registerEditorComponent(image);

View File

@ -24,8 +24,6 @@ import {
deleteUnpublishedEntry
} from 'Actions/editorialWorkflow';
import { deserializeValues } from 'Lib/serializeEntryValues';
import { addAsset } from 'Actions/media';
import { openMediaLibrary, removeInsertedMedia } from 'Actions/mediaLibrary';
import { selectEntry, selectUnpublishedEntry, getAsset } from 'Reducers';
import { selectFields } from 'Reducers/collections';
import { status } from 'Constants/publishModes';
@ -40,7 +38,6 @@ const navigateToEntry = (collectionName, slug) => navigateCollection(`${collecti
class Editor extends React.Component {
static propTypes = {
addAsset: PropTypes.func.isRequired,
boundGetAsset: PropTypes.func.isRequired,
changeDraftField: PropTypes.func.isRequired,
changeDraftFieldValidation: PropTypes.func.isRequired,
@ -49,14 +46,11 @@ class Editor extends React.Component {
createEmptyDraft: PropTypes.func.isRequired,
discardDraft: PropTypes.func.isRequired,
entry: ImmutablePropTypes.map,
mediaPaths: ImmutablePropTypes.map.isRequired,
entryDraft: ImmutablePropTypes.map.isRequired,
loadEntry: PropTypes.func.isRequired,
persistEntry: PropTypes.func.isRequired,
deleteEntry: PropTypes.func.isRequired,
showDelete: PropTypes.bool.isRequired,
openMediaLibrary: PropTypes.func.isRequired,
removeInsertedMedia: PropTypes.func.isRequired,
fields: ImmutablePropTypes.list.isRequired,
slug: PropTypes.string,
newEntry: PropTypes.bool.isRequired,
@ -268,14 +262,10 @@ class Editor extends React.Component {
entry,
entryDraft,
fields,
mediaPaths,
boundGetAsset,
collection,
changeDraftField,
changeDraftFieldValidation,
openMediaLibrary,
addAsset,
removeInsertedMedia,
user,
hasChanged,
displayUrl,
@ -303,12 +293,8 @@ class Editor extends React.Component {
fields={fields}
fieldsMetaData={entryDraft.get('fieldsMetaData')}
fieldsErrors={entryDraft.get('fieldsErrors')}
mediaPaths={mediaPaths}
onChange={changeDraftField}
onValidate={changeDraftFieldValidation}
onOpenMediaLibrary={openMediaLibrary}
onAddAsset={addAsset}
onRemoveInsertedMedia={removeInsertedMedia}
onPersist={this.handlePersistEntry}
onDelete={this.handleDeleteEntry}
onDeleteUnpublishedChanges={this.handleDeleteUnpublishedChanges}
@ -339,7 +325,6 @@ function mapStateToProps(state, ownProps) {
const fields = selectFields(collection, slug);
const entry = newEntry ? null : selectEntry(state, collectionName, slug);
const boundGetAsset = getAsset.bind(null, state);
const mediaPaths = mediaLibrary.get('controlMedia');
const user = auth && auth.get('user');
const hasChanged = entryDraft.get('hasChanged');
const displayUrl = config.get('display_url');
@ -353,7 +338,6 @@ function mapStateToProps(state, ownProps) {
collections,
newEntry,
entryDraft,
mediaPaths,
boundGetAsset,
fields,
slug,
@ -373,9 +357,6 @@ export default connect(
{
changeDraftField,
changeDraftFieldValidation,
openMediaLibrary,
removeInsertedMedia,
addAsset,
loadEntry,
loadEntries,
createDraftFromEntry,

View File

@ -1,8 +1,12 @@
import React from 'react';
import styled, { css, cx } from 'react-emotion';
import { partial, uniqueId } from 'lodash';
import { connect } from 'react-redux';
import { colors, colorsRaw, transitions, lengths, borders } from 'netlify-cms-ui-default';
import { resolveWidget } from 'Lib/registry';
import { resolveWidget, getEditorComponents } from 'Lib/registry';
import { addAsset } from 'Actions/media';
import { openMediaLibrary, removeInsertedMedia } from 'Actions/mediaLibrary';
import { getAsset } from 'Reducers';
import Widget from './Widget';
const styles = {
@ -100,7 +104,7 @@ const ControlErrorsList = styled.ul`
export default class EditorControl extends React.Component {
class EditorControl extends React.Component {
state = {
activeLabel: false,
};
@ -112,11 +116,11 @@ export default class EditorControl extends React.Component {
fieldsMetaData,
fieldsErrors,
mediaPaths,
getAsset,
boundGetAsset,
onChange,
onOpenMediaLibrary,
onAddAsset,
onRemoveInsertedMedia,
openMediaLibrary,
addAsset,
removeInsertedMedia,
onValidate,
processControlRef,
} = this.props;
@ -165,18 +169,34 @@ export default class EditorControl extends React.Component {
metadata={metadata}
onChange={(newValue, newMetadata) => onChange(fieldName, newValue, newMetadata)}
onValidate={onValidate && partial(onValidate, fieldName)}
onOpenMediaLibrary={onOpenMediaLibrary}
onRemoveInsertedMedia={onRemoveInsertedMedia}
onAddAsset={onAddAsset}
getAsset={getAsset}
onOpenMediaLibrary={openMediaLibrary}
onRemoveInsertedMedia={removeInsertedMedia}
onAddAsset={addAsset}
getAsset={boundGetAsset}
hasActiveStyle={this.state.styleActive}
setActiveStyle={() => this.setState({ styleActive: true })}
setInactiveStyle={() => this.setState({ styleActive: false })}
resolveWidget={resolveWidget}
getEditorComponents={getEditorComponents}
ref={processControlRef && partial(processControlRef, fieldName)}
editorControl={EditorControl}
editorControl={ConnectedEditorControl}
/>
</ControlContainer>
);
}
}
const mapStateToProps = (state, ownProps) => ({
mediaPaths: state.mediaLibrary.get('controlMedia'),
boundGetAsset: getAsset.bind(null, state),
});
const mapDispatchToProps = {
openMediaLibrary,
removeInsertedMedia,
addAsset,
};
const ConnectedEditorControl = connect(mapStateToProps, mapDispatchToProps)(EditorControl);
export default ConnectedEditorControl;

View File

@ -36,12 +36,7 @@ export default class ControlPane extends React.Component {
entry,
fieldsMetaData,
fieldsErrors,
mediaPaths,
getAsset,
onChange,
onOpenMediaLibrary,
onAddAsset,
onRemoveInsertedMedia,
onValidate,
} = this.props;
@ -62,12 +57,7 @@ export default class ControlPane extends React.Component {
value={entry.getIn(['data', field.get('name')])}
fieldsMetaData={fieldsMetaData}
fieldsErrors={fieldsErrors}
mediaPaths={mediaPaths}
getAsset={getAsset}
onChange={onChange}
onOpenMediaLibrary={onOpenMediaLibrary}
onAddAsset={onAddAsset}
onRemoveInsertedMedia={onRemoveInsertedMedia}
onValidate={onValidate}
processControlRef={this.processControlRef}
/>
@ -83,11 +73,6 @@ ControlPane.propTypes = {
fields: ImmutablePropTypes.list.isRequired,
fieldsMetaData: ImmutablePropTypes.map.isRequired,
fieldsErrors: ImmutablePropTypes.map.isRequired,
mediaPaths: ImmutablePropTypes.map.isRequired,
getAsset: PropTypes.func.isRequired,
onOpenMediaLibrary: PropTypes.func.isRequired,
onAddAsset: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onValidate: PropTypes.func.isRequired,
onRemoveInsertedMedia: PropTypes.func.isRequired,
};

View File

@ -40,6 +40,7 @@ export default class Widget extends Component {
onRemoveInsertedMedia: PropTypes.func.isRequired,
getAsset: PropTypes.func.isRequired,
resolveWidget: PropTypes.func.isRequired,
getEditorComponents: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps) {
@ -192,6 +193,7 @@ export default class Widget extends Component {
editorControl,
uniqueFieldId,
resolveWidget,
getEditorComponents,
} = this.props;
return React.createElement(controlComponent, {
field,
@ -216,6 +218,7 @@ export default class Widget extends Component {
hasActiveStyle,
editorControl,
resolveWidget,
getEditorComponents,
});
}
}

View File

@ -152,7 +152,6 @@ class EditorInterface extends Component {
fields,
fieldsMetaData,
fieldsErrors,
mediaPaths,
getAsset,
onChange,
enableSave,
@ -162,9 +161,6 @@ class EditorInterface extends Component {
onChangeStatus,
onPublish,
onValidate,
onOpenMediaLibrary,
onAddAsset,
onRemoveInsertedMedia,
user,
hasChanged,
displayUrl,
@ -188,13 +184,8 @@ class EditorInterface extends Component {
fields={fields}
fieldsMetaData={fieldsMetaData}
fieldsErrors={fieldsErrors}
mediaPaths={mediaPaths}
getAsset={getAsset}
onChange={onChange}
onValidate={onValidate}
onOpenMediaLibrary={onOpenMediaLibrary}
onAddAsset={onAddAsset}
onRemoveInsertedMedia={onRemoveInsertedMedia}
ref={c => this.controlPaneRef = c} // eslint-disable-line
/>
</ControlPaneContainer>
@ -283,10 +274,7 @@ EditorInterface.propTypes = {
fields: ImmutablePropTypes.list.isRequired,
fieldsMetaData: ImmutablePropTypes.map.isRequired,
fieldsErrors: ImmutablePropTypes.map.isRequired,
mediaPaths: ImmutablePropTypes.map.isRequired,
getAsset: PropTypes.func.isRequired,
onOpenMediaLibrary: PropTypes.func.isRequired,
onAddAsset: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onValidate: PropTypes.func.isRequired,
onPersist: PropTypes.func.isRequired,
@ -296,7 +284,6 @@ EditorInterface.propTypes = {
onDeleteUnpublishedChanges: PropTypes.func.isRequired,
onPublish: PropTypes.func.isRequired,
onChangeStatus: PropTypes.func.isRequired,
onRemoveInsertedMedia: PropTypes.func.isRequired,
user: ImmutablePropTypes.map,
hasChanged: PropTypes.bool,
displayUrl: PropTypes.string,

View File

@ -1,13 +0,0 @@
@import "./Object/Object.css";
@import "./List/List.css";
@import "./withMedia/withMedia.css";
@import "./Image/Image.css";
@import "./File/FileControl.css";
@import "./Markdown/Markdown.css";
@import "./Boolean/Boolean.css";
@import "./Relation/Relation.css";
@import "./DateTime/DateTime.css";
:root {
--widgetNestDistance: 14px;
}

View File

@ -1,4 +0,0 @@
@import "./MarkdownControl/RawEditor/index.css";
@import "./MarkdownControl/Toolbar/Toolbar.css";
@import "./MarkdownControl/Toolbar/ToolbarButton.css";
@import "./MarkdownControl/VisualEditor/index.css";

View File

@ -1,15 +0,0 @@
.nc-rawEditor-rawWrapper {
position: relative;
}
.nc-rawEditor-rawEditor {
position: relative;
overflow: hidden;
overflow-x: auto;
min-height: var(--richTextEditorMinHeight);
font-family: var(--fontFamilyMono);
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: 0;
margin-top: calc(-1 * var(--stickyDistanceBottom));
}

View File

@ -1,48 +0,0 @@
.nc-toolbar-Toolbar {
background-color: var(--textFieldBorderColor);
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 11px 14px;
min-height: 58px;
transition: background-color var(--transition), color var(--transition);
}
.nc-markdownWidget-toolbar-toggle {
flex-shrink: 0;
display: flex;
align-items: center;
font-size: 14px;
margin: 0 10px;
}
.nc-markdownWidget-toolbar-toggle-label {
display: inline-block;
text-align: center;
white-space: nowrap;
line-height: 20px;
}
.nc-markdownWidget-toolbar-toggle-label-active {
font-weight: 600;
color: #3a69c7;
}
.nc-toolbar-ToolbarActive {
background-color: var(--colorActive);
color: var(--colorTextLight);
& .nc-markdownWidget-toolbar-toggle-label {
color: var(--colorTextLight);
}
& .nc-markdownWidget-toolbar-toggle-background {
background-color: var(--textFieldBorderColor);
}
}
.nc-toolbar-dropdown {
display: inline-block;
position: relative;
}

View File

@ -1,22 +0,0 @@
.nc-toolbarButton-button {
display: inline-block;
padding: 6px;
border: none;
background-color: transparent;
font-size: 16px;
color: inherit;
cursor: pointer;
&:disabled {
cursor: auto;
opacity: 0.5;
}
& .nc-icon {
display: block;
}
}
.nc-toolbarButton-active {
color: #1e2532;
}

View File

@ -1,22 +0,0 @@
.nc-visualEditor-shortcode {
border-radius: var(--borderRadius);
border: 2px solid var(--textFieldBorderColor);
margin: 12px 0;
padding: 14px;
}
.nc-visualEditor-shortcode-topBar {
background-color: var(--textFieldBorderColor);
margin: calc(-1 * var(--widgetNestDistance)) calc(-1 * var(--widgetNestDistance)) 0;
border-radius: 0;
}
.nc-visualEditor-shortcode-collapsed {
background-color: var(--textFieldBorderColor);
cursor: pointer;
}
.nc-visualEditor-shortcode-collapsedTitle {
padding: 8px;
color: var(--controlLabelColor);
}

View File

@ -1,130 +0,0 @@
@import './Shortcode.css';
:root {
--stickyDistanceBottom: 100px;
}
.nc-visualEditor-editorControlBar {
z-index: 1;
position: sticky;
top: 0;
margin-bottom: var(--stickyDistanceBottom);
}
.nc-visualEditor-wrapper {
position: relative;
}
.nc-visualEditor-editor {
position: relative;
overflow: hidden;
overflow-x: auto;
min-height: var(--richTextEditorMinHeight);
font-family: var(--fontFamilyPrimary);
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: 0;
margin-top: calc(-1 * var(--stickyDistanceBottom));
}
.nc-visualEditor-editor h1 {
font-size: 32px;
margin-top: 16px;
}
.nc-visualEditor-editor h2 {
font-size: 24px;
margin-top: 12px;
}
.nc-visualEditor-editor h3 {
font-size: 20px;
margin-top: 8px;
}
.nc-visualEditor-editor h4 {
font-size: 18px;
margin-top: 8px;
}
.nc-visualEditor-editor h5,
.nc-visualEditor-editor h6 {
font-size: 16px;
margin-top: 8px;
}
.nc-visualEditor-editor h1,
.nc-visualEditor-editor h2,
.nc-visualEditor-editor h3,
.nc-visualEditor-editor h4,
.nc-visualEditor-editor h5,
.nc-visualEditor-editor h6 {
font-weight: 700;
line-height: 1;
}
.nc-visualEditor-editor p,
.nc-visualEditor-editor pre,
.nc-visualEditor-editor blockquote,
.nc-visualEditor-editor ul,
.nc-visualEditor-editor ol {
margin-top: 16px;
margin-bottom: 16px;
}
.nc-visualEditor-editor a {
text-decoration: underline;
}
.nc-visualEditor-editor hr {
border: 1px solid;
margin-bottom: 16px;
}
.nc-visualEditor-editor li > p {
margin: 0;
}
.nc-visualEditor-editor ul,
.nc-visualEditor-editor ol {
padding-left: 30px;
}
.nc-visualEditor-editor pre {
white-space: pre-wrap;
}
.nc-visualEditor-editor pre > code {
display: block;
width: 100%;
overflow-y: auto;
background-color: #000;
color: #ccc;
border-radius: var(--borderRadius);
padding: 10px;
}
.nc-visualEditor-editor code {
background-color: var(--colorBackground);
border-radius: var(--borderRadius);
padding: 0 2px;
font-size: 85%;
}
.nc-visualEditor-editor blockquote {
padding-left: 16px;
border-left: 3px solid var(--colorBackground);
margin-left: 0;
margin-right: 0;
}
.nc-visualEditor-editor table {
border-collapse: collapse;
}
.nc-visualEditor-editor td,
.nc-visualEditor-editor th {
border: 2px solid black;
padding: 8px;
text-align: left;
}

View File

@ -7,14 +7,11 @@ import TextControl from './Text/TextControl';
import TextPreview from './Text/TextPreview';
import SelectControl from './Select/SelectControl';
import SelectPreview from './Select/SelectPreview';
import MarkdownControl from './Markdown/MarkdownControl';
import MarkdownPreview from './Markdown/MarkdownPreview';
import RelationControl from './Relation/RelationControl';
import RelationPreview from './Relation/RelationPreview';
registerWidget('text', TextControl, TextPreview);
registerWidget('number', NumberControl, NumberPreview);
registerWidget('markdown', MarkdownControl, MarkdownPreview);
registerWidget('select', SelectControl, SelectPreview);
registerWidget('relation', RelationControl, RelationPreview);
registerWidget('unknown', UnknownControl, UnknownPreview);

View File

@ -1,5 +1,5 @@
import { Map } from 'immutable';
import { newEditorPlugin } from 'EditorWidgets/Markdown/MarkdownControl/plugins';
import EditorComponent from 'ValueObjects/EditorComponent'
/**
* Global Registry Object
@ -76,7 +76,7 @@ export function resolveWidget(name) {
* Markdown Editor Custom Components
*/
export function registerEditorComponent(component) {
const plugin = newEditorPlugin(component);
const plugin = EditorComponent(component);
registry.editorComponents = registry.editorComponents.set(plugin.get('id'), plugin);
};
export function getEditorComponents() {

View File

@ -36,7 +36,7 @@ class Plugin extends Component { // eslint-disable-line
}
}
export function newEditorPlugin(config) {
export default function createEditorComponent(config) {
const configObj = new EditorComponent({
id: config.id || config.label.replace(/[^A-Z0-9]+/ig, '_'),
label: config.label,

View File

@ -42,6 +42,7 @@ const Toggle = ({
renderBackground,
onFocus,
onBlur,
className,
Container = ToggleContainer,
Background = ToggleBackground,
Handle = ToggleHandle,
@ -53,6 +54,7 @@ const Toggle = ({
aria-checked={on.toString()}
onFocus={onFocus}
onBlur={onBlur}
className={className}
{...getElementTogglerProps()}
>
<Background isActive={on}/>

View File

@ -257,18 +257,13 @@ export default class ListControl extends React.Component {
/>
<NestedObjectLabel collapsed={collapsed}>{this.objectLabel(item)}</NestedObjectLabel>
<ObjectControl
value={item}
field={field}
onChangeObject={this.handleChangeFor(index)}
getAsset={getAsset}
onOpenMediaLibrary={onOpenMediaLibrary}
mediaPaths={mediaPaths}
onAddAsset={onAddAsset}
onRemoveInsertedMedia={onRemoveInsertedMedia}
classNameWrapper={cx(
classNameWrapper,
{ [styles.collapsedObjectControl]: collapsed },
)}
value={item}
field={field}
onChangeObject={this.handleChangeFor(index)}
editorControl={editorControl}
resolveWidget={resolveWidget}
forList

View File

@ -0,0 +1,53 @@
{
"name": "netlify-cms-widget-markdown",
"description": "Widget for editing markdown in Netlify CMS.",
"version": "2.0.0-alpha.0",
"main": "dist/netlify-cms-widget-markdown.js",
"license": "MIT",
"keywords": [
"netlify",
"netlify-cms",
"widget",
"markdown",
"editor"
],
"sideEffects": false,
"scripts": {
"watch": "webpack -w",
"build": "webpack"
},
"dependencies": {
"is-hotkey": "^0.1.1",
"mdast-util-definitions": "^1.2.2",
"mdast-util-to-string": "^1.0.4",
"rehype-parse": "^3.1.0",
"rehype-remark": "^2.0.0",
"rehype-stringify": "^3.0.0",
"remark-parse": "^3.0.1",
"remark-rehype": "^2.0.0",
"remark-stringify": "^3.0.1",
"slate": "^0.30.0",
"slate-edit-list": "^0.10.1",
"slate-edit-table": "^0.12.0",
"slate-plain-serializer": "^0.4.0",
"slate-react": "0.10.11",
"slate-soft-break": "^0.6.0",
"unified": "^6.1.4",
"unist-builder": "^1.0.2",
"unist-util-visit-parents": "^1.1.1"
},
"devDependencies": {
"webpack": "^4.16.1",
"webpack-cli": "^3.1.0"
},
"peerDependencies": {
"immutable": "^3.7.6",
"lodash": "^4.17.10",
"netlify-cms-ui-default": "^2.0.0-alpha.0",
"prop-types": "^15.5.10",
"react": "^16.4.1",
"react-dom": "^16.0.0",
"react-emotion": "^9.2.5",
"react-immutable-proptypes": "^2.1.0"
}
}

View File

@ -1,10 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import React from 'react';
import styled, { css, cx } from 'react-emotion';
import { Editor as Slate } from 'slate-react';
import Plain from 'slate-plain-serializer';
import { debounce } from 'lodash';
import Toolbar from 'EditorWidgets/Markdown/MarkdownControl/Toolbar/Toolbar';
import { lengths, fonts } from 'netlify-cms-ui-default';
import { editorStyleVars, EditorControlBar } from '../styles';
import Toolbar from './Toolbar';
const styles = {
slateRaw: css`
position: relative;
overflow: hidden;
overflow-x: auto;
min-height: ${lengths.richTextEditorMinHeight};
font-family: ${fonts.mono};
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: 0;
margin-top: -${editorStyleVars.stickyDistanceBottom};
`,
};
const RawEditorContainer = styled.div`
position: relative;
`;
export default class RawEditor extends React.Component {
constructor(props) {
@ -53,23 +74,22 @@ export default class RawEditor extends React.Component {
render() {
const { className, field } = this.props;
return (
<div className="nc-rawEditor-rawWrapper">
<div className="nc-visualEditor-editorControlBar">
<RawEditorContainer>
<EditorControlBar>
<Toolbar
onToggleMode={this.handleToggleMode}
buttons={field.get('buttons')}
className="nc-markdownWidget-toolbarRaw"
disabled
rawMode
/>
</div>
</EditorControlBar>
<Slate
className={`${className} nc-rawEditor-rawEditor`}
className={cx(className, styles.slateRaw)}
value={this.state.value}
onChange={this.handleChange}
onPaste={this.handlePaste}
/>
</div>
</RawEditorContainer>
);
}
}

View File

@ -1,16 +1,35 @@
import React from 'react';
import c from 'classnames';
import { Map } from 'immutable';
import { connect } from 'react-redux';
import styled, { css } from 'react-emotion';
import { partial, capitalize } from 'lodash';
import { resolveWidget, getEditorComponents } from 'Lib/registry';
import { openMediaLibrary, removeInsertedMedia } from 'Actions/mediaLibrary';
import { addAsset } from 'Actions/media';
import { getAsset } from 'Reducers';
import { ListItemTopBar } from 'netlify-cms-ui-default';
import { getEditorControl } from '../index';
import { ListItemTopBar, components, colors, lengths } from 'netlify-cms-ui-default';
import { getEditorControl, getEditorComponents } from './index';
class Shortcode extends React.Component {
const ShortcodeContainer = styled.div`
${components.objectWidgetTopBarContainer};
border-radius: ${lengths.borderRadius};
border: 2px solid ${colors.textFieldBorder};
margin: 12px 0;
padding: 14px;
${props => props.collapsed && css`
background-color: ${colors.textFieldBorder};
cursor: pointer;
`}
`
const ShortcodeTopBar = styled(ListItemTopBar)`
background-color: ${colors.textFieldBorder};
margin: -14px -14px 0;
border-radius: 0;
`
const ShortcodeTitle = styled.div`
padding: 8px;
color: ${colors.controlLabel};
`
export default class Shortcode extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -59,27 +78,11 @@ class Shortcode extends React.Component {
}
renderControl = (shortcodeData, field, index) => {
const {
onAddAsset,
boundGetAsset,
mediaPaths,
onOpenMediaLibrary,
onRemoveInsertedMedia,
} = this.props;
if (field.get('widget') === 'hidden') return null;
const value = shortcodeData.get(field.get('name'));
const key = `field-${ field.get('name') }`;
const Control = getEditorControl();
const controlProps = {
field,
value,
onAddAsset,
getAsset: boundGetAsset,
onChange: this.handleChange,
mediaPaths,
onOpenMediaLibrary,
onRemoveInsertedMedia,
};
const controlProps = { field, value, onChange: this.handleChange };
return (
<div key={key}>
@ -94,44 +97,19 @@ class Shortcode extends React.Component {
const pluginId = node.data.get('shortcode');
const shortcodeData = Map(this.props.node.data.get('shortcodeData'));
const plugin = getEditorComponents().get(pluginId);
const className = c(
'nc-objectControl-root',
'nc-visualEditor-shortcode',
{ 'nc-visualEditor-shortcode-collapsed': collapsed },
);
return (
<div {...attributes} className={className} onClick={this.handleClick}>
<ListItemTopBar
className="nc-visualEditor-shortcode-topBar"
<ShortcodeContainer collapsed={collapsed} {...attributes} onClick={this.handleClick}>
<ShortcodeTopBar
collapsed={collapsed}
onCollapseToggle={this.handleCollapseToggle}
onRemove={this.handleRemove}
/>
{
collapsed
? <div className="nc-visualEditor-shortcode-collapsedTitle">{capitalize(pluginId)}</div>
? <ShortcodeTitle>{capitalize(pluginId)}</ShortcodeTitle>
: plugin.get('fields').map(partial(this.renderControl, shortcodeData))
}
</div>
</ShortcodeContainer>
);
}
}
const mapStateToProps = (state, ownProps) => {
const { attributes, node, editor } = ownProps;
return {
mediaPaths: state.mediaLibrary.get('controlMedia'),
boundGetAsset: getAsset.bind(null, state),
attributes,
node,
editor,
};
};
const mapDispatchToProps = {
onAddAsset: addAsset,
onOpenMediaLibrary: openMediaLibrary,
onRemoveInsertedMedia: removeInsertedMedia,
};
export default connect(mapStateToProps, mapDispatchToProps)(Shortcode);

View File

@ -1,11 +1,58 @@
import PropTypes from 'prop-types';
import React from 'react';
import { List } from 'immutable';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import c from 'classnames';
import { Icon, Toggle, Dropdown, DropdownItem, DropdownButton } from 'netlify-cms-ui-default';
import styled, { css } from 'react-emotion';
import { List } from 'immutable';
import {
Icon,
Toggle,
Dropdown,
DropdownItem,
DropdownButton,
colors,
transitions,
} from 'netlify-cms-ui-default';
import ToolbarButton from './ToolbarButton';
const ToolbarContainer = styled.div`
background-color: ${colors.textFieldBorder};
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 11px 14px;
min-height: 58px;
transition: background-color ${transitions.main}, color ${transitions.main};
`;
const ToolbarDropdownWrapper = styled.div`
display: inline-block;
position: relative;
`
const ToolbarToggle = styled.div`
flex-shrink: 0;
display: flex;
align-items: center;
font-size: 14px;
margin: 0 10px;
`
const StyledToggle = ToolbarToggle.withComponent(Toggle);
const ToolbarToggleLabel = styled.span`
display: inline-block;
text-align: center;
white-space: nowrap;
line-height: 20px;
width: ${props => props.offPosition ? '62px' : '70px'};
${props => props.isActive && css`
font-weight: 600;
color: ${colors.active};
`}
`
export default class Toolbar extends React.Component {
static propTypes = {
buttons: PropTypes.object,
@ -16,7 +63,6 @@ export default class Toolbar extends React.Component {
onAddAsset: PropTypes.func,
getAsset: PropTypes.func,
disabled: PropTypes.bool,
className: PropTypes.string,
buttons: ImmutablePropTypes.list
};
@ -47,23 +93,12 @@ export default class Toolbar extends React.Component {
getAsset,
disabled,
onSubmit,
className,
} = this.props;
const { activePlugin } = this.state;
/**
* Because the toggle labels change font weight for active/inactive state,
* we need to set estimated widths for them to maintain position without
* moving other inline items on font weight change.
*/
const toggleOffLabel = 'Rich text';
const toggleOffLabelWidth = '62px';
const toggleOnLabel = 'Markdown';
const toggleOnLabelWidth = '70px';
return (
<div className={c(className, 'nc-toolbar-Toolbar')}>
<ToolbarContainer>
<div>
<ToolbarButton
type="bold"
@ -155,7 +190,7 @@ export default class Toolbar extends React.Component {
isHidden={this.isHidden('numbered-list')}
disabled={disabled}
/>
<div className="nc-toolbar-dropdown">
<ToolbarDropdownWrapper>
<Dropdown
dropdownTopOverlap="36px"
renderButton={() => (
@ -173,35 +208,14 @@ export default class Toolbar extends React.Component {
<DropdownItem key={idx} label={plugin.get('label')} onClick={() => onSubmit(plugin.get('id'))} />
))}
</Dropdown>
</div>
</ToolbarDropdownWrapper>
</div>
<div className="nc-markdownWidget-toolbar-toggle">
<span
style={{ width: toggleOffLabelWidth }}
className={c(
'nc-markdownWidget-toolbar-toggle-label',
{ 'nc-markdownWidget-toolbar-toggle-label-active': !rawMode },
)}
>
{toggleOffLabel}
</span>
<Toggle
active={rawMode}
onChange={onToggleMode}
className="nc-markdownWidget-toolbar-toggle"
classNameBackground="nc-markdownWidget-toolbar-toggle-background"
/>
<span
style={{ width: toggleOnLabelWidth }}
className={c(
'nc-markdownWidget-toolbar-toggle-label',
{ 'nc-markdownWidget-toolbar-toggle-label-active': rawMode },
)}
>
{toggleOnLabel}
</span>
</div>
</div>
<ToolbarToggle>
<ToolbarToggleLabel isActive={!rawMode} offPosition>Rich Text</ToolbarToggleLabel>
<StyledToggle active={rawMode} onChange={onToggleMode}/>
<ToolbarToggleLabel isActive={rawMode}>Markdown</ToolbarToggleLabel>
</ToolbarToggle>
</ToolbarContainer>
);
}
}

View File

@ -1,24 +1,42 @@
import PropTypes from 'prop-types';
import React from 'react';
import c from 'classnames';
import { Icon } from 'netlify-cms-ui-default';
import PropTypes from 'prop-types';
import styled from 'react-emotion';
import { Icon, buttons } from 'netlify-cms-ui-default';
const StyledToolbarButton = styled.button`
${buttons.button};
display: inline-block;
padding: 6px;
border: none;
background-color: transparent;
font-size: 16px;
color: ${props => props.isActive ? '#1e2532' : 'inherit'};
cursor: pointer;
&:disabled {
cursor: auto;
opacity: 0.5;
}
${Icon} {
display: block;
}
`
const ToolbarButton = ({ type, label, icon, onClick, isActive, isHidden, disabled }) => {
const active = isActive && type && isActive(type);
if (isHidden) {
return null;
}
return (
<button
className={c('nc-toolbarButton-button', { ['nc-toolbarButton-active']: active })}
<StyledToolbarButton
isActive={isActive && type && isActive(type)}
onClick={e => onClick && onClick(e, type)}
title={label}
disabled={disabled}
>
{ icon ? <Icon type={icon}/> : label }
</button>
</StyledToolbarButton>
);
};

View File

@ -1,17 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import React, { Component } from 'react';
import styled, { css, cx } from 'react-emotion';
import { get, isEmpty, debounce } from 'lodash';
import { Map } from 'immutable';
import { Value, Document, Block, Text } from 'slate';
import { Editor as Slate } from 'slate-react';
import { slateToMarkdown, markdownToSlate, htmlToSlate } from 'EditorWidgets/Markdown/serializers';
import { getEditorComponents } from 'Lib/registry';
import Toolbar from 'EditorWidgets/Markdown/MarkdownControl/Toolbar/Toolbar';
import { lengths, fonts } from 'netlify-cms-ui-default';
import { slateToMarkdown, markdownToSlate, htmlToSlate } from '../serializers';
import Toolbar from '../MarkdownControl/Toolbar';
import { renderNode, renderMark } from './renderers';
import { validateNode } from './validators';
import plugins, { EditListConfigured } from './plugins';
import onKeyDown from './keys';
import visualEditorStyles from './visualEditorStyles';
import { editorStyleVars, EditorControlBar } from '../styles';
const VisualEditorContainer = styled.div`
position: relative;
`
const createEmptyRawDoc = () => {
const emptyText = Text.create('');
@ -26,7 +33,7 @@ const createSlateValue = (rawValue) => {
return Value.create({ document });
}
export default class Editor extends Component {
export default class Editor extends React.Component {
static propTypes = {
onAddAsset: PropTypes.func.isRequired,
getAsset: PropTypes.func.isRequired,
@ -41,7 +48,6 @@ export default class Editor extends Component {
super(props);
this.state = {
value: createSlateValue(props.value),
shortcodePlugins: getEditorComponents(),
};
}
@ -173,10 +179,11 @@ export default class Editor extends Component {
handleDocumentChange = debounce(change => {
const { onChange, getEditorComponents } = this.props;
const raw = change.value.document.toJSON();
const plugins = this.state.shortcodePlugins;
const markdown = slateToMarkdown(raw, plugins);
this.props.onChange(markdown);
const plugins = getEditorComponents();
const markdown = slateToMarkdown(raw);
onChange(markdown);
}, 150);
handleChange = change => {
@ -191,11 +198,11 @@ export default class Editor extends Component {
}
render() {
const { onAddAsset, getAsset, className, field } = this.props;
const { onAddAsset, getAsset, className, field, getEditorComponents } = this.props;
return (
<div className="nc-visualEditor-wrapper">
<div className="nc-visualEditor-editorControlBar">
<VisualEditorContainer>
<EditorControlBar>
<Toolbar
onMarkClick={this.handleMarkClick}
onBlockClick={this.handleBlockClick}
@ -204,15 +211,15 @@ export default class Editor extends Component {
selectionHasBlock={this.selectionHasBlock}
selectionHasLink={this.hasLinks}
onToggleMode={this.handleToggle}
plugins={this.state.shortcodePlugins}
plugins={getEditorComponents()}
onSubmit={this.handlePluginAdd}
onAddAsset={onAddAsset}
getAsset={getAsset}
buttons={field.get('buttons')}
/>
</div>
</EditorControlBar>
<Slate
className={`${className} nc-visualEditor-editor`}
className={cx(className, visualEditorStyles)}
value={this.state.value}
renderNode={renderNode}
renderMark={renderMark}
@ -224,7 +231,7 @@ export default class Editor extends Component {
ref={this.processRef}
spellCheck
/>
</div>
</VisualEditorContainer>
);
}
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import { fromJS } from 'immutable';
import { markdownToSlate } from 'EditorWidgets/Markdown/serializers';
import { markdownToSlate } from '../../serializers';
const parser = markdownToSlate;

View File

@ -1,15 +1,15 @@
import PropTypes from 'prop-types';
import React from 'react';
import c from 'classnames';
import { markdownToRemark, remarkToMarkdown } from 'EditorWidgets/Markdown/serializers'
import PropTypes from 'prop-types';
import RawEditor from './RawEditor';
import VisualEditor from './VisualEditor';
const MODE_STORAGE_KEY = 'cms.md-mode';
let editorControl;
let _getEditorComponents = () => [];
export const getEditorControl = () => editorControl;
export const getEditorComponents = () => _getEditorComponents();
export default class MarkdownControl extends React.Component {
static propTypes = {
@ -28,6 +28,7 @@ export default class MarkdownControl extends React.Component {
constructor(props) {
super(props);
editorControl = props.editorControl;
_getEditorComponents = props.getEditorComponents;
this.state = { mode: localStorage.getItem(MODE_STORAGE_KEY) || 'visual' };
}
@ -45,7 +46,8 @@ export default class MarkdownControl extends React.Component {
getAsset,
value,
classNameWrapper,
field
field,
getEditorComponents,
} = this.props;
const { mode } = this.state;
@ -59,6 +61,7 @@ export default class MarkdownControl extends React.Component {
className={classNameWrapper}
value={value}
field={field}
getEditorComponents={getEditorComponents}
/>
</div>
);

View File

@ -1,6 +1,5 @@
import React from 'react';
import { List } from 'immutable';
import cn from 'classnames';
import Shortcode from './Shortcode';
/**

View File

@ -0,0 +1,106 @@
import { css } from 'react-emotion';
import { colors, lengths, fonts } from 'netlify-cms-ui-default';
import { editorStyleVars } from '../styles';
export default css`
position: relative;
overflow: hidden;
overflow-x: auto;
min-height: ${lengths.richTextEditorMinHeight};
font-family: ${fonts.primary};
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: 0;
margin-top: -${editorStyleVars.stickyDistanceBottom};
h1 {
font-size: 32px;
margin-top: 16px;
}
h2 {
font-size: 24px;
margin-top: 12px;
}
h3 {
font-size: 20px;
margin-top: 8px;
}
h4 {
font-size: 18px;
margin-top: 8px;
}
h5,
h6 {
font-size: 16px;
margin-top: 8px;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
line-height: 1;
}
p, pre, blockquote, ul, ol {
margin-top: 16px;
margin-bottom: 16px;
}
a {
text-decoration: underline;
}
hr {
border: 1px solid;
margin-bottom: 16px;
}
li > p {
margin: 0;
}
ul, ol {
padding-left: 30px;
}
pre {
white-space: pre-wrap;
}
pre > code {
display: block;
width: 100%;
overflow-y: auto;
background-color: #000;
color: #ccc;
border-radius: ${lengths.borderRadius};
padding: 10px;
}
code {
background-color: ${colors.background};
border-radius: ${lengths.borderRadius};
padding: 0 2px;
font-size: 85%;
}
blockquote {
padding-left: 16px;
border-left: 3px solid ${colors.background};
margin-left: 0;
margin-right: 0;
}
table {
border-collapse: collapse;
}
td, th {
border: 2px solid black;
padding: 8px;
text-align: left;
}
`;

View File

@ -1,13 +1,14 @@
import PropTypes from 'prop-types';
import React from 'react';
import { markdownToHtml } from 'EditorWidgets/Markdown/serializers';
import PropTypes from 'prop-types';
import { WidgetPreviewContainer } from 'netlify-cms-ui-default';
import { markdownToHtml } from './serializers';
const MarkdownPreview = ({ value, getAsset }) => {
if (value === null) {
return null;
}
const html = markdownToHtml(value, getAsset);
return <div className="nc-widgetPreview" dangerouslySetInnerHTML={{__html: html}}></div>;
return <WidgetPreviewContainer dangerouslySetInnerHTML={{__html: html}}/>
};
MarkdownPreview.propTypes = {

View File

@ -4,7 +4,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { padStart } from 'lodash';
import MarkdownPreview from '../index';
import { markdownToHtml } from 'EditorWidgets/Markdown/serializers';
import { markdownToHtml } from '../../serializers';
const parser = markdownToHtml;

View File

@ -0,0 +1,2 @@
export MarkdownControl from './MarkdownControl'
export MarkdownPreview from './MarkdownPreview'

View File

@ -7,7 +7,6 @@ import remarkToRehype from 'remark-rehype';
import rehypeToHtml from 'rehype-stringify';
import htmlToRehype from 'rehype-parse';
import rehypeToRemark from 'rehype-remark';
import { getEditorComponents } from 'Lib/registry';
import remarkToRehypeShortcodes from './remarkRehypeShortcodes';
import rehypePaperEmoji from './rehypePaperEmoji';
import remarkAssertParents from './remarkAssertParents';
@ -21,6 +20,7 @@ import remarkEscapeMarkdownEntities from './remarkEscapeMarkdownEntities';
import remarkStripTrailingBreaks from './remarkStripTrailingBreaks';
import remarkAllowHtmlEntities from './remarkAllowHtmlEntities';
import slateToRemark from './slateRemark';
import { getEditorComponents } from '../MarkdownControl';
/**
* This module contains all serializers for the Markdown widget.

View File

@ -1,5 +1,5 @@
import { has, flow, partial, flatMap, flatten, map } from 'lodash';
import { joinPatternSegments, combinePatterns, replaceWhen } from 'Lib/regexHelper';
import { joinPatternSegments, combinePatterns, replaceWhen } from '../regexHelper';
/**
* Reusable regular expressions segments.

View File

@ -0,0 +1,12 @@
import styled, { css } from 'react-emotion';
export const editorStyleVars = {
stickyDistanceBottom: '100px',
};
export const EditorControlBar = styled.div`
z-index: 1;
position: sticky;
top: 0;
margin-bottom: ${editorStyleVars.stickyDistanceBottom};
`

View File

@ -0,0 +1,3 @@
const { getConfig } = require('../../scripts/webpack.js');
module.exports = getConfig();

View File

@ -18,11 +18,6 @@ const styles = {
export default class ObjectControl extends Component {
static propTypes = {
onChangeObject: PropTypes.func.isRequired,
onOpenMediaLibrary: PropTypes.func.isRequired,
mediaPaths: ImmutablePropTypes.map.isRequired,
onAddAsset: PropTypes.func.isRequired,
onRemoveInsertedMedia: PropTypes.func.isRequired,
getAsset: PropTypes.func.isRequired,
value: PropTypes.oneOfType([
PropTypes.node,
PropTypes.object,
@ -58,17 +53,7 @@ export default class ObjectControl extends Component {
}
controlFor(field, key) {
const {
onAddAsset,
onOpenMediaLibrary,
mediaPaths,
onRemoveInsertedMedia,
getAsset,
value,
onChangeObject,
editorControl: EditorControl,
resolveWidget,
} = this.props;
const { value, onChangeObject, editorControl: EditorControl, resolveWidget } = this.props;
if (field.get('widget') === 'hidden') {
return null;
@ -79,17 +64,7 @@ export default class ObjectControl extends Component {
const fieldValue = value && Map.isMap(value) ? value.get(fieldName) : value;
return (
<EditorControl
key={key}
field={field}
value={fieldValue}
mediaPaths={mediaPaths}
getAsset={getAsset}
onChange={onChangeObject}
onOpenMediaLibrary={onOpenMediaLibrary}
onAddAsset={onAddAsset}
onRemoveInsertedMedia={onRemoveInsertedMedia}
/>
<EditorControl key={key} field={field} value={fieldValue} onChange={onChangeObject}/>
);
}

View File

@ -1705,10 +1705,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
change-emitter@^0.1.2:
version "0.1.6"
resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
character-entities-html4@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.2.tgz#c44fdde3ce66b52e8d321d6c1bf46101f0150610"
@ -3123,7 +3119,7 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.9:
fbjs@^0.8.16, fbjs@^0.8.9:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
dependencies:
@ -3728,7 +3724,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^2.1.0, hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
hoist-non-react-statics@^2.1.0, hoist-non-react-statics@^2.5.0:
version "2.5.5"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
@ -6421,7 +6417,7 @@ react-is@^16.4.1:
version "16.4.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e"
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4:
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -6667,17 +6663,6 @@ realpath-native@^1.0.0:
dependencies:
util.promisify "^1.0.0"
recompose@^0.27.1:
version "0.27.1"
resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.27.1.tgz#1a49e931f183634516633bbb4f4edbfd3f38a7ba"
dependencies:
babel-runtime "^6.26.0"
change-emitter "^0.1.2"
fbjs "^0.8.1"
hoist-non-react-statics "^2.3.1"
react-lifecycles-compat "^3.0.2"
symbol-observable "^1.0.4"
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
@ -7712,7 +7697,7 @@ svg-inline-loader@^0.8.0:
object-assign "^4.0.1"
simple-html-tokenizer "^0.1.1"
symbol-observable@^1.0.3, symbol-observable@^1.0.4:
symbol-observable@^1.0.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"