Merge branch 'master' into markitup-react
This commit is contained in:
commit
ca34def49e
@ -4,7 +4,10 @@ env:
|
||||
jest: true
|
||||
|
||||
parser: babel-eslint
|
||||
plugins: [ "react" ]
|
||||
plugins: [
|
||||
"react",
|
||||
"class-property"
|
||||
]
|
||||
|
||||
rules:
|
||||
# Possible Errors
|
||||
@ -100,6 +103,8 @@ rules:
|
||||
react/self-closing-comp: 1
|
||||
react/sort-comp: 1
|
||||
|
||||
class-property/class-property-semicolon: 2
|
||||
|
||||
# Global scoped method and vars
|
||||
globals:
|
||||
netlify: true
|
||||
|
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2016 Netlify <team@netlify.com>
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -3,6 +3,9 @@
|
||||
A CMS for static site generators. Give non-technical users a simple way to edit
|
||||
and add content to any site built with a static site generator.
|
||||
|
||||
Netlify CMS is released under the [MIT License](LICENSE).
|
||||
Please make sure you understand its [implications and guarantees](https://writing.kemitchell.com/2016/09/21/MIT-License-Line-by-Line.html).
|
||||
|
||||
## How it works
|
||||
|
||||
Netlify CMS is a single-page app that you pull into the `/admin` part of your site.
|
||||
|
@ -44,6 +44,7 @@
|
||||
"css-loader": "^0.23.1",
|
||||
"enzyme": "^2.4.1",
|
||||
"eslint": "^3.5.0",
|
||||
"eslint-plugin-class-property": "^1.0.1",
|
||||
"eslint-plugin-react": "^5.1.1",
|
||||
"expect": "^1.20.2",
|
||||
"exports-loader": "^0.6.3",
|
||||
|
@ -6,13 +6,9 @@ export default class AuthenticationPage extends React.Component {
|
||||
onLogin: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.handleLogin = this.handleLogin.bind(this);
|
||||
}
|
||||
state = {};
|
||||
|
||||
handleLogin(e) {
|
||||
handleLogin = e => {
|
||||
e.preventDefault();
|
||||
let auth;
|
||||
if (document.location.host.split(':')[0] === 'localhost') {
|
||||
@ -28,7 +24,7 @@ export default class AuthenticationPage extends React.Component {
|
||||
}
|
||||
this.props.onLogin(data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loginError } = this.state;
|
||||
|
@ -5,13 +5,9 @@ export default class AuthenticationPage extends React.Component {
|
||||
onLogin: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.handleLogin = this.handleLogin.bind(this);
|
||||
}
|
||||
state = {};
|
||||
|
||||
handleLogin(e) {
|
||||
handleLogin = e => {
|
||||
e.preventDefault();
|
||||
const { email, password } = this.state;
|
||||
this.setState({ authenticating: true });
|
||||
@ -33,7 +29,7 @@ export default class AuthenticationPage extends React.Component {
|
||||
this.setState({ loginError: data.msg });
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleChange(key) {
|
||||
return (e) => {
|
||||
|
@ -5,21 +5,16 @@ export default class AuthenticationPage extends React.Component {
|
||||
onLogin: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { email: '' };
|
||||
this.handleLogin = this.handleLogin.bind(this);
|
||||
this.handleEmailChange = this.handleEmailChange.bind(this);
|
||||
}
|
||||
state = { email: '' };
|
||||
|
||||
handleLogin(e) {
|
||||
handleLogin = e => {
|
||||
e.preventDefault();
|
||||
this.props.onLogin(this.state);
|
||||
}
|
||||
};
|
||||
|
||||
handleEmailChange(e) {
|
||||
handleEmailChange = e => {
|
||||
this.setState({ email: e.target.value });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <form onSubmit={this.handleLogin}>
|
||||
|
@ -10,26 +10,26 @@ export default class AppHeader extends React.Component {
|
||||
|
||||
state = {
|
||||
createMenuActive: false
|
||||
}
|
||||
};
|
||||
|
||||
handleCreatePostClick = collectionName => {
|
||||
const { onCreateEntryClick } = this.props;
|
||||
if (onCreateEntryClick) {
|
||||
onCreateEntryClick(collectionName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleCreateButtonClick = () => {
|
||||
this.setState({
|
||||
createMenuActive: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleCreateMenuHide = () => {
|
||||
this.setState({
|
||||
createMenuActive: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
@ -43,41 +43,41 @@ export default class AppHeader extends React.Component {
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
fixed
|
||||
theme={styles}
|
||||
fixed
|
||||
theme={styles}
|
||||
>
|
||||
<IconButton
|
||||
icon="menu"
|
||||
inverse
|
||||
onClick={toggleNavDrawer}
|
||||
icon="menu"
|
||||
inverse
|
||||
onClick={toggleNavDrawer}
|
||||
/>
|
||||
<IndexLink to="/">
|
||||
Dashboard
|
||||
</IndexLink>
|
||||
<FindBar
|
||||
commands={commands}
|
||||
defaultCommands={defaultCommands}
|
||||
runCommand={runCommand}
|
||||
commands={commands}
|
||||
defaultCommands={defaultCommands}
|
||||
runCommand={runCommand}
|
||||
/>
|
||||
<Button
|
||||
className={styles.createBtn}
|
||||
icon='add'
|
||||
floating
|
||||
accent
|
||||
onClick={this.handleCreateButtonClick}
|
||||
className={styles.createBtn}
|
||||
icon='add'
|
||||
floating
|
||||
accent
|
||||
onClick={this.handleCreateButtonClick}
|
||||
>
|
||||
<Menu
|
||||
active={createMenuActive}
|
||||
position="topRight"
|
||||
onHide={this.handleCreateMenuHide}
|
||||
active={createMenuActive}
|
||||
position="topRight"
|
||||
onHide={this.handleCreateMenuHide}
|
||||
>
|
||||
{
|
||||
collections.valueSeq().map(collection =>
|
||||
<MenuItem
|
||||
key={collection.get('name')}
|
||||
value={collection.get('name')}
|
||||
onClick={this.handleCreatePostClick.bind(this, collection.get('name'))}
|
||||
caption={pluralize(collection.get('label'), 1)}
|
||||
key={collection.get('name')}
|
||||
value={collection.get('name')}
|
||||
onClick={this.handleCreatePostClick.bind(this, collection.get('name'))}
|
||||
caption={pluralize(collection.get('label'), 1)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -17,9 +17,8 @@ export default class EntryListing extends React.Component {
|
||||
{ mq: '495px', columns: 2, gutter: 15 },
|
||||
{ mq: '750px', columns: 3, gutter: 15 },
|
||||
{ mq: '1005px', columns: 4, gutter: 15 },
|
||||
{ mq: '1260px', columns: 5, gutter: 15 },
|
||||
{ mq: '1515px', columns: 6, gutter: 15 },
|
||||
{ mq: '1770px', columns: 7, gutter: 15 },
|
||||
{ mq: '1515px', columns: 5, gutter: 15 },
|
||||
{ mq: '1770px', columns: 6, gutter: 15 },
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -8,8 +8,18 @@ export const SEARCH = 'SEARCH';
|
||||
const PLACEHOLDER = 'Search or enter a command';
|
||||
|
||||
class FindBar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
static propTypes = {
|
||||
commands: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
pattern: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
defaultCommands: PropTypes.arrayOf(PropTypes.string),
|
||||
runCommand: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._compiledCommands = [];
|
||||
this._searchCommand = {
|
||||
search: true,
|
||||
@ -26,18 +36,6 @@ class FindBar extends Component {
|
||||
};
|
||||
|
||||
this._getSuggestions = _.memoize(this._getSuggestions, (value, activeScope) => value + activeScope);
|
||||
this.compileCommand = this.compileCommand.bind(this);
|
||||
this.matchCommand = this.matchCommand.bind(this);
|
||||
this.maybeRemoveActiveScope = this.maybeRemoveActiveScope.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.handleInputBlur = this.handleInputBlur.bind(this);
|
||||
this.handleInputFocus = this.handleInputFocus.bind(this);
|
||||
this.handleInputClick = this.handleInputClick.bind(this);
|
||||
this.getSuggestions = this.getSuggestions.bind(this);
|
||||
this.highlightCommandFromMouse = this.highlightCommandFromMouse.bind(this);
|
||||
this.selectCommandFromMouse = this.selectCommandFromMouse.bind(this);
|
||||
this.setIgnoreBlur = this.setIgnoreBlur.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
@ -58,7 +56,7 @@ class FindBar extends Component {
|
||||
}
|
||||
|
||||
// Generates a regexp and splits a token and param details for a command
|
||||
compileCommand(command) {
|
||||
compileCommand = command => {
|
||||
let regexp = '';
|
||||
let param = null;
|
||||
|
||||
@ -79,11 +77,11 @@ class FindBar extends Component {
|
||||
token,
|
||||
param
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the entered string matches any command.
|
||||
// adds a scope (so user can type param value) and dispatches action for fully matched commands
|
||||
matchCommand() {
|
||||
matchCommand = () => {
|
||||
const string = this.state.activeScope ? this.state.activeScope + this.state.value : this.state.value;
|
||||
let match;
|
||||
let command = this._compiledCommands.find(command => {
|
||||
@ -133,20 +131,20 @@ class FindBar extends Component {
|
||||
}
|
||||
this.props.runCommand(command.type, payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
maybeRemoveActiveScope() {
|
||||
maybeRemoveActiveScope = () => {
|
||||
if (this.state.value.length === 0 && this.state.activeScope) {
|
||||
this.setState({
|
||||
activeScope: null,
|
||||
placeholder: PLACEHOLDER
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getSuggestions() {
|
||||
getSuggestions = () => {
|
||||
return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands, this.props.defaultCommands);
|
||||
}
|
||||
};
|
||||
|
||||
// Memoized version
|
||||
_getSuggestions(value, scope, commands, defaultCommands) {
|
||||
@ -173,7 +171,7 @@ class FindBar extends Component {
|
||||
return returnResults;
|
||||
}
|
||||
|
||||
handleKeyDown(event) {
|
||||
handleKeyDown = event => {
|
||||
let highlightedIndex, index;
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
@ -240,37 +238,37 @@ class FindBar extends Component {
|
||||
isOpen: true
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleChange(event) {
|
||||
handleChange = event => {
|
||||
this.setState({
|
||||
value: event.target.value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleInputBlur() {
|
||||
handleInputBlur = () => {
|
||||
if (this._ignoreBlur) return;
|
||||
this.setState({
|
||||
isOpen: false,
|
||||
highlightedIndex: 0
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleInputFocus() {
|
||||
handleInputFocus = () => {
|
||||
if (this._ignoreBlur) return;
|
||||
this.setState({ isOpen: true });
|
||||
}
|
||||
};
|
||||
|
||||
handleInputClick() {
|
||||
handleInputClick = () => {
|
||||
if (this.state.isOpen === false)
|
||||
this.setState({ isOpen: true });
|
||||
}
|
||||
};
|
||||
|
||||
highlightCommandFromMouse(index) {
|
||||
highlightCommandFromMouse = index => {
|
||||
this.setState({ highlightedIndex: index });
|
||||
}
|
||||
};
|
||||
|
||||
selectCommandFromMouse(command) {
|
||||
selectCommandFromMouse = command => {
|
||||
const newState = {
|
||||
isOpen: false,
|
||||
highlightedIndex: 0
|
||||
@ -283,11 +281,11 @@ class FindBar extends Component {
|
||||
this._input.focus();
|
||||
this.setIgnoreBlur(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setIgnoreBlur(ignore) {
|
||||
setIgnoreBlur = ignore => {
|
||||
this._ignoreBlur = ignore;
|
||||
}
|
||||
};
|
||||
|
||||
renderMenu() {
|
||||
const commands = this.getSuggestions().map((command, index) => {
|
||||
@ -309,11 +307,11 @@ class FindBar extends Component {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
|
||||
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
|
||||
onMouseDown={() => this.setIgnoreBlur(true)}
|
||||
onMouseEnter={() => this.highlightCommandFromMouse(index)}
|
||||
onClick={() => this.selectCommandFromMouse(command)}
|
||||
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
|
||||
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
|
||||
onMouseDown={() => this.setIgnoreBlur(true)}
|
||||
onMouseEnter={() => this.highlightCommandFromMouse(index)}
|
||||
onClick={() => this.selectCommandFromMouse(command)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
@ -347,15 +345,15 @@ class FindBar extends Component {
|
||||
<label className={styles.inputArea}>
|
||||
{scope}
|
||||
<input
|
||||
className={styles.inputField}
|
||||
ref={(c) => this._input = c}
|
||||
onFocus={this.handleInputFocus}
|
||||
onBlur={this.handleInputBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onClick={this.handleInputClick}
|
||||
placeholder={this.state.placeholder}
|
||||
value={this.state.value}
|
||||
className={styles.inputField}
|
||||
ref={(c) => this._input = c}
|
||||
onFocus={this.handleInputFocus}
|
||||
onBlur={this.handleInputBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onClick={this.handleInputClick}
|
||||
placeholder={this.state.placeholder}
|
||||
value={this.state.value}
|
||||
/>
|
||||
</label>
|
||||
{menu}
|
||||
@ -364,14 +362,4 @@ class FindBar extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
FindBar.propTypes = {
|
||||
commands: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
pattern: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
defaultCommands: PropTypes.arrayOf(PropTypes.string),
|
||||
runCommand: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default FindBar;
|
||||
|
@ -3,14 +3,10 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import styles from './Loader.css';
|
||||
|
||||
export default class Loader extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentItem: 0,
|
||||
};
|
||||
this.setAnimation = this.setAnimation.bind(this);
|
||||
this.renderChild = this.renderChild.bind(this);
|
||||
}
|
||||
|
||||
state = {
|
||||
currentItem: 0,
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.interval) {
|
||||
@ -18,7 +14,7 @@ export default class Loader extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
setAnimation() {
|
||||
setAnimation = () => {
|
||||
if (this.interval) return;
|
||||
const { children } = this.props;
|
||||
|
||||
@ -27,9 +23,9 @@ export default class Loader extends React.Component {
|
||||
const nextItem = (this.state.currentItem === children.length - 1) ? 0 : this.state.currentItem + 1;
|
||||
this.setState({ currentItem: nextItem });
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
renderChild() {
|
||||
renderChild = () => {
|
||||
const { children } = this.props;
|
||||
const { currentItem } = this.state;
|
||||
if (!children) {
|
||||
@ -40,15 +36,15 @@ export default class Loader extends React.Component {
|
||||
this.setAnimation();
|
||||
return <div className={styles.text}>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={styles}
|
||||
transitionEnterTimeout={500}
|
||||
transitionLeaveTimeout={500}
|
||||
transitionName={styles}
|
||||
transitionEnterTimeout={500}
|
||||
transitionLeaveTimeout={500}
|
||||
>
|
||||
<div key={currentItem} className={styles.animateItem}>{children[currentItem]}</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { active, style, className = '' } = this.props;
|
||||
|
@ -3,14 +3,10 @@ import { Icon } from '../index';
|
||||
import styles from './Toast.css';
|
||||
|
||||
export default class Toast extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
shown: false
|
||||
};
|
||||
|
||||
this.autoHideTimeout = this.autoHideTimeout.bind(this);
|
||||
}
|
||||
state = {
|
||||
shown: false
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
if (this.props.show) {
|
||||
@ -32,12 +28,12 @@ export default class Toast extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
autoHideTimeout() {
|
||||
autoHideTimeout = () => {
|
||||
clearTimeout(this.timeOut);
|
||||
this.timeOut = setTimeout(() => {
|
||||
this.setState({ shown: false });
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { style, type, className, children } = this.props;
|
||||
|
@ -24,25 +24,26 @@
|
||||
|
||||
.card {
|
||||
width: 100% !important;
|
||||
margin: 7px 0;
|
||||
margin: 7px 0 0 10px;
|
||||
padding: 7px 0;
|
||||
}
|
||||
|
||||
& h2 {
|
||||
font-size: 17px;
|
||||
& small {
|
||||
font-weight: normal;
|
||||
}
|
||||
.cardHeading {
|
||||
font-size: 17px;
|
||||
& small {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
& p {
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.cardText {
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
& button {
|
||||
margin: 10px 10px 0 0;
|
||||
float: right;
|
||||
}
|
||||
.button {
|
||||
margin: 10px 10px 0 0;
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,28 +8,21 @@ import { status, statusDescriptions } from '../constants/publishModes';
|
||||
import styles from './UnpublishedListing.css';
|
||||
|
||||
class UnpublishedListing extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.renderColumns = this.renderColumns.bind(this);
|
||||
this.handleChangeStatus = this.handleChangeStatus.bind(this);
|
||||
this.requestPublish = this.requestPublish.bind(this);
|
||||
}
|
||||
|
||||
handleChangeStatus(newStatus, dragProps) {
|
||||
handleChangeStatus = (newStatus, dragProps) => {
|
||||
const slug = dragProps.slug;
|
||||
const collection = dragProps.collection;
|
||||
const oldStatus = dragProps.ownStatus;
|
||||
this.props.handleChangeStatus(collection, slug, oldStatus, newStatus);
|
||||
}
|
||||
};
|
||||
|
||||
requestPublish(collection, slug, ownStatus) {
|
||||
requestPublish = (collection, slug, ownStatus) => {
|
||||
if (ownStatus !== status.last()) return;
|
||||
if (window.confirm('Are you sure you want to publish this entry?')) {
|
||||
this.props.handlePublish(collection, slug, ownStatus);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderColumns(entries, column) {
|
||||
renderColumns = (entries, column) => {
|
||||
if (!entries) return;
|
||||
|
||||
if (!column) {
|
||||
@ -60,10 +53,10 @@ class UnpublishedListing extends React.Component {
|
||||
<DragSource key={slug} slug={slug} collection={collection} ownStatus={ownStatus}>
|
||||
<div className={styles.drag}>
|
||||
<Card className={styles.card}>
|
||||
<h2><Link to={link}>{entry.getIn(['data', 'title'])}</Link> <small>by {author}</small></h2>
|
||||
<p>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p>
|
||||
<span className={styles.cardHeading}><Link to={link}>{entry.getIn(['data', 'title'])}</Link> <small>by {author}</small></span>
|
||||
<p className={styles.cardText}>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p>
|
||||
{(ownStatus === status.last()) &&
|
||||
<button onClick={this.requestPublish.bind(this, collection, slug, status)}>Publish now</button>
|
||||
<button className={styles.button} onClick={this.requestPublish.bind(this, collection, slug, status)}>Publish now</button>
|
||||
}
|
||||
</Card>
|
||||
</div>
|
||||
@ -74,7 +67,13 @@ class UnpublishedListing extends React.Component {
|
||||
)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
entries: ImmutablePropTypes.orderedMap,
|
||||
handleChangeStatus: PropTypes.func.isRequired,
|
||||
handlePublish: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const columns = this.renderColumns(this.props.entries);
|
||||
@ -89,10 +88,4 @@ class UnpublishedListing extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
UnpublishedListing.propTypes = {
|
||||
entries: ImmutablePropTypes.orderedMap,
|
||||
handleChangeStatus: PropTypes.func.isRequired,
|
||||
handlePublish: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default HTML5DragDrop(UnpublishedListing);
|
||||
|
@ -2,14 +2,9 @@ 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) {
|
||||
handleChange = datetime => {
|
||||
this.props.onChange(datetime);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <DateTime value={this.props.value || new Date()} onChange={this.handleChange}/>;
|
||||
|
@ -5,36 +5,25 @@ import MediaProxy from '../../valueObjects/MediaProxy';
|
||||
const MAX_DISPLAY_LENGTH = 50;
|
||||
|
||||
export default class ImageControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleFileInputRef = this.handleFileInputRef.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleDragEnter = this.handleDragEnter.bind(this);
|
||||
this.handleDragOver = this.handleDragOver.bind(this);
|
||||
this.renderImageName = this.renderImageName.bind(this);
|
||||
}
|
||||
|
||||
handleFileInputRef(el) {
|
||||
handleFileInputRef = el => {
|
||||
this._fileInput = el;
|
||||
}
|
||||
};
|
||||
|
||||
handleClick(e) {
|
||||
handleClick = e => {
|
||||
this._fileInput.click();
|
||||
}
|
||||
};
|
||||
|
||||
handleDragEnter(e) {
|
||||
handleDragEnter = e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
handleDragOver(e) {
|
||||
handleDragOver = e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
handleChange(e) {
|
||||
handleChange = e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@ -58,9 +47,9 @@ export default class ImageControl extends React.Component {
|
||||
this.props.onChange(null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
renderImageName() {
|
||||
renderImageName = () => {
|
||||
if (!this.props.value) return null;
|
||||
if (this.value instanceof MediaProxy) {
|
||||
return truncateMiddle(this.props.value.path, MAX_DISPLAY_LENGTH);
|
||||
@ -68,25 +57,25 @@ export default class ImageControl extends React.Component {
|
||||
return truncateMiddle(this.props.value, MAX_DISPLAY_LENGTH);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const imageName = this.renderImageName();
|
||||
return (
|
||||
<div
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDrop={this.handleChange}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDrop={this.handleChange}
|
||||
>
|
||||
<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"
|
||||
accept="image/*"
|
||||
onChange={this.handleChange}
|
||||
style={styles.input}
|
||||
ref={this.handleFileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={this.handleChange}
|
||||
style={styles.input}
|
||||
ref={this.handleFileInputRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -7,6 +7,14 @@ import { connect } from 'react-redux';
|
||||
import { switchVisualMode } from '../../actions/editor';
|
||||
|
||||
class MarkdownControl extends React.Component {
|
||||
static propTypes = {
|
||||
editor: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onAddMedia: PropTypes.func.isRequired,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
switchVisualMode: PropTypes.func.isRequired,
|
||||
value: PropTypes.node,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.useRawEditor();
|
||||
@ -15,11 +23,11 @@ class MarkdownControl extends React.Component {
|
||||
|
||||
useVisualEditor = () => {
|
||||
this.props.switchVisualMode(true);
|
||||
}
|
||||
};
|
||||
|
||||
useRawEditor = () => {
|
||||
this.props.switchVisualMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { editor, onChange, onAddMedia, getMedia, value } = this.props;
|
||||
@ -28,11 +36,11 @@ class MarkdownControl extends React.Component {
|
||||
<div className='cms-editor-visual'>
|
||||
{null && <button onClick={this.useRawEditor}>Switch to Raw Editor</button>}
|
||||
<VisualEditor
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
getMedia={getMedia}
|
||||
registeredComponents={editor.get('registeredComponents')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
getMedia={getMedia}
|
||||
registeredComponents={editor.get('registeredComponents')}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -41,10 +49,10 @@ class MarkdownControl extends React.Component {
|
||||
<div className='cms-editor-raw'>
|
||||
{null && <button onClick={this.useVisualEditor}>Switch to Visual Editor</button>}
|
||||
<RawEditor
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
getMedia={getMedia}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onAddMedia={onAddMedia}
|
||||
getMedia={getMedia}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -52,19 +60,6 @@ class MarkdownControl extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownControl.propTypes = {
|
||||
editor: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onAddMedia: PropTypes.func.isRequired,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
switchVisualMode: PropTypes.func.isRequired,
|
||||
value: PropTypes.node,
|
||||
};
|
||||
|
||||
MarkdownControl.contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => ({ editor: state.editor }),
|
||||
{ switchVisualMode }
|
||||
|
@ -72,7 +72,14 @@ const SCHEMA = {
|
||||
}
|
||||
};
|
||||
|
||||
class RawEditor extends React.Component {
|
||||
export default class RawEditor extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
onAddMedia: PropTypes.func.isRequired,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -104,12 +111,12 @@ class RawEditor extends React.Component {
|
||||
*/
|
||||
handleChange = state => {
|
||||
this.setState({ state });
|
||||
}
|
||||
};
|
||||
|
||||
handleDocumentChange = (document, state) => {
|
||||
const content = Plain.serialize(state, { terse: true });
|
||||
this.props.onChange(content);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
@ -125,12 +132,3 @@ class RawEditor extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RawEditor;
|
||||
|
||||
RawEditor.propTypes = {
|
||||
onAddMedia: PropTypes.func.isRequired,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
@ -5,21 +5,17 @@ import MediaProxy from '../../../../valueObjects/MediaProxy';
|
||||
import styles from './BlockTypesMenu.css';
|
||||
|
||||
class BlockTypesMenu extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expanded: false
|
||||
};
|
||||
static propTypes = {
|
||||
plugins: PropTypes.array.isRequired,
|
||||
onClickBlock: PropTypes.func.isRequired,
|
||||
onClickPlugin: PropTypes.func.isRequired,
|
||||
onClickImage: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
this.toggleMenu = this.toggleMenu.bind(this);
|
||||
this.handleBlockTypeClick = this.handleBlockTypeClick.bind(this);
|
||||
this.handlePluginClick = this.handlePluginClick.bind(this);
|
||||
this.handleFileUploadClick = this.handleFileUploadClick.bind(this);
|
||||
this.handleFileUploadChange = this.handleFileUploadChange.bind(this);
|
||||
this.renderBlockTypeButton = this.renderBlockTypeButton.bind(this);
|
||||
this.renderPluginButton = this.renderPluginButton.bind(this);
|
||||
}
|
||||
state = {
|
||||
expanded: false
|
||||
};
|
||||
|
||||
componentWillUpdate() {
|
||||
if (this.state.expanded) {
|
||||
@ -27,27 +23,27 @@ class BlockTypesMenu extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
toggleMenu = () => {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
};
|
||||
|
||||
handleBlockTypeClick(e, type) {
|
||||
handleBlockTypeClick = (e, type) => {
|
||||
this.props.onClickBlock(type);
|
||||
}
|
||||
};
|
||||
|
||||
handlePluginClick(e, plugin) {
|
||||
handlePluginClick = (e, plugin) => {
|
||||
const data = {};
|
||||
plugin.fields.forEach(field => {
|
||||
data[field.name] = window.prompt(field.label); // eslint-disable-line
|
||||
});
|
||||
this.props.onClickPlugin(plugin.id, data);
|
||||
}
|
||||
};
|
||||
|
||||
handleFileUploadClick() {
|
||||
handleFileUploadClick = () => {
|
||||
this._fileInput.click();
|
||||
}
|
||||
};
|
||||
|
||||
handleFileUploadChange(e) {
|
||||
handleFileUploadChange = e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@ -67,21 +63,21 @@ class BlockTypesMenu extends Component {
|
||||
this.props.onClickImage(mediaProxy);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
renderBlockTypeButton(type, icon) {
|
||||
renderBlockTypeButton = (type, icon) => {
|
||||
const onClick = e => this.handleBlockTypeClick(e, type);
|
||||
return (
|
||||
<Icon key={type} type={icon} onClick={onClick} className={styles.icon}/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderPluginButton(plugin) {
|
||||
renderPluginButton = plugin => {
|
||||
const onClick = e => this.handlePluginClick(e, plugin);
|
||||
return (
|
||||
<Icon key={plugin.id} type={plugin.icon} onClick={onClick} className={styles.icon}/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderMenu() {
|
||||
const { plugins } = this.props;
|
||||
@ -117,11 +113,4 @@ class BlockTypesMenu extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
BlockTypesMenu.propTypes = {
|
||||
plugins: PropTypes.array.isRequired,
|
||||
onClickBlock: PropTypes.func.isRequired,
|
||||
onClickPlugin: PropTypes.func.isRequired,
|
||||
onClickImage: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withPortalAtCursorPosition(BlockTypesMenu);
|
||||
|
@ -5,43 +5,39 @@ import styles from './StylesMenu.css';
|
||||
|
||||
class StylesMenu extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.hasMark = this.hasMark.bind(this);
|
||||
this.hasBlock = this.hasBlock.bind(this);
|
||||
this.renderMarkButton = this.renderMarkButton.bind(this);
|
||||
this.renderBlockButton = this.renderBlockButton.bind(this);
|
||||
this.renderLinkButton = this.renderLinkButton.bind(this);
|
||||
this.handleMarkClick = this.handleMarkClick.bind(this);
|
||||
this.handleInlineClick = this.handleInlineClick.bind(this);
|
||||
this.handleBlockClick = this.handleBlockClick.bind(this);
|
||||
}
|
||||
static propTypes = {
|
||||
marks: PropTypes.object.isRequired,
|
||||
blocks: PropTypes.object.isRequired,
|
||||
inlines: PropTypes.object.isRequired,
|
||||
onClickBlock: PropTypes.func.isRequired,
|
||||
onClickMark: PropTypes.func.isRequired,
|
||||
onClickInline: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to set toolbar buttons to active state
|
||||
*/
|
||||
hasMark(type) {
|
||||
hasMark = type => {
|
||||
const { marks } = this.props;
|
||||
return marks.some(mark => mark.type == type);
|
||||
}
|
||||
};
|
||||
|
||||
hasBlock(type) {
|
||||
hasBlock = type => {
|
||||
const { blocks } = this.props;
|
||||
return blocks.some(node => node.type == type);
|
||||
}
|
||||
};
|
||||
|
||||
hasLinks(type) {
|
||||
hasLinks = type => {
|
||||
const { inlines } = this.props;
|
||||
return inlines.some(inline => inline.type == 'link');
|
||||
}
|
||||
};
|
||||
|
||||
handleMarkClick(e, type) {
|
||||
handleMarkClick = (e, type) => {
|
||||
e.preventDefault();
|
||||
this.props.onClickMark(type);
|
||||
}
|
||||
};
|
||||
|
||||
renderMarkButton(type, icon) {
|
||||
renderMarkButton = (type, icon) => {
|
||||
const isActive = this.hasMark(type);
|
||||
const onMouseDown = e => this.handleMarkClick(e, type);
|
||||
return (
|
||||
@ -49,14 +45,14 @@ class StylesMenu extends Component {
|
||||
<Icon type={icon}/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handleInlineClick(e, type, isActive) {
|
||||
handleInlineClick = (e, type, isActive) => {
|
||||
e.preventDefault();
|
||||
this.props.onClickInline(type, isActive);
|
||||
}
|
||||
};
|
||||
|
||||
renderLinkButton() {
|
||||
renderLinkButton = () => {
|
||||
const isActive = this.hasLinks();
|
||||
const onMouseDown = e => this.handleInlineClick(e, 'link', isActive);
|
||||
return (
|
||||
@ -64,16 +60,16 @@ class StylesMenu extends Component {
|
||||
<Icon type="link"/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handleBlockClick(e, type) {
|
||||
handleBlockClick = (e, type) => {
|
||||
e.preventDefault();
|
||||
const isActive = this.hasBlock(type);
|
||||
const isList = this.hasBlock('list-item');
|
||||
this.props.onClickBlock(type, isActive, isList);
|
||||
}
|
||||
};
|
||||
|
||||
renderBlockButton(type, icon, checkType) {
|
||||
renderBlockButton = (type, icon, checkType) => {
|
||||
checkType = checkType || type;
|
||||
const isActive = this.hasBlock(checkType);
|
||||
const onMouseDown = e => this.handleBlockClick(e, type);
|
||||
@ -82,7 +78,7 @@ class StylesMenu extends Component {
|
||||
<Icon type={icon}/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
@ -98,16 +94,6 @@ class StylesMenu extends Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StylesMenu.propTypes = {
|
||||
marks: PropTypes.object.isRequired,
|
||||
blocks: PropTypes.object.isRequired,
|
||||
inlines: PropTypes.object.isRequired,
|
||||
onClickBlock: PropTypes.func.isRequired,
|
||||
onClickMark: PropTypes.func.isRequired,
|
||||
onClickInline: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withPortalAtCursorPosition(StylesMenu);
|
||||
|
@ -13,11 +13,18 @@ import BlockTypesMenu from './BlockTypesMenu';
|
||||
/**
|
||||
* Slate Render Configuration
|
||||
*/
|
||||
class VisualEditor extends React.Component {
|
||||
export default class VisualEditor extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onAddMedia: PropTypes.func.isRequired,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.getMedia = this.getMedia.bind(this);
|
||||
const MarkdownSyntax = getSyntaxes(this.getMedia).markdown;
|
||||
this.markdown = new MarkupIt(MarkdownSyntax);
|
||||
|
||||
@ -46,48 +53,36 @@ class VisualEditor extends React.Component {
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleDocumentChange = this.handleDocumentChange.bind(this);
|
||||
this.handleMarkStyleClick = this.handleMarkStyleClick.bind(this);
|
||||
this.handleBlockStyleClick = this.handleBlockStyleClick.bind(this);
|
||||
this.handleInlineClick = this.handleInlineClick.bind(this);
|
||||
this.handleBlockTypeClick = this.handleBlockTypeClick.bind(this);
|
||||
this.handlePluginClick = this.handlePluginClick.bind(this);
|
||||
this.handleImageClick = this.handleImageClick.bind(this);
|
||||
this.focusAndAddParagraph = this.focusAndAddParagraph.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.renderBlockTypesMenu = this.renderBlockTypesMenu.bind(this);
|
||||
}
|
||||
|
||||
getMedia(src) {
|
||||
getMedia = src => {
|
||||
return this.props.getMedia(src);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Slate keeps track of selections, scroll position etc.
|
||||
* So, onChange gets dispatched on every interaction (click, arrows, everything...)
|
||||
* It also have an onDocumentChange, that get's dispached only when the actual
|
||||
* It also have an onDocumentChange, that get's dispatched only when the actual
|
||||
* content changes
|
||||
*/
|
||||
handleChange(state) {
|
||||
handleChange = state => {
|
||||
if (this.blockEdit) {
|
||||
this.blockEdit = false;
|
||||
} else {
|
||||
this.setState({ state });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleDocumentChange(document, state) {
|
||||
handleDocumentChange = (document, state) => {
|
||||
const rawJson = Raw.serialize(state, { terse: true });
|
||||
const content = SlateUtils.decode(rawJson);
|
||||
this.props.onChange(this.markdown.toText(content));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle marks / blocks when button is clicked
|
||||
*/
|
||||
handleMarkStyleClick(type) {
|
||||
handleMarkStyleClick = type => {
|
||||
let { state } = this.state;
|
||||
|
||||
state = state
|
||||
@ -96,9 +91,9 @@ class VisualEditor extends React.Component {
|
||||
.apply();
|
||||
|
||||
this.setState({ state });
|
||||
}
|
||||
};
|
||||
|
||||
handleBlockStyleClick(type, isActive, isList) {
|
||||
handleBlockStyleClick = (type, isActive, isList) => {
|
||||
let { state } = this.state;
|
||||
let transform = state.transform();
|
||||
const { document } = state;
|
||||
@ -142,7 +137,7 @@ class VisualEditor extends React.Component {
|
||||
|
||||
state = transform.apply();
|
||||
this.setState({ state });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When clicking a link, if the selection has a link in it, remove the link.
|
||||
@ -151,7 +146,7 @@ class VisualEditor extends React.Component {
|
||||
* @param {Event} e
|
||||
*/
|
||||
|
||||
handleInlineClick(type, isActive) {
|
||||
handleInlineClick = (type, isActive) => {
|
||||
let { state } = this.state;
|
||||
|
||||
if (type === 'link') {
|
||||
@ -177,9 +172,9 @@ class VisualEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
this.setState({ state });
|
||||
}
|
||||
};
|
||||
|
||||
handleBlockTypeClick(type) {
|
||||
handleBlockTypeClick = type => {
|
||||
let { state } = this.state;
|
||||
|
||||
state = state
|
||||
@ -191,9 +186,9 @@ class VisualEditor extends React.Component {
|
||||
.apply();
|
||||
|
||||
this.setState({ state }, this.focusAndAddParagraph);
|
||||
}
|
||||
};
|
||||
|
||||
handlePluginClick(type, data) {
|
||||
handlePluginClick = (type, data) => {
|
||||
let { state } = this.state;
|
||||
|
||||
state = state
|
||||
@ -209,9 +204,9 @@ class VisualEditor extends React.Component {
|
||||
.apply();
|
||||
|
||||
this.setState({ state });
|
||||
}
|
||||
};
|
||||
|
||||
handleImageClick(mediaProxy) {
|
||||
handleImageClick = mediaProxy => {
|
||||
let { state } = this.state;
|
||||
this.props.onAddMedia(mediaProxy);
|
||||
|
||||
@ -221,9 +216,9 @@ class VisualEditor extends React.Component {
|
||||
.apply();
|
||||
|
||||
this.setState({ state });
|
||||
}
|
||||
};
|
||||
|
||||
focusAndAddParagraph() {
|
||||
focusAndAddParagraph = () => {
|
||||
const { state } = this.state;
|
||||
const blocks = state.document.getBlocks();
|
||||
const last = blocks.last();
|
||||
@ -237,9 +232,9 @@ class VisualEditor extends React.Component {
|
||||
snapshot: false
|
||||
});
|
||||
this.setState({ state: normalized });
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyDown(evt) {
|
||||
handleKeyDown = evt => {
|
||||
if (evt.shiftKey && evt.key === 'Enter') {
|
||||
this.blockEdit = true;
|
||||
let { state } = this.state;
|
||||
@ -250,9 +245,9 @@ class VisualEditor extends React.Component {
|
||||
|
||||
this.setState({ state });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderBlockTypesMenu() {
|
||||
renderBlockTypesMenu = () => {
|
||||
const currentBlock = this.state.state.blocks.get(0);
|
||||
const isOpen = (this.props.value !== undefined && currentBlock.isEmpty && currentBlock.type !== 'horizontal-rule');
|
||||
|
||||
@ -265,7 +260,7 @@ class VisualEditor extends React.Component {
|
||||
onClickImage={this.handleImageClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderStylesMenu() {
|
||||
const { state } = this.state;
|
||||
@ -302,12 +297,3 @@ class VisualEditor extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VisualEditor;
|
||||
|
||||
VisualEditor.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onAddMedia: PropTypes.func.isRequired,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
@ -18,6 +18,14 @@ const EditorComponent = Record({
|
||||
|
||||
|
||||
class Plugin extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.element.isRequired
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
plugins: PropTypes.object
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return { plugins: plugins };
|
||||
}
|
||||
@ -27,13 +35,6 @@ class Plugin extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
Plugin.propTypes = {
|
||||
children: PropTypes.element.isRequired
|
||||
};
|
||||
Plugin.childContextTypes = {
|
||||
plugins: PropTypes.object
|
||||
};
|
||||
|
||||
export function newEditorPlugin(config) {
|
||||
const configObj = new EditorComponent({
|
||||
id: config.id || config.label.replace(/[^A-Z0-9]+/ig, '_'),
|
||||
|
@ -1,14 +1,9 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default class StringControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
handleChange = e => {
|
||||
this.props.onChange(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <input type="text" value={this.props.value || ''} onChange={this.handleChange}/>;
|
||||
|
@ -1,20 +1,14 @@
|
||||
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) {
|
||||
handleChange = e => {
|
||||
this.props.onChange(e.target.value);
|
||||
this.updateHeight();
|
||||
}
|
||||
};
|
||||
|
||||
updateHeight() {
|
||||
if (this.element.scrollHeight > this.element.clientHeight) {
|
||||
@ -22,9 +16,9 @@ export default class StringControl extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleRef(ref) {
|
||||
handleRef = ref => {
|
||||
this.element = ref;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <textarea ref={this.handleRef} value={this.props.value || ''} onChange={this.handleChange}/>;
|
||||
|
@ -4,7 +4,17 @@
|
||||
.nav {
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
& .heading {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.main {
|
||||
padding-top: 54px;
|
||||
}
|
||||
|
||||
.navDrawer {
|
||||
max-width: 240px !important;
|
||||
& .drawerContent {
|
||||
max-width: 240px !important;
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ import styles from './App.css';
|
||||
class App extends React.Component {
|
||||
|
||||
state = {
|
||||
navDrawerIsVisible: false
|
||||
}
|
||||
navDrawerIsVisible: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatch(loadConfig());
|
||||
@ -100,7 +100,7 @@ class App extends React.Component {
|
||||
this.setState({
|
||||
navDrawerIsVisible: !this.state.navDrawerIsVisible
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navDrawerIsVisible } = this.state;
|
||||
@ -135,18 +135,19 @@ class App extends React.Component {
|
||||
return (
|
||||
<Layout theme={styles}>
|
||||
<NavDrawer
|
||||
active={navDrawerIsVisible}
|
||||
scrollY
|
||||
permanentAt="md"
|
||||
active={navDrawerIsVisible}
|
||||
scrollY
|
||||
permanentAt={navDrawerIsVisible ? 'lg' : null}
|
||||
theme={styles}
|
||||
>
|
||||
<nav className={styles.nav}>
|
||||
<h1>Collections</h1>
|
||||
<h1 className={styles.heading}>Collections</h1>
|
||||
<Navigation type='vertical'>
|
||||
{
|
||||
collections.valueSeq().map(collection =>
|
||||
<Link
|
||||
key={collection.get('name')}
|
||||
onClick={navigateToCollection.bind(this, collection.get('name'))}
|
||||
key={collection.get('name')}
|
||||
onClick={navigateToCollection.bind(this, collection.get('name'))}
|
||||
>
|
||||
{collection.get('label')}
|
||||
</Link>
|
||||
@ -157,14 +158,14 @@ class App extends React.Component {
|
||||
</NavDrawer>
|
||||
<Panel scrollY>
|
||||
<AppHeader
|
||||
collections={collections}
|
||||
commands={commands}
|
||||
defaultCommands={defaultCommands}
|
||||
runCommand={runCommand}
|
||||
onCreateEntryClick={createNewEntryInCollection}
|
||||
toggleNavDrawer={this.toggleNavDrawer}
|
||||
collections={collections}
|
||||
commands={commands}
|
||||
defaultCommands={defaultCommands}
|
||||
runCommand={runCommand}
|
||||
onCreateEntryClick={createNewEntryInCollection}
|
||||
toggleNavDrawer={this.toggleNavDrawer}
|
||||
/>
|
||||
<div className={`${styles.alignable} ${styles.main}`}>
|
||||
<div className={styles.main}>
|
||||
{children}
|
||||
</div>
|
||||
</Panel>
|
||||
|
@ -1,39 +1,39 @@
|
||||
.alignable {
|
||||
margin: 0px auto;
|
||||
.root {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 749px) and (min-width: 495px) {
|
||||
.alignable {
|
||||
.root {
|
||||
width: 495px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1004px) and (min-width: 750px) {
|
||||
.alignable {
|
||||
.root {
|
||||
width: 750px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1259px) and (min-width: 1005px) {
|
||||
.alignable {
|
||||
@media (max-width: 1514px) and (min-width: 1005px) {
|
||||
.root {
|
||||
width: 1005px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1514px) and (min-width: 1260px) {
|
||||
.alignable {
|
||||
width: 1260px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1769px) and (min-width: 1515px) {
|
||||
.alignable {
|
||||
.root {
|
||||
width: 1515px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1770px) {
|
||||
.alignable {
|
||||
.root {
|
||||
width: 1770px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,13 @@ import styles from './CollectionPage.css';
|
||||
import CollectionPageHOC from './editorialWorkflow/CollectionPageHOC';
|
||||
|
||||
class DashboardPage extends React.Component {
|
||||
static propTypes = {
|
||||
collection: ImmutablePropTypes.map.isRequired,
|
||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
entries: ImmutablePropTypes.list,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { collection, dispatch } = this.props;
|
||||
if (collection) {
|
||||
@ -30,7 +37,7 @@ class DashboardPage extends React.Component {
|
||||
}
|
||||
|
||||
|
||||
return <div className={styles.alignable}>
|
||||
return <div className={styles.root}>
|
||||
{entries ?
|
||||
<EntryListing collection={collection} entries={entries}/>
|
||||
:
|
||||
@ -39,12 +46,6 @@ class DashboardPage extends React.Component {
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
DashboardPage.propTypes = {
|
||||
collection: ImmutablePropTypes.map.isRequired,
|
||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
entries: ImmutablePropTypes.list,
|
||||
};
|
||||
|
||||
/*
|
||||
* Instead of checking the publish mode everywhere to dispatch & render the additional editorial workflow stuff,
|
||||
|
@ -15,11 +15,22 @@ import EntryEditor from '../components/EntryEditor/EntryEditor';
|
||||
import EntryPageHOC from './editorialWorkflow/EntryPageHOC';
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.createDraft = this.createDraft.bind(this);
|
||||
this.handlePersistEntry = this.handlePersistEntry.bind(this);
|
||||
}
|
||||
static propTypes = {
|
||||
addMedia: PropTypes.func.isRequired,
|
||||
boundGetMedia: PropTypes.func.isRequired,
|
||||
changeDraft: PropTypes.func.isRequired,
|
||||
collection: ImmutablePropTypes.map.isRequired,
|
||||
createDraftFromEntry: PropTypes.func.isRequired,
|
||||
createEmptyDraft: PropTypes.func.isRequired,
|
||||
discardDraft: PropTypes.func.isRequired,
|
||||
entry: ImmutablePropTypes.map,
|
||||
entryDraft: ImmutablePropTypes.map.isRequired,
|
||||
loadEntry: PropTypes.func.isRequired,
|
||||
persistEntry: PropTypes.func.isRequired,
|
||||
removeMedia: PropTypes.func.isRequired,
|
||||
slug: PropTypes.string,
|
||||
newEntry: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.newEntry) {
|
||||
@ -45,13 +56,13 @@ class EntryPage extends React.Component {
|
||||
this.props.discardDraft();
|
||||
}
|
||||
|
||||
createDraft(entry) {
|
||||
createDraft = entry => {
|
||||
if (entry) this.props.createDraftFromEntry(entry);
|
||||
}
|
||||
};
|
||||
|
||||
handlePersistEntry() {
|
||||
handlePersistEntry = () => {
|
||||
this.props.persistEntry(this.props.collection, this.props.entryDraft);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
@ -75,23 +86,6 @@ class EntryPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
EntryPage.propTypes = {
|
||||
addMedia: PropTypes.func.isRequired,
|
||||
boundGetMedia: PropTypes.func.isRequired,
|
||||
changeDraft: PropTypes.func.isRequired,
|
||||
collection: ImmutablePropTypes.map.isRequired,
|
||||
createDraftFromEntry: PropTypes.func.isRequired,
|
||||
createEmptyDraft: PropTypes.func.isRequired,
|
||||
discardDraft: PropTypes.func.isRequired,
|
||||
entry: ImmutablePropTypes.map,
|
||||
entryDraft: ImmutablePropTypes.map.isRequired,
|
||||
loadEntry: PropTypes.func.isRequired,
|
||||
persistEntry: PropTypes.func.isRequired,
|
||||
removeMedia: PropTypes.func.isRequired,
|
||||
slug: PropTypes.string,
|
||||
newEntry: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const { collections, entryDraft } = state;
|
||||
const collection = collections.get(ownProps.params.name);
|
||||
|
@ -10,6 +10,11 @@ import styles from '../CollectionPage.css';
|
||||
|
||||
export default function CollectionPageHOC(CollectionPage) {
|
||||
class CollectionPageHOC extends CollectionPage {
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
isEditorialWorkflow: PropTypes.bool.isRequired,
|
||||
unpublishedEntries: ImmutablePropTypes.map,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { dispatch, isEditorialWorkflow } = this.props;
|
||||
@ -24,24 +29,20 @@ export default function CollectionPageHOC(CollectionPage) {
|
||||
if (!isEditorialWorkflow) return super.render();
|
||||
|
||||
return (
|
||||
<div className={styles.alignable}>
|
||||
<UnpublishedListing
|
||||
<div>
|
||||
<div className={styles.root}>
|
||||
<UnpublishedListing
|
||||
entries={unpublishedEntries}
|
||||
handleChangeStatus={updateUnpublishedEntryStatus}
|
||||
handlePublish={publishUnpublishedEntry}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
{super.render()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionPageHOC.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
isEditorialWorkflow: PropTypes.bool.isRequired,
|
||||
unpublishedEntries: ImmutablePropTypes.map,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const publish_mode = state.config.get('publish_mode');
|
||||
const isEditorialWorkflow = (publish_mode === EDITORIAL_WORKFLOW);
|
||||
|
Loading…
x
Reference in New Issue
Block a user