Home Reference Source

application/components/content/content-item.js

import React from 'react';
import Immutable from 'immutable';
import _ from 'lodash';

import {Entity} from 'draft-js';

import {browserHistory, Link} from 'react-router';
import BaseView from './../base-view';
import {connect} from 'react-redux';

import moment from 'moment';

import {withDraftable} from './../../hocs/draftable';

import ceoTheme from './../../theme';

import RaisedButton from 'material-ui/RaisedButton';
import FlatButton from 'material-ui/FlatButton';
import IconButton from 'material-ui/IconButton';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import Checkbox from 'material-ui/Checkbox';
import Toggle from 'material-ui/Toggle';
import FontIcon from 'material-ui/FontIcon';
import Paper from 'material-ui/Paper';
import Divider from 'material-ui/Divider';
import CircularProgress from 'material-ui/CircularProgress';
import {List, ListItem} from 'material-ui/List';
import {GridList, GridTile} from 'material-ui/GridList';
import Subheader from 'material-ui/Subheader';
import Avatar from 'material-ui/Avatar';

import {Row, Col} from './../flexbox';

import {stateToHTML} from 'draft-js-export-html';
// import stateToHTML from './../../exportToHTML_modified';

import {error, info, warning} from './../../util/console';
import {sluggify, timeToFormat} from './../../util/strings';
import Config from './../../config';

import CurrentUser from './../../current-user';

import LoadingIndicator from './../common/loading-indicator';
import RichEditor from './../common/rich-editor';
import RichEditor2 from './../common/rich-editor2';
import DraftSerializer from '../common/rich-editor2/util/draft-serializer';

import {ALIGNMENT_DATA_KEY} from './../common/rich-editor/libs/extend-rich-utils';
import SimpleEditor from './../common/simple-editor';
import UserSearchBox from './../common/user-search-box';

import DraftLastAutosave from './../draftable/draft-last-autosave';
import DraftRestore from './../draftable/draft-restore';

import ContentAttachmentView from './content-attachment-view';
import ContentAttachmentPreview from './content-attachment-preview';
import ContentMetaProperties from './content-meta-properties';
import ContentLock from './content-lock';
import PublishModal from './content-publish';
import SimplePublishModal from './content-simple-publish';
import DiffModal from './content-diff';
import ExportModal from './content-export';
import DominantMediaModal from './content-dom-media';
import ContentAssignment from './content-assignment';
import AuthorSearchBox from './../common/author-search-box';
import TagSearchBox from './../common/tag-search-box';
import SstsSearchBox from './../common/ssts-search-box';

import NoteViewer from './../common/rich-editor/note-viewer';
import DeleteButton from './../common/delete-button';
import CharacterCounter from './../common/character-counter';
import WordCounter from './../common/word-counter';

import UrlHandler from './url-handler';

import {rawToHtml} from './../../util/rawToHtml';

import {
    workflowsMaybeFetch,
    workflowsFilterContentOnly,
    workflowsFilterPrintOnly
} from './../../redux/actions/workflow-actions';

import {
    workflowSectionsMaybeFetch,
    workflowSectionsFilterContentOnly,
    workflowSectionsFilterPrintOnly
} from './../../redux/actions/workflow-section-actions';

import {
    issuesMaybeFetch,
    issuesFilterOpen
} from './../../redux/actions/issue-actions';

import {
    snackbarShowMessage,
    snackbarClose
} from './../../redux/actions/snackbar-actions';

import {
    alertShowMessage
} from './../../redux/actions/alert-actions';

import {
    globalHistoryPush,
    globalHistoryPop
} from './../../redux/actions/global-history-actions';

import {
    applicationIsDirty,
    applicationFetchSettings
} from './../../redux/actions/application-actions';


import {
    contentMaybeFetchCache,
    contentFetchOne,
    contentCheckIn,
    contentCheckOut,
    contentRemove,
    contentUpdate,
    contentCreate,
    contentAttachmentUpdate,
    contentSyncPrint,
    contentClearLock
} from './../../redux/actions/content-actions';

import {
    containersFetch
} from './../../redux/actions/container-actions';

class ContentItem extends BaseView {
    constructor(props) {
        super(props);

        this.getUuid = this.getUuid.bind(this);

        this.handleSave = this.handleSave.bind(this);
        this.handleSaveCheckIn = this.handleSaveCheckIn.bind(this);
        this.handlePreview = this.handlePreview.bind(this);
        this.handleEditorChange = this.handleEditorChange.bind(this);
        this.handleEditorChange2 = this.handleEditorChange2.bind(this);
        this.handleFormChange = this.handleFormChange.bind(this);
        this.handleMetaFormChange = this.handleMetaFormChange.bind(this);
        this.handleCheckout = this.handleCheckout.bind(this);
        this.handleCheckin = this.handleCheckin.bind(this);
        this.handleDelete = this.handleDelete.bind(this);
        this.handleRestoreDraft = this.handleRestoreDraft.bind(this);
        this.handleOpenPublish = this.handleOpenPublish.bind(this);
        this.handleClosePublish = this.handleClosePublish.bind(this);

        this.isUserEditable = this.isUserEditable.bind(this);

        this.freezeContent = this.freezeContent.bind(this);
        this.thawContent = this.thawContent.bind(this);

        this.keyTimer = null;
        this.contentData = {};
        this.abstractData = {};

        this.onChange = this.onChange.bind(this);

        this.getRouter = this.getRouter.bind(this);

        this.richEditorRefCount = 0;

        this.state = {
            content: Immutable.fromJS({
                authors: [],
                tags: []
            }),
            diffIsOpen: false,
            exportIsOpen: false,
            domMediaIsOpen: false,
            contentLimbo: '',
            abstractLimbo: '',
            didDelete: false
        };
    }

    onChange(state) {
        this.setState(state);
    }

    getRouter() {
        return this.props.router;
    }

    /**
     * Return the requested UID, starts with UUID property,
     * then goes to the URL property.
     * @return {string}
     */
    getUuid() {
        if (this.props.uuid) {
            return this.props.uuid;
        }

        if (this.props.params && this.props.params.id) {
            return this.props.params.id;
        }

        return false;
    }

    toHtml(contentState) {
        return rawToHtml(contentState);
    }

    componentWillUpdate(nextProps, nextState) {
        if (nextProps.params.id && nextProps.params.id != this.props.params.id) {
            this.componentWillMount.call(this);
        }
    }

    componentWillMount() {

        const {dispatch} = this.props;

        if (!this.isNew()) {
            dispatch(contentFetchOne(this.getUuid()))
                .then(() => {
                    let content = this.props.content.items.find((item) => item.get('uuid') == this.getUuid());
                    let dirty = false;

                    // pause here for a second and see if we have a modified version in the global history state
                    const savedState = this.props.globalHistory.items.find((item) => item.get('uuid') == this.getUuid());

                    if (savedState && savedState.size && savedState.get('isDirty')) {
                        // we have a freeze dried state. reconstitute that
                        // then set the dirty state to 'true'
                        content = this.thawContent(savedState.get('cachedObject'));
                        dirty = true;
                    }

                    this.setState({'content': content, 'dirty': dirty}, () => {
                        this.props.draftable.doesHaveDraft({'srn': this.state.content.get('srn')}, (draft) => {
                            if (draft) {
                                this.setState({'pendingDraft': draft});
                            }
                        });

                        this.props.draftable.setDraftCreatedHook(() => {
                            this.setState({'lastSaved': moment().utc()});
                        });
                    });
                });
        } else {
            this.setState({'content': Immutable.fromJS({
                authors: [],
                tags: [],
                type: this.getRouter().location.query.type ? this.getRouter().location.query.type : 'article'
            })});
        }

        dispatch(workflowsMaybeFetch())
            .then(() => dispatch(containersFetch({type: ['blog']})))
            .then(() => dispatch(workflowsFilterContentOnly()))
            .then(() => dispatch(workflowsFilterPrintOnly()))
            .then(() => {
                if (this.isNew() && this.getRouter().location.query.type == 'post' && this.props.containers.items) {
                    this.setState({
                        'content': this.state.content.set('container_id', this.props.containers.items.first().get('id'))
                    });
                }
            });

        dispatch(workflowSectionsMaybeFetch());
        dispatch(issuesMaybeFetch())
            .then(() => dispatch(issuesFilterOpen()));

        this.setState({'publishIsOpen': false});
    }

    componentDidMount() {
        if (this.state.content.get('content') && !this.state.contentLimbo) {
            this.state.contentLimbo = this.state.content.get('content').replace(/(<([^>]+)>)/ig, "")
        }

        this.getRouter().setRouteLeaveHook(this.props.route, this.routerWillLeave.bind(this))
    }

    routerWillLeave(nextLocation) {
        if (!this.isNew() && !this.state.didDelete) {
            const {dispatch} = this.props;
            dispatch(globalHistoryPush({
                'label': this.state.content.get('title') ? this.state.content.get('title') : this.state.content.get('slug'),
                'type': 'content',
                'contentType': this.state.content.get('type'),
                'url': '/ceo/content/' + this.state.content.get('uuid'),
                'uuid': this.state.content.get('uuid'),
                'isDirty': this.state.dirty,
                'cachedObject': (this.state.dirty ? this.freezeContent(this.state.content) : {})
            }));
        }

        if (this.isNew() && this.isUserEditable() && this.state.dirty && this.state.dirty === true) {
            return 'You have not saved this item! Any unsaved changes will be lost once you click OK. Click cancel and then save to preserve your work. Are you still sure you want to leave this item?';
        }
    }

