diff --git a/.gitignore b/.gitignore index 93c09b646905425496e9085a191795a42bed5a46..e44031e5057361cb1195da5cda6c37dd14ff18d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ +.env.* api/db/* api/db/* logs/* node_modules public/assets/* pubsweet.log - diff --git a/app/components/BookBuilder/BookBuilder.jsx b/app/components/BookBuilder/BookBuilder.jsx index a7b2f30cd4a2de357cbbc8aaff6833d0457243c3..dfed3caa6c5df9a0694f696d3aa9a107a5f0581a 100644 --- a/app/components/BookBuilder/BookBuilder.jsx +++ b/app/components/BookBuilder/BookBuilder.jsx @@ -5,7 +5,7 @@ import { connect } from 'react-redux' import * as Actions from 'pubsweet-frontend/src/actions' import Division from './Division' -import BookBuilderModal from './BookBuilderModal' +import Modal from '../utils/Modal' import styles from './styles/bookBuilder.local.scss' // import { fragmentsOfCollection } from 'pubsweet-core/app/helpers/Utils' @@ -117,7 +117,7 @@ export class BookBuilder extends React.Component { render () { const { book, chapters, teams, users } = this.props - const { createFragment, deleteFragment, updateFragment, updateTeam } = this.props.actions + const { createFragment, deleteFragment, ink, updateFragment, updateTeam } = this.props.actions const { outerContainer } = this.state const roles = this._getRoles() @@ -168,49 +168,52 @@ export class BookBuilder extends React.Component { </div> <Division - title='Front Matter' - type='front' - outerContainer={outerContainer} add={createFragment} - remove={deleteFragment} - update={updateFragment} + book={book} chapters={frontChapters} + ink={ink} + outerContainer={outerContainer} + remove={deleteFragment} roles={roles} - book={book} + title='Front Matter' + type='front' + update={updateFragment} /> <div className={styles.sectionDivider} /> <Division - title='Body' - type='body' - outerContainer={outerContainer} add={createFragment} - remove={deleteFragment} - update={updateFragment} + book={book} chapters={bodyChapters} + ink={ink} + outerContainer={outerContainer} + remove={deleteFragment} roles={roles} - book={book} + title='Body' + type='body' + update={updateFragment} /> <div className={styles.sectionDivider} /> <Division - title='Back Matter' - type='back' - outerContainer={outerContainer} add={createFragment} - remove={deleteFragment} - update={updateFragment} + book={book} chapters={backChapters} + ink={ink} + outerContainer={outerContainer} + remove={deleteFragment} roles={roles} - book={book} + title='Back Matter' + type='back' + update={updateFragment} /> </div> </div> - <BookBuilderModal + <Modal title='Editoria Team Manager' action='EditoriaTeamManager' show={this.state.showTeamManager} diff --git a/app/components/BookBuilder/Chapter.jsx b/app/components/BookBuilder/Chapter.jsx index 85773664ccde2a82e9448f4cf75e17ac13ac567f..636c9d578bb43d0ab6b52530f3bb6f2aff17be04 100644 --- a/app/components/BookBuilder/Chapter.jsx +++ b/app/components/BookBuilder/Chapter.jsx @@ -1,467 +1,97 @@ +import { flow } from 'lodash' 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 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() - } -} - -const collectDrop = (connect, monitor) => { - return { - connectDropTarget: connect.dropTarget() - } -} +import { chapterSource, chapterTarget, collectDrag, collectDrop, itemTypes } from '../utils/DnD' 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.toggleUpload = this.toggleUpload.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 + isUploadInProgress: 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 - } - 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 - }) - } - update (changedChapter) { const { book, update } = this.props update(book, changedChapter) } - render () { - const { book, chapter, type, title, connectDragSource, connectDropTarget, isDragging, roles, outerContainer } = 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 } - </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 - - 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 } </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}> + toggleUpload () { + this.setState({ + isUploadInProgress: !this.state.isUploadInProgress + }) - <i - className={styles.lockIcon + ' fa fa-lock'} - aria-hidden='true' - alt='unlock' - /> - <span className={styles.lockMessage}> - { chapter.lock.editor.username + message} - </span> + // if (!this.state.isUploadInProgress) this.removeUploadState() + } + + // getLocalStorageKey () { + // const { chapter } = this.props + // return 'chapter:upload:' + chapter.id + // } + // + // persistUploadState () { + // const key = this.getLocalStorageKey() + // window.localStorage.setItem(key, true) + // } + // + // removeUploadState () { + // const key = this.getLocalStorageKey() + // window.localStorage.removeItem(key) + // } + // + // componentWillMount () { + // const key = this.getLocalStorageKey() + // var entry = window.localStorage.getItem(key) + // if (entry) { + // this.setState({ + // isUploadInProgress: true + // }) + // } + // } + // + // componentWillUnmount () { + // if (this.state.isUploadInProgress) { + // this.persistUploadState() + // } + // } - </a> - ) + render () { + const { + book, + chapter, + connectDragSource, + connectDropTarget, + ink, + isDragging, + outerContainer, + remove, + roles, + title, + type + } = this.props + + const { isUploadInProgress } = this.state + + const listItemStyle = { + opacity: isDragging ? 0 : 1 } - const rightArea = chapter.lock ? editorArea : buttons - + // TODO -- refactor these huge class names + // TODO -- make the dot and line component/s return connectDragSource(connectDropTarget( <li className={styles.chapterContainer + ' col-lg-12 bb-chapter ' + (chapter.subCategory === 'chapter' ? styles.isChapter : styles.isPart)} - style={{ opacity: opacity }}> + style={listItemStyle} + > + <div className={styles.grabIcon + ' ' + (chapter.division === 'body' ? styles.grabIconBody : '')}> <i className='fa fa-circle' /> <div className={styles.tooltip}> @@ -470,103 +100,31 @@ 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} + isUploadInProgress={isUploadInProgress} + outerContainer={outerContainer} + remove={remove} + roles={roles} + 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 - type='file' - accept='.docx' - title=' ' - /> - </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} + convertFile={ink} + outerContainer={outerContainer} + roles={roles} + toggleUpload={this.toggleUpload} + 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> )) @@ -574,21 +132,18 @@ export class Chapter extends React.Component { } Chapter.propTypes = { - chapter: React.PropTypes.object.isRequired, book: React.PropTypes.object.isRequired, - - remove: React.PropTypes.func.isRequired, - update: React.PropTypes.func.isRequired, - type: React.PropTypes.string.isRequired, - title: React.PropTypes.string.isRequired, - outerContainer: React.PropTypes.object.isRequired, - // roles: React.PropTypes.array.isRequired, - roles: React.PropTypes.array, - - // react-dnd + chapter: React.PropTypes.object.isRequired, connectDragSource: React.PropTypes.func.isRequired, connectDropTarget: React.PropTypes.func.isRequired, - isDragging: React.PropTypes.bool.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 } // combine them, as each chapter can be both a source and a target diff --git a/app/components/BookBuilder/Chapter/AlignmentBox.jsx b/app/components/BookBuilder/Chapter/AlignmentBox.jsx new file mode 100644 index 0000000000000000000000000000000000000000..19d98f0a2eb75991d0425ab169bb0fcf56b98861 --- /dev/null +++ b/app/components/BookBuilder/Chapter/AlignmentBox.jsx @@ -0,0 +1,45 @@ +import { includes } from 'lodash' +import React from 'react' + +import styles from '../styles/bookBuilder.local.scss' + +class AlignmentBox extends React.Component { + constructor (props) { + super(props) + this.onClick = this.onClick.bind(this) + } + + onClick () { + const { chapter, position, update } = this.props + + if (!includes(['left', 'right'], position)) return + + chapter.alignment[position] = !chapter.alignment[position] + update(chapter) + } + + render () { + const { chapter, position } = this.props + const selected = chapter.alignment[position] + + // TODO -- fix classes here + const outerClass = styles.leftRightBox + ' ' + styles[position + 'Box'] + const innerClass = selected ? styles.boxActive : styles.boxInactiveHover + + return ( + <li onClick={this.onClick}> + <div className={outerClass}> + <div className={innerClass} /> + </div> + </li> + ) + } +} + +AlignmentBox.propTypes = { + chapter: React.PropTypes.object.isRequired, + position: React.PropTypes.string.isRequired, + update: React.PropTypes.func.isRequired +} + +export default AlignmentBox diff --git a/app/components/BookBuilder/Chapter/AlignmentTool.jsx b/app/components/BookBuilder/Chapter/AlignmentTool.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a8e694718021a5b5f528549a485edfa4d6d44759 --- /dev/null +++ b/app/components/BookBuilder/Chapter/AlignmentTool.jsx @@ -0,0 +1,43 @@ +import React from 'react' + +import AlignmentBox from './AlignmentBox' +import styles from '../styles/bookBuilder.local.scss' + +export class AlignmentTool extends React.Component { + render () { + const { chapter, update } = this.props + + // TODO -- what is boxDiver? divider? + + return ( + <ul className={styles.pagePosition}> + <li>left </li> + + <AlignmentBox + chapter={chapter} + position='left' + update={update} + /> + + <AlignmentBox + chapter={chapter} + position='right' + update={update} + /> + + <li> + <div className={styles.boxDiver} /> + </li> + + <li> right</li> + </ul> + ) + } +} + +AlignmentTool.propTypes = { + chapter: React.PropTypes.object.isRequired, + update: React.PropTypes.func.isRequired +} + +export default AlignmentTool diff --git a/app/components/BookBuilder/Chapter/ChapterButtons.jsx b/app/components/BookBuilder/Chapter/ChapterButtons.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3326dcac33d2f1f6ffdd2e3cf49db395b7f29d7e --- /dev/null +++ b/app/components/BookBuilder/Chapter/ChapterButtons.jsx @@ -0,0 +1,181 @@ +import { get, includes } from 'lodash' +import React from 'react' +import { LinkContainer } from 'react-router-bootstrap' + +import DeleteModal from './DeleteModal' +import EditingNotification from './EditingNotification' +import styles from '../styles/bookBuilder.local.scss' + +class ChapterButtons extends React.Component { + constructor (props) { + super(props) + + this.canEdit = this.canEdit.bind(this) + this.isLocked = this.isLocked.bind(this) + this.renderDeleteButton = this.renderDeleteButton.bind(this) + this.renderEditButton = this.renderEditButton.bind(this) + this.renderEditingNotification = this.renderEditingNotification.bind(this) + this.renderRenameButton = this.renderRenameButton.bind(this) + this.renderRightArea = this.renderRightArea.bind(this) + this.toggleDeleteModal = this.toggleDeleteModal.bind(this) + + this.state = { + showDeleteModal: false + } + } + + // TODO -- should maybe check for lock + isLocked () { + const { chapter } = this.props + return get(chapter, 'lock.editor.username') + } + + canEdit () { + const { chapter, roles } = this.props + + if (includes(roles, 'admin') || includes(roles, 'production-editor')) { + return true + } + + if (includes(roles, 'copy-editor')) { + const isEditing = (chapter.progress.edit === 1) + if (isEditing) return true + } + + if (includes(roles, 'author')) { + const isReviewing = (chapter.progress.review === 1) + if (isReviewing) return true + } + + return false + } + + toggleDeleteModal () { + this.setState({ + showDeleteModal: !this.state.showDeleteModal + }) + } + + renderEditingNotification () { + const { chapter, modalContainer, roles, update } = this.props + + return ( + <EditingNotification + chapter={chapter} + modalContainer={modalContainer} + roles={roles} + update={update} + /> + ) + } + + renderRenameButton () { + const { isRenaming, onClickRename, onSaveRename, type } = this.props + + if (type === 'chapter' || type === 'part') { + let renameButtonText = 'Rename' + let renameButtonFunction = onClickRename + + if (isRenaming) { + renameButtonText = 'Save' + renameButtonFunction = onSaveRename + } + + return ( + <a id='bb-rename' + onClick={renameButtonFunction}> + { renameButtonText } + </a> + ) + } + + return null + } + + renderEditButton () { + const { bookId, chapter } = this.props + const text = this.canEdit() ? 'Edit' : 'View' + const url = `/books/${bookId}/fragments/${chapter.id}` + + return ( + <LinkContainer id='bb-edit' to={url} > + <a> { text } </a> + </LinkContainer> + ) + } + + renderDeleteButton () { + const { chapter, modalContainer, remove } = this.props + const { showDeleteModal } = this.state + const toggle = this.toggleDeleteModal + + const deleteModal = ( + <DeleteModal + chapter={chapter} + container={modalContainer} + remove={remove} + show={showDeleteModal} + toggle={toggle} + /> + ) + + return ( + <a id='bb-delete' onClick={toggle} > + Delete + { deleteModal } + </a> + ) + } + + renderRightArea () { + const { isUploadInProgress } = this.props + + if (this.isLocked()) return this.renderEditingNotification() + + const renameButton = this.renderRenameButton() + const editButton = this.renderEditButton() + const deleteButton = this.renderDeleteButton() + + let buttonsStyle = {} + if (isUploadInProgress) { + buttonsStyle = { + 'opacity': '0.3', + 'pointerEvents': 'none' + } + } + + return ( + <div style={buttonsStyle}> + { renameButton } + { editButton } + { deleteButton } + </div> + ) + } + + render () { + let rightArea = this.renderRightArea() + + return ( + <div className={styles.chapterActions + ' pull-right'}> + { rightArea } + </div> + ) + } +} + +ChapterButtons.propTypes = { + bookId: React.PropTypes.string.isRequired, + chapter: React.PropTypes.object.isRequired, + isRenaming: React.PropTypes.bool.isRequired, + isUploadInProgress: React.PropTypes.bool.isRequired, + modalContainer: React.PropTypes.object.isRequired, + onClickRename: React.PropTypes.func.isRequired, + onSaveRename: React.PropTypes.func.isRequired, + remove: React.PropTypes.func.isRequired, + roles: React.PropTypes.array.isRequired, + type: React.PropTypes.string.isRequired, + update: React.PropTypes.func.isRequired +} + +export default ChapterButtons diff --git a/app/components/BookBuilder/Chapter/ChapterTitle.jsx b/app/components/BookBuilder/Chapter/ChapterTitle.jsx new file mode 100644 index 0000000000000000000000000000000000000000..bd531a39e73a88b40ed7741cef54dc6d44b789e0 --- /dev/null +++ b/app/components/BookBuilder/Chapter/ChapterTitle.jsx @@ -0,0 +1,126 @@ +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 { + renderHasContent () { + const { chapter, type } = this.props + const source = chapter.source + const hasContent = source.trim().length > 0 + + if (!hasContent) return null + + // TODO -- move styles to classes (after css refactor) + let marginTop + switch (type) { + case 'part': + marginTop = '4px' + break + case 'chapter': + marginTop = '2px' + break + default: + marginTop = '5px' + break + } + + const styles = { + 'color': '#3e644b', + 'float': 'left', + 'font-size': '14px', + 'margin-top': marginTop, + 'margin-right': '7px' + } + + const hoverTitle = 'This ' + type + ' has content' + + return ( + <i + className='fa fa-check-circle' + style={styles} + title={hoverTitle} + /> + ) + } + + renderUploadIndicator () { + const { isUploadInProgress } = this.props + + if (!isUploadInProgress) return null + + return ( + <i className={styles['animate-flicker'] + ' fa fa-upload'} /> + ) + } + + renderTitle () { + const { chapter, isRenaming, onClickRename, onSaveRename, title, type, update } = this.props + + if (type === 'chapter' || type === 'part') { + return ( + <Title + isRenaming={isRenaming} + onClickRename={onClickRename} + onSaveRename={onSaveRename} + title={title} + /> + ) + } else if (type === 'component') { + return ( + <DropdownTitle + chapter={chapter} + title={title} + update={update} + /> + ) + } + } + + renderError () { + const { isRenameEmpty } = this.props + + return ( + <RenameEmptyError + isRenameEmpty={isRenameEmpty} + /> + ) + } + + render () { + const hasContent = this.renderHasContent() + const title = this.renderTitle() + const uploadIndicator = this.renderUploadIndicator() + const renameEmptyError = this.renderError() + + return ( + <div className={styles.chapterTitle}> + + { hasContent } + { title } + { uploadIndicator } + { renameEmptyError } + + <div className={styles.separator} /> + + </div> + ) + } +} + +ChapterTitle.propTypes = { + chapter: React.PropTypes.object.isRequired, + isRenaming: React.PropTypes.bool.isRequired, + isRenameEmpty: React.PropTypes.bool.isRequired, + isUploadInProgress: React.PropTypes.bool.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/DeleteModal.jsx b/app/components/BookBuilder/Chapter/DeleteModal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4a10c6b258343a9a3992412cb6bdc4323e8e6f72 --- /dev/null +++ b/app/components/BookBuilder/Chapter/DeleteModal.jsx @@ -0,0 +1,46 @@ +import React from 'react' + +import Modal from '../../utils/Modal' + +class DeleteModal extends React.Component { + constructor (props) { + super(props) + this.onDelete = this.onDelete.bind(this) + } + + onDelete () { + const { chapter, remove, toggle } = this.props + + remove(chapter) + toggle() + } + + render () { + const { chapter, container, show, toggle } = this.props + const type = chapter.type + + return ( + <Modal + title={'Delete ' + type} + chapter={chapter} + action='delete' + successText='Delete' + type={type} + successAction={this.onDelete} + show={show} + toggle={toggle} + container={container} + /> + ) + } +} + +DeleteModal.propTypes = { + chapter: React.PropTypes.object.isRequired, + container: React.PropTypes.object.isRequired, + show: React.PropTypes.bool.isRequired, + remove: React.PropTypes.func.isRequired, + toggle: React.PropTypes.func.isRequired +} + +export default DeleteModal diff --git a/app/components/BookBuilder/Chapter/DropdownTitle.jsx b/app/components/BookBuilder/Chapter/DropdownTitle.jsx new file mode 100644 index 0000000000000000000000000000000000000000..627c1f13734b62f9c75e470036628248cbeb15b0 --- /dev/null +++ b/app/components/BookBuilder/Chapter/DropdownTitle.jsx @@ -0,0 +1,201 @@ +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 { chapter } = this.props + const division = chapter.division + + 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) + } + + handleClickOutside (event) { + var domNode = findDOMNode(this) + + if (domNode.classList.contains('open')) { + if (!domNode.contains(event.target)) { + this.close() + } + } + } + + componentDidMount () { + window.addEventListener('click', this.handleClickOutside) + } + + componentWillUnmount () { + window.removeEventListener('click', this.handleClickOutside) + } + + 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, + title: React.PropTypes.string.isRequired, + update: React.PropTypes.func.isRequired +} + +export default DropdownTitle diff --git a/app/components/BookBuilder/Chapter/EditingNotification.jsx b/app/components/BookBuilder/Chapter/EditingNotification.jsx new file mode 100644 index 0000000000000000000000000000000000000000..40986c81002afd65dda368d46b4be2cd314c5f50 --- /dev/null +++ b/app/components/BookBuilder/Chapter/EditingNotification.jsx @@ -0,0 +1,113 @@ +import { includes } from 'lodash' +import React from 'react' + +import noop from '../../utils/noop' +import styles from '../styles/bookBuilder.local.scss' +import UnlockModal from './UnlockModal' + +class EditingNotification extends React.Component { + constructor (props) { + super(props) + + this.formatDate = this.formatDate.bind(this) + this.isAdmin = this.isAdmin.bind(this) + this.toggleModal = this.toggleModal.bind(this) + + this.state = { + showModal: false + } + } + + toggleModal () { + this.setState({ + showModal: !this.state.showModal + }) + } + + 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 + } + + render () { + const { chapter, modalContainer, update } = this.props + const { showModal } = this.state + const username = chapter.lock.editor.username + + let message = username + ' is editing' + let hoverTitle, unlockModal + let toggle = noop + + if (this.isAdmin()) { + toggle = this.toggle() + + unlockModal = ( + <UnlockModal + chapter={chapter} + container={modalContainer} + show={showModal} + toggle={toggle} + update={update} + /> + ) + + if (chapter.lock.timestamp) { + const date = this.formatDate(chapter.lock.timestamp) + hoverTitle = username + ' has been editing since ' + date + } + } + + return ( + <a id='bb-unlock' + className={styles.lEditing} + onClick={toggle} + title={hoverTitle} + > + <i + className={styles.lockIcon + ' fa fa-lock'} + aria-hidden='true' + alt='unlock' + /> + + <span className={styles.lockMessage}> + { message } + </span> + + { unlockModal } + </a> + ) + } +} + +EditingNotification.propTypes = { + chapter: React.PropTypes.object.isRequired, + modalContainer: React.PropTypes.object.isRequired, + roles: React.PropTypes.array.isRequired, + update: React.PropTypes.func.isRequired +} + +export default EditingNotification diff --git a/app/components/BookBuilder/Chapter/FirstRow.jsx b/app/components/BookBuilder/Chapter/FirstRow.jsx new file mode 100644 index 0000000000000000000000000000000000000000..367c7bc75d1e4e45a90f88f5e78d94bbfbbccce7 --- /dev/null +++ b/app/components/BookBuilder/Chapter/FirstRow.jsx @@ -0,0 +1,91 @@ +import React from 'react' + +import ChapterButtons from './ChapterButtons' +import ChapterTitle from './ChapterTitle' + +class ChapterFirstRow extends React.Component { + constructor (props) { + super(props) + + this.onClickRename = this.onClickRename.bind(this) + this.onSaveRename = this.onSaveRename.bind(this) + + this.state = { + isRenameEmpty: false, + isRenamingTitle: false + } + } + + onClickRename () { + this.setState({ + isRenamingTitle: true + }) + } + + // TODO -- trim title + onSaveRename (title) { + const { book, chapter, update } = this.props + + // TODO -- DOESN'T WORK ANYMORE + // handle save button click (from outside the text input component) + if (typeof title !== 'string') { + title = this.refs.chapterInput.state.value + } + + if (title.length === 0) return this.setState({ isRenameEmpty: true }) + this.setState({ isRenameEmpty: false }) + + chapter.title = title + update(book, chapter) + this.setState({ isRenamingTitle: false }) + } + + render () { + const { book, chapter, isUploadInProgress, outerContainer, remove, roles, title, type, update } = this.props + const { isRenameEmpty, isRenamingTitle } = this.state + + return ( + <span> + <ChapterTitle + chapter={chapter} + isRenaming={isRenamingTitle} + isRenameEmpty={isRenameEmpty} + isUploadInProgress={isUploadInProgress} + onClickRename={this.onClickRename} + onSaveRename={this.onSaveRename} + title={title} + type={type} + update={update} + /> + + <ChapterButtons + bookId={book.id} + chapter={chapter} + isRenaming={isRenamingTitle} + isUploadInProgress={isUploadInProgress} + modalContainer={outerContainer} + onClickRename={this.onClickRename} + onSaveRename={this.onSaveRename} + remove={remove} + roles={roles} + type={type} + update={update} + /> + </span> + ) + } +} + +ChapterFirstRow.propTypes = { + book: React.PropTypes.object.isRequired, + chapter: React.PropTypes.object.isRequired, + isUploadInProgress: 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 ChapterFirstRow diff --git a/app/components/BookBuilder/Chapter/ProgressItem.jsx b/app/components/BookBuilder/Chapter/ProgressItem.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d384dc146de899fee604738f451fa1267c043d8c --- /dev/null +++ b/app/components/BookBuilder/Chapter/ProgressItem.jsx @@ -0,0 +1,175 @@ +import { includes } from 'lodash' +import React from 'react' +import { Alert } from 'react-bootstrap' + +import ProgressModal from './ProgressModal' +import styles from '../styles/bookBuilder.local.scss' + +export class ProgressItem extends React.Component { + constructor (props) { + super(props) + + this.canChange = this.canChange.bind(this) + this.changeProgressState = this.changeProgressState.bind(this) + this.onClick = this.onClick.bind(this) + this.renderErrorMessage = this.renderErrorMessage.bind(this) + this.renderIcon = this.renderIcon.bind(this) + this.renderModal = this.renderModal.bind(this) + this.toggleModal = this.toggleModal.bind(this) + + // TODO -- move to config + this.progressValues = { + style: ['To Style', 'Styling', 'Styled'], + edit: ['To Edit', 'Editing', 'Edited'], + review: ['To Review', 'Reviewing', 'Reviewed'], + clean: ['To Clean', 'Cleaning', 'Cleaned'] + } + + this.state = { + showModal: false, + showError: false + } + } + + toggleModal () { + this.setState({ + showModal: !this.state.showModal + }) + } + + changeProgressState () { + const { chapter, update, type } = this.props + const { progressValues } = this + + const list = progressValues[type] + const len = list.length + + let position = chapter.progress[type] + position += 1 // move up a level + if (position >= len) position = 0 // or cycle back to the beginning + + chapter.progress[type] = position + update(chapter) + + this.setState({ showModal: false }) + } + + canChange () { + const { type, roles, chapter } = this.props + + if (includes(roles, 'admin') || includes(roles, 'production-editor')) return true + + const isActive = (chapter.progress[type] === 1) + + if (isActive) { + if (type === 'edit') { + if (includes(roles, 'copy-editor')) return true + } + if (type === 'review') { + if (includes(roles, 'author')) return true + } + } + + return false + } + + onClick () { + const { roles } = this.props + + if (!this.canChange()) { + this.setState({ showError: true }) + + return setTimeout(() => { + this.setState({ showError: false }) + }, 3000) + } + + if (includes(roles, 'production-editor')) { + return this.changeProgressState() + } + + this.toggleModal() + } + + renderModal () { + const { chapter, modalContainer, type } = this.props + const { showModal } = this.state + + const typesWithModal = ['edit', 'review'] + if (!includes(typesWithModal, type)) return null + + return ( + <ProgressModal + changeProgressState={this.changeProgressState} + chapter={chapter} + container={modalContainer} + show={showModal} + toggle={this.toggleModal} + /> + ) + } + + renderIcon () { + const { hasIcon } = this.props + if (!hasIcon) return null + + return (<i className='fa fa-angle-right' />) + } + + renderErrorMessage () { + const { showError } = this.state + if (!showError) return null + + return ( + <Alert + bsStyle='warning' + className={styles.noWritesError} + > + You don't have access to perfom this action. + Please contact your Production Editor. + </Alert> + ) + } + + render () { + const { type, chapter } = this.props + const { progressValues } = this + + const currentStateValue = chapter.progress[type] + const currentStateText = progressValues[type][currentStateValue] + + const errorMessage = this.renderErrorMessage() + const icon = this.renderIcon() + const warningModal = this.renderModal() + + // TODO -- find a nicer way to display the error message + + return ( + <span> + { errorMessage } + + <li + className={'progress' + currentStateValue} + onClick={this.onClick} + > + + { currentStateText } + { icon } + { warningModal } + + </li> + </span> + ) + } +} + +ProgressItem.propTypes = { + chapter: React.PropTypes.object.isRequired, + hasIcon: React.PropTypes.bool, + modalContainer: React.PropTypes.object, + roles: React.PropTypes.array.isRequired, + type: React.PropTypes.string.isRequired, + update: React.PropTypes.func.isRequired +} + +export default ProgressItem diff --git a/app/components/BookBuilder/Chapter/ProgressList.jsx b/app/components/BookBuilder/Chapter/ProgressList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f69b04be6b65cb96300984dda8790364b641d386 --- /dev/null +++ b/app/components/BookBuilder/Chapter/ProgressList.jsx @@ -0,0 +1,56 @@ +import React from 'react' + +import ProgressItem from './ProgressItem' +import styles from '../styles/bookBuilder.local.scss' + +class ProgressList extends React.Component { + render () { + const { chapter, roles, modalContainer, update } = this.props + + return ( + <ul className={styles.secondActions + ' col-lg-7 col-md-12 col-sm-12 col-xs-12'}> + <ProgressItem + chapter={chapter} + hasIcon + roles={roles} + type='style' + update={update} + /> + + <ProgressItem + chapter={chapter} + hasIcon + modalContainer={modalContainer} + roles={roles} + type='edit' + update={update} + /> + + <ProgressItem + chapter={chapter} + hasIcon + modalContainer={modalContainer} + roles={roles} + type='review' + update={update} + /> + + <ProgressItem + chapter={chapter} + roles={roles} + type='clean' + update={update} + /> + </ul> + ) + } +} + +ProgressList.propTypes = { + chapter: React.PropTypes.object.isRequired, + modalContainer: React.PropTypes.object.isRequired, + roles: React.PropTypes.array.isRequired, + update: React.PropTypes.func.isRequired +} + +export default ProgressList diff --git a/app/components/BookBuilder/Chapter/ProgressModal.jsx b/app/components/BookBuilder/Chapter/ProgressModal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d8d543c16a070c80152ad56b1aca99c68029d94b --- /dev/null +++ b/app/components/BookBuilder/Chapter/ProgressModal.jsx @@ -0,0 +1,35 @@ +import React from 'react' + +import Modal from '../../utils/Modal' + +class ProgressModal extends React.Component { + render () { + const { changeProgressState, chapter, container, show, toggle } = this.props + const type = chapter.type + + return ( + <Modal + action='workflow-warning' + chapter={chapter} + container={container} + type={type} + show={show} + successAction={changeProgressState} + successText='OK' + title='Change of workflow status' + toggle={toggle} + /> + + ) + } +} + +ProgressModal.propTypes = { + changeProgressState: React.PropTypes.func.isRequired, + chapter: React.PropTypes.object.isRequired, + container: React.PropTypes.object.isRequired, + show: React.PropTypes.bool.isRequired, + toggle: React.PropTypes.func.isRequired +} + +export default ProgressModal diff --git a/app/components/BookBuilder/Chapter/RenameEmptyError.jsx b/app/components/BookBuilder/Chapter/RenameEmptyError.jsx new file mode 100644 index 0000000000000000000000000000000000000000..822f2642b10f1192ceac6698837b590eefc81d9d --- /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 0000000000000000000000000000000000000000..f07d399c5a19f8db4c34c71c235eb126c20c1bd2 --- /dev/null +++ b/app/components/BookBuilder/Chapter/SecondRow.jsx @@ -0,0 +1,59 @@ +import React from 'react' + +import AlignmentTool from './AlignmentTool' +import ProgressList from './ProgressList' +import UploadButton from './UploadButton' + +import styles from '../styles/bookBuilder.local.scss' + +class ChapterSecondRow extends React.Component { + render () { + const { chapter, convertFile, outerContainer, roles, toggleUpload, update } = this.props + + // TODO -- surrounding divs should go inside the components + + return ( + <div className={styles.secondLineContainer}> + + <div className={styles.noPadding + ' col-lg-2 col-md-12 col-sm-12 col-xs-12'}> + <UploadButton + accept='.docx' + chapter={chapter} + convertFile={convertFile} + title=' ' + toggleUpload={toggleUpload} + type='file' + update={update} + /> + </div> + + <ProgressList + chapter={chapter} + modalContainer={outerContainer} + roles={roles} + update={update} + /> + + <div className={styles.noPadding + ' col-lg-3 col-md-12 col-sm-12 col-xs-12'}> + <AlignmentTool + chapter={chapter} + update={update} + /> + </div> + + <div className={styles.separator} /> + </div> + ) + } +} + +ChapterSecondRow.propTypes = { + chapter: React.PropTypes.object.isRequired, + convertFile: React.PropTypes.func.isRequired, + outerContainer: React.PropTypes.object.isRequired, + roles: React.PropTypes.array, + toggleUpload: React.PropTypes.func.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 0000000000000000000000000000000000000000..eb65cd74218620547bb325505327120372698983 --- /dev/null +++ b/app/components/BookBuilder/Chapter/Title.jsx @@ -0,0 +1,36 @@ +import React from 'react' + +import TextInput from '../../utils/TextInput' + +class Title extends React.Component { + 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/Chapter/UnlockModal.jsx b/app/components/BookBuilder/Chapter/UnlockModal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..03fdae44fd52a43633a3c7d92dd31578220ba64c --- /dev/null +++ b/app/components/BookBuilder/Chapter/UnlockModal.jsx @@ -0,0 +1,47 @@ +import React from 'react' + +import Modal from '../../utils/Modal' + +class UnlockModal extends React.Component { + constructor (props) { + super(props) + this.onUnlock = this.onUnlock.bind(this) + } + + onUnlock () { + const { chapter, toggle, update } = this.props + + chapter.lock = null + update(chapter) + toggle() + } + + render () { + const { chapter, container, show, toggle } = this.props + const type = chapter.type + + return ( + <Modal + title={'Unlock ' + type} + chapter={chapter} + action='unlock' + successText='Unlock' + type={type} + successAction={this.onUnlock} + show={show} + toggle={toggle} + container={container} + /> + ) + } +} + +UnlockModal.propTypes = { + chapter: React.PropTypes.object.isRequired, + container: React.PropTypes.object.isRequired, + show: React.PropTypes.bool.isRequired, + toggle: React.PropTypes.func.isRequired, + update: React.PropTypes.func.isRequired +} + +export default UnlockModal diff --git a/app/components/BookBuilder/Chapter/UploadButton.jsx b/app/components/BookBuilder/Chapter/UploadButton.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b0b3b59150c3d50f21b086eafcd6a9a3910be658 --- /dev/null +++ b/app/components/BookBuilder/Chapter/UploadButton.jsx @@ -0,0 +1,68 @@ +import React from 'react' +import styles from '../styles/bookBuilder.local.scss' + +export class UploadButton extends React.Component { + constructor (props) { + super(props) + this.handleFileUpload = this.handleFileUpload.bind(this) + } + + handleFileUpload (event) { + event.preventDefault() + + const file = event.target.files[0] + const { + chapter, + convertFile, + toggleUpload, + update + } = this.props + + toggleUpload() + + convertFile(file).then(response => { + // console.log('response', response) + chapter.source = response.converted + update(chapter) + toggleUpload() + }).catch((e) => { + // console.log('error', e) + toggleUpload() + }) + } + + render () { + const { accept, title, type } = this.props + + const input = ( + <input + accept={accept} + onChange={this.handleFileUpload} + title={title} + type={type} + /> + ) + + return ( + <div + className={styles.btnFile} + id='bb-upload' + > + Upload Word + { input } + </div> + ) + } +} + +UploadButton.propTypes = { + accept: React.PropTypes.string.isRequired, + chapter: React.PropTypes.object.isRequired, + convertFile: React.PropTypes.func.isRequired, + title: React.PropTypes.string.isRequired, + toggleUpload: React.PropTypes.func.isRequired, + type: React.PropTypes.string.isRequired, + update: React.PropTypes.func.isRequired +} + +export default UploadButton diff --git a/app/components/BookBuilder/Division.jsx b/app/components/BookBuilder/Division.jsx index ece9cbadbd88459bc2df55baaa1daca6e262c42f..97238b878460ec4b606817afd5962cc5fb39b380 100644 --- a/app/components/BookBuilder/Division.jsx +++ b/app/components/BookBuilder/Division.jsx @@ -104,7 +104,7 @@ export class Division extends React.Component { } render () { - const { book, title, type, chapters, update, roles, outerContainer } = this.props + const { book, chapters, ink, outerContainer, roles, title, type, update } = this.props const { _onAddClick, _onRemove, _onMove } = this const chapterType = (type === 'body') ? 'chapter' : 'component' @@ -114,16 +114,17 @@ export class Division extends React.Component { <Chapter book={book} chapter={c} - type={c.subCategory} - title={c.title} - remove={_onRemove} - update={update} - move={_onMove} - key={c.index} id={c.id} + ink={ink} + key={c.index} + move={_onMove} no={i} outerContainer={outerContainer} + remove={_onRemove} roles={roles} + title={c.title} + type={c.subCategory} + update={update} /> ) }) @@ -133,11 +134,11 @@ export class Division extends React.Component { addButtons = ( <span> <AddButton - group="chapter" + group='chapter' add={_onAddClick} /> <AddButton - group="part" + group='part' add={_onAddClick} /> </span> @@ -145,7 +146,7 @@ export class Division extends React.Component { } else { addButtons = ( <AddButton - group="component" + group='component' add={_onAddClick} /> ) @@ -177,7 +178,7 @@ export class Division extends React.Component { <div className={styles.separator} /> </div> - <div id="displayed"> + <div id='displayed'> { displayed } </div> </div> @@ -186,16 +187,16 @@ export class Division extends React.Component { } Division.propTypes = { + add: React.PropTypes.func.isRequired, book: React.PropTypes.object.isRequired, chapters: React.PropTypes.array.isRequired, - title: React.PropTypes.string.isRequired, - type: React.PropTypes.string.isRequired, - add: React.PropTypes.func.isRequired, + ink: React.PropTypes.func.isRequired, + outerContainer: React.PropTypes.object.isRequired, remove: React.PropTypes.func.isRequired, - update: React.PropTypes.func.isRequired, - // roles: React.PropTypes.array.isRequired, roles: React.PropTypes.array, - outerContainer: React.PropTypes.object.isRequired + title: React.PropTypes.string.isRequired, + type: React.PropTypes.string.isRequired, + update: React.PropTypes.func.isRequired } export default DragDropContext(HTML5Backend)(Division) diff --git a/app/components/BookBuilder/PagePositionAlignment.jsx b/app/components/BookBuilder/PagePositionAlignment.jsx deleted file mode 100644 index e84f038ed5689ab15f7c746d88148db96e55e290..0000000000000000000000000000000000000000 --- a/app/components/BookBuilder/PagePositionAlignment.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react' -import styles from './styles/bookBuilder.local.scss' -import { includes } from 'lodash' - -export class PagePositionAlignment extends React.Component { - constructor (props) { - super(props) - this._onClickAlignment = this._onClickAlignment.bind(this) - } - - _onClickAlignment (position) { - const { chapter, update } = this.props - if (!includes(['left', 'right'], position)) { return } - - function clickAlignment () { - chapter.alignment[position] = !chapter.alignment[position] - update(chapter) - } - - return clickAlignment - } - - render () { - const { chapter } = this.props - const clickAlignmentLeft = this._onClickAlignment('left') - const clickAlignmentRight = this._onClickAlignment('right') - const align = chapter.alignment - - return ( - <ul className={styles.pagePosition}> - <li>left </li> - - <li onClick={clickAlignmentLeft}> - <div className={styles.leftRightBox + ' ' + styles.leftBox}> - <div className={align.left ? styles.boxActive : styles.boxInactiveHover} /> - </div> - </li> - - <li onClick={clickAlignmentRight}> - <div className={styles.leftRightBox + ' ' + styles.rightBox}> - <div className={align.right ? styles.boxActive : styles.boxInactiveHover} /> - </div> - </li> - - <li> - <div className={styles.boxDiver} /> - </li> - - <li> right</li> - </ul> - ) - } -} - -PagePositionAlignment.propTypes = { - chapter: React.PropTypes.object.isRequired, - update: React.PropTypes.func.isRequired -} - -export default PagePositionAlignment diff --git a/app/components/BookBuilder/ProgressIndicator.jsx b/app/components/BookBuilder/ProgressIndicator.jsx deleted file mode 100644 index 154088acdde121c973256b5c704db31f353b9b94..0000000000000000000000000000000000000000 --- a/app/components/BookBuilder/ProgressIndicator.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react' -import { includes } from 'lodash' -import { Alert } from 'react-bootstrap' -import BookBuilderModal from './BookBuilderModal' -import styles from './styles/bookBuilder.local.scss' - -export class ProgressIndicator extends React.Component { - constructor (props) { - super(props) - - this._isAllowedToChange = this._isAllowedToChange.bind(this) - this._onClick = this._onClick.bind(this) - this._toggleModal = this._toggleModal.bind(this) - this._changeWorkflowState = this._changeWorkflowState.bind(this) - - this.progressValues = { - style: ['To Style', 'Styling', 'Styled'], - edit: ['To Edit', 'Editing', 'Edited'], - review: ['To Review', 'Reviewing', 'Reviewed'], - clean: ['To Clean', 'Cleaning', 'Cleaned'] - } - - this.state = { - showWarning: false, - showError: false - } - } - - _isAllowedToChange () { - const { type, roles, chapter } = this.props - - if (includes(roles, 'production-editor')) return true - - const isOne = (chapter.progress[type] === 1) - - if (chapter.progress[type] === 1 && type === 'edit' && includes(roles, 'copy-editor')) return true - if (isOne && type === 'review' && includes(roles, 'author')) return true - - return false - } - - _onClick () { - const { roles } = this.props - if (!this._isAllowedToChange()) { - this.setState({ showError: true }) - setTimeout(() => { this.setState({showError: false}) }, 3000) - return - } else if (includes(roles, 'production-editor')) { - this._changeWorkflowState() - } else { - this._toggleModal() - } - } - - _toggleModal () { - this.setState({ showWarning: !this.state.showWarning }) - } - - _changeWorkflowState () { - const { chapter, update, type, viewOrEdit } = this.props - const { progressValues } = this - - const list = progressValues[type] - - let position = chapter.progress[type] - position += 1 // move up a level - - if (position >= list.length) { - position = 0 // or cycle back to the beginning - } - - chapter.progress[type] = position - - update(chapter) - this.setState({ showWarning: false }) - viewOrEdit() - } - - render () { - const { type, chapter, hasIcon, outerContainer } = this.props - const { progressValues } = this - - let ErrorMsg = this.state.showError - ? ( - <Alert bsStyle='warning' className={styles.noWritesError}> - You don't have access to perfom this action. Please contact your Production Editor. - </Alert> - ) - : null - - let icon = '' - if (hasIcon) { - icon = <i className='fa fa-angle-right' /> - } - - const warningModal = ( - <BookBuilderModal - title='Change of workflow status' - action='workflow-warning' - successText='OK' - successAction={this._changeWorkflowState} - show={this.state.showWarning} - toggle={this._toggleModal} - container={outerContainer} - /> - ) - - return ( - <span> - { ErrorMsg } - <li className={'progress' + chapter.progress[type]} - onClick={this._onClick}> - - { progressValues[type][chapter.progress[type]] } - { icon } - { warningModal } - - </li> - </span> - ) - } -} - -ProgressIndicator.propTypes = { - type: React.PropTypes.string.isRequired, - chapter: React.PropTypes.object.isRequired, - hasIcon: React.PropTypes.bool, - update: React.PropTypes.func.isRequired, - roles: React.PropTypes.array.isRequired, - outerContainer: React.PropTypes.object.isRequired, - viewOrEdit: React.PropTypes.func -} - -export default ProgressIndicator diff --git a/app/components/BookBuilder/UploadWordBtn.jsx b/app/components/BookBuilder/UploadWordBtn.jsx deleted file mode 100644 index 448f1d6b8f3bab67fbe4b5c09b8921dbbb592e06..0000000000000000000000000000000000000000 --- a/app/components/BookBuilder/UploadWordBtn.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import styles from './styles/bookBuilder.local.scss' - -export class UploadWordButton extends React.Component { - - render () { - const { type, accept, title } = this.props - - return ( - <div id='bb-upload' className={styles.btnFile}> - Upload Word - <input - type={type} - accept={accept} - title={title} - /> - </div> - ) - } -} - -UploadWordButton.propTypes = { - type: React.PropTypes.string.isRequired, - accept: React.PropTypes.string.isRequired, - title: React.PropTypes.string.isRequired -} - -export default UploadWordButton diff --git a/app/components/BookBuilder/styles/_animate.scss b/app/components/BookBuilder/styles/_animate.scss new file mode 100644 index 0000000000000000000000000000000000000000..a85b7a9ba8f2cc2a2f26f7ee3370cc77abbb1ce1 --- /dev/null +++ b/app/components/BookBuilder/styles/_animate.scss @@ -0,0 +1,30 @@ +@keyframes flickerAnimation { + 0% { opacity: 1; } + 50% { opacity: 0; } + 100% { opacity: 1; } +} + +@-o-keyframes flickerAnimation { + 0% { opacity: 1; } + 50% { opacity: 0; } + 100% { opacity: 1; } +} + +@-moz-keyframes flickerAnimation { + 0% { opacity: 1; } + 50% { opacity: 0; } + 100% { opacity: 1; } +} + +@-webkit-keyframes flickerAnimation { + 0% { opacity: 1; } + 50% { opacity: 0; } + 100% { opacity: 1; } +} + +.animate-flicker { + -webkit-animation: flickerAnimation 2s infinite; + -moz-animation: flickerAnimation 2s infinite; + -o-animation: flickerAnimation 2s infinite; + animation: flickerAnimation 2s infinite; +} diff --git a/app/components/BookBuilder/styles/bookBuilder.local.scss b/app/components/BookBuilder/styles/bookBuilder.local.scss index 0f75b4f25663322a0c1b33c8ef066f6cee25236b..59fd61ff481d4b289e31641f8477e4cc2d58984f 100644 --- a/app/components/BookBuilder/styles/bookBuilder.local.scss +++ b/app/components/BookBuilder/styles/bookBuilder.local.scss @@ -5,6 +5,8 @@ $white: #fff; .bookBuilder { + @import 'animate'; + counter-reset: chapter; font-family: 'Fira Sans'; diff --git a/app/components/SimpleEditor/SimpleEditorWrapper.jsx b/app/components/SimpleEditor/SimpleEditorWrapper.jsx index 05a0989f3d457ce5b5f6e0aee49d6812caffd0e2..08233a0d6da01d700bb47947b0725c5766a3ee57 100644 --- a/app/components/SimpleEditor/SimpleEditorWrapper.jsx +++ b/app/components/SimpleEditor/SimpleEditorWrapper.jsx @@ -100,6 +100,7 @@ export class SimpleEditorWrapper extends React.Component { render () { const { book, fragment, history, user } = this.props + // console.log(fragment) return ( <SimpleEditor diff --git a/app/components/utils/DnD.js b/app/components/utils/DnD.js new file mode 100644 index 0000000000000000000000000000000000000000..782f3c2d3cfcf359b740303cbec6f3b37dfff13a --- /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/BookBuilder/BookBuilderModal.jsx b/app/components/utils/Modal.jsx similarity index 89% rename from app/components/BookBuilder/BookBuilderModal.jsx rename to app/components/utils/Modal.jsx index 0a2a649b539fb72953c265d0d3518d5252d15331..82162fe3c2bb771fded56df3b5731eca2586db68 100644 --- a/app/components/BookBuilder/BookBuilderModal.jsx +++ b/app/components/utils/Modal.jsx @@ -1,21 +1,21 @@ import React from 'react' import { Modal } from 'react-bootstrap' -import TeamManager from './TeamManager/TeamManager' +import TeamManager from '../BookBuilder/TeamManager/TeamManager' export class BookBuilderModal extends React.Component { render () { const { - title, - show, - toggle, - successText, - successAction, - container, - chapter, - type, action, + chapter, + container, size, + show, + successAction, + successText, teams, + title, + toggle, + type, users, updateTeam } = this.props @@ -28,6 +28,7 @@ export class BookBuilderModal extends React.Component { { successText } </a> : null + // TODO -- move to individual modal components if (action === 'delete') { modalBodyText = ( <div> @@ -43,6 +44,7 @@ export class BookBuilderModal extends React.Component { </div> ) } else if (action === 'EditoriaTeamManager') { + // TODO -- ESPECIALLY THIS ONE modalBodyText = ( <div> <TeamManager @@ -55,7 +57,7 @@ export class BookBuilderModal extends React.Component { } else if (action === 'workflow-warning') { modalBodyText = ( <div> - This action will revoke your access to this fragment. + This action will revoke your write access to this fragment. <br /> Are you sure you want to continue? </div> @@ -100,19 +102,19 @@ export class BookBuilderModal extends React.Component { } BookBuilderModal.propTypes = { - chapter: React.PropTypes.object, - title: React.PropTypes.string.isRequired, action: React.PropTypes.string.isRequired, - type: React.PropTypes.string, - successText: React.PropTypes.string, - successAction: React.PropTypes.func, - show: React.PropTypes.bool.isRequired, - toggle: React.PropTypes.func.isRequired, + chapter: React.PropTypes.object, container: React.PropTypes.object.isRequired, + show: React.PropTypes.bool.isRequired, size: React.PropTypes.string, + successAction: React.PropTypes.func, + successText: React.PropTypes.string, teams: React.PropTypes.array, - users: React.PropTypes.array, - updateTeam: React.PropTypes.func + title: React.PropTypes.string.isRequired, + toggle: React.PropTypes.func.isRequired, + type: React.PropTypes.string, + updateTeam: React.PropTypes.func, + users: React.PropTypes.array } export default BookBuilderModal diff --git a/app/components/utils/config.js b/app/components/utils/config.js new file mode 100644 index 0000000000000000000000000000000000000000..59ce4b32dfc1034030075c5896532d7a79926924 --- /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/app/components/utils/noop.js b/app/components/utils/noop.js new file mode 100644 index 0000000000000000000000000000000000000000..9b98d9cfac1f3db00f9834ac85738cc0974cebe4 --- /dev/null +++ b/app/components/utils/noop.js @@ -0,0 +1,3 @@ +const noop = () => {} + +export default noop diff --git a/config/dev.js b/config/dev.js index 6fa983e0eb913974673d4d301709585840bdb3b5..8f9fe04658aaebcb1efe93e9d85e772fe33958bc 100644 --- a/config/dev.js +++ b/config/dev.js @@ -3,21 +3,22 @@ const path = require('path') const universal = require('./universal') module.exports = { + authsome: { + mode: universal.mode, + teams: universal.teams + }, + pubsweet: { + components: universal.components + }, 'pubsweet-backend': { dbPath: path.join(__dirname, '..', 'api', 'db'), secret: 'c0d9a89b-fcce-49b3-83bc-0ccf3326d0ef', API_ENDPOINT: '/api' }, + 'pubsweet-component-ink-backend': universal.inkBackend, 'pubsweet-frontend': { theme: universal.theme, routes: 'app/routes.jsx', navigation: 'app/components/Navigation/Navigation.jsx' - }, - authsome: { - mode: universal.mode, - teams: universal.teams - }, - pubsweet: { - components: ['pubsweet-component-signup', 'pubsweet-component-login'] } } diff --git a/config/production.js b/config/production.js index da468a6a91cfc2927b020cc7d23393a4f5a46a0f..0d1a71d53bb45803fe8492eadef21fa5698a2883 100644 --- a/config/production.js +++ b/config/production.js @@ -3,21 +3,22 @@ const path = require('path') const universal = require('./universal') module.exports = { + authsome: { + mode: universal.mode, + teams: universal.teams + }, + pubsweet: { + components: universal.components + }, 'pubsweet-backend': { dbPath: path.join(__dirname, '..', 'api', 'db'), secret: '71dcce42-2245-4944-925b-0a62b83425ce', API_ENDPOINT: '/api' }, + 'pubsweet-component-ink-backend': universal.inkBackend, 'pubsweet-frontend': { theme: universal.theme, routes: 'app/routes.jsx', navigation: 'app/components/Navigation/Navigation.jsx' - }, - authsome: { - mode: universal.mode, - teams: universal.teams - }, - pubsweet: { - components: ['pubsweet-component-signup', 'pubsweet-component-login'] } } diff --git a/config/universal.js b/config/universal.js index 3575878a5305114a30ccb0cdfd5714847d09b7b7..392ae36c1ac5b88aadbe7c3f2e95eb5ea8a29f97 100644 --- a/config/universal.js +++ b/config/universal.js @@ -1,6 +1,17 @@ const editoriaMode = require('../app/authsome_editoria') module.exports = { + components: [ + 'pubsweet-component-ink-backend', + 'pubsweet-component-ink-frontend', + 'pubsweet-component-login', + 'pubsweet-component-signup' + ], + inkBackend: { + inkEndpoint: 'http://ink-api.coko.foundation', + email: 'editoria@coko.foundation', + password: 'editoria' + }, mode: editoriaMode, teams: { teamProduction: { diff --git a/package.json b/package.json index a3ee3a69a7a205438ae3d5f07328215f0e02789b..467f8d9858d1ad99f9eb0c04e3fd36b6b46b3096 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "babel-preset-react": "^6.11.1", "babel-preset-stage-2": "^6.13.0", "bootstrap-sass": "^3.3.7", + "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.25.0", "eslint": "^3.6.0", "eslint-config-standard": "^6.2.0", @@ -26,6 +27,8 @@ "json-loader": "^0.5.4", "pubsweet-backend": "^0.5.0", "pubsweet-component-blog": "^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", @@ -33,7 +36,7 @@ "pubsweet-component-teams-manager": "^0.1.1", "pubsweet-component-theme-editoria": "git+https://gitlab.coko.foundation/yannisbarlas/pubsweet-component-theme-editoria.git", "pubsweet-component-users-manager": "^0.1.0", - "pubsweet-frontend": "^0.6.0", + "pubsweet-frontend": "^0.6.2", "pubsweet-theme-plugin": "^0.0.1", "react-dnd": "^2.1.4", "react-dnd-html5-backend": "^2.1.2", diff --git a/static/pubsweet-rgb-small.jpg b/static/pubsweet-rgb-small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..57efdcb962ffac6c6ce60777af38e62d4f872c87 Binary files /dev/null and b/static/pubsweet-rgb-small.jpg differ diff --git a/static/pubsweet.jpg b/static/pubsweet.jpg new file mode 100644 index 0000000000000000000000000000000000000000..57efdcb962ffac6c6ce60777af38e62d4f872c87 Binary files /dev/null and b/static/pubsweet.jpg differ diff --git a/webpack/babel-includes.js b/webpack/babel-includes.js index 709ed365ac32d83ce5f5d6154fc944237538ed6d..d6fb06b9c6148cc61f0e63a9d9b89a3ad9e2667c 100644 --- a/webpack/babel-includes.js +++ b/webpack/babel-includes.js @@ -1,5 +1,5 @@ const path = require('path') -const fs = require('fs') +// const fs = require('fs') var babelIncludes = [ new RegExp(path.join(__dirname, '../node_modules/pubsweet-frontend/src')), @@ -7,15 +7,16 @@ var babelIncludes = [ new RegExp(path.join(__dirname, '../node_modules/pubsweet-.*')) ] -let componentsDir = path.resolve(__dirname, '..', 'node_modules') -let componentsRegex = new RegExp(path.join(__dirname, '../node_modules/pubsweet-.*')) +// let componentsDir = path.resolve(__dirname, '..', 'node_modules') +// let componentsRegex = new RegExp(path.join(__dirname, '../node_modules/pubsweet-.*')) +// +// let symlinkedComponents = fs.readdirSync(componentsDir).map(file => { +// return path.resolve(componentsDir, file) +// }).filter(file => { +// return fs.lstatSync(file).isSymbolicLink() && componentsRegex.test(file) +// }).map(function (componentSymlink) { +// return new RegExp(path.join(fs.realpathSync(componentSymlink), '(?!node_modules)')) +// }) -let symlinkedComponents = fs.readdirSync(componentsDir).map(file => { - return path.resolve(componentsDir, file) -}).filter(file => { - return fs.lstatSync(file).isSymbolicLink() && componentsRegex.test(file) -}).map(function (componentSymlink) { - return new RegExp(path.join(fs.realpathSync(componentSymlink), '(?!node_modules)')) -}) - -module.exports = babelIncludes.concat(symlinkedComponents) +// module.exports = babelIncludes.concat(symlinkedComponents) +module.exports = babelIncludes diff --git a/webpack/common-rules.js b/webpack/common-rules.js index 79ed8406326b38ff40c04f61d1799c4b981df68d..60b40180b34325d84969f5638a492166f75722fb 100644 --- a/webpack/common-rules.js +++ b/webpack/common-rules.js @@ -12,6 +12,7 @@ const resolve = (type, entry) => { const resolvePreset = entry => resolve('preset', entry) const resolvePlugin = entry => resolve('plugin', entry) +const frontendComponents = config.pubsweet.components.filter(name => require(name).frontend) module.exports = [ { @@ -88,7 +89,7 @@ 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 } diff --git a/webpack/webpack.dev.config.js b/webpack/webpack.dev.config.js index 193f806f4581a5cfddf3afda814b54d912f16cd3..842a8b598b2457da24c488b4ed748724d44502b0 100644 --- a/webpack/webpack.dev.config.js +++ b/webpack/webpack.dev.config.js @@ -2,6 +2,7 @@ var path = require('path') var webpack = require('webpack') var ThemePlugin = require('pubsweet-theme-plugin') var config = require('../config/dev') +var CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = [ { @@ -46,7 +47,10 @@ module.exports = [ }), new webpack.ProvidePlugin({ 'CONFIG': path.resolve(__dirname, '..', 'config', 'dev.js') - }) + }), + new CopyWebpackPlugin([ + { from: '../static' } + ]) ], node: { fs: 'empty',