commit
a1aa51364b
15
example/example.css
Normal file
15
example/example.css
Normal file
@ -0,0 +1,15 @@
|
||||
html, body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #444;
|
||||
}
|
||||
body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
font-size: 32px;
|
||||
margin-top: 20px;
|
||||
}
|
@ -69,6 +69,26 @@
|
||||
|
||||
<script src='/cms.js'></script>
|
||||
<script>
|
||||
var PostPreview = createClass({
|
||||
render: function() {
|
||||
var entry = this.props.entry;
|
||||
var image = entry.getIn(['data', 'image']);
|
||||
var bg = image && this.props.getMedia(image);
|
||||
return h('div', {},
|
||||
h('div', {className: "cover"},
|
||||
h('h1', {}, entry.getIn(['data', 'title'])),
|
||||
bg ? h('img', {src: bg.toString()}) : null
|
||||
),
|
||||
h('p', {},
|
||||
h('small', {}, "Written " + entry.getIn(['data', 'date']))
|
||||
),
|
||||
h('div', {"className": "text"}, this.props.widgetFor('body'))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
CMS.registerPreviewTemplate("posts", PostPreview);
|
||||
CMS.registerPreviewStyle("/example.css");
|
||||
CMS.registerEditorComponent({
|
||||
id: "youtube",
|
||||
label: "Youtube",
|
||||
@ -88,7 +108,7 @@
|
||||
'<img src="http://img.youtube.com/vi/' + obj.id + '/maxresdefault.jpg" alt="Youtube Video"/>'
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -76,6 +76,7 @@
|
||||
"markup-it": "git+https://github.com/cassiozen/markup-it.git",
|
||||
"pluralize": "^3.0.0",
|
||||
"prismjs": "^1.5.1",
|
||||
"react-datetime": "^2.6.0",
|
||||
"react-addons-css-transition-group": "^15.3.1",
|
||||
"react-portal": "^2.2.1",
|
||||
"selection-position": "^1.0.0",
|
||||
|
@ -55,7 +55,7 @@ export default class API {
|
||||
}
|
||||
|
||||
checkMetadataRef() {
|
||||
return this.request(`${this.repoURL}/git/refs/meta/_netlify_cms?${Date.now()}`, {
|
||||
return this.request(`${this.repoURL}/refs/meta/_netlify_cms?${Date.now()}`, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
.then(response => response.object)
|
||||
@ -66,7 +66,7 @@ export default class API {
|
||||
};
|
||||
|
||||
return this.uploadBlob(readme)
|
||||
.then(item => this.request(`${this.repoURL}/git/trees`, {
|
||||
.then(item => this.request(`${this.repoURL}/trees`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tree: [{ path: 'README.md', mode: '100644', type: 'blob', sha: item.sha }] })
|
||||
}))
|
||||
@ -99,9 +99,9 @@ export default class API {
|
||||
return cache.then((cached) => {
|
||||
if (cached && cached.expires > Date.now()) { return cached.data; }
|
||||
|
||||
return this.request(`${this.repoURL}/files/${key}.json`, {
|
||||
return this.request(`${this.repoURL}/files/${key}.json?ref=refs/meta/_netlify_cms`, {
|
||||
params: { ref: 'refs/meta/_netlify_cms' },
|
||||
headers: { Accept: 'application/vnd.github.VERSION.raw' },
|
||||
headers: { 'Content-Type': 'application/vnd.netlify.raw' },
|
||||
cache: 'no-store',
|
||||
}).then((result) => {
|
||||
LocalForage.setItem(`gh.meta.${key}`, {
|
||||
@ -119,7 +119,7 @@ export default class API {
|
||||
if (cached) { return cached; }
|
||||
|
||||
return this.request(`${this.repoURL}/files/${path}`, {
|
||||
headers: { Accept: 'application/vnd.github.VERSION.raw' },
|
||||
headers: { 'Content-Type': 'application/vnd.netlify.raw' },
|
||||
params: { ref: this.branch },
|
||||
cache: false,
|
||||
raw: true
|
||||
@ -173,7 +173,7 @@ export default class API {
|
||||
}
|
||||
|
||||
createRef(type, name, sha) {
|
||||
return this.request(`${this.repoURL}/git/refs`, {
|
||||
return this.request(`${this.repoURL}/refs`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ref: `refs/${type}/${name}`, sha }),
|
||||
});
|
||||
@ -184,7 +184,7 @@ export default class API {
|
||||
}
|
||||
|
||||
patchRef(type, name, sha) {
|
||||
return this.request(`${this.repoURL}/git/refs/${type}/${name}`, {
|
||||
return this.request(`${this.repoURL}/refs/${type}/${name}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ sha })
|
||||
});
|
||||
@ -195,7 +195,7 @@ export default class API {
|
||||
}
|
||||
|
||||
getBranch() {
|
||||
return this.request(`${this.repoURL}/branches/${this.branch}`);
|
||||
return this.request(`${this.repoURL}/refs/heads/${this.branch}`);
|
||||
}
|
||||
|
||||
createPR(title, head, base = 'master') {
|
||||
@ -207,7 +207,7 @@ export default class API {
|
||||
}
|
||||
|
||||
getTree(sha) {
|
||||
return sha ? this.request(`${this.repoURL}/git/trees/${sha}`) : Promise.resolve({ tree: [] });
|
||||
return sha ? this.request(`${this.repoURL}/trees/${sha}`) : Promise.resolve({ tree: [] });
|
||||
}
|
||||
|
||||
toBase64(str) {
|
||||
@ -220,7 +220,7 @@ export default class API {
|
||||
const content = item instanceof MediaProxy ? item.toBase64() : this.toBase64(item.raw);
|
||||
|
||||
return content.then((contentBase64) => {
|
||||
return this.request(`${this.repoURL}/git/blobs`, {
|
||||
return this.request(`${this.repoURL}/blobs`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
content: contentBase64,
|
||||
@ -263,7 +263,7 @@ export default class API {
|
||||
}
|
||||
return Promise.all(updates)
|
||||
.then((updates) => {
|
||||
return this.request(`${this.repoURL}/git/trees`, {
|
||||
return this.request(`${this.repoURL}/trees`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ base_tree: sha, tree: updates })
|
||||
});
|
||||
@ -276,7 +276,7 @@ export default class API {
|
||||
commit(message, changeTree) {
|
||||
const tree = changeTree.sha;
|
||||
const parents = changeTree.parentSha ? [changeTree.parentSha] : [];
|
||||
return this.request(`${this.repoURL}/git/commits`, {
|
||||
return this.request(`${this.repoURL}/commits`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ message, tree, parents })
|
||||
});
|
||||
|
@ -9,7 +9,7 @@ export default class ImageCard extends React.Component {
|
||||
return (
|
||||
<Card onClick={onClick} className={styles.root}>
|
||||
<img src={image} onLoad={onImageLoaded} />
|
||||
<h1>{text}</h1>
|
||||
<h2>{text}</h2>
|
||||
|
||||
{description ? <p>{description}</p> : null}
|
||||
</Card>
|
||||
|
@ -1,26 +1,29 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Widgets from './Widgets';
|
||||
import { resolveWidget } from './Widgets';
|
||||
|
||||
export default class ControlPane extends React.Component {
|
||||
controlFor(field) {
|
||||
const { entry, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
|
||||
const widget = Widgets[field.get('widget')] || Widgets._unknown;
|
||||
return React.createElement(widget.Control, {
|
||||
field: field,
|
||||
value: entry.getIn(['data', field.get('name')]),
|
||||
onChange: (value) => onChange(entry.setIn(['data', field.get('name')], value)),
|
||||
onAddMedia: onAddMedia,
|
||||
onRemoveMedia: onRemoveMedia,
|
||||
getMedia: getMedia
|
||||
});
|
||||
const widget = resolveWidget(field.get('widget'));
|
||||
return <div className="cms-control">
|
||||
<label>{field.get('label')}</label>
|
||||
{React.createElement(widget.control, {
|
||||
field: field,
|
||||
value: entry.getIn(['data', field.get('name')]),
|
||||
onChange: (value) => onChange(entry.setIn(['data', field.get('name')], value)),
|
||||
onAddMedia: onAddMedia,
|
||||
onRemoveMedia: onRemoveMedia,
|
||||
getMedia: getMedia
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { collection } = this.props;
|
||||
if (!collection) { return null; }
|
||||
return <div>
|
||||
{collection.get('fields').map((field) => <div key={field.get('name')}>{this.controlFor(field)}</div>)}
|
||||
{collection.get('fields').map((field) => <div key={field.get('name')} className="cms-widget">{this.controlFor(field)}</div>)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
25
src/components/EntryEditor.css
Normal file
25
src/components/EntryEditor.css
Normal file
@ -0,0 +1,25 @@
|
||||
.entryEditor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
.footer {
|
||||
background: #fff;
|
||||
height: 45px;
|
||||
border-top: 1px solid #e8eae8;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.controlPane {
|
||||
width: 50%;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
padding: 0 20px;
|
||||
border-right: 1px solid #e8eae8;
|
||||
}
|
||||
.previewPane {
|
||||
width: 50%;
|
||||
}
|
@ -2,38 +2,60 @@ import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ControlPane from './ControlPane';
|
||||
import PreviewPane from './PreviewPane';
|
||||
import styles from './EntryEditor.css';
|
||||
|
||||
export default function EntryEditor({ collection, entry, getMedia, onChange, onAddMedia, onRemoveMedia, onPersist }) {
|
||||
return <div>
|
||||
<h1>Entry in {collection.get('label')}</h1>
|
||||
<h2>{entry && entry.get('title')}</h2>
|
||||
<div className="cms-container" style={styles.container}>
|
||||
<div className="cms-control-pane" style={styles.pane}>
|
||||
<ControlPane
|
||||
collection={collection}
|
||||
entry={entry}
|
||||
getMedia={getMedia}
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
onRemoveMedia={onRemoveMedia}
|
||||
/>
|
||||
</div>
|
||||
<div className="cms-preview-pane" style={styles.pane}>
|
||||
<PreviewPane collection={collection} entry={entry} getMedia={getMedia} />
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={onPersist}>Save</button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: 'flex'
|
||||
},
|
||||
pane: {
|
||||
width: '50%'
|
||||
export default class EntryEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.calculateHeight();
|
||||
window.addEventListener('resize', this.handleResize, false);
|
||||
}
|
||||
|
||||
componengWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.calculateHeight();
|
||||
}
|
||||
|
||||
calculateHeight() {
|
||||
const height = window.innerHeight - 54;
|
||||
console.log('setting height to %s', height);
|
||||
this.setState({height});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { collection, entry, getMedia, onChange, onAddMedia, onRemoveMedia, onPersist } = this.props;
|
||||
const {height} = this.state;
|
||||
|
||||
return <div className={styles.entryEditor} style={{height}}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.controlPane}>
|
||||
<ControlPane
|
||||
collection={collection}
|
||||
entry={entry}
|
||||
getMedia={getMedia}
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
onRemoveMedia={onRemoveMedia}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.previewPane}>
|
||||
<PreviewPane collection={collection} entry={entry} getMedia={getMedia} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<button onClick={onPersist}>Save</button>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
EntryEditor.propTypes = {
|
||||
collection: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -60,7 +60,8 @@ export default class EntryListing extends React.Component {
|
||||
|
||||
cardFor(collection, entry, link) {
|
||||
//const { entry, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
|
||||
const card = Cards[collection.getIn(['card', 'type'])] || Cards._unknown;
|
||||
const cartType = collection.getIn(['card', 'type']) || 'alltype';
|
||||
const card = Cards[cartType] || Cards._unknown;
|
||||
return React.createElement(card, {
|
||||
key: entry.get('slug'),
|
||||
collection: collection,
|
||||
|
6
src/components/PreviewPane.css
Normal file
6
src/components/PreviewPane.css
Normal file
@ -0,0 +1,6 @@
|
||||
.frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: #fff;
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Widgets from './Widgets';
|
||||
import registry from '../lib/registry';
|
||||
import { resolveWidget } from './Widgets';
|
||||
import styles from './PreviewPane.css';
|
||||
|
||||
export default class PreviewPane extends React.Component {
|
||||
class Preview extends React.Component {
|
||||
previewFor(field) {
|
||||
const { entry, getMedia } = this.props;
|
||||
const widget = Widgets[field.get('widget')] || Widgets._unknown;
|
||||
return React.createElement(widget.Preview, {
|
||||
const widget = resolveWidget(field.get('widget'));
|
||||
return React.createElement(widget.preview, {
|
||||
field: field,
|
||||
value: entry.getIn(['data', field.get('name')]),
|
||||
getMedia: getMedia,
|
||||
@ -17,13 +20,69 @@ export default class PreviewPane extends React.Component {
|
||||
const { collection } = this.props;
|
||||
if (!collection) { return null; }
|
||||
|
||||
|
||||
return <div>
|
||||
{collection.get('fields').map((field) => <div key={field.get('name')}>{this.previewFor(field)}</div>)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
Preview.propTypes = {
|
||||
collection: ImmutablePropTypes.map.isRequired,
|
||||
entry: ImmutablePropTypes.map.isRequired,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default class PreviewPane extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleIframeRef = this.handleIframeRef.bind(this);
|
||||
this.widgetFor = this.widgetFor.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.renderPreview();
|
||||
}
|
||||
|
||||
widgetFor(name) {
|
||||
const { collection, entry, getMedia } = this.props;
|
||||
const field = collection.get('fields').find((field) => field.get('name') === name);
|
||||
const widget = resolveWidget(field.get('widget'));
|
||||
return React.createElement(widget.preview, {
|
||||
field: field,
|
||||
value: entry.getIn(['data', field.get('name')]),
|
||||
getMedia: getMedia,
|
||||
});
|
||||
}
|
||||
|
||||
renderPreview() {
|
||||
const props = Object.assign({}, this.props, {widgetFor: this.widgetFor});
|
||||
const component = registry.getPreviewTemplate(props.collection.get('name')) || Preview;
|
||||
|
||||
render(React.createElement(component, props), this.previewEl);
|
||||
}
|
||||
|
||||
handleIframeRef(ref) {
|
||||
if (ref) {
|
||||
registry.getPreviewStyles().forEach((style) => {
|
||||
const linkEl = document.createElement('link');
|
||||
linkEl.setAttribute('rel', 'stylesheet');
|
||||
linkEl.setAttribute('href', style);
|
||||
ref.contentDocument.head.appendChild(linkEl);
|
||||
});
|
||||
this.previewEl = document.createElement('div');
|
||||
ref.contentDocument.body.appendChild(this.previewEl);
|
||||
this.renderPreview();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { collection } = this.props;
|
||||
if (!collection) { return null; }
|
||||
|
||||
return <iframe className={styles.frame} ref={this.handleIframeRef}></iframe>;
|
||||
}
|
||||
}
|
||||
|
||||
PreviewPane.propTypes = {
|
||||
collection: ImmutablePropTypes.map.isRequired,
|
||||
entry: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import dateFormat from 'dateFormat';
|
||||
import moment from 'moment';
|
||||
import { Card } from './UI';
|
||||
import { Link } from 'react-router'
|
||||
import { statusDescriptions } from '../constants/publishModes';
|
||||
@ -22,7 +22,7 @@ export default class UnpublishedListing extends React.Component {
|
||||
{entries.map(entry => {
|
||||
// Look for an "author" field. Fallback to username on backend implementation;
|
||||
const author = entry.getIn(['data', 'author'], entry.getIn(['metaData', 'user']));
|
||||
const timeStamp = dateFormat(Date.parse(entry.getIn(['metaData', 'timeStamp'])), 'longDate');
|
||||
const timeStamp = moment(entry.getIn(['metaData', 'timeStamp'])).formate('llll');
|
||||
const link = `/editorialworkflow/${entry.getIn(['metaData', 'collection'])}/${entry.getIn(['metaData', 'status'])}/${entry.get('slug')}`;
|
||||
return (
|
||||
<Card key={entry.get('slug')} className={styles.card}>
|
||||
|
@ -1,30 +1,24 @@
|
||||
import registry from '../lib/registry';
|
||||
import UnknownControl from './Widgets/UnknownControl';
|
||||
import UnknownPreview from './Widgets/UnknownPreview';
|
||||
import StringControl from './Widgets/StringControl';
|
||||
import StringPreview from './Widgets/StringPreview';
|
||||
import TextControl from './Widgets/TextControl';
|
||||
import TextPreview from './Widgets/TextPreview';
|
||||
import MarkdownControl from './Widgets/MarkdownControl';
|
||||
import MarkdownPreview from './Widgets/MarkdownPreview';
|
||||
import ImageControl from './Widgets/ImageControl';
|
||||
import ImagePreview from './Widgets/ImagePreview';
|
||||
import DateTimeControl from './Widgets/DateTimeControl';
|
||||
import DateTimePreview from './Widgets/DateTimePreview';
|
||||
|
||||
registry.registerWidget('string', StringControl, StringPreview);
|
||||
registry.registerWidget('text', TextControl, TextPreview);
|
||||
registry.registerWidget('markdown', MarkdownControl, MarkdownPreview);
|
||||
registry.registerWidget('image', ImageControl, ImagePreview);
|
||||
registry.registerWidget('datetime', DateTimeControl, DateTimePreview);
|
||||
registry.registerWidget('_unknown', UnknownControl, UnknownPreview);
|
||||
|
||||
const Widgets = {
|
||||
_unknown: {
|
||||
Control: UnknownControl,
|
||||
Preview: UnknownPreview
|
||||
},
|
||||
string: {
|
||||
Control: StringControl,
|
||||
Preview: StringPreview
|
||||
},
|
||||
markdown: {
|
||||
Control: MarkdownControl,
|
||||
Preview: MarkdownPreview
|
||||
},
|
||||
image: {
|
||||
Control: ImageControl,
|
||||
Preview: ImagePreview
|
||||
}
|
||||
};
|
||||
|
||||
export default Widgets;
|
||||
export function resolveWidget(name) {
|
||||
return registry.getWidget(name) || registry.getWidget('_unknown');
|
||||
}
|
||||
|
22
src/components/Widgets/DateTimeControl.js
Normal file
22
src/components/Widgets/DateTimeControl.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import DateTime from 'react-datetime';
|
||||
|
||||
export default class DateTimeControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
handleChange(datetime) {
|
||||
this.props.onChange(datetime);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <DateTime value={this.props.value || new Date()} onChange={this.handleChange}/>;
|
||||
}
|
||||
}
|
||||
|
||||
DateTimeControl.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.object,
|
||||
};
|
9
src/components/Widgets/DateTimePreview.js
Normal file
9
src/components/Widgets/DateTimePreview.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default function StringPreview({ value }) {
|
||||
return <span>{value}</span>;
|
||||
}
|
||||
|
||||
StringPreview.propTypes = {
|
||||
value: PropTypes.node,
|
||||
};
|
@ -78,8 +78,8 @@ export default class ImageControl extends React.Component {
|
||||
onDragOver={this.handleDragOver}
|
||||
onDrop={this.handleChange}
|
||||
>
|
||||
<span onClick={this.handleClick}>
|
||||
{imageName ? imageName : 'Click or drop image here.'}
|
||||
<span style={styles.imageUpload} onClick={this.handleClick}>
|
||||
{imageName ? imageName : 'Tip: Click here to upload an image from your file browser, or drag an image directly into this box from your desktop'}
|
||||
</span>
|
||||
<input
|
||||
type="file"
|
||||
@ -96,6 +96,16 @@ export default class ImageControl extends React.Component {
|
||||
const styles = {
|
||||
input: {
|
||||
display: 'none'
|
||||
},
|
||||
imageUpload: {
|
||||
backgroundColor: '#fff',
|
||||
textAlign: 'center',
|
||||
color: '#999',
|
||||
padding: '20px',
|
||||
display: 'block',
|
||||
border: '1px dashed #eee',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import registry from '../../lib/registry';
|
||||
import RawEditor from './MarkdownControlElements/RawEditor';
|
||||
import VisualEditor from './MarkdownControlElements/VisualEditor';
|
||||
import { processEditorPlugins } from './richText';
|
||||
@ -13,7 +14,8 @@ class MarkdownControl extends React.Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
processEditorPlugins(this.context.plugins.editor);
|
||||
this.useRawEditor();
|
||||
processEditorPlugins(registry.getEditorComponents());
|
||||
}
|
||||
|
||||
useVisualEditor() {
|
||||
@ -28,8 +30,8 @@ class MarkdownControl extends React.Component {
|
||||
const { editor, onChange, onAddMedia, getMedia, value } = this.props;
|
||||
if (editor.get('useVisualMode')) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.useRawEditor}>Switch to Raw Editor</button>
|
||||
<div className='cms-editor-visual'>
|
||||
{null && <button onClick={this.useRawEditor}>Switch to Raw Editor</button>}
|
||||
<VisualEditor
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
@ -41,8 +43,8 @@ class MarkdownControl extends React.Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.useVisualEditor}>Switch to Visual Editor</button>
|
||||
<div className='cms-editor-raw'>
|
||||
{null && <button onClick={this.useVisualEditor}>Switch to Visual Editor</button>}
|
||||
<RawEditor
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
@ -64,8 +66,6 @@ class MarkdownControl extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default MarkdownControl;
|
||||
|
||||
MarkdownControl.propTypes = {
|
||||
editor: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
|
@ -42,6 +42,7 @@ class VisualEditor extends React.Component {
|
||||
let rawJson;
|
||||
if (props.value !== undefined) {
|
||||
const content = this.markdown.toContent(props.value);
|
||||
console.log('md: %o', content);
|
||||
rawJson = SlateUtils.encode(content, null, ['mediaproxy'].concat(getPlugins().map(plugin => plugin.id)));
|
||||
} else {
|
||||
rawJson = emptyParagraphBlock;
|
||||
|
@ -16,23 +16,6 @@ const EditorComponent = Record({
|
||||
toPreview: function(attributes) { return 'Plugin'; }
|
||||
});
|
||||
|
||||
function CMS() {
|
||||
this.registerEditorComponent = (config) => {
|
||||
const configObj = new EditorComponent({
|
||||
id: config.id || config.label.replace(/[^A-Z0-9]+/ig, '_'),
|
||||
label: config.label,
|
||||
icon: config.icon,
|
||||
fields: config.fields,
|
||||
pattern: config.pattern,
|
||||
fromBlock: _.isFunction(config.fromBlock) ? config.fromBlock.bind(null) : null,
|
||||
toBlock: _.isFunction(config.toBlock) ? config.toBlock.bind(null) : null,
|
||||
toPreview: _.isFunction(config.toPreview) ? config.toPreview.bind(null) : config.toBlock.bind(null)
|
||||
});
|
||||
|
||||
plugins.editor = plugins.editor.push(configObj);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class Plugin extends Component {
|
||||
getChildContext() {
|
||||
@ -51,8 +34,18 @@ Plugin.childContextTypes = {
|
||||
plugins: PropTypes.object
|
||||
};
|
||||
|
||||
export function newEditorPlugin(config) {
|
||||
const configObj = new EditorComponent({
|
||||
id: config.id || config.label.replace(/[^A-Z0-9]+/ig, '_'),
|
||||
label: config.label,
|
||||
icon: config.icon,
|
||||
fields: config.fields,
|
||||
pattern: config.pattern,
|
||||
fromBlock: _.isFunction(config.fromBlock) ? config.fromBlock.bind(null) : null,
|
||||
toBlock: _.isFunction(config.toBlock) ? config.toBlock.bind(null) : null,
|
||||
toPreview: _.isFunction(config.toPreview) ? config.toPreview.bind(null) : config.toBlock.bind(null)
|
||||
});
|
||||
|
||||
export const initPluginAPI = () => {
|
||||
window.CMS = new CMS();
|
||||
return Plugin;
|
||||
};
|
||||
|
||||
return configObj;
|
||||
}
|
37
src/components/Widgets/TextControl.js
Normal file
37
src/components/Widgets/TextControl.js
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default class StringControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleRef = this.handleRef.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateHeight();
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.props.onChange(e.target.value);
|
||||
this.updateHeight();
|
||||
}
|
||||
|
||||
updateHeight() {
|
||||
if (this.element.scrollHeight > this.element.clientHeight) {
|
||||
this.element.style.height = this.element.scrollHeight + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
handleRef(ref) {
|
||||
this.element = ref;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <textarea ref={this.handleRef} value={this.props.value || ''} onChange={this.handleChange}/>;
|
||||
}
|
||||
}
|
||||
|
||||
StringControl.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.node,
|
||||
};
|
4
src/components/Widgets/TextPreview.js
Normal file
4
src/components/Widgets/TextPreview.js
Normal file
@ -0,0 +1,4 @@
|
||||
import StringPreview from './StringPreview';
|
||||
|
||||
export default class TextPreview extends StringPreview {
|
||||
}
|
@ -48,8 +48,6 @@ function processEditorPlugins(plugins) {
|
||||
<div className="plugin_fields" contentEditable={false}>
|
||||
{ plugin.fields.map(field => `${field.label}: “${node.data.get(field.name)}”`) }
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,43 +1,3 @@
|
||||
.alignable {
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
@media (max-width: 749px) and (min-width: 495px) {
|
||||
.alignable {
|
||||
width: 495px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1004px) and (min-width: 750px) {
|
||||
.alignable {
|
||||
width: 750px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1259px) and (min-width: 1005px) {
|
||||
.alignable {
|
||||
width: 1005px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1514px) and (min-width: 1260px) {
|
||||
.alignable {
|
||||
width: 1260px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1769px) and (min-width: 1515px) {
|
||||
.alignable {
|
||||
width: 1515px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1770px) {
|
||||
.alignable {
|
||||
width: 1770px;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
padding-top: 60px;
|
||||
padding-top: 54px;
|
||||
}
|
||||
|
39
src/containers/CollectionPage.css
Normal file
39
src/containers/CollectionPage.css
Normal file
@ -0,0 +1,39 @@
|
||||
.alignable {
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
@media (max-width: 749px) and (min-width: 495px) {
|
||||
.alignable {
|
||||
width: 495px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1004px) and (min-width: 750px) {
|
||||
.alignable {
|
||||
width: 750px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1259px) and (min-width: 1005px) {
|
||||
.alignable {
|
||||
width: 1005px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1514px) and (min-width: 1260px) {
|
||||
.alignable {
|
||||
width: 1260px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1769px) and (min-width: 1515px) {
|
||||
.alignable {
|
||||
width: 1515px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1770px) {
|
||||
.alignable {
|
||||
width: 1770px;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import { loadEntries } from '../actions/entries';
|
||||
import { selectEntries } from '../reducers';
|
||||
import { Loader } from '../components/UI';
|
||||
import EntryListing from '../components/EntryListing';
|
||||
import styles from './CollectionPage.css';
|
||||
import CollectionPageHOC from './editorialWorkflow/CollectionPageHOC';
|
||||
|
||||
class DashboardPage extends React.Component {
|
||||
@ -28,7 +29,8 @@ class DashboardPage extends React.Component {
|
||||
return <h1>No collections defined in your config.yml</h1>;
|
||||
}
|
||||
|
||||
return <div>
|
||||
|
||||
return <div className={styles.alignable}>
|
||||
{entries ?
|
||||
<EntryListing collection={collection} entries={entries}/>
|
||||
:
|
||||
|
@ -1,9 +1,9 @@
|
||||
:root {
|
||||
--foregroundColor: #555;
|
||||
--backgroundColor: rgba(245, 245, 245, 0.98);
|
||||
--foregroundColor: #fff;
|
||||
--backgroundColor: #272e30;
|
||||
--textFieldBorderColor: #e7e7e7;
|
||||
--highlightFGColor: #444;
|
||||
--highlightBGColor: #d2dee4;
|
||||
--highlightFGColor: #fff;
|
||||
--highlightBGColor: #3ab7a5;
|
||||
}
|
||||
|
||||
.root {
|
||||
@ -30,6 +30,7 @@
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
border-right: 1px solid var(--textFieldBorderColor);
|
||||
margin:0;
|
||||
}
|
||||
|
||||
.inputField {
|
||||
|
@ -1,5 +1,37 @@
|
||||
import YAML from './yaml';
|
||||
import YAMLFrontmatter from './yaml-frontmatter';
|
||||
|
||||
export function resolveFormat(collectionOrEntity, entry) {
|
||||
return new YAMLFrontmatter();
|
||||
const yamlFormatter = new YAML();
|
||||
const YamlFrontmatterFormatter = new YAMLFrontmatter();
|
||||
|
||||
function formatByType(type) {
|
||||
// Right now the only type is "editorialWorkflow" and
|
||||
// we always returns the same format
|
||||
return YamlFrontmatterFormatter;
|
||||
}
|
||||
|
||||
function formatByExtension(extension) {
|
||||
return {
|
||||
'yml': yamlFormatter,
|
||||
'md': YamlFrontmatterFormatter,
|
||||
'markdown': YamlFrontmatterFormatter,
|
||||
'html': YamlFrontmatterFormatter
|
||||
}[extension] || YamlFrontmatterFormatter;
|
||||
}
|
||||
|
||||
function formatByName(name) {
|
||||
return {
|
||||
'yaml': yamlFormatter,
|
||||
'frontmatter': YamlFrontmatterFormatter
|
||||
}[name] || YamlFrontmatterFormatter;
|
||||
}
|
||||
|
||||
export function resolveFormat(collectionOrEntity, entry) {
|
||||
if (typeof collectionOrEntity === 'string') {
|
||||
return formatByType(collectionOrEntity);
|
||||
}
|
||||
if (entry && entry.path) {
|
||||
return formatByExtension(entry.path.split('.').pop());
|
||||
}
|
||||
return formatByName(collectionOrEntity.get('format'));
|
||||
}
|
||||
|
292
src/index.css
292
src/index.css
@ -3,6 +3,7 @@ html {
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
margin: 0;
|
||||
font-family: Roboto,"Helvetica Neue",HelveticaNeue,Helvetica,Arial,sans-serif;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
@ -11,14 +12,16 @@ html {
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
height: 100%;
|
||||
background-color: #fafafa;
|
||||
background-color: #f2f5f4;
|
||||
color:#7c8382;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #fff;
|
||||
background-color: #272e30;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.22);
|
||||
height: 54px;
|
||||
border-bottom:2px solid #3ab7a5;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
@ -31,3 +34,288 @@ header {
|
||||
h1, h2, h3, h4, h5, h6, p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1{
|
||||
color: #3ab7a5;
|
||||
border-bottom: 1px solid #3ab7a5;
|
||||
margin: 30px auto 25px;
|
||||
padding-bottom: 15px;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
header input{
|
||||
margin-bottom:0;
|
||||
}
|
||||
button{
|
||||
border: 1px solid #3ab7a5;
|
||||
padding: 3px 20px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
background-color:#fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:global {
|
||||
& .cms-widget {
|
||||
border-bottom: 1px solid #e8eae8;
|
||||
position: relative;
|
||||
}
|
||||
& .cms-widget:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 42px;
|
||||
bottom: -7px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #f2f5f4;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
z-index: 1;
|
||||
border-right: 1px solid #e8eae8;
|
||||
border-bottom: 1px solid #e8eae8;
|
||||
}
|
||||
& .cms-widget:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
& .cms-widget:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
& .cms-control {
|
||||
color: #7c8382;
|
||||
position: relative;
|
||||
padding: 20px 0;
|
||||
& label {
|
||||
color: #AAB0AF;
|
||||
font-size: 12px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
& input,
|
||||
& textarea,
|
||||
& select,
|
||||
& .cms-editor-raw {
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
background: 0 0;
|
||||
font-size: 18px;
|
||||
color: #7c8382;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
& .rdt {
|
||||
position: relative;
|
||||
}
|
||||
& .rdtPicker {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 250px;
|
||||
padding: 4px;
|
||||
margin-top: 1px;
|
||||
z-index: 99999 !important;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.1);
|
||||
border: 1px solid #f9f9f9;
|
||||
}
|
||||
& .rdtOpen .rdtPicker {
|
||||
display: block;
|
||||
}
|
||||
& .rdtStatic .rdtPicker {
|
||||
box-shadow: none;
|
||||
position: static;
|
||||
}
|
||||
|
||||
& .rdtPicker .rdtTimeToggle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .rdtPicker table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
& .rdtPicker td,
|
||||
& .rdtPicker th {
|
||||
text-align: center;
|
||||
height: 28px;
|
||||
}
|
||||
& .rdtPicker td {
|
||||
cursor: pointer;
|
||||
}
|
||||
& .rdtPicker td.rdtDay:hover,
|
||||
& .rdtPicker td.rdtHour:hover,
|
||||
& .rdtPicker td.rdtMinute:hover,
|
||||
& .rdtPicker td.rdtSecond:hover,
|
||||
& .rdtPicker .rdtTimeToggle:hover {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
& .rdtPicker td.rdtOld,
|
||||
& .rdtPicker td.rdtNew {
|
||||
color: #999999;
|
||||
}
|
||||
& .rdtPicker td.rdtToday {
|
||||
position: relative;
|
||||
}
|
||||
& .rdtPicker td.rdtToday:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-bottom: 7px solid #428bca;
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
& .rdtPicker td.rdtActive,
|
||||
& .rdtPicker td.rdtActive:hover {
|
||||
background-color: #428bca;
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
& .rdtPicker td.rdtActive.rdtToday:before {
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
& .rdtPicker td.rdtDisabled,
|
||||
& .rdtPicker td.rdtDisabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
& .rdtPicker td span.rdtOld {
|
||||
color: #999999;
|
||||
}
|
||||
& .rdtPicker td span.rdtDisabled,
|
||||
& .rdtPicker td span.rdtDisabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
& .rdtPicker th {
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
}
|
||||
& .rdtPicker .dow {
|
||||
width: 14.2857%;
|
||||
border-bottom: none;
|
||||
}
|
||||
& .rdtPicker th.rdtSwitch {
|
||||
width: 100px;
|
||||
}
|
||||
& .rdtPicker th.rdtNext,
|
||||
& .rdtPicker th.rdtPrev {
|
||||
font-size: 21px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
& .rdtPrev span,
|
||||
& .rdtNext span {
|
||||
display: block;
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Chrome/Safari/Opera */
|
||||
-khtml-user-select: none; /* Konqueror */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
& .rdtPicker th.rdtDisabled,
|
||||
& .rdtPicker th.rdtDisabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
& .rdtPicker thead tr:first-child th {
|
||||
cursor: pointer;
|
||||
}
|
||||
& .rdtPicker thead tr:first-child th:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
& .rdtPicker tfoot {
|
||||
border-top: 1px solid #f9f9f9;
|
||||
}
|
||||
|
||||
& .rdtPicker button {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
& .rdtPicker button:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
& .rdtPicker thead button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
& td.rdtMonth,
|
||||
& td.rdtYear {
|
||||
height: 50px;
|
||||
width: 25%;
|
||||
cursor: pointer;
|
||||
}
|
||||
& td.rdtMonth:hover,
|
||||
& td.rdtYear:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
& .rdtCounters {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
& .rdtCounters > div {
|
||||
float: left;
|
||||
}
|
||||
|
||||
& .rdtCounter {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
& .rdtCounter {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
& .rdtCounterSeparator {
|
||||
line-height: 100px;
|
||||
}
|
||||
|
||||
& .rdtCounter .rdtBtn {
|
||||
height: 40%;
|
||||
line-height: 40px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Chrome/Safari/Opera */
|
||||
-khtml-user-select: none; /* Konqueror */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
}
|
||||
& .rdtCounter .rdtBtn:hover {
|
||||
background: #eee;
|
||||
}
|
||||
& .rdtCounter .rdtCount {
|
||||
height: 20%;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
& .rdtMilli {
|
||||
vertical-align: middle;
|
||||
padding-left: 8px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
& .rdtMilli input {
|
||||
width: 100%;
|
||||
font-size: 1.2em;
|
||||
margin-top: 37px;
|
||||
}
|
||||
}
|
||||
|
20
src/index.js
20
src/index.js
@ -2,10 +2,10 @@ import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router';
|
||||
import registry from './lib/registry';
|
||||
import configureStore from './store/configureStore';
|
||||
import routes from './routing/routes';
|
||||
import history, { syncHistory } from './routing/history';
|
||||
import { initPluginAPI } from './plugins';
|
||||
import 'file?name=index.html!../example/index.html';
|
||||
import './index.css';
|
||||
|
||||
@ -14,18 +14,22 @@ const store = configureStore();
|
||||
// Create an enhanced history that syncs navigation events with the store
|
||||
syncHistory(store);
|
||||
|
||||
const Plugin = initPluginAPI();
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.id = 'root';
|
||||
document.body.appendChild(el);
|
||||
|
||||
render((
|
||||
<Provider store={store}>
|
||||
<Plugin>
|
||||
<Router history={history}>
|
||||
{routes}
|
||||
</Router>
|
||||
</Plugin>
|
||||
<Router history={history}>
|
||||
{routes}
|
||||
</Router>
|
||||
</Provider>
|
||||
), el);
|
||||
|
||||
window.CMS = {};
|
||||
console.log('reg: ', registry);
|
||||
for (const method in registry) {
|
||||
window.CMS[method] = registry[method];
|
||||
}
|
||||
window.createClass = React.createClass;
|
||||
window.h = React.createElement;
|
||||
|
36
src/lib/registry.js
Normal file
36
src/lib/registry.js
Normal file
@ -0,0 +1,36 @@
|
||||
import {List} from 'immutable';
|
||||
import {newEditorPlugin} from '../components/Widgets/MarkdownControlElements/plugins';
|
||||
|
||||
const _registry = {
|
||||
templates: {},
|
||||
previewStyles: [],
|
||||
widgets: {},
|
||||
editorComponents: List([])
|
||||
};
|
||||
|
||||
export default {
|
||||
registerPreviewStyle(style) {
|
||||
_registry.previewStyles.push(style);
|
||||
},
|
||||
registerPreviewTemplate(name, component) {
|
||||
_registry.templates[name] = component;
|
||||
},
|
||||
getPreviewTemplate(name) {
|
||||
return _registry.templates[name];
|
||||
},
|
||||
getPreviewStyles() {
|
||||
return _registry.previewStyles;
|
||||
},
|
||||
registerWidget(name, control, preview) {
|
||||
_registry.widgets[name] = { control, preview };
|
||||
},
|
||||
getWidget(name) {
|
||||
return _registry.widgets[name];
|
||||
},
|
||||
registerEditorComponent(component) {
|
||||
_registry.editorComponents = _registry.editorComponents.push(newEditorPlugin(component));
|
||||
},
|
||||
getEditorComponents() {
|
||||
return _registry.editorComponents;
|
||||
}
|
||||
};
|
@ -13,7 +13,7 @@ module.exports = {
|
||||
{ test: /\.json$/, loader: 'json-loader' },
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: ExtractTextPlugin.extract("style", "css?modules&importLoaders=1!postcss"),
|
||||
loader: ExtractTextPlugin.extract("style", "css?modules&importLoaders=1&&localIdentName=cms__[name]__[local]!postcss"),
|
||||
},
|
||||
{
|
||||
loader: 'babel',
|
||||
|
Loading…
x
Reference in New Issue
Block a user