    /**
     * Freeze-dry content to be so it can be picked up later
     * @param {object} content
     */
    freezeContent(content) {
        let toFreeze = content.asMutable();

        // ok, so here's the thing, the rich editor is _initialized_ via the content_raw property, but
        // the actual output is stored in contentData.raw. So we have to sort of manage the state
        // for both items when freezing and thawing.
        let rawContent = this.contentData.raw ? JSON.stringify(this.contentData.raw) : toFreeze.get('content_raw');
        let rawAbstract = this.abstractData.raw ? JSON.stringify(this.abstractData.raw) : toFreeze.get('abstract_raw');

        toFreeze.set('content_raw', rawContent);
        toFreeze.set('content', this.contentData.html ? this.contentData.html : toFreeze.get('content'));
        toFreeze.set('abstract_raw', rawAbstract);
        toFreeze.set('abstract', this.abstractData.html ? this.abstractData.html : toFreeze.get('abstract'));

        return toFreeze.asImmutable();
    }

    /**
     * Thaw freeze-dried content
     * @param {object} content
     */
    thawContent(content) {
        let toThaw = content.asMutable();

        // We need to populate this so the user can save right after thawing a content item and the content block
        // saves properly. If we don't set this, then the text content of wouldn't update properly unless the user
        // made changes in the editor before saving (forcing the component to update contentData.raw itself).
        this.contentData.raw = toThaw.get('content_raw') ? JSON.parse(toThaw.get('content_raw')) : '';
        this.contentData.html = toThaw.get('content');
        this.abstractData.raw = toThaw.get('abstract_raw') ? JSON.parse(toThaw.get('abstract_raw')) : '';
        this.abstractData.html = toThaw.get('abstract');

        return toThaw.asImmutable();
    }

    isNew() {
        if (!this.getUuid() || this.getUuid() === 'new') {
            return true;
        }

        return false;
    }

    isLocked() {
        if (this.isNew()) {
            return false;
        }

        if (this.state.content.get('lock')) {
            return true;
        }

        return false;
    }

    isUserEditable() {
        if (this.isNew()) {
            return true;
        }

        if (!this.state.content.get('lock')) {
            return false;
        }

        if (this.state.content.get('lock').get('user_id') === CurrentUser.getId()) {
            return true;
        }

        return false;
    }

    handleCheckout(e) {
        const {dispatch} = this.props;
        return dispatch(contentCheckOut(this.state.content.get('srn')))
            .then(() => dispatch(contentFetchOne(this.state.content.get('uuid'))))
            .then(() => {
                const content = this.props.content.items.find((item) => item.get('uuid') == this.state.content.get('uuid'));
                this.contentData = {};
                this.abstractData = {};
                this.setState({'content': content});
            });
    }

    handleCheckin(e) {
        const {dispatch} = this.props;
        return dispatch(contentCheckIn(this.getUuid(), this.state.content.get('lock').get('uuid')))
            .then(() => dispatch(contentFetchOne(this.getUuid())))
            .then(() => {
                const content = this.props.content.items.find((item) => item.get('uuid') == this.state.content.get('uuid'));
                this.setState({'content': content});
            });
    }

    handleDelete(e) {
        const {dispatch} = this.props;
        dispatch(contentRemove(this.getUuid()))
            .then(() => dispatch(globalHistoryPop(this.getUuid())))
            .then(() => this.setState({'didDelete': true}, () => {
                browserHistory.push('/ceo/content');
                dispatch(snackbarShowMessage('Content removed'));
            }))
            .catch(() => {
                dispatch(snackbarClose());
                dispatch(alertShowMessage({
                    'title': 'Oops',
                    'message': (
                        <p>There was a problem removing your content. You may not have permission to delete.</p>
                    )
                }));
                this.setState({'dirty': true});
                dispatch(applicationIsDirty(true));
            });
    }

    handleRestoreDraft() {

        if (!this.state.pendingDraft) {
            return;
        }

        const newContent = this.state.content.set(
            'content_raw',
            this.state.pendingDraft.get('content_raw')
        );

        this.setState({'content': newContent, 'pendingDraft': false});
    }

    handleFormChange(e) {
        var prop = e.target.getAttribute('for');
        if (prop) {
            let newContent;

            if (prop === 'slug') {
                newContent = this.state.content.set(
                    'slug', sluggify(e.target.value, true)
                );
            } else if (prop === 'title') {
                newContent = this.state.content
                    .set('title', e.target.value);
            } else if (prop === 'title_url') {
                newContent = this.state.content
                    .set('title_url', sluggify(e.target.value, true));
            } else if (prop === 'weight') {
                newContent = this.state.content.set(
                    'weight', parseInt(e.target.value)
                );
            } else if (prop == 'click_through') {
                newContent = this.state.content.set(
                    'click_through', e.target.value
                );
            }

            this.setState({'content': newContent, 'dirty': true});
            this.props.dispatch(applicationIsDirty(true));

        }
    }

    handleSelectChange(id, e, index, val) {
        this.setState({'content': this.state.content.set(id, val), 'dirty': true});
        this.props.dispatch(applicationIsDirty(true));
    }

    handleWorkflowChange(e, index, val) {
        this.setState({'content': this.state.content.set('workflow_id', val), 'dirty': true});
        this.props.dispatch(applicationIsDirty(true));
    }

    handlePrintWorkflowChange(e, index, val) {
        let expt = this.state.content.get('export');

        if (!expt) {
            expt = Immutable.fromJS({});
        }

        expt = expt.set('workflow_id', val);
        this.setState({'content': this.state.content.set('export', expt), 'dirty': true});
        this.props.dispatch(applicationIsDirty(true));
    }

    handlePrintWorkflowSectionChange(e, index, val) {
        let expt = this.state.content.get('export');

        if (!expt) {
            expt = Immutable.fromJS({});
        }

        expt = expt.set('workflow_section_id', val);
        this.setState({'content': this.state.content.set('export', expt), 'dirty': true});
        this.props.dispatch(applicationIsDirty(true));
    }

    handlePrintIssueChange(e, index, val) {
        let expt = this.state.content.get('export');

        if (!expt) {
            expt = Immutable.fromJS({});
        }

        expt = expt.set('issue_id', val);
        this.setState({'content': this.state.content.set('export', expt), 'dirty': true});
        this.props.dispatch(applicationIsDirty(true));
    }

    onSelectUser(user) {
        let expt = this.state.content.get('export');

        if (!expt) {
            expt = Immutable.fromJS({});
        }

        expt = expt.set('assignee_id', user.get('id')).set('assignee', user);
        this.setState({'content': this.state.content.set('export', expt), 'dirty': true});
        this.props.dispatch(applicationIsDirty(true));
    }

    onRemoveUser(user) {
        let expt = this.state.content.get('export');

        if (!expt) {
            expt = Immutable.fromJS({});
        }

        expt = expt.set('assignee_id', null).set('assignee', null);
        this.setState({'content': this.state.content.set('export', expt), 'dirty': true});
        this.props.dispatch(applicationIsDirty(true));
    }

    handleMetaFormChange(fieldData) {
        if (fieldData) {
            let newContent = this.state.content;
            let meta = this.state.content.get('meta');

            if (!meta) {
                meta = Immutable.fromJS({});
            }
            meta = meta.set(fieldData.field, Immutable.fromJS(fieldData));

            this.setState({'content': newContent.set('meta', meta), 'dirty': true});
        }
    }

    handleEditorChange(raw, state, name) {
        if (!this.state.dirty) {
            this.setState({'dirty': true});
            this.props.dispatch(applicationIsDirty(true));
        }

        this.state.contentLimbo = this.toHtml(state.getCurrentContent()).replace(/(<([^>]+)>)/ig, "");

        this.contentData.raw = raw;
        this.contentData.html = this.toHtml(state.getCurrentContent());
        if (this.keyTimer !== null) {
            clearTimeout(this.keyTimer);

        }
        this.keyTimer = setTimeout(this.handleDraftTimer.bind(this), 1000);
    }

    handleEditorChange2(e, raw, serializer) {
        if (!this.state.dirty) {
            this.setState({'dirty': true});
            this.props.dispatch(applicationIsDirty(true));
        }

        this.state.contentLimbo = serializer(raw).replace(/(<([^>]+)>)/ig, "");

        this.contentData.raw = raw.toJSON();
        this.contentData.html = serializer(raw);
        if (this.keyTimer !== null) {
            clearTimeout(this.keyTimer);

        }
        this.keyTimer = setTimeout(this.handleDraftTimer.bind(this), 1000);
    }

    handleConvertContent(e) {
        if (!confirm('Are you sure you want to convert this? Converting content to Editor2 will remove existing editorial notes and may require reformatting.')) {
            return;
        }
        const raw = DraftSerializer.deserialize(JSON.parse(this.state.content.get('content_raw')));
        const serializer = (data) => {
            return '';
        };
        this.handleEditorChange2.call(this, e, raw, serializer);

        this.setState({
            content: this.state.content.set('content_raw', JSON.stringify(raw))
        });
    }

