From 5ead2a52c0caa78a4ce16b314837ba92019acaf5 Mon Sep 17 00:00:00 2001
From: john <johnbarlas39@gmail.com>
Date: Tue, 13 Dec 2016 02:31:42 +0200
Subject: [PATCH] chapter refactor - part one

---
 app/components/BookBuilder/Chapter.jsx        | 575 ++----------------
 .../BookBuilder/Chapter/ChapterButtons.jsx    |  12 +
 .../BookBuilder/Chapter/ChapterTitle.jsx      |  73 +++
 .../BookBuilder/Chapter/DropdownTitle.jsx     | 200 ++++++
 .../BookBuilder/Chapter/FirstRow.jsx          | 308 ++++++++++
 .../{ => Chapter}/PagePositionAlignment.jsx   |   2 +-
 .../{ => Chapter}/ProgressIndicator.jsx       |   4 +-
 .../BookBuilder/Chapter/RenameEmptyError.jsx  |  25 +
 .../BookBuilder/Chapter/SecondRow.jsx         |  99 +++
 app/components/BookBuilder/Chapter/Title.jsx  |  50 ++
 .../{ => Chapter}/UploadWordBtn.jsx           |   2 +-
 app/components/utils/DnD.js                   |  69 +++
 app/components/utils/config.js                |  26 +
 package.json                                  |   2 +-
 webpack/common-rules.js                       |   1 -
 15 files changed, 915 insertions(+), 533 deletions(-)
 create mode 100644 app/components/BookBuilder/Chapter/ChapterButtons.jsx
 create mode 100644 app/components/BookBuilder/Chapter/ChapterTitle.jsx
 create mode 100644 app/components/BookBuilder/Chapter/DropdownTitle.jsx
 create mode 100644 app/components/BookBuilder/Chapter/FirstRow.jsx
 rename app/components/BookBuilder/{ => Chapter}/PagePositionAlignment.jsx (96%)
 rename app/components/BookBuilder/{ => Chapter}/ProgressIndicator.jsx (97%)
 create mode 100644 app/components/BookBuilder/Chapter/RenameEmptyError.jsx
 create mode 100644 app/components/BookBuilder/Chapter/SecondRow.jsx
 create mode 100644 app/components/BookBuilder/Chapter/Title.jsx
 rename app/components/BookBuilder/{ => Chapter}/UploadWordBtn.jsx (94%)
 create mode 100644 app/components/utils/DnD.js
 create mode 100644 app/components/utils/config.js

diff --git a/app/components/BookBuilder/Chapter.jsx b/app/components/BookBuilder/Chapter.jsx
index ead3156..3cece69 100644
--- a/app/components/BookBuilder/Chapter.jsx
+++ b/app/components/BookBuilder/Chapter.jsx
@@ -1,265 +1,38 @@
 import React from 'react'
-import { DropdownButton, MenuItem } from 'react-bootstrap'
-import { LinkContainer } from 'react-router-bootstrap'
 import { DragSource, DropTarget } from 'react-dnd'
-import { findDOMNode } from 'react-dom'
 
-import { includes, get, map, flow, slice } from 'lodash'
+import { flow } from 'lodash'
 
-import BookBuilderModal from './BookBuilderModal'
-import PagePositionAlignment from './PagePositionAlignment'
-import ProgressIndicator from './ProgressIndicator'
-import TextInput from '../utils/TextInput'
-import UploadWordButton from './UploadWordBtn'
+import FirstRow from './Chapter/FirstRow'
+// import SecondRow from './Chapter/SecondRow'
 
-import styles from './styles/bookBuilder.local.scss'
-
-const itemTypes = {
-  CHAPTER: 'chapter'
-}
-
-const chapterSource = {
-  beginDrag (props) {
-    return {
-      id: props.id,
-      no: props.no,
-      division: props.chapter.division
-    }
-  },
-
-  isDragging (props, monitor) {
-    return props.id === monitor.getItem().id
-  }
-}
-
-const chapterTarget = {
-  // for an explanation of how this works go to
-  // https://github.com/gaearon/react-dnd/blob/master/examples/04%20Sortable/Simple/Card.js
-
-  hover (props, monitor, component) {
-    // can only reorder within the same division
-    const dragDivision = monitor.getItem().division
-    const hoverDivision = props.chapter.division
-
-    if (dragDivision !== hoverDivision) { return }
-
-    const dragIndex = monitor.getItem().no
-    const hoverIndex = props.no
-
-    if (dragIndex === hoverIndex) { return }
-
-    const hoverBoundingRect = findDOMNode(component).getBoundingClientRect()
-    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
-    const clientOffset = monitor.getClientOffset()
-    const hoverClientY = clientOffset.y - hoverBoundingRect.top
-
-    if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { return }
-    if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { return }
-
-    props.move(dragIndex, hoverIndex)
-    monitor.getItem().no = hoverIndex
-  }
-}
-
-const collectDrag = (connect, monitor) => {
-  return {
-    connectDragSource: connect.dragSource(),
-    isDragging: monitor.isDragging()
-  }
-}
+import { chapterSource, chapterTarget, collectDrag, collectDrop, itemTypes } from '../utils/DnD'
 
