migrate markdown widget
This commit is contained in:
parent
3f47fe6dbf
commit
f1a2eb33b4
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
@import "./MarkdownControl/RawEditor/index.css";
|
||||
@import "./MarkdownControl/Toolbar/Toolbar.css";
|
||||
@import "./MarkdownControl/Toolbar/ToolbarButton.css";
|
||||
@import "./MarkdownControl/VisualEditor/index.css";
|
@ -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));
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
@ -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}/>
|
||||
|
@ -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
|
||||
|
53
packages/netlify-cms-widget-markdown/package.json
Normal file
53
packages/netlify-cms-widget-markdown/package.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
@ -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>
|
||||
</ToolbarDropdownWrapper>
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { List } from 'immutable';
|
||||
import cn from 'classnames';
|
||||
import Shortcode from './Shortcode';
|
||||
|
||||
/**
|
@ -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;
|
||||
}
|
||||
`;
|
@ -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 = {
|
@ -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;
|
||||
|
2
packages/netlify-cms-widget-markdown/src/index.js
Normal file
2
packages/netlify-cms-widget-markdown/src/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export MarkdownControl from './MarkdownControl'
|
||||
export MarkdownPreview from './MarkdownPreview'
|
@ -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.
|
@ -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.
|
12
packages/netlify-cms-widget-markdown/src/styles.js
Normal file
12
packages/netlify-cms-widget-markdown/src/styles.js
Normal 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};
|
||||
`
|
3
packages/netlify-cms-widget-markdown/webpack.config.js
Normal file
3
packages/netlify-cms-widget-markdown/webpack.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
const { getConfig } = require('../../scripts/webpack.js');
|
||||
|
||||
module.exports = getConfig();
|
@ -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}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
23
yarn.lock
23
yarn.lock
@ -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"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user