    handleSimpleEditorChange(raw, state, name) {
        if (!this.state.dirty) {
            this.setState({'dirty': true});
            this.props.dispatch(applicationIsDirty(true));
        }

        this.setState({
            abstractLimbo: this.toHtml(state.getCurrentContent()).replace(/(<([^>]+)>)/ig, "")
        });

        this.abstractData.raw = raw;
        this.abstractData.html = this.toHtml(state.getCurrentContent());
    }

    handleDraftTimer() {
        if (!this.isNew() && this.isUserEditable()) {
            this.props.draftable.shouldCreateDraft({
                srn: this.state.content.get('srn'),
                raw: this.contentData.raw,
                html: this.contentData.html
            });
        }
    }

    handleEditorStateChange(refCount) {
        if (refCount >= 1 && this.props.draftable) {
            this.props.draftable.haltDraft(true);
        } else {
            this.props.draftable.haltDraft(false);
        }
    }

    handleSaveCheckIn() {
        this.handleSave(true);
    }

    handleSave(andCheckin = false) {
        const {dispatch} = this.props;

        var data = {
            'slug':             this.state.content.get('slug'),
            'type':             this.state.content.get('type'),
            'title':            this.state.content.get('title'),
            'title_url':        this.state.content.get('title_url'),
            'layout_template':  this.state.content.get('layout_template'),
            'content_raw':      this.contentData.raw ? this.contentData.raw : this.state.content.get('content_raw'),
            'content':          this.contentData.html ? this.contentData.html : this.state.content.get('content'),
            'abstract_raw':     this.abstractData.raw ? this.abstractData.raw : this.state.content.get('abstract_raw'),
            'abstract':         this.abstractData.html ? this.abstractData.html : this.state.content.get('abstract'),
            'published_at':     this.state.content.get('published_at'),
            'workflow_id':      this.state.content.get('workflow_id'),
            'container_id':     this.state.content.get('container_id'),
            'weight':           this.state.content.get('weight'),
            'click_through':    this.state.content.get('click_through')
        };

        if (!this.isUserEditable()) {
            return;
        }

        dispatch(snackbarShowMessage('Saving...', false));

        if (this.state.content.get('export')) {
            data.export = {};

            if (this.state.content.get('export').get('workflow_id')) {
                data.export.workflow_id = this.state.content.get('export').get('workflow_id');
            }
            if (this.state.content.get('export').get('workflow_section_id')) {
                data.export.workflow_section_id = this.state.content.get('export').get('workflow_section_id');
            }
            if (this.state.content.get('export').get('issue_id')) {
                data.export.issue_id = this.state.content.get('export').get('issue_id');
            }
            if (this.state.content.get('export').get('assignee_id')) {
                data.export.assignee_id = this.state.content.get('export').get('assignee_id');
            }
        }

        // This is set by the callback in the UrlHandler component
        if (this.state.customUrl) {
            data.urls = [{url: this.state.customUrl, is_default: 1}];
        }

        if (this.state.content.get('meta') && this.state.content.get('meta').size) {
            data.meta = this.state.content.get('meta').toJS();
        }

        if (this.state.content.get('authors') && this.state.content.get('authors').size) {
            data['authors'] = this.state.content.get('authors').map((author, i) => {
                return author.get('uuid');
            });
        } else {
            data['authors'] = [];
        }

        if (this.state.content.get('tags') && this.state.content.get('tags').size) {
            data['tags'] = this.state.content.get('tags').map((tag, i) => {
                return tag.get('uuid');
            });
        } else {
            data['tags'] = [];
        }

        if (this.state.content.get('ssts_id')) {
            data['ssts_id'] = this.state.content.get('ssts_id');
        } else {
            data['ssts_id'] = null;
        }

        if (this.state.content.get('dominantAttachment')) {
            data['dominantAttachment'] = this.state.content.get('dominantAttachment').get('uuid');
        } else {
            data['dominantAttachment'] = false;
        }

        // the assignment query param is set by the assignment-item's "Create content" button
        if (this.props.location.query && this.props.location.query.assignment) {
            const assignment = this.props.assignments.items.find((item) => item.get('uuid') == this.props.location.query.assignment);
            data['assignment_id'] = assignment.size ? assignment.get('id') : null;
        }

        if (!this.isNew()) {
            // Update existing is pretty straightforward
            // update via the uuid
            dispatch(contentUpdate(this.getUuid(), data))
                .then(() => {
                    // reset the object to the new item
                    const content = this.props.content.items.find((item) => item.get('uuid') == this.state.content.get('uuid'));
                    this.setState({'content': content});
                })
                .then(() => {
                    // flash the message
                    dispatch(snackbarShowMessage('Content updated'));
                    this.setState({'dirty': false});
                    dispatch(applicationIsDirty(false));
                })
                .then(() => {
                    // if we're checking in too, do that
                    if (andCheckin !== true) {
                        return;
                    }
                    this.handleCheckin();
                })
                .catch(() => {
                    dispatch(snackbarClose());
                    dispatch(alertShowMessage({
                        'title': 'Oops',
                        'message': (
                            <p>There was a problem saving your content. Usually this means you don't have permission to perform an action, like publishing or deleting.</p>
                        )
                    }));
                    this.setState({'dirty': true});
                    dispatch(applicationIsDirty(true));
                });
        } else {
            // Create is slightly different
            // first, create it
            dispatch(contentCreate(data))
                .then(() => {
                    // then grab the newly created item and pass it on
                    const content = this.props.content.items.find((item) => item.get('uuid') == this.props.content.selected);
                    dispatch(snackbarShowMessage('Content created'));
                    return content;
                })
                .then((content) => {
                    // update the state with the newly created object
                    this.setState({
                        'dirty': false,
                        'content': content
                    }, () => {
                        // after that's done check it out
                        dispatch(applicationIsDirty(false));
                        this.handleCheckout({})
                            .then(() => {
                                // when the checkout is complete, update the URL
                                // if you don't wait, you can get a race condition that can
                                // result in a no-op warning
                                browserHistory.push('/ceo/content/' + this.props.content.selected);
                            });
                    });
                })
                .catch(() => {
                    dispatch(snackbarClose());
                    dispatch(alertShowMessage({
                        'title': 'Oops',
                        'message': (
                            <p>There was a problem saving your content. Usually this means you don't have permission to perform an action, like publishing or deleting.</p>
                        )
                    }));
                    this.setState({'dirty': true});
                    dispatch(applicationIsDirty(true));
                });
        }

    }

    handlePreview() {
        const url = '/content/preview/' + this.state.content.get('uuid');

        window.open(url, 'preview');
    }

    handleOpenPublish() {
        if (this.props.application.settings.get('publicationIsLocked')) {
            this.props.dispatch(alertShowMessage({
                'title': 'Publication Locked',
                'message': (
                    <p>Publication is currently locked. Please have an administrator <a href="https://snworks.zendesk.com/hc/en-us/requests/new?__cifsub=CEO+Billing+Issue" target="_blank">contact support</a> for more information.</p>
                )
            }));
            return;
        }

        this.setState({'publishIsOpen': true});
    }

    handleClosePublish() {
        this.setState({'publishIsOpen': false});
    }

    handleRestore(content) {
        this.setState({'content': content});
    }

    handleOpenDiff() {
        this.setState({'diffIsOpen': true});
    }
    handleCloseDiff() {
        this.setState({'diffIsOpen': false});
    }

    handleOpenExport() {
        this.setState({'exportIsOpen': true});
    }
    handleCloseExport() {
        this.setState({'exportIsOpen': false});
    }

    handleOpenDomMedia() {
        this.setState({'domMediaIsOpen': true});
    }
    handleCloseDomMedia() {
        this.setState({'domMediaIsOpen': false});
    }

    onPublish(pubData) {
        if (pubData.abstract !== null) {
            this.abstractData.html = pubData.abstract;
            this.abstractData.raw = pubData.abstract_raw;
        }

        const newContent = this.state.content.set('published_at', pubData.published_at.toString())

        this.setState({'content': newContent}, () => {
            this.handleSave();
        });
    }

    handleCancelPublish() {
        const newContent = this.state.content.set('published_at', null)
        this.setState({'content': newContent}, () => {
            this.handleSave();
        });
    }

    // callbacks to handle the author selection
    onSelectAuthor(author) {
        let authors = this.state.content.get('authors');
        if (!authors) {
            authors = Immutable.fromJS({});
        }

        const existing = authors.find((item) => item.get('uuid') == author.get('uuid'));

        if (existing) {
            return;
        }

        authors = authors.push(author);

        this.setState({
            'content': this.state.content.set('authors', authors),
            'dirty': true
        });
        this.props.dispatch(applicationIsDirty(true));
    }

    onRemoveAuthor(author) {
        let authors = this.state.content.get('authors');
        let newAuthors = authors.filter((item, i) => {
            return author.get('uuid') != item.get('uuid');
        });

        this.setState({
            'content': this.state.content.set('authors', newAuthors),
            'dirty': true
        });
        this.props.dispatch(applicationIsDirty(true));
    }