-const collectDrop = (connect, monitor) => {
-  return {
-    connectDropTarget: connect.dropTarget()
-  }
-}
+import styles from './styles/bookBuilder.local.scss'
 
 export class Chapter extends React.Component {
   constructor (props) {
     super(props)
 
-    this._onClickRename = this._onClickRename.bind(this)
-    this._onSaveRename = this._onSaveRename.bind(this)
-
-    this._onClickDelete = this._onClickDelete.bind(this)
-    this._onClickUnlock = this._onClickUnlock.bind(this)
-    this._toggleDelete = this._toggleDelete.bind(this)
-    this._toggleUnlock = this._toggleUnlock.bind(this)
-
-    this._isAdmin = this._isAdmin.bind(this)
-    this._formatDate = this._formatDate.bind(this)
-
-    this._onClickTitleDropdown = this._onClickTitleDropdown.bind(this)
-    this._onClickCustomTitle = this._onClickCustomTitle.bind(this)
-    this._toggleList = this._toggleList.bind(this)
-    this._myHandler = this._myHandler.bind(this)
-    this._viewOrEdit = this._viewOrEdit.bind(this)
-
     this.update = this.update.bind(this)
 
     this.state = {
-      isRenamingTitle: false,
-      isRenameEmpty: false,
-      showDeleteModal: false,
-      showUnlockModal: false,
-      open: false, // control if the dropdwon list is open or not
-      canEdit: false
-    }
-  }
-
-  _onClickRename () {
-    this.setState({
-      isRenamingTitle: true
-    })
-  }
-
-  _onSaveRename (title) {
-    // save button has been clicked from outside the component
-    if (typeof title !== 'string') {
-      // console.log(this.refs)
-      title = this.refs.chapterInput.state.value
-    }
-
-    if (title.length === 0) {
-      this.setState({
-        isRenameEmpty: true
-      })
-      return
-    }
-
-    this.setState({
-      isRenameEmpty: false
-    })
-
-    const { book, chapter, update } = this.props
-    chapter.title = title
-
-    update(book, chapter)
-
-    this.setState({
-      isRenamingTitle: false
-    })
-  }
-
-  _onClickDelete () {
-    const { chapter, remove } = this.props
-    remove(chapter)
-    this._toggleDelete()
-  }
-
-  _isAdmin () {
-    const { roles } = this.props
-    return includes(roles, 'admin')
-  }
-
-  _formatDate (timestamp) {
-    const date = new Date(timestamp)
-
-    const day = date.getDate()
-    const month = date.getMonth() + 1
-    const year = date.getFullYear()
-
-    let hours = date.getHours().toString()
-    if (hours.length === 1) {
-      hours = '0' + hours
-    }
-
-    let minutes = date.getMinutes().toString()
-    if (minutes.length === 1) {
-      minutes = '0' + minutes
-    }
-
-    const theDate = month + '/' + day + '/' + year
-    const theTime = hours + ':' + minutes
-    const formatted = theDate + ' ' + theTime
-    return formatted
-  }
-
-  _toggleDelete () {
-    this.setState({ showDeleteModal: !this.state.showDeleteModal })
-  }
-
-  _toggleUnlock () {
-    if (!this._isAdmin()) { return }
-    this.setState({ showUnlockModal: !this.state.showUnlockModal })
-  }
-
-  _onClickUnlock () {
-    const { book, chapter, update } = this.props
-    const isAdmin = this._isAdmin()
-
-    if (!isAdmin) { return }
-
-    chapter.lock = null
-    update(book, chapter)
-    this._toggleUnlock()
-  }
-
-  _viewOrEdit () {
-    const { roles, chapter } = this.props
-
-    if (includes(roles, 'production-editor')) return this.setState({ canEdit: true })
-
-    if (chapter.progress['review'] === 1 && includes(roles, 'author') ||
-        chapter.progress['edit'] === 1 && includes(roles, 'copy-editor')) {
-      this.setState({ canEdit: true })
-    } else {
-      this.setState({ canEdit: false })
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('click', this._myHandler)
-    this._viewOrEdit()
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('click', this._myHandler)
-  }
-
-  _myHandler (evt) {
-    if (evt.target.id === 'dropbutton' ||
-        evt.target.parentElement.id === 'dropbutton' ||
-        evt.target.classList.contains('caret') ||
-        evt.target.classList.contains('drop-input')) {
-      const input = findDOMNode(this.refs.dropDownInput)
-      if (input) input.focus()
-      return
+      isUploadInProgress: false
     }
-    this.setState({
-      open: false
-    })
-  }
-
-  _onClickTitleDropdown (title) {
-    const { book, chapter, update } = this.props
-    const self = this
-
-    if (title === '') {
-      return
-    }
-
-    function clickTitleDropdown () {
-      chapter.title = title
-      update(book, chapter)
-      setTimeout(() => {
-        self.setState({open: false})
-      }, 10)
-    }
-
-    return clickTitleDropdown
-  }
-
-  _onClickCustomTitle () {
-    let customTitle = get(this.refs, 'dropDownInput.state.value', null)
-    this._onClickTitleDropdown(customTitle)()
   }
 
-  _toggleList () {
-    this.setState({
-      open: !this.state.open
-    })
-  }
+  // _viewOrEdit () {
+  //   const { roles, chapter } = this.props
+  //
+  //   if (includes(roles, 'production-editor')) return this.setState({ canEdit: true })
+  //
+  //   if (chapter.progress['review'] === 1 && includes(roles, 'author') ||
+  //       chapter.progress['edit'] === 1 && includes(roles, 'copy-editor')) {
+  //     this.setState({ canEdit: true })
+  //   } else {
+  //     this.setState({ canEdit: false })
+  //   }
+  // }
 
   update (changedChapter) {
     const { book, update } = this.props
@@ -267,201 +40,26 @@ export class Chapter extends React.Component {
   }
 
   render () {
-    const { book, chapter, connectDragSource, connectDropTarget, ink, isDragging, outerContainer, roles, title, type } = this.props
-    const { isRenamingTitle, isRenameEmpty } = this.state
-    // const { _onSaveRename } = this
-    const opacity = isDragging ? 0 : 1
-
-    let titleArea = null
-    let renameButton = null
-
-    let renameEmptyError = isRenameEmpty
-    ? (
-      <span className={styles.emptyTitle}>
-        New title cannot be empty
-      </span>
-    )
-    : null
-
-    if (type === 'chapter' || type === 'part') {
-      // if type is chapter, make the title editable text
-      let renameButtonText, renameButtonFunction
-
-      const input = (
-        <TextInput
-          className='edit'
-          ref='chapterInput'
-          onSave={this._onSaveRename}
-          value={title}
-        />
-      )
-
-      if (isRenamingTitle) {
-        titleArea = input
-        renameButtonText = 'Save'
-        renameButtonFunction = this._onSaveRename
-      } else {
-        titleArea = (<h3 onDoubleClick={this._onClickRename}> { title } </h3>)
-        renameButtonText = 'Rename'
-        renameButtonFunction = this._onClickRename
-      }
-
-      // add id so that it can be selected for testing
-      // could do with refs, but that would mean mounting instead of
-      // shallow rendering to access enzyme's refs() api method
-      renameButton = (
-        <a id='bb-rename'
-          onClick={renameButtonFunction}>
-          { renameButtonText } &nbsp;&nbsp;
-        </a>
-      )
-    } else if (type === 'component') {
-      // if type is component, make title a dropdown choice
-
-      let dropdownOptions
-      if (chapter.division === 'front') {
-        dropdownOptions = [
-          'Table of Contents',
-          'Introduction',
-          'Preface',
-          'Preface 1',
-          'Preface 2',
-          'Preface 3',
-          'Preface 4',
-          'Preface 5',
-          'Preface 6',
-          'Preface 7',
-          'Preface 8',
-          'Preface 9',
-          'Preface 10'
-        ]
-      } else if (chapter.division === 'back') {
-        dropdownOptions = [
-          'Appendix A',
-          'Appendix B',
-          'Appendix C'
-        ]
-      }
-
-      const onClickTitleDropdown = this._onClickTitleDropdown
-
-      let width = 180
-      let TotalColumns = 1
-      if (dropdownOptions.length > 9) {
-        TotalColumns = Math.ceil(dropdownOptions.length / 5)
-      }
-
-      const menuItems = map(dropdownOptions, function (item, i) {
-        const onClickItem = onClickTitleDropdown(item)
-
-        return (
-          <MenuItem
-            className={styles.menuItem}
-            onClick={onClickItem}
-            key={i}>
-            { item }
-          </MenuItem>
-        )
-      })
-
-      let columns = menuItems
-
-      if (TotalColumns > 1) {
-        columns = []
-        let loopIt = 1
+    const {
+      book,
+      chapter,
+      connectDragSource,
+      connectDropTarget,
+      // ink,
+      isDragging,
+      outerContainer,
+      // roles,
+      title,
+      type
+    } = this.props
 
-        while (loopIt <= width) {
-          let start = (loopIt - 1) * 5
-          let end = start + 5
-          columns.push(slice(menuItems, start, end))
-          loopIt += 1
-        }
-        columns = map(columns, function (column, i) {
-          return (
-            <div className={styles.menuItemContainer} key={i}>
-              { column }
-            </div>
-          )
-        })
-      }
-
-      width = (width * TotalColumns)
-
-      titleArea = (
-        <DropdownButton
-          title={title}
-          id='dropbutton'
-          className={styles.dropDown}
-          open={this.state.open}
-          onClick={this._toggleList}
-        >
-          <div style={{ width: width }}>
-            <div className={styles.dropDownInputContairer}>
-              <TextInput
-                ref='dropDownInput'
-                className={'drop-input ' + styles.dropDownInput}
-                onSave={this._onClickCustomTitle}
-                placeholder='Type a custom title'
-              />
-            </div>
-
-            { columns }
-          </div>
-
-        </DropdownButton>
-      )
-    }
-
-    const editOrView = this.state.canEdit ? 'Edit' : 'View'
-
-    const buttons = (
-      <div>
-        { renameButton }
-        <LinkContainer
-          to={`/books/${book.id}/fragments/${chapter.id}`}
-          id='bb-edit'
-        >
-          <a>{ editOrView } &nbsp;&nbsp;</a>
-        </LinkContainer>
-
-        <a id='bb-delete'
-          onClick={this._toggleDelete}>
-          Delete
-        </a>
-      </div>
-    )
-
-    let editorArea
-    if (get(chapter, 'lock.editor.username')) {
-      let message = ' is editing'
-      if (chapter.lock.timestamp && this._isAdmin()) {
-        message = ' has been editing since ' + this._formatDate(chapter.lock.timestamp)
-      }
-
-      editorArea = (
-        <a id='bb-unlock'
-          className={styles.lEditing}
-          onClick={this._toggleUnlock}>
-
-          <i
-            className={styles.lockIcon + ' fa fa-lock'}
-            aria-hidden='true'
-            alt='unlock'
-          />
-          <span className={styles.lockMessage}>
-            { chapter.lock.editor.username + message}
-          </span>
-
-        </a>
-      )
-    }
-
-    const rightArea = chapter.lock ? editorArea : buttons
+    const opacity = isDragging ? 0 : 1
 
     return connectDragSource(connectDropTarget(
       <li
         className={styles.chapterContainer + ' col-lg-12 bb-chapter ' + (chapter.subCategory === 'chapter' ? styles.isChapter : styles.isPart)}
         style={{ opacity: opacity }}>
+
         <div className={styles.grabIcon + ' ' + (chapter.division === 'body' ? styles.grabIconBody : '')}>
           <i className='fa fa-circle' />
           <div className={styles.tooltip}>
@@ -470,104 +68,27 @@ export class Chapter extends React.Component {
         </div>
 
         <div className={styles.chapterMainContent}>
-          <div className={styles.chapterTitle}>
-            { titleArea }
-            { renameEmptyError }
-            <div className={styles.separator} />
-          </div>
-
-          <div className={styles.chapterActions + ' pull-right'}>
-            {rightArea}
-          </div>
+          <FirstRow
+            book={book}
+            chapter={chapter}
+            outerContainer={outerContainer}
+            title={title}
+            type={type}
+            update={this.update}
+          />
 
           <div className={styles.chapterBottomLine} />
 
-          <div className={styles.secondLineContainer}>
-            <div className={styles.noPadding + ' col-lg-2 col-md-12 col-sm-12 col-xs-12'}>
-              <UploadWordButton
-                accept='.docx'
-                ink={ink}
-                title=' '
-                type='file'
-              />
-            </div>
-
-            <ul className={styles.secondActions + ' col-lg-7 col-md-12 col-sm-12 col-xs-12'}>
-              <ProgressIndicator
-                type='style'
-                chapter={chapter}
-                update={this.update}
-                roles={roles}
-                outerContainer={outerContainer}
-                hasIcon
-                viewOrEdit={this._viewOrEdit}
-              />
-
-              <ProgressIndicator
-                type='edit'
-                chapter={chapter}
-                update={this.update}
-                roles={roles}
-                outerContainer={outerContainer}
-                hasIcon
-                viewOrEdit={this._viewOrEdit}
-              />
-
-              <ProgressIndicator
-                type='review'
-                chapter={chapter}
-                update={this.update}
-                roles={roles}
-                outerContainer={outerContainer}
-                hasIcon
-                viewOrEdit={this._viewOrEdit}
-              />
-
-              <ProgressIndicator
-                type='clean'
-                chapter={chapter}
-                roles={roles}
-                outerContainer={outerContainer}
-                update={this.update}
-                viewOrEdit={this._viewOrEdit}
-              />
-            </ul>
-
-            <div className={styles.noPadding + ' col-lg-3 col-md-12 col-sm-12 col-xs-12'}>
-              <PagePositionAlignment
-                chapter={chapter}
-                update={this.update}
-              />
-            </div>
-
-            <div className={styles.separator} />
-          </div>
+          {/* <SecondRow
+            chapter={chapter}
+            ink={ink}
+            outerContainer={outerContainer}
+            roles={roles}
+            update={this.update}
+            viewOrEdit={this._viewOrEdit}
+          /> */}
         </div>
 
-        <BookBuilderModal
-          title={'Delete ' + type}
-          chapter={chapter}
-          action='delete'
-          successText='Delete'
-          type={type}
-          successAction={this._onClickDelete}
-          show={this.state.showDeleteModal}
-          toggle={this._toggleDelete}
-          container={outerContainer}
-        />
-
-        <BookBuilderModal
-          title={'Unlock ' + type}
-          chapter={chapter}
-          action='unlock'
-          successText='Unlock'
-          type={type}
-          successAction={this._onClickUnlock}
-          show={this.state.showUnlockModal}
-          toggle={this._toggleUnlock}
-          container={outerContainer}
-        />
-
         <div className={chapter.division === 'body' ? styles.leftBorderBody : styles.leftBorderComponent} />
       </li>
     ))
diff --git a/app/components/BookBuilder/Chapter/ChapterButtons.jsx b/app/components/BookBuilder/Chapter/ChapterButtons.jsx
new file mode 100644
index 0000000..d10878d
--- /dev/null
+++ b/app/components/BookBuilder/Chapter/ChapterButtons.jsx
@@ -0,0 +1,12 @@
+// import React from 'react'
+// import { LinkContainer } from 'react-router-bootstrap'
+//
+// class ChapterButtons extends React.Component {
+//   // constructor (props) {
+//   //   super(props)
+//   // }
+//
+//   render () {
+//
+//   }
+// }
diff --git a/app/components/BookBuilder/Chapter/ChapterTitle.jsx b/app/components/BookBuilder/Chapter/ChapterTitle.jsx
new file mode 100644
index 0000000..8334f89
--- /dev/null
+++ b/app/components/BookBuilder/Chapter/ChapterTitle.jsx
@@ -0,0 +1,73 @@
+import React from 'react'
+
+import DropdownTitle from './DropdownTitle'
+import RenameEmptyError from './RenameEmptyError'
+import Title from './Title'
+
+import styles from '../styles/bookBuilder.local.scss'
+
+class ChapterTitle extends React.Component {
+  constructor (props) {
+    super(props)
+
+    this.state = {
+      isRenameEmpty: false,
+      isRenamingTitle: false
+    }
+  }
+
+  render () {
+    const {
+      chapter,
+      division,
+      onClickRename,
+      onSaveRename,
+      title,
+      type,
+      update
+    } = this.props
+    const { isRenameEmpty, isRenaming } = this.state
+
+    let titleArea
+
+    if (type === 'chapter' || type === 'part') {
+      titleArea = (
+        <Title
+          isRenaming={isRenaming}
+          onClickRename={onClickRename}
+          onSaveRename={onSaveRename}
+          title={title}
+        />
+      )
+    } else if (type === 'component') {
+      titleArea = (
+        <DropdownTitle
+          chapter={chapter}
+          division={division}
+          title={title}
+          update={update}
+        />
+      )
+    }
+
+    return (
+      <div className={styles.chapterTitle}>
+        { titleArea }
+        <RenameEmptyError isRenameEmpty={isRenameEmpty} />
+        <div className={styles.separator} />
+      </div>
+    )
+  }
+}
+
+ChapterTitle.propTypes = {
+  chapter: React.PropTypes.object.isRequired,
+  division: React.PropTypes.string.isRequired,
+  onClickRename: React.PropTypes.func.isRequired,
+  onSaveRename: React.PropTypes.func.isRequired,
+  title: React.PropTypes.string.isRequired,
+  type: React.PropTypes.string.isRequired,
+  update: React.PropTypes.func.isRequired
+}
+
+export default ChapterTitle
diff --git a/app/components/BookBuilder/Chapter/DropdownTitle.jsx b/app/components/BookBuilder/Chapter/DropdownTitle.jsx
new file mode 100644
index 0000000..0015521
--- /dev/null
+++ b/app/components/BookBuilder/Chapter/DropdownTitle.jsx
@@ -0,0 +1,200 @@
+import {
+  get,
+  map,
+  slice
+} from 'lodash'
+
+import React from 'react'
+import { DropdownButton, MenuItem } from 'react-bootstrap'
+import { findDOMNode } from 'react-dom'
+
+import TextInput from '../../utils/TextInput'
+import { chapter as config } from '../../utils/config'
+
+import styles from '../styles/bookBuilder.local.scss'
+
+class DropdownTitle extends React.Component {
+  constructor (props) {
+    super(props)
+
+    this.breakIntoColumns = this.breakIntoColumns.bind(this)
+    this.close = this.close.bind(this)
+    this.getColumnCount = this.getColumnCount.bind(this)
+    this.getDropdownOptions = this.getDropdownOptions.bind(this)
+    this.getMenuItems = this.getMenuItems.bind(this)
+    this.handleClickOutside = this.handleClickOutside.bind(this)
+    this.onClickOption = this.onClickOption.bind(this)
+    this.setCustomTitle = this.setCustomTitle.bind(this)
+    this.toggle = this.toggle.bind(this)
+    this.update = this.update.bind(this)
+
+    this.state = {
+      open: false
+    }
+
+    this.maxItemsInColumn = 5
+    this.width = 180
+  }
+
+  breakIntoColumns (items) {
+    const max = this.maxItemsInColumn
+    const width = this.width
+
+    const columns = []
+    let loopIt = 1
+
+    // TODO -- width is 180, why am I looping that?!
+    while (loopIt <= width) {
+      let start = (loopIt - 1) * max
+      let end = start + max
+
+      columns.push(slice(items, start, end))
+      loopIt += 1
+    }
+
+    return map(columns, function (column, i) {
+      return (
+        <div
+          className={styles.menuItemContainer}
+          key={i}
+        >
+          { column }
+        </div>
+      )
+    })
+  }
+
+  getColumnCount () {
+    const dropdownOptions = this.getDropdownOptions()
+    const len = dropdownOptions.length
+
+    if (len > 9) return Math.ceil(len / 5)
+    return 1
+  }
+
+  getDropdownOptions () {
+    const { division } = this.props
+    return config.dropdownValues[division]
+  }
+
+  getMenuItems () {
+    const dropdownOptions = this.getDropdownOptions()
+    const onClickOption = this.onClickOption
+
+    const menuItems = map(dropdownOptions, function (item, i) {
+      return (
+        <MenuItem
+          className={styles.menuItem}
+          onClick={onClickOption}
+          key={i}
+        >
+          { item }
+        </MenuItem>
+      )
+    })
+
+    return menuItems
+  }
+
+  onClickOption (event) {
+    const value = event.target.innerHTML.trim()
+    this.update(value)
+    this.close()
+  }
+
+  setCustomTitle (e) {
+    let value = get(this.refs, 'dropDownInput.state.value', null)
+    this.update(value)
+    // TODO -- why the timeout here?
+    setTimeout(() => this.close(), 10)
+  }
+
+  toggle () {
+    this.setState({ open: !this.state.open })
+  }
+
+  close () {
+    this.setState({ open: false })
+  }
+
+  update (title) {
+    const { chapter, update } = this.props
+
+    chapter.title = title
+    update(chapter)
+  }
+
+  componentDidMount () {
+    window.addEventListener('click', this.handleClickOutside)
+  }
+
+  componentWillUnmount () {
+    window.removeEventListener('click', this.handleClickOutside)
+  }
+
+  handleClickOutside (event) {
+    var domNode = findDOMNode(this)
+
+    if (domNode.classList.contains('open')) {
+      if (!domNode.contains(event.target)) {
+        this.close()
+      }
+    }
+  }
+
+  renderInput () {
+    return (
+      <div className={styles.dropDownInputContairer}>
+        <TextInput
+          ref='dropDownInput'
+          className={'drop-input ' + styles.dropDownInput}
+          onSave={this.setCustomTitle}
+          placeholder='Type a custom title'
+        />
+      </div>
+    )
+  }
+
+  render () {
+    const { title } = this.props
+
+    const columnCount = this.getColumnCount()
+    const menuItems = this.getMenuItems()
+    const input = this.renderInput()
+    const width = this.width
+
+    let columns = menuItems
+    if (columnCount > 1) columns = this.breakIntoColumns(menuItems)
+
+    const dropdownStyle = {
+      width: width * columnCount
+    }
+
+    return (
+      <DropdownButton
+        className={styles.dropDown}
+        id={'dropdown-title-menu'}
+        open={this.state.open}
+        onClick={this.toggle}
+        title={title}
+        ref={'dropdown-title'}
+      >
+
+        <div style={dropdownStyle}>
+          { input }
+          { columns }
+        </div>
+
+      </DropdownButton>
+    )
+  }
+}
+
+DropdownTitle.propTypes = {
+  chapter: React.PropTypes.object.isRequired,
+  division: React.PropTypes.string.isRequired,
+  title: React.PropTypes.string.isRequired,
+  update: React.PropTypes.func.isRequired
+}
+
+export default DropdownTitle
diff --git a/app/components/BookBuilder/Chapter/FirstRow.jsx b/app/components/BookBuilder/Chapter/FirstRow.jsx
new file mode 100644
index 0000000..17344be
--- /dev/null
+++ b/app/components/BookBuilder/Chapter/FirstRow.jsx
@@ -0,0 +1,308 @@
+import { get, includes, map, slice } from 'lodash'
+import React from 'react'
+import { DropdownButton, MenuItem } from 'react-bootstrap'
+import { LinkContainer } from 'react-router-bootstrap'
+
+import { findDOMNode } from 'react-dom'
+
+import BookBuilderModal from '../BookBuilderModal'
+import TextInput from '../../utils/TextInput'
+
+import ChapterTitle from './ChapterTitle'
+
+class ChapterFirstRow extends React.Component {
+  constructor (props) {
+    super(props)
+
+    this._viewOrEdit = this._viewOrEdit.bind(this)
+
+    this._onClickRename = this._onClickRename.bind(this)
+    this._onSaveRename = this._onSaveRename.bind(this)
+
+    this._onClickDelete = this._onClickDelete.bind(this)
+    this._onClickUnlock = this._onClickUnlock.bind(this)
+    this._toggleDelete = this._toggleDelete.bind(this)
+    this._toggleUnlock = this._toggleUnlock.bind(this)
+
+    this._isAdmin = this._isAdmin.bind(this)
+    this._formatDate = this._formatDate.bind(this)
+
+    this._viewOrEdit = this._viewOrEdit.bind(this)
+
+    // this._onClickTitleDropdown = this._onClickTitleDropdown.bind(this)
+    // this._onClickCustomTitle = this._onClickCustomTitle.bind(this)
+
+    this.state = {
+      canEdit: true,
+      isRenameEmpty: false,
+      isRenamingTitle: false,
+      showDeleteModal: false,
+      showUnlockModal: false
+    }
+  }
+
+  _toggleDelete () {
+    this.setState({ showDeleteModal: !this.state.showDeleteModal })
+  }
+
+  _toggleUnlock () {
+    if (!this._isAdmin()) { return }
+    this.setState({ showUnlockModal: !this.state.showUnlockModal })
+  }
+
+  _isAdmin () {
+    const { roles } = this.props
+    return includes(roles, 'admin')
+  }
+
+  _onClickUnlock () {
+    const { book, chapter, update } = this.props
+    const isAdmin = this._isAdmin()
+
+    if (!isAdmin) { return }
+
+    chapter.lock = null
+    update(book, chapter)
+    this._toggleUnlock()
+  }
+
+  _onClickRename () {
+    this.setState({
+      isRenamingTitle: true
+    })
+  }
+
+  _onSaveRename (title) {
+    // save button has been clicked from outside the component
+    if (typeof title !== 'string') {
+      // console.log(this.refs)
+      title = this.refs.chapterInput.state.value
+    }
+
+    if (title.length === 0) {
+      this.setState({
+        isRenameEmpty: true
+      })
+      return
+    }
+
+    this.setState({
+      isRenameEmpty: false
+    })
+
+    const { book, chapter, update } = this.props
+    chapter.title = title
+
+    update(book, chapter)
+
+    this.setState({
+      isRenamingTitle: false
+    })
+  }
+
+  _onClickDelete () {
+    const { chapter, remove } = this.props
+    remove(chapter)
+    this._toggleDelete()
+  }
+
+  _viewOrEdit () {
+    const { roles, chapter } = this.props
+
+    if (includes(roles, 'production-editor')) return this.setState({ canEdit: true })
+
+    if (chapter.progress['review'] === 1 && includes(roles, 'author') ||
+        chapter.progress['edit'] === 1 && includes(roles, 'copy-editor')) {
+      this.setState({ canEdit: true })
+    } else {
+      this.setState({ canEdit: false })
+    }
+  }
+
+  _formatDate (timestamp) {
+    const date = new Date(timestamp)
+
+    const day = date.getDate()
+    const month = date.getMonth() + 1
+    const year = date.getFullYear()
+
+    let hours = date.getHours().toString()
+    if (hours.length === 1) {
+      hours = '0' + hours
+    }
+
+    let minutes = date.getMinutes().toString()
+    if (minutes.length === 1) {
+      minutes = '0' + minutes
+    }
+
+    const theDate = month + '/' + day + '/' + year
+    const theTime = hours + ':' + minutes
+    const formatted = theDate + ' ' + theTime
+    return formatted
+  }
+
+  render () {
+    const {
+      book,
+      chapter,
+      outerContainer,
+      title,
+      type,
+      update
+    } = this.props
+
+    const { isRenameEmpty, isRenamingTitle } = this.state
+
+    // let titleArea = null
+    // let renameButton = null
+
+    // let renameEmptyError = isRenameEmpty
+    // ? (
+    //   <span className={styles.emptyTitle}>
+    //     New title cannot be empty
+    //   </span>
+    // )
+    // : null
+
+    // if (type === 'chapter' || type === 'part') {
+    //   // if type is chapter, make the title editable text
+    //   let renameButtonText, renameButtonFunction
+    //
+    //   const input = (
+    //     <TextInput
+    //       className='edit'
+    //       ref='chapterInput'
+    //       onSave={this._onSaveRename}
+    //       value={title}
+    //     />
+    //   )
+    //
+    //   if (isRenamingTitle) {
+    //     titleArea = input
+    //     renameButtonText = 'Save'
+    //     renameButtonFunction = this._onSaveRename
+    //   } else {
+    //     titleArea = (<h3 onDoubleClick={this._onClickRename}> { title } </h3>)
+    //     renameButtonText = 'Rename'
+    //     renameButtonFunction = this._onClickRename
+    //   }
+    //
+    //   // add id so that it can be selected for testing
+    //   // could do with refs, but that would mean mounting instead of
+    //   // shallow rendering to access enzyme's refs() api method
+    //   renameButton = (
+    //     <a id='bb-rename'
+    //       onClick={renameButtonFunction}>
+    //       { renameButtonText } &nbsp;&nbsp;
+    //     </a>
+    //   )
+    // }
+
+    // const editOrView = this.state.canEdit ? 'Edit' : 'View'
+
+    // const buttons = (
+    //   <div>
+    //     { renameButton }
+    //     <LinkContainer
+    //       to={`/books/${book.id}/fragments/${chapter.id}`}
+    //       id='bb-edit'
+    //     >
+    //       <a>{ editOrView } &nbsp;&nbsp;</a>
+    //     </LinkContainer>
+    //
+    //     <a id='bb-delete'
+    //       onClick={this._toggleDelete}>
+    //       Delete
+    //     </a>
+    //   </div>
+    // )
+
+    // let editorArea
+    // if (get(chapter, 'lock.editor.username')) {
+    //   let message = ' is editing'
+    //   if (chapter.lock.timestamp && this._isAdmin()) {
+    //     message = ' has been editing since ' + this._formatDate(chapter.lock.timestamp)
+    //   }
+    //
+    //   editorArea = (
+    //     <a id='bb-unlock'
+    //       className={styles.lEditing}
+    //       onClick={this._toggleUnlock}>
+    //
+    //       <i
+    //         className={styles.lockIcon + ' fa fa-lock'}
+    //         aria-hidden='true'
+    //         alt='unlock'
+    //       />
+    //       <span className={styles.lockMessage}>
+    //         { chapter.lock.editor.username + message}
+    //       </span>
+    //
+    //     </a>
+    //   )
+    // }
+
+    // const rightArea = chapter.lock ? editorArea : buttons
+
+    const division = chapter.division
+
+    return (
+      <span>
+        <ChapterTitle
+          chapter={chapter}
+          division={division}
+          onClickRename={this._onClickRename}
+          onSaveRename={this._onSaveRename}
+          title={title}
+          type={type}
+          update={update}
+        />
+
+        {/* <ChapterButtons /> */}
+
+        {/* <div className={styles.chapterActions + ' pull-right'}>
+          { rightArea }
+        </div> */}
+
+        <BookBuilderModal
+          title={'Delete ' + type}
+          chapter={chapter}
+          action='delete'
+          successText='Delete'
+          type={type}
+          successAction={this._onClickDelete}
+          show={this.state.showDeleteModal}
+          toggle={this._toggleDelete}
+          container={outerContainer}
+        />
+
+        <BookBuilderModal
+          title={'Unlock ' + type}
+          chapter={chapter}
+          action='unlock'
+          successText='Unlock'
+          type={type}
+          successAction={this._onClickUnlock}
+          show={this.state.showUnlockModal}
+          toggle={this._toggleUnlock}
+          container={outerContainer}
+        />
+      </span>
+    )
+  }
+}
+
+ChapterFirstRow.propTypes = {
+  book: React.PropTypes.object.isRequired,
+  chapter: React.PropTypes.object.isRequired,
+  // ink: React.PropTypes.func.isRequired,
+  outerContainer: React.PropTypes.object.isRequired,
+  remove: React.PropTypes.func.isRequired,
+  roles: React.PropTypes.array,
+  title: React.PropTypes.string.isRequired,
+  type: React.PropTypes.string.isRequired,
+  update: React.PropTypes.func.isRequired
+}
+
+export default ChapterFirstRow
diff --git a/app/components/BookBuilder/PagePositionAlignment.jsx b/app/components/BookBuilder/Chapter/PagePositionAlignment.jsx
similarity index 96%
rename from app/components/BookBuilder/PagePositionAlignment.jsx
rename to app/components/BookBuilder/Chapter/PagePositionAlignment.jsx
index e84f038..2c0f603 100644
--- a/app/components/BookBuilder/PagePositionAlignment.jsx
+++ b/app/components/BookBuilder/Chapter/PagePositionAlignment.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import styles from './styles/bookBuilder.local.scss'
+import styles from '../styles/bookBuilder.local.scss'
 import { includes } from 'lodash'
 
 export class PagePositionAlignment extends React.Component {
diff --git a/app/components/BookBuilder/ProgressIndicator.jsx b/app/components/BookBuilder/Chapter/ProgressIndicator.jsx
similarity index 97%
rename from app/components/BookBuilder/ProgressIndicator.jsx
rename to app/components/BookBuilder/Chapter/ProgressIndicator.jsx
index 154088a..f5015cf 100644
--- a/app/components/BookBuilder/ProgressIndicator.jsx
+++ b/app/components/BookBuilder/Chapter/ProgressIndicator.jsx
@@ -1,8 +1,8 @@
 import React from 'react'
 import { includes } from 'lodash'
 import { Alert } from 'react-bootstrap'
-import BookBuilderModal from './BookBuilderModal'
-import styles from './styles/bookBuilder.local.scss'
+import BookBuilderModal from '../BookBuilderModal'
+import styles from '../styles/bookBuilder.local.scss'
 
 export class ProgressIndicator extends React.Component {
   constructor (props) {
diff --git a/app/components/BookBuilder/Chapter/RenameEmptyError.jsx b/app/components/BookBuilder/Chapter/RenameEmptyError.jsx
new file mode 100644
index 0000000..822f264
--- /dev/null
+++ b/app/components/BookBuilder/Chapter/RenameEmptyError.jsx
@@ -0,0 +1,25 @@
+import React from 'react'
+
+import styles from '../styles/bookBuilder.local.scss'
+
+class RenameEmptyError extends React.Component {
+  render () {
+    const { isRenameEmpty } = this.props
+
+    if (isRenameEmpty) {
+      return (
+        <span className={styles.emptyTitle}>
+          New title cannot be empty
+        </span>
+      )
+    }
+
+    return null
+  }
+}
+
+RenameEmptyError.propTypes = {
+  isRenameEmpty: React.PropTypes.bool.isRequired
+}
+
+export default RenameEmptyError
diff --git a/app/components/BookBuilder/Chapter/SecondRow.jsx b/app/components/BookBuilder/Chapter/SecondRow.jsx
new file mode 100644
index 0000000..131623d
--- /dev/null
+++ b/app/components/BookBuilder/Chapter/SecondRow.jsx
@@ -0,0 +1,99 @@
+import React from 'react'
+
+import PagePositionAlignment from './PagePositionAlignment'
+import ProgressIndicator from './ProgressIndicator'
+import UploadWordButton from './UploadWordBtn'
+
+import styles from '../styles/bookBuilder.local.scss'
+
+class ChapterSecondRow extends React.Component {
+  render () {
+    const {
+      chapter,
+      ink,
+      outerContainer,
+      roles
+    } = this.props
+
+    return (
+      <div className={styles.secondLineContainer}>
+
+        <div className={styles.noPadding + ' col-lg-2 col-md-12 col-sm-12 col-xs-12'}>
+          <UploadWordButton
+            accept='.docx'
+            ink={ink}
+            title=' '
+            type='file'
+          />
+        </div>
+
+        <ul className={styles.secondActions + ' col-lg-7 col-md-12 col-sm-12 col-xs-12'}>
+          <ProgressIndicator
+            type='style'
+            chapter={chapter}
+            update={this.update}
+            roles={roles}
+            outerContainer={outerContainer}
+            hasIcon
+            viewOrEdit={this._viewOrEdit}
+          />
+
+          <ProgressIndicator
+            type='edit'
+            chapter={chapter}
+            update={this.update}
+            roles={roles}
+            outerContainer={outerContainer}
+            hasIcon
+            viewOrEdit={this._viewOrEdit}
+          />
+
+          <ProgressIndicator
+            type='review'
+            chapter={chapter}
+            update={this.update}
+            roles={roles}
+            outerContainer={outerContainer}
+            hasIcon
+            viewOrEdit={this._viewOrEdit}
+          />
+
+          <ProgressIndicator
+            type='clean'
+            chapter={chapter}
+            roles={roles}
+            outerContainer={outerContainer}
+            update={this.update}
+            viewOrEdit={this._viewOrEdit}
+          />
+        </ul>
+
+        <div className={styles.noPadding + ' col-lg-3 col-md-12 col-sm-12 col-xs-12'}>
+          <PagePositionAlignment
+            chapter={chapter}
+            update={this.update}
+          />
+        </div>
+
+        <div className={styles.separator} />
+      </div>
+    )
+  }
+}
+
+ChapterSecondRow.propTypes = {
+  book: React.PropTypes.object.isRequired,
+  chapter: React.PropTypes.object.isRequired,
+  connectDragSource: React.PropTypes.func.isRequired,
+  connectDropTarget: React.PropTypes.func.isRequired,
+  ink: React.PropTypes.func.isRequired,
+  isDragging: React.PropTypes.bool.isRequired,
+  outerContainer: React.PropTypes.object.isRequired,
+  remove: React.PropTypes.func.isRequired,
+  roles: React.PropTypes.array,
+  title: React.PropTypes.string.isRequired,
+  type: React.PropTypes.string.isRequired,
+  update: React.PropTypes.func.isRequired
+}
+
+export default ChapterSecondRow
diff --git a/app/components/BookBuilder/Chapter/Title.jsx b/app/components/BookBuilder/Chapter/Title.jsx
new file mode 100644
index 0000000..dac49bc
--- /dev/null
+++ b/app/components/BookBuilder/Chapter/Title.jsx
@@ -0,0 +1,50 @@
+import React from 'react'
+
+import TextInput from '../../utils/TextInput'
+
+class Title extends React.Component {
+  // constructor (props) {
+  //   super(props)
+  //
+  //   // this.state = {
+  //   //   isRenaming: false
+  //   // }
+  // }
+
+  // _onClickRename () {
+  //   this.setState({
+  //     isRenaming: true
+  //   })
+  // }
+
+  render () {
+    const { isRenaming, onClickRename, onSaveRename, title } = this.props
+
+    const input = (
+      <TextInput
+        className='edit'
+        ref='chapterInput'
+        onSave={onSaveRename}
+        value={title}
+      />
+    )
+
+    const plainTitle = (
+      <h3 onDoubleClick={onClickRename}>
+        { title }
+      </h3>
+    )
+
+    if (isRenaming) return input
+    return plainTitle
+  }
+}
+
+Title.propTypes = {
+  isRenaming: React.PropTypes.bool.isRequired,
+  onClickRename: React.PropTypes.func.isRequired,
+  onSaveRename: React.PropTypes.func.isRequired,
+  title: React.PropTypes.string.isRequired
+}
+
+export default Title
diff --git a/app/components/BookBuilder/UploadWordBtn.jsx b/app/components/BookBuilder/Chapter/UploadWordBtn.jsx
similarity index 94%
rename from app/components/BookBuilder/UploadWordBtn.jsx
rename to app/components/BookBuilder/Chapter/UploadWordBtn.jsx
index 41e27a0..2e827d9 100644
--- a/app/components/BookBuilder/UploadWordBtn.jsx
+++ b/app/components/BookBuilder/Chapter/UploadWordBtn.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import styles from './styles/bookBuilder.local.scss'
+import styles from '../styles/bookBuilder.local.scss'
 
 export class UploadWordButton extends React.Component {
   constructor (props) {
diff --git a/app/components/utils/DnD.js b/app/components/utils/DnD.js
new file mode 100644
index 0000000..782f3c2
--- /dev/null
+++ b/app/components/utils/DnD.js
@@ -0,0 +1,69 @@
+import { findDOMNode } from 'react-dom'
+
+const itemTypes = {
+  CHAPTER: 'chapter'
+}
+
+const chapterSource = {
+  beginDrag (props) {
+    return {
+      id: props.id,
+      no: props.no,
+      division: props.chapter.division
+    }
+  },
+
+  isDragging (props, monitor) {
+    return props.id === monitor.getItem().id
+  }
+}
+
+const chapterTarget = {
+  // for an explanation of how this works go to
+  // https://github.com/gaearon/react-dnd/blob/master/examples/04%20Sortable/Simple/Card.js
+
+  hover (props, monitor, component) {
+    // can only reorder within the same division
+    const dragDivision = monitor.getItem().division
+    const hoverDivision = props.chapter.division
+
+    if (dragDivision !== hoverDivision) { return }
+
+    const dragIndex = monitor.getItem().no
+    const hoverIndex = props.no
+
+    if (dragIndex === hoverIndex) { return }
+
+    const hoverBoundingRect = findDOMNode(component).getBoundingClientRect()
+    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
+    const clientOffset = monitor.getClientOffset()
+    const hoverClientY = clientOffset.y - hoverBoundingRect.top
+
+    if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { return }
+    if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { return }
+
+    props.move(dragIndex, hoverIndex)
+    monitor.getItem().no = hoverIndex
+  }
+}
+
+const collectDrag = (connect, monitor) => {
+  return {
+    connectDragSource: connect.dragSource(),
+    isDragging: monitor.isDragging()
+  }
+}
+
+const collectDrop = (connect, monitor) => {
+  return {
+    connectDropTarget: connect.dropTarget()
+  }
+}
+
+export {
+  chapterSource,
+  chapterTarget,
+  collectDrag,
+  collectDrop,
+  itemTypes
+}
diff --git a/app/components/utils/config.js b/app/components/utils/config.js
new file mode 100644
index 0000000..59ce4b3
--- /dev/null
+++ b/app/components/utils/config.js
@@ -0,0 +1,26 @@
+const chapter = {
+  dropdownValues: {
+    front: [
+      'Table of Contents',
+      'Introduction',
+      'Preface',
+      'Preface 1',
+      'Preface 2',
+      'Preface 3',
+      'Preface 4',
+      'Preface 5',
+      'Preface 6',
+      'Preface 7',
+      'Preface 8',
+      'Preface 9',
+      'Preface 10'
+    ],
+    back: [
+      'Appendix A',
+      'Appendix B',
+      'Appendix C'
+    ]
+  }
+}
+
+export { chapter }
diff --git a/package.json b/package.json
index 0728413..ab469f5 100644
--- a/package.json
+++ b/package.json
@@ -26,9 +26,9 @@
     "json-loader": "^0.5.4",
     "pubsweet-backend": "^0.5.0",
     "pubsweet-component-blog": "^0.1.0",
-    "pubsweet-component-login": "^0.1.0",
     "pubsweet-component-ink-backend": "^0.0.3",
     "pubsweet-component-ink-frontend": "^0.0.1",
+    "pubsweet-component-login": "^0.1.0",
     "pubsweet-component-manage": "^0.1.0",
     "pubsweet-component-navigation": "^0.1.0",
     "pubsweet-component-signup": "^0.1.0",
diff --git a/webpack/common-rules.js b/webpack/common-rules.js
index c68872f..60b4018 100644
--- a/webpack/common-rules.js
+++ b/webpack/common-rules.js
@@ -89,7 +89,6 @@ module.exports = [
     loader: 'string-replace-loader',
     query: {
       search: 'PUBSWEET_COMPONENTS',
-      // replace: '[' + config.pubsweet.components.map(component => `require('${component}')`).join(', ') + ']'
       replace: '[' + frontendComponents.map(component => `require('${component}')`).join(', ') + ']'
     },
     include: babelIncludes
-- 
GitLab