    // callbacks to handle the tag selection
    onSelectTag(tag) {
        let tags = this.state.content.get('tags');
        if (!tags) {
            tags = Immutable.fromJS({});
        }

        const existing = tags.find((item) => item.get('uuid') == tag.get('uuid'));

        if (existing) {
            return;
        }

        tags = tags.push(tag);

        this.setState({
            'content': this.state.content.set('tags', tags),
            'dirty': true
        });
        this.props.dispatch(applicationIsDirty(true));
    }

    onRemoveTag(tag) {
        let tags = this.state.content.get('tags');
        let newTags = tags.filter((item, i) => {
            return tag.get('uuid') != item.get('uuid');
        });

        this.setState({
            'content': this.state.content.set('tags', newTags),
            'dirty': true
        });
        this.props.dispatch(applicationIsDirty(true));
    }

    onUpdateSsts(ssts) {
        this.setState({
            'content': this.state.content.set('ssts_id', ssts.id),
            'dirty': true
        });
        this.props.dispatch(applicationIsDirty(true));
    }

    handleSelectDomMedia(media) {

        const newContent = this.state.content.set('dominantAttachment', media);

        this.setState({
            'content': newContent,
            'dirty': true
        });
        this.handleCloseDomMedia.call(this);
        this.props.dispatch(applicationIsDirty(true));
    }

    handleRemoveDomMedia() {
        if (!this.isUserEditable()) {
            return;
        }
        const newContent = this.state.content.set('dominantAttachment', null);

        this.setState({
            'content': newContent,
            'dirty': true
        });
        this.props.dispatch(applicationIsDirty(true));
    }

    handleSyncFromPrint(e) {
        const {dispatch} = this.props;

        if (confirm('Are you sure you want to sync this story from print? It will overwrite any changes in the web version.')) {
            return dispatch(contentSyncPrint(this.getUuid()))
                .then((data) => {
                    // const content = this.props.content.items.find((item) => item.get('uuid') == this.state.edited.uuid);
                    let content = data.first()
                    if (!data.first().get('content_raw')) {
                        // no content raw, reload the page
                        // browserHistory.push("/ceo/redirect?next=content/" + this.state.content.get('uuid'));
                        content = content.set('content_raw', content.get('content').replace(/\n/g, ""));

                    }

                    this.setState({'content': content});
                    dispatch(snackbarShowMessage('Content synced'));
                });
        }
    }

    handleClearLock(e) {
        if (!CurrentUser.hasRole('Administrator')) {
            return;
        }

        if (!confirm('Are you sure you want to clear this lock? You may overwrite pending changes.')) {
            return;
        }

        const {dispatch} = this.props;
        return dispatch(contentClearLock(CurrentUser.getUuid(), this.state.content.get('lock').get('uuid')))
            .then(() => dispatch(contentFetchOne(this.getUuid())))
            .then(() => {
                const content = this.props.content.items.find((item) => item.get('uuid') == this.state.content.get('uuid'));
                this.setState({'content': content});
            });
    }

    render() {
        if (!this.isNew() && !this.state.content.get('uuid')) {
            return (
                <div className="content-item-root">
                    <Row>
                        <Col xs={12}>
                            <Paper className='padded clear-top clear-bottom'>
                                <LoadingIndicator />
                            </Paper>
                        </Col>
                    </Row>
                </div>
            );
        }

        let attachment = false;
        if (this.state.content.get('attachment') && this.state.content.get('attachment').get('id')) {
            attachment = true;
        }

        return (
            <div className="content-item-root">
                <Row>
                    <Col xs={12}>
                        <Paper className='toolbar' style={{ paddingTop:'10px', paddingBottom:'10px' }}>
                            <Row middle='xs'>
                                <Col xs={8}>
                                    <ContentLock
                                        onSave={this.handleSave}
                                        onSaveCheckIn={this.handleSaveCheckIn}
                                        onCheckIn={this.handleCheckin}
                                        onCheckOut={this.handleCheckout}
                                        userEditable={this.isUserEditable()}
                                        content={this.state.content}
                                        />
                                </Col>
                                <Col xs={4} end='xs'>
                                    <FlatButton
                                        className='action-preview'
                                        onClick={this.handlePreview}
                                        disabled={this.isNew() ? true : false}
                                        label={"View " + (this.state.content.get('type') ? this.state.content.get('type') : 'Article')}
                                        icon={<FontIcon className='mui-icons'>desktop_mac</FontIcon>}
                                        />
                                </Col>
                            </Row>
                        </Paper>

                        <Row top='xs'>
                            <Col xs={9}>
                                <Paper className='padded'>
                                    <Row>
                                        <Col xs={attachment ? 6 : 12}>

                                            {
                                                // because of the way the sluggify works, the `|| ''` bit in the value property
                                                // avoids an invariant warning bug in the test suite
                                            }

                                            <ContentTitleUrlBox
                                                disabled={this.isUserEditable() ? false : true}
                                                content={this.state.content}
                                                handleFormChange={this.handleFormChange}
                                                onChange={this.onChange}
                                                isNew={this.isNew() ? true : false}
                                                />
                                        </Col>
                                        {
                                            attachment
                                            ? <ContentAttachmentView isUserEditable={this.isUserEditable()} content={this.state.content} dispatch={this.props.dispatch}/>
                                            : ''
                                        }
                                    </Row>
                                    <Row>
                                        <Col xs={12}>
                                            <ContentMetaProperties
                                                group='content'
                                                disabled={this.isUserEditable()}
                                                onChange={this.handleMetaFormChange.bind(this)}
                                                content={this.state.content}
                                                />
                                            {
                                                this.state.content.get('type') != 'page'
                                                ? (
                                                <ContentAuthors
                                                    authors={this.state.content.get('authors')}
                                                    disabled={this.isUserEditable() ? false : true}
                                                    onRemoveAuthor={this.onRemoveAuthor.bind(this)}
                                                    onSelectAuthor={this.onSelectAuthor.bind(this)}
                                                    />
                                                )
                                                : ''
                                            }

                                            <ContentTypeBox
                                                content={this.state.content}
                                                containers={this.props.containers}
                                                disabled={this.isUserEditable() ? false : true}
                                                handleSelectChange={this.handleSelectChange.bind(this)}
                                                />

                                            <ContentTags
                                                tags={this.state.content.get('tags')}
                                                disabled={this.isUserEditable() ? false : true}
                                                onRemoveTag={this.onRemoveTag.bind(this)}
                                                onSelectTag={this.onSelectTag.bind(this)}
                                                />

                                            {
                                                this.props.application.settings.get('ssts')
                                                ? (
                                                    <SstsSearchBox
                                                        value={this.state.content.get('ssts_path')}
                                                        onChange={this.onUpdateSsts.bind(this)}
                                                        />
                                                )
                                                : ''
                                            }

                                        </Col>
                                    </Row>

                                    <ContentRichEditorContainer
                                        lastSaved={this.state.lastSaved}
                                        pendingDraft={this.state.pendingDraft}
                                        handleRestoreDraft={this.handleRestoreDraft.bind(this)}
                                        disabled={this.isUserEditable() ? false : true}
                                        handleEditorChange={this.handleEditorChange}
                                        handleEditorChange2={this.handleEditorChange2}
                                        convertContent={this.handleConvertContent.bind(this)}
                                        handleStateChange={this.handleEditorStateChange.bind(this)}
                                        content={this.state.content}
                                        contentLimbo={this.state.contentLimbo}
                                        editor2={this.props.application.settings.get('labsEditor2') ? true : false}
                                        useSmartQuotes={this.props.application.settings.get('educateQuotes') ? true : false}
                                        useSuggestedLinks={this.props.application.settings.get('relatedContent') ? true : false}
                                        />

                                    <ContentAbstractEditorContainer
                                        disabled={this.isUserEditable() ? false : true}
                                        content={this.state.content}
                                        abstractLimbo={this.state.abstractLimbo}
                                        handleEditorChange={this.handleSimpleEditorChange.bind(this)}
                                        />
                                </Paper>

                                <Paper className='clear-top padded'>
                                    <Subheader>Meta Properties</Subheader>

                                    <ContentMetaProperties
                                        group='content-lower'
                                        disabled={this.isUserEditable()}
                                        onChange={this.handleMetaFormChange.bind(this)}
                                        content={this.state.content}
                                        />
                                </Paper>
                            </Col>
                            <Col xs={3}>

                                {
                                    attachment
                                    ? <ContentAttachmentPreview content={this.state.content} dispatch={this.props.dispatch}/>
                                    : ''
                                }

                                <ContentStatusBox
                                    content={this.state.content}
                                    handleOpenDiff={this.handleOpenDiff.bind(this)}
                                    handleWorkflowChange={this.handleWorkflowChange.bind(this)}
                                    workflows={this.props.workflows}
                                    disabled={this.isUserEditable() ? false : true}
                                    />

                                {
                                Config.get('cclicense') && (!this.state.content.get('type') || this.state.content.get('type') == 'article')
                                ? (
                                    <ContentPrintStatusBox
                                        content={this.state.content}
                                        handlePrintWorkflowChange={this.handlePrintWorkflowChange.bind(this)}
                                        handlePrintWorkflowSectionChange={this.handlePrintWorkflowSectionChange.bind(this)}
                                        handlePrintIssueChange={this.handlePrintIssueChange.bind(this)}
                                        onSelectUser={this.onSelectUser.bind(this)}
                                        onRemoveUser={this.onRemoveUser.bind(this)}
                                        workflows={this.props.workflows}
                                        workflowSections={this.props.workflowSections}
                                        assignments={this.props.assignments}
                                        issues={this.props.issues}
                                        handleSyncFromPrint={this.handleSyncFromPrint.bind(this)}
                                        disabled={this.isUserEditable() ? false : true}
                                        />
                                )
                                : ''
                                }

                                <ContentPublishBox
                                    content={this.state.content}
                                    handleFormChange={this.handleFormChange.bind(this)}
                                    disabled={this.isUserEditable() ? false : true}
                                    handleCancelPublish={this.handleCancelPublish.bind(this)}
                                    handleOpenPublish={this.handleOpenPublish}
                                    />

                                {
                                    this.props.application.settings.get('relatedContent')
                                    ? (
                                        <ContentRelatedBox
                                            content={this.state.content}
                                            />
                                    )
                                    : ''
                                }

                                <DominantAttachmentBox
                                    content={this.state.content}
                                    handleRemoveDomMedia={this.handleRemoveDomMedia.bind(this)}
                                    handleOpenDomMedia={this.handleOpenDomMedia.bind(this)}
                                    />

                                <ContentAssignment content={this.state.content} />

                                <div className='clear-bottom'>
                                    <RaisedButton
                                        label='Export'
                                        disabled={this.state.content.get('uuid') ? false : true}
                                        style={{'width':'100%'}}
                                        onClick={this.handleOpenExport.bind(this)}
                                        icon={<FontIcon className='mui-icons'>import_export</FontIcon>}
                                        />
                                </div>

                                {
                                    this.isLocked()
                                    ? (
                                        <div className='clear-bottom'>
                                            <RaisedButton
                                                label='Clear Lock'
                                                style={{width:'100%'}}
                                                icon={<FontIcon className='mui-icons'>clear</FontIcon>}
                                                onClick={this.handleClearLock.bind(this)}
                                                disabled={CurrentUser.hasRole('Administrator') && !this.isNew() ? false : true}
                                                />
                                        </div>
                                    )
                                    : ''
                                }

                                <div className='clear-bottom'>
                                    <DeleteButton
                                        className='full'
                                        fullWidth={true}
                                        icon={true}
                                        disabled={this.isNew() || !this.isUserEditable() ? true : false} onDelete={this.handleDelete}
                                        />
                                </div>

                                <NoteViewer editor="content" version={this.state.content.get('version')} contentUuid={this.state.content ? this.state.content.get('uuid') : false} />

                            </Col>
                        </Row>

                        {
                            this.state.publishIsOpen && this.state.content.get('type') != 'media'
                            ? (
                                <PublishModal
                                    content={this.state.content}
                                    isOpen={this.state.publishIsOpen}
                                    onPublish={this.onPublish.bind(this)}
                                    onClose={this.handleClosePublish.bind(this)}
                                    abstract={(this.abstractData.html && this.abstractData.html.length) ? this.abstractData : false}
                                    />
                            )
                            : ''
                        }
                        {
                            this.state.publishIsOpen && this.state.content.get('type') == 'media'
                            ? (
                                <SimplePublishModal
                                    content={this.state.content}
                                    isOpen={this.state.publishIsOpen}
                                    onPublish={this.onPublish.bind(this)}
                                    onClose={this.handleClosePublish.bind(this)}
                                    abstract={(this.abstractData.html && this.abstractData.html.length) ? this.abstractData : false}
                                    />
                            )
                            : ''
                        }

                        {
                            this.state.diffIsOpen
                            ? (
                                <DiffModal
                                    content={this.state.content}
                                    isOpen={this.state.diffIsOpen}
                                    disabled={this.isUserEditable() ? false : true}
                                    onRestore={this.handleRestore.bind(this)}
                                    onRequestClose={this.handleCloseDiff.bind(this)}
                                    logs={this.state.content.get('audit')}
                                    />
                            )
                            : ''
                        }

                        {
                            this.state.domMediaIsOpen
                            ? (
                                <DominantMediaModal
                                    content={this.state.content}
                                    isOpen={this.state.domMediaIsOpen}
                                    onRequestClose={this.handleCloseDomMedia.bind(this)}
                                    onSelectMedia={this.handleSelectDomMedia.bind(this)}
                                    />
                            )
                            : ''
                        }

                        {
                            this.state.content.get('uuid')
                            ? (
                                <ExportModal
                                    content={this.state.content}
                                    isOpen={this.state.exportIsOpen}
                                    onRequestClose={this.handleCloseExport.bind(this)}
                                    />
                            )
                            : ''
                        }
                    </Col>
                </Row>
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        workflows: state.workflows,
        content: state.content,
        assignments: state.assignments,
        containers: state.containers,
        workflowSections: state.workflowSections,
        issues: state.issues,
        globalHistory: state.globalHistory,
        application: state.application
    };
}

class ContentTitleUrlBox extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (!Immutable.is(this.props.content.get('urls'), nextProps.content.get('urls'))) {
            return true;
        }
        if (this.props.content.get('title') != nextProps.content.get('title')) {
            return true;
        }
        if (this.props.content.get('slug') != nextProps.content.get('slug')) {
            return true;
        }
        if (this.props.content.get('title_url') != nextProps.content.get('title_url')) {
            return true;
        }
        if (this.props.content.get('click_through') != nextProps.content.get('click_through')) {
            return true;
        }

        return false;
    }

    componentDidMount() {
        if (this.refs.slugTextField && !this.refs.slugTextField.input.value) {
            setTimeout(() => this.refs.slugTextField.focus(), 0);
        }
    }

    render() {
        return (
            <div>
                <TextField
                    disabled={this.props.disabled}
                    onChange={this.props.handleFormChange}
                    value={this.props.content.get('slug') || ''}
                    htmlFor='slug'
                    floatingLabelText="Slug"
                    hintText='Enter a Slug for the content'
                    fullWidth={true}
                    ref='slugTextField'
                />

                <TextField
                    disabled={this.props.disabled}
                    onChange={this.props.handleFormChange}
                    value={this.props.content.get('title_url') || ''}
                    htmlFor='title_url'
                    floatingLabelText="URL"
                    hintText='Enter a URL for the content'
                    fullWidth={true}
                />

                <TextField
                    disabled={this.props.disabled}
                    onChange={this.props.handleFormChange}
                    value={this.props.content.get('title')}
                    htmlFor='title'
                    floatingLabelText="Title"
                    hintText='Enter a descriptive Title for the content'
                    fullWidth={true}
                    />
                <small>
                    <CharacterCounter count={this.props.content.get('title')} displayEmpty={true} />
                </small>

                {
                    !this.props.isNew
                    ? (
                        <div>
                            <UrlHandler disabled={this.props.disabled} onChange={this.props.onChange} content={this.props.content} />
                        </div>
                    )
                    : ''
                }

                {
                    this.props.content.get('type') == 'media'
                    ? (
                        <TextField
                            disabled={this.props.disabled}
                            onChange={this.props.handleFormChange}
                            value={this.props.content.get('click_through')}
                            htmlFor='click_through'
                            floatingLabelText="Click Through URL"
                            hintText='Custom click through URL for media file'
                            fullWidth={true}
                        />
                    )
                    : ''
                }
            </div>
        );
    }
}

class ContentTimeList extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.modified_at != nextProps.modified_at
         || this.props.created_at != nextProps.created_at) {
            return true;
        }

        return false
    }

    render() {
        return (
            <List>
                <ListItem
                    disabled={true}
                    primaryText={timeToFormat(this.props.modified_at, 'LT l')}
                    secondaryText='last modified time'
                    />
                <ListItem
                    disabled={true}
                    primaryText={timeToFormat(this.props.created_at, 'LT l')}
                    secondaryText='created time'
                    />
            </List>
        )
    }
}

class ContentAuthors extends React.Component {

    onRemoveAuthor(author) {
        this.props.onRemoveAuthor(author);
    }

    onSelectAuthor(author) {
        this.props.onSelectAuthor(author);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (!Immutable.is(nextProps.authors, this.props.authors)) {
            return true;
        }

        return false;
    }


    render() {
        return (
            <Row middle='xs' flexy={true}>
                <Col flex={0} style={{'paddingRight': this.props.authors.size ? '10px' : 0}}>
                    {this.props.authors.map((author, i) => {
                        return (
                            <div
                                style={{'marginTop': '25px'}}
                                className='pill'
                                key={i}
                                >
                                {author.get('name')}
                                <FontIcon
                                    className='mui-icons'
                                    onClick={this.onRemoveAuthor.bind(this, author)}
                                    >close</FontIcon>
                            </div>
                        );
                    })}
                </Col>
                <Col flex={5}>
                    <AuthorSearchBox
                        onSelectAuthor={this.onSelectAuthor.bind(this)}
                        disabled={this.props.disabled}
                        />
                </Col>
            </Row>
        );
    }
}


class ContentTags extends React.Component {
    onRemoveTag(tag) {
        this.props.onRemoveTag(tag);
    }

    onSelectTag(tag) {
        this.props.onSelectTag(tag);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (!Immutable.is(nextProps.tags, this.props.tags)) {
            return true;
        }

        return false;
    }


    render() {
        return (
            <Row middle='xs' flexy={true}>
                <Col flex={0} style={{'paddingRight': this.props.tags.size ? '10px' : 0}}>
                    {this.props.tags.map((tag, i) => {
                        return (
                            <div
                                style={{'marginTop': '25px'}}
                                className='pill'
                                key={i}
                                >
                                {tag.get('name')}
                                <FontIcon
                                    className='mui-icons'
                                    onClick={this.onRemoveTag.bind(this, tag)}
                                    >close</FontIcon>
                            </div>
                        );
                    })}
                </Col>
                <Col flex={5}>
                    <TagSearchBox
                        onSelectTag={this.onSelectTag.bind(this)}
                        disabled={this.props.disabled}
                        />
                </Col>
            </Row>
        );
    }
}

class ContentTypeBox extends React.Component {

    handleSelectChange(id, e, index, val) {
        if (val == 'media') {
            return;
        }
        this.props.handleSelectChange(id, e, index, val);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (this.props.content.get('type') != nextProps.content.get('type')) {
            return true;
        }
        if (this.props.content.get('layout_template') != nextProps.content.get('layout_template')) {
            return true;
        }
        if (this.props.content.get('container_id') != nextProps.content.get('container_id')) {
            return true;
        }
        if (this.props.containers != nextProps.containers) {
            return true;
        }

        return false;
    }

    render() {
        let disabled = this.props.disabled;
        const content = this.props.content;

        let content_types = [];
        if (Config && Config.get('content_types')) {
            content_types = Config.get('content_types');
        }

        if (this.props.content.get('uuid') && this.props.content.get('type') == 'media') {
            disabled = true;
        }

        let layout_templates = [];
        if (Config && Config.get('layout_templates')) {
            const all_layout_templates = Config.get('layout_templates');
            layout_templates = all_layout_templates[content.get('type')] ? all_layout_templates[content.get('type')] : [] ;
        }

        return (
            <Row middle='xs'>
                <Col xs={6}>
                    <SelectField
                        fullWidth={true}
                        floatingLabelText="Content Type"
                        value={content.get('type')}
                        onChange={this.handleSelectChange.bind(this, 'type')}
                        disabled={disabled}
                        >
                        {content_types.map((type, i) => {
                            return <MenuItem key={i} value={type} primaryText={type} />;
                        })}
                    </SelectField>
                </Col>
                <Col xs={6}>
                    <SelectField
                        fullWidth={true}
                        floatingLabelText="Layout Template"
                        value={content.get('layout_template')}
                        onChange={this.handleSelectChange.bind(this, 'layout_template')}
                        disabled={this.props.disabled}
                        >
                        <MenuItem value={false} primaryText='None' />
                        {layout_templates.map((template, i) => {
                            return <MenuItem key={i} value={template} primaryText={template} />;
                        })}
                    </SelectField>
                </Col>
                {
                    content.get('type') == 'post'
                    ? (
                        <Col xs={12}>
                            <SelectField
                                fullWidth={true}
                                floatingLabelText="Blog"
                                value={content.get('container_id')}
                                onChange={this.handleSelectChange.bind(this, 'container_id')}
                                disabled={disabled}
                                >
                                {this.props.containers.items.map((container, i) => {
                                    return <MenuItem key={i} value={container.get('id')} primaryText={container.get('title')} />;
                                })}
                            </SelectField>
                        </Col>
                    )
                    : ''
                }
            </Row>

        );
    }
}

class ContentStatusBox extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.content.get('modified_at') != nextProps.content.get('modified_at')
         || this.props.content.get('created_at') != nextProps.content.get('created_at')) {
            return true;
        }
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (this.props.content.get('version') != nextProps.content.get('version')) {
            return true;
        }
        if (!Immutable.is(this.props.content.get('versions'), nextProps.content.get('versions'))) {
            return true;
        }
        if (this.props.content.get('workflow_id') != nextProps.content.get('workflow_id')) {
            return true;
        }
        if (!Immutable.is(this.props.content.get('export'), nextProps.content.get('export'))) {
            return true;
        }
        if (!Immutable.is(this.props.workflows.items, nextProps.workflows.items)) {
            return true;
        }

        return false;
    }

    render() {

        return (
            <Paper className='clear-bottom'>
                <ContentTimeList
                    modified_at={this.props.content.get('modified_at')}
                    created_at={this.props.content.get('created_at')}
                    />
                <List>
                    <Subheader>Web</Subheader>
                    <ContentVersionListItem
                        handleOpenDiff={this.props.handleOpenDiff}
                        content={this.props.content}
                        disabled={this.props.disabled}
                        />
                    <ContentWebWorkflowListItem
                        handleWorkflowChange={this.props.handleWorkflowChange}
                        content={this.props.content}
                        workflows={this.props.workflows}
                        disabled={this.props.disabled}
                        />
                </List>
            </Paper>
        );
    }
}

class ContentPrintStatusBox extends React.Component {
    onSelectUser(user) {
        this.props.onSelectUser(user);
    }

    onRemoveUser(user) {
        this.props.onRemoveUser(user);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.content.get('modified_at') != nextProps.content.get('modified_at')
         || this.props.content.get('created_at') != nextProps.content.get('created_at')) {
            return true;
        }
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (this.props.content.get('version') != nextProps.content.get('version')) {
            return true;
        }
        if (!Immutable.is(this.props.content.get('versions'), nextProps.content.get('versions'))) {
            return true;
        }
        if (this.props.content.get('workflow_id') != nextProps.content.get('workflow_id')) {
            return true;
        }
        if (!Immutable.is(this.props.content.get('export'), nextProps.content.get('export'))) {
            return true;
        }
        if (!Immutable.is(this.props.workflows.items, nextProps.workflows.items)) {
            return true;
        }

        return false;
    }

    render() {

        return (
            <Paper className='clear-bottom'>
                <List>
            <Subheader>Print</Subheader>
            <ContentPrintVersionListItem
                content={this.props.content}
                />
            <ContentPrintWorkflowListItem
                handlePrintWorkflowChange={this.props.handlePrintWorkflowChange}
                content={this.props.content}
                workflows={this.props.workflows}
                disabled={this.props.disabled}
                />
            <ContentPrintWorkflowSectionListItem
                handlePrintWorkflowSectionChange={this.props.handlePrintWorkflowSectionChange}
                content={this.props.content}
                workflows={this.props.workflow}
                workflowSections={this.props.workflowSections}
                disabled={this.props.disabled}
                />
            <ContentPrintIssueListItem
                handlePrintIssueChange={this.props.handlePrintIssueChange}
                content={this.props.content}
                issues={this.props.issues}
                disabled={this.props.disabled}
                />
            {!this.props.content.get('uuid') || !this.props.content.get('export') || !parseInt(this.props.content.get('export').get('assignee_id')) || !this.props.content.get('export').get('assignee')
                ? (
                    <div>
                        <Subheader>Assignee</Subheader>
                        <ListItem disabled={true} style={{marginTop:'-16px', paddingTop:'0px'}}>
                            <UserSearchBox
                                onSelectUser={this.onSelectUser.bind(this)}
                                disabled={this.props.disabled}
                                />
                        </ListItem>
                    </div>
                )
                : (
                    <ListItem
                        disabled={true}
                        primaryText={this.props.content.get('export').get('assignee').get('name')}
                        rightIcon={
                            <FontIcon
                                className='mui-icons '
                                style={{'cursor': 'pointer'}}
                                onClick={this.onRemoveUser.bind(this, this.props.content.get('export').get('assignee'))}
                                >close</FontIcon>
                        }
                        >
                    </ListItem>
                )
            }
            <div className='center-align clear-top'>
                <RaisedButton
                    style={{width:'90%'}}
                    label='update from print'
                    labelStyle={{fontSize:'13px'}}
                    className='secondary-button'
                    onClick={this.props.handleSyncFromPrint}
                    disabled={this.props.disabled}
                    icon={<FontIcon className='mui-icons'>sync</FontIcon>}
                    />
            </div>
                </List>
            </Paper>
        )
    }
}

class ContentVersionListItem extends React.Component {

    handleOpenDiff(e) {
        this.props.handleOpenDiff(e);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.content.get('version') != nextProps.content.get('version')) {
            return true;
        }
        if (!Immutable.is(this.props.content.get('versions'), nextProps.content.get('versions'))) {
            return true;
        }
        if (!Immutable.is(this.props.content.get('user'), nextProps.content.get('user'))) {
            return true;
        }

        return false;
    }

    render() {
        let name = '';
        if(parseInt(this.props.content.get('user_id')) && this.props.content.get('user')) {
            name = ' ' + this.props.content.get('user').get('name');
        }

        return (
            <ListItem
                style={{color:ceoTheme.palette.primary3Color}}
                onClick={this.handleOpenDiff.bind(this)}
                disabled={parseInt(this.props.content.get('version')) && parseInt(this.props.content.get('version')) <= 1 ? true : false}
                >
                {
                    this.props.content.get('version')
                    ? 'v'+this.props.content.get('version')
                    : 'New'
                }
                {name}
            </ListItem>

        )
    }
}

class ContentWebWorkflowListItem extends React.Component {
    handleWorkflowChange(e, index, val) {
        this.props.handleWorkflowChange(e, index, val);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (!Immutable.is(this.props.workflows.items, nextProps.workflows.items)) {
            return true;
        }
        if (this.props.content.get('workflow_id') != nextProps.content.get('workflow_id')) {
            return true;
        }

        return false;
    }

    render() {
        return (
            <ListItem disabled={true} style={{marginTop:'-16px', paddingTop:'0px'}}>
                <SelectField
                    fullWidth={true}
                    autoWidth={true}
                    value={parseInt(this.props.content.get('workflow_id')) ? this.props.content.get('workflow_id') : null}
                    onChange={this.handleWorkflowChange.bind(this)}
                    floatingLabelText='Workflow Status'
                    disabled={this.props.disabled}
                    >
                    {this.props.workflows.filters.contentOnly.map((id, i) => {
                        const wf = this.props.workflows.items.find((workflow) => {
                            return workflow.get('uuid') == id;
                        })
                        return <MenuItem key={i} value={wf.get('id')} primaryText={wf.get('name')} />
                    })}
                </SelectField>
            </ListItem>
        );
    }
}

class ContentPrintVersionListItem extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (!Immutable.is(this.props.content.get('export'), nextProps.content.get('export'))) {
            return true;
        }

        return false;
    }

    render() {
        return (
            <ListItem>
                {
                    this.props.content.get('export')
                    ? 'v' + this.props.content.get('export').get('version')
                    : 'No print version'
                }
                {
                    this.props.content.get('export') && parseInt(this.props.content.get('export').get('user_id')) && this.props.content.get('export').get('user')
                    ? ' ' + this.props.content.get('export').get('user').get('name')
                    : ''
                }
            </ListItem>
        )
    }
}

class ContentPrintWorkflowListItem extends React.Component {
    handlePrintWorkflowChange(e, index, val) {
        this.props.handlePrintWorkflowChange(e, index, val);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (!Immutable.is(this.props.workflows.items, nextProps.workflows.items)) {
            return true;
        }
        if (this.props.content.get('export') != nextProps.content.get('export')) {
            return true;
        }

        return false;
    }

    render() {
        return (
            <ListItem disabled={true} style={{marginTop:'-16px', paddingTop:'0px'}}>
                <SelectField
                    fullWidth={true}
                    autoWidth={true}
                    value={this.props.content.get('export') && parseInt(this.props.content.get('export').get('workflow_id')) ? this.props.content.get('export').get('workflow_id') : null}
                    onChange={this.handlePrintWorkflowChange.bind(this)}
                    floatingLabelText='Workflow Status'
                    disabled={this.props.disabled}
                    >
                    {this.props.workflows.filters.printOnly.map((id, i) => {
                        const wf = this.props.workflows.items.find((workflow) => {
                            return workflow.get('uuid') == id;
                        })
                        return <MenuItem key={i} value={wf.get('id')} primaryText={wf.get('name')} />
                    })}
                </SelectField>
            </ListItem>
        );
    }
}

class ContentPrintWorkflowSectionListItem extends React.Component {
    handlePrintWorkflowSectionChange(e, index, val) {
        this.props.handlePrintWorkflowSectionChange(e, index, val);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (!Immutable.is(this.props.workflowSections.items, nextProps.workflowSections.items)) {
            return true;
        }
        if (this.props.content.get('export') != nextProps.content.get('export')) {
            return true;
        }

        return false;
    }

    render() {
        return (
            <ListItem disabled={true} style={{marginTop:'-16px', paddingTop:'0px'}}>
                <SelectField
                    fullWidth={true}
                    autoWidth={true}
                    value={this.props.content.get('export') && parseInt(this.props.content.get('export').get('workflow_section_id')) ? this.props.content.get('export').get('workflow_section_id') : null}
                    onChange={this.handlePrintWorkflowSectionChange.bind(this)}
                    floatingLabelText='Workflow Section'
                    disabled={this.props.disabled}
                    >
                    {this.props.workflowSections.items.map((workflowSection, i) => {
                        return <MenuItem key={i} value={workflowSection.get('id')} primaryText={workflowSection.get('name')} />
                    })}
                </SelectField>
            </ListItem>
        );
    }
}

class ContentPrintIssueListItem extends React.Component {
    handlePrintIssueChange(e, index, val) {
        this.props.handlePrintIssueChange(e, index, val);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (!Immutable.is(this.props.issues.items, nextProps.issues.items)) {
            return true;
        }
        if (this.props.content.get('export') != nextProps.content.get('export')) {
            return true;
        }

        return false;
    }

    render() {
        // Allows existing items attached to closed issues
        // to still show the issue in the select drop down
        let useIssue = null;
        let didUseIssue = false;
        if (this.props.content.get('export')
            && this.props.content.get('export').get('issue')
            && !parseInt(this.props.content.get('export').get('issue').get('status')) ) {

            let tempIssue = this.props.content.get('export').get('issue');
            let useLabel = [];
            if (tempIssue.get('published_at')) {
                useLabel.push(timeToFormat(tempIssue.get('published_at'), 'M/DD/YYYY'));
            }
            if (tempIssue.get('label')) {
                useLabel.push(tempIssue.get('label'));
            }
            useIssue = tempIssue.set('decorated_label', useLabel.join(' - '));
        }

        return (
            <ListItem disabled={true} style={{marginTop:'-16px', paddingTop:'0px'}}>
                <SelectField
                    fullWidth={true}
                    autoWidth={true}
                    value={this.props.content.get('export') && parseInt(this.props.content.get('export').get('issue_id')) ? this.props.content.get('export').get('issue_id') : null}
                    onChange={this.handlePrintIssueChange.bind(this)}
                    floatingLabelText='Issue'
                    disabled={this.props.disabled}
                    >
                    {this.props.issues.open.map((uuid, i) => {
                        const issue = this.props.issues.items.find((item) => item.get('uuid') == uuid);
                        if (useIssue && issue.get('uuid') == useIssue.get('uuid')) {
                            didUseIssue = true;
                        }
                        let label = [];
                        if (issue.get('published_at')) {
                            label.push(timeToFormat(issue.get('published_at'), 'M/DD/YYYY'));
                        }
                        if (issue.get('label')) {
                            label.push(issue.get('label'));
                        }
                        return <MenuItem key={i} value={issue.get('id')} primaryText={label.join(' - ')} />
                    })}
                    {
                        useIssue && !didUseIssue
                        ? <MenuItem value={useIssue.get('id')} primaryText={useIssue.get('decorated_label')} />
                        : ''
                    }
                </SelectField>
            </ListItem>
        );
    }
}

class ContentPublishBox extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (this.props.content.get('published_at') != nextProps.content.get('published_at')) {
            return true;
        }
        if (this.props.content.get('weight') != nextProps.content.get('weight')) {
            return true;
        }

        return false;
    }

    handleFormChange(e) {
        this.props.handleFormChange(e);
    }

    handleCancelPublish() {
        this.props.handleCancelPublish();
    }

    handleOpenPublish() {
        this.props.handleOpenPublish();
    }

    render() {
        return (
            <Paper className='clear-bottom'>
                <List>
                    <Subheader>Published</Subheader>
                    {
                        this.props.content.get('published_at')
                        ? (
                            <ListItem
                                disabled={true}
                                primaryText={this.props.content.get('published_at') ? timeToFormat(this.props.content.get('published_at'), 'LT l') : 'Not published'}
                                leftIcon={<FontIcon className='mui-icons'>check</FontIcon>}
                                />
                        )
                        : (
                            <ListItem
                                disabled={true}
                                primaryText='Not Published'
                                leftIcon={<FontIcon className='mui-icons'>error_outline</FontIcon>}
                                />
                        )
                    }
                    <Subheader>Weight</Subheader>
                    <ListItem disabled={true} hoverColor='#FFFFFF' style={{marginTop:'-16px', paddingTop:'0px'}}>
                        <TextField
                            disabled={this.props.disabled}
                            onChange={this.handleFormChange.bind(this)}
                            value={this.props.content.get('weight')}
                            htmlFor='weight'
                            fullWidth={true}
                            type="number"
                            min={0}
                            max={5}
                            />
                    </ListItem>
                    {
                        this.props.content.get('published_at')
                        ? (
                            <div className='center-align'>
                                <RaisedButton
                                    style={{width:'90%'}}
                                    className='secondary-button'
                                    onClick={this.handleCancelPublish.bind(this)}
                                    disabled={this.props.disabled}
                                    label="Unpublish"
                                    icon={<FontIcon className='mui-icons'>cloud_download</FontIcon>}
                                    />
                            </div>
                        )
                        : (
                            <div className='center-align'>
                                <RaisedButton
                                    style={{width:'90%'}}
                                    className='secondary-button'
                                    onClick={this.handleOpenPublish.bind(this)}
                                    disabled={this.props.disabled}
                                    label="Publish"
                                    icon={<FontIcon className='mui-icons'>cloud_upload</FontIcon>}
                                    />
                            </div>
                        )
                    }
                </List>
            </Paper>
        );
    }
}

class DominantAttachmentBox extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (!Immutable.is(this.props.content.get('dominantAttachment'), nextProps.content.get('dominantAttachment'))) {
            return true;
        }

        return false;
    }

    editAttachment(url, e) {
        browserHistory.push(url);

        window._ceoScrollTop();
    }


    render() {
        let linkLocation = '';
        if (this.props.content.get('dominantAttachment')) {
            linkLocation = '/ceo/locate/' + this.props.content.get('dominantAttachment').get('uuid');
        }

        const avatars = {
            gallery: 'collections',
            image: 'photo',
            video: 'videocam',
            pdf: 'picture_as_pdf',
            vimeo: 'ondemand_video',
            youtube: 'ondemand_video',
            audio: 'audiotrack'
        }

        let actionIcon = '';

        if (this.props.content.get('dominantAttachment')) {
            actionIcon = avatars[this.props.content.get('dominantAttachment').get('attachment').get('type')];
        }

        return (
            <div className='clear-bottom'>
                {
                    this.props.content.get('dominantAttachment')
                    ? (
                        <Paper>
                            <Subheader>Dominant Media</Subheader>
                            <GridList style={{padding:'0px 16px 16px'}}>
                                <GridTile
                                    cols={2}
                                    title={this.props.content.get('dominantAttachment').get('title') ? this.props.content.get('dominantAttachment').get('title') : this.props.content.get('dominantAttachment').get('slug')}
                                    subtitle={'type: '+this.props.content.get('dominantAttachment').get('attachment').get('type')}
                                    actionIcon={
                                        <IconButton className='secondary-button' onClick={this.props.handleRemoveDomMedia}>
                                            <FontIcon className='mui-icons'>close</FontIcon>
                                        </IconButton>
                                    }
                                    className='grid-tile'
                                    >
                                    <img src={this.props.content.get('dominantAttachment').get('attachment').get('public_url')} onClick={this.editAttachment.bind(this, linkLocation)} style={{cursor:'pointer'}} />
                                </GridTile>
                            </GridList>
                        </Paper>
                    )
                    : (
                        <RaisedButton
                            label='Dominant Media'
                            style={{'width':'100%'}}
                            onClick={this.props.handleOpenDomMedia}
                            icon={<FontIcon className='mui-icons'>perm_media</FontIcon>}
                            />
                    )
                }
            </div>
        );
    }
}

class ContentRichEditorContainer extends React.Component {

    constructor(props) {
        super(props);

        this.editorRef = React.createRef();
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            if (this.editorRef.current) {
                this.editorRef.current.resetEditorContent(nextProps.content.get('content_raw') ? nextProps.content.get('content_raw') : nextProps.content.get('content'));
            }
            return true;
        }
        if (this.props.lastSaved != nextProps.lastSaved
            || this.props.contentLimbo != nextProps.contentLimbo) {
            return true;
        }
        if (!Immutable.is(this.props.pendingDraft, nextProps.pendingDraft)) {
            return true;
        }
        if (this.props.content.get('content_raw') != nextProps.content.get('content_raw')
            || this.props.content.get('content') != nextProps.content.get('content')
            || this.props.content.get('version') != nextProps.content.get('version')
            || this.props.content.get('uuid') != nextProps.content.get('uuid')
            || this.props.useSmartQuotes != nextProps.useSmartQuotes
            || this.props.useSuggestedLinks != nextProps.useSuggestedLinks
            || this.props.editor2 != nextProps.editor2) {


            if (this.editorRef.current) {
                this.editorRef.current.resetEditorContent(nextProps.content.get('content_raw'));
            }
            return true;
        }

        return false;
    }

    render() {
        let useOriginal = this.props.editor2 == false;
        if (this.props.content.get('content_raw') && this.props.content.get('content_raw').indexOf('entityMap') !== -1) {
            useOriginal = true;
        }
        if (this.props.content.get('content_raw') && this.props.content.get('content_raw').indexOf('"object":"value"') !== -1) {
            useOriginal = false;
        }

        return (
            <div className='editor-container'>
                {
                    useOriginal
                    ? (
                        <RichEditor
                            label='Content'
                            name="content"
                            toolbarRightElement={
                                <div>
                                    {
                                        this.props.lastSaved
                                        ? (
                                            <DraftLastAutosave lastsave={this.props.lastSaved} />
                                        )
                                        : (
                                            <DraftRestore buttonLabel='Restore Draft' pendingDraft={this.props.pendingDraft} onRestore={this.props.handleRestoreDraft} />
                                        )
                                    }
                                </div>
                            }
                            onStateChange={this.props.handleStateChange}
                            readOnly={this.props.disabled}
                            enableComments={true}
                            version={this.props.content.get('version')}
                            defaultValue={(this.props.content.get('content_raw') && this.props.content.get('content_raw').length) ? this.props.content.get('content_raw') : this.props.content.get('content')}
                            onChange={this.props.handleEditorChange}
                            contentUuid={this.props.content.get('uuid') ? this.props.content.get('uuid') : false}
                        />
                    )
                    : (
                        <RichEditor2
                            contentUuid={this.props.content.get('uuid') ? this.props.content.get('uuid') : false}
                            contentVersion={this.props.content.get('version') ? this.props.content.get('version') : 1}
                            onChange={this.props.handleEditorChange2}
                            initialValue={(this.props.content.get('content_raw') && this.props.content.get('content_raw').length) ? this.props.content.get('content_raw') : this.props.content.get('content')}
                            label='Content'
                            readOnly={this.props.disabled}
                            smartQuotes={this.props.useSmartQuotes}
                            related={this.props.useSuggestedLinks ? this.props.content.get('relatedContent') : null}
                            keywords={this.props.useSuggestedLinks ? this.props.content.get('keywords') : null}
                            ref={this.editorRef}
                            />
                    )
                }
                <Divider />
                {
                    this.props.contentLimbo
                    ? (
                        <small>
                            <WordCounter count={this.props.contentLimbo} displayEmpty={true} />
                        </small>
                    )
                    : ''
                }
                {
                    useOriginal && this.props.editor2
                    ? (
                        <React.Fragment>
                            <RaisedButton
                                label="Convert to RichEditor2"
                                disabled={!this.props.editor2}
                                onClick={this.props.convertContent}
                                style={{marginLeft:'10px'}}
                                />
                            <IconButton
                                href='https://snworks.zendesk.com/hc/en-us/articles/360020387252'
                                target='help'
                                >
                                <FontIcon className="mui-icons">help</FontIcon>
                            </IconButton>
                        </React.Fragment>
                    )
                    : (
                        ''
                    )
                }

            </div>
        );
    }
}

class ContentAbstractEditorContainer extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.disabled != nextProps.disabled) {
            return true;
        }
        if (this.props.content.get('abstract_raw') != nextProps.content.get('abstract_raw')
            || this.props.content.get('abstract') != nextProps.content.get('abstract')) {
            return true;
        }
        if (this.props.abstractLimbo != nextProps.abstractLimbo) {
            return true;
        }

        return false;
    }

    render() {
        let useVal;
        try {
            if (this.props.content.get('abstract_raw') && this.props.content.get('abstract_raw').length) {
                JSON.parse(this.props.content.get('abstract_raw'));
            }

            useVal = (this.props.content.get('abstract_raw') && this.props.content.get('abstract_raw').length)
                            ? this.props.content.get('abstract_raw')
                            : this.props.content.get('abstract');
        } catch (e) {
            useVal = this.props.content.get('abstract');
        }

        return (
            <div className='editor-container'>
                <RichEditor
                    label='Abstract'
                    name="abstract"
                    readOnly={this.props.disabled}
                    defaultValue={useVal}
                    onChange={this.props.handleEditorChange}
                    />
                    <Divider />
                    {
                        this.props.abstractLimbo
                        ? (
                            <small>
                                <WordCounter count={this.props.abstractLimbo} displayEmpty={true} />
                            </small>
                        )
                        : ''
                    }
            </div>
        );
    }
}

class ContentRelatedBox extends React.Component
{
    handleClick(item, e) {
        browserHistory.push("/ceo/redirect?next=content/" + item.get('uuid'));
    }
    render() {
        if (!this.props.content.get('relatedContent') || !this.props.content.get('relatedContent').size) {
            return '';
        }

        return (
            <div className='clear-bottom'>
                <Paper>
                    <Subheader>Related Items</Subheader>
                    <div style={{maxHeight:'400px', overflow: 'auto'}} className='padded'>
                        {this.props.content.get('relatedContent').map((item) => (
                            <div style={{marginBottom:'10px'}} onClick={this.handleClick.bind(this, item)}>
                                <span className="link-on-hover">{item.get('title')}</span>
                            </div>
                        ))}
                    </div>
                </Paper>
            </div>
        )
    }
}

export default connect(mapStateToProps)(withDraftable(ContentItem));