diff --git a/package.json b/package.json index 10b54898..1b1d9b48 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,13 @@ "version": "0.1.0", "private": true, "dependencies": { + "draft-js": "^0.10.5", + "draft-js-autolist-plugin": "^2.0.0", + "draft-js-markdown-plugin": "^3.0.0", + "draft-js-plugins-editor": "^2.0.8", "glamor": "^2.20.40", + "markdown-draft-js": "^1.0.1", + "prismjs": "^1.15.0", "react": "^15.6.1", "react-animated-number": "^0.4.3", "react-big-calendar": "^0.19.0", diff --git a/src/components/Content/Calendar/Calendar.js b/src/components/Content/Calendar/Calendar.js index 2f66f071..ab5efb06 100644 --- a/src/components/Content/Calendar/Calendar.js +++ b/src/components/Content/Calendar/Calendar.js @@ -1,11 +1,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import moment from 'moment'; -import { - getTasks, - getPursuances, - rpShowTaskDetails, -} from '../../../actions'; +import {getTasks, getPursuances, rpShowTaskDetails} from '../../../actions'; import BigCalendar from 'react-big-calendar'; import 'react-big-calendar/lib/css/react-big-calendar.css'; import './Calendar.css'; @@ -14,7 +10,6 @@ import '../Content.css'; BigCalendar.momentLocalizer(moment); class Calendar extends Component { - componentDidMount() { const { getPursuances, @@ -44,8 +39,7 @@ class Calendar extends Component { t.due_date && t.status !== 'Done' && t.assigned_to === user.username && - (t.pursuance_id === currentPursuanceId || - t.assigned_to_pursuance_id === currentPursuanceId) + (t.pursuance_id === currentPursuanceId || t.assigned_to_pursuance_id === currentPursuanceId) }) .map((gid) => { const t = taskMap[gid]; @@ -99,5 +93,5 @@ export default connect(({ pursuances, currentPursuanceId, tasks, rightPanel, use ({ pursuances, currentPursuanceId, tasks, rightPanel, user }), { getTasks, getPursuances, - rpShowTaskDetails, + rpShowTaskDetails })(Calendar); diff --git a/src/components/Content/RightPanel/TaskDetails/TaskDetails.css b/src/components/Content/RightPanel/TaskDetails/TaskDetails.css index f79227fd..264c4b73 100644 --- a/src/components/Content/RightPanel/TaskDetails/TaskDetails.css +++ b/src/components/Content/RightPanel/TaskDetails/TaskDetails.css @@ -109,7 +109,7 @@ .subtasks-list { list-style: none; - padding-left: 0 + padding-left: 0; } .subtask-item { diff --git a/src/components/Content/RightPanel/TaskDetails/TaskDetails.js b/src/components/Content/RightPanel/TaskDetails/TaskDetails.js index 0c5b3ba6..3b980aba 100644 --- a/src/components/Content/RightPanel/TaskDetails/TaskDetails.js +++ b/src/components/Content/RightPanel/TaskDetails/TaskDetails.js @@ -3,17 +3,16 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { showTaskInPursuance } from '../../../../utils/tasks'; import { getPursuances, getTasks, getUsers, rpShowTaskDetails, patchTask } from '../../../../actions'; -import ReactMarkdown from 'react-markdown'; import FaCircleO from 'react-icons/lib/fa/circle-o'; import TaskDetailsTopbar from './TaskDetailsTopbar'; import TaskTitle from './TaskTitle/TaskTitle'; import TaskIcons from './TaskIcons/TaskIcons'; import TaskForm from '../../TaskManager/TaskForm/TaskForm'; +import Wysiwyg from './Wysiwyg/Wysiwyg'; import './TaskDetails.css'; class TaskDetails extends Component { - componentWillMount() { const { currentPursuanceId, @@ -28,8 +27,7 @@ class TaskDetails extends Component { // If this task was assigned to this pursuance from another // pursuance, grab the current pursuance's tasks, too - if (currentPursuanceId && - thisTasksPursuanceId !== currentPursuanceId.toString()) { + if (currentPursuanceId && thisTasksPursuanceId !== currentPursuanceId.toString()) { getTasks(currentPursuanceId); } } @@ -93,17 +91,7 @@ class TaskDetails extends Component { </div> <div className="task-deliverables-ctn"> <h4><strong>Description / Deliverables</strong></h4> - <span> - <ReactMarkdown - source={task.deliverables} - render={{Link: props => { - if (props.href.startsWith('/')) { - return <a href={props.href}>{props.children}</a>; - } - // If link to external site, open in new tab - return <a href={props.href} target="_blank">{props.children}</a>; - }}} /> - </span> + <Wysiwyg taskGid={taskGid} attributeName='deliverables' patchTask={this.props.patchTask} /> </div> <div className="subtasks-ctn"> <h4><strong>Subtasks</strong></h4> @@ -129,5 +117,7 @@ class TaskDetails extends Component { }; } -export default withRouter(connect(({currentPursuanceId, pursuances, tasks, rightPanel, users}) => ({currentPursuanceId, pursuances, tasks, rightPanel, users}), - { getPursuances, getTasks, getUsers, rpShowTaskDetails, patchTask })(TaskDetails)); +export default withRouter(connect( + ({currentPursuanceId, pursuances, tasks, rightPanel, users}) => ({currentPursuanceId, pursuances, tasks, rightPanel, users}), + {getPursuances, getTasks, getUsers, rpShowTaskDetails, patchTask} +)(TaskDetails)); diff --git a/src/components/Content/RightPanel/TaskDetails/Wysiwyg/Wysiwyg.css b/src/components/Content/RightPanel/TaskDetails/Wysiwyg/Wysiwyg.css new file mode 100644 index 00000000..f060e287 --- /dev/null +++ b/src/components/Content/RightPanel/TaskDetails/Wysiwyg/Wysiwyg.css @@ -0,0 +1,21 @@ +.wysiwyg { + box-sizing: border-box; + border: 1px solid #ccc; + cursor: text; + padding: 16px; + border-radius: 5px; + margin: 20px 0; + box-shadow: inset 0px 1px 8px 3px #ABABAB; + background: #fefefe; + color: black; +} + +.wysiwyg :global(.public-DraftEditor-content) { + min-height: 140px; +} + +.wysiwyg-save, +.wysiwyg-edit { + border-radius: 5px; + background-color: #000; +} \ No newline at end of file diff --git a/src/components/Content/RightPanel/TaskDetails/Wysiwyg/Wysiwyg.js b/src/components/Content/RightPanel/TaskDetails/Wysiwyg/Wysiwyg.js new file mode 100644 index 00000000..50511401 --- /dev/null +++ b/src/components/Content/RightPanel/TaskDetails/Wysiwyg/Wysiwyg.js @@ -0,0 +1,103 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {EditorState, convertToRaw, convertFromRaw} from 'draft-js'; +import Editor from 'draft-js-plugins-editor'; +import createAutoListPlugin from 'draft-js-autolist-plugin' +import createMarkdownPlugin from 'draft-js-markdown-plugin'; +import {markdownToDraft, draftToMarkdown} from 'markdown-draft-js'; +import ReactMarkdown from 'react-markdown'; +import './Wysiwyg.css'; + +const autoListPlugin = createAutoListPlugin(); + +const plugins = [ + autoListPlugin, + createMarkdownPlugin() +]; + +class Wysiwyg extends Component { + componentWillMount() { + const {tasks: { taskMap }, taskGid, attributeName} = this.props, + attributeValue = taskMap[taskGid][attributeName], + content = attributeValue ? markdownToDraft(attributeValue, { + remarkableOptions: { + html: false, + preserveNewlines: true + } + }) : markdownToDraft(''); + + this.state = { + editMode: false, + editorState: EditorState.createWithContent(convertFromRaw(content)) + }; + } + + editModeEnable = () => { + this.setState({ + editMode: true + }); + }; + + onChange = (editorState) => { + this.setState({ + editorState + }); + }; + + save = () => { + const {patchTask} = this.props, + markdown = draftToMarkdown(convertToRaw(this.state.editorState.getCurrentContent())), + payload = {gid: this.props.taskGid}; + + payload[this.props.attributeName] = markdown; + + patchTask(payload); + + this.setState({ + editMode: false + }); + } + + render() { + const {tasks: {taskMap}} = this.props, + attributeValue = taskMap[this.props.taskGid][this.props.attributeName]; + + return ( + <div> + { + this.state.editMode && ( + <div> + <div className='wysiwyg'> + <Editor + editorState={this.state.editorState} + onChange={this.onChange} + plugins={plugins} + /> + </div> + <button className='wysiwyg-save' + onClick={this.save}>Save</button> + </div> + ) + } + { + !this.state.editMode && ( + <div> + <ReactMarkdown + source={attributeValue} + render={{Link: props => { + if (props.href.startsWith('/')) { + return <a href={props.href}>{props.children}</a>; + } + // If link to external site, open in new tab + return <a href={props.href} target="_blank">{props.children}</a>; + }}} /> + <button className='wysiwyg-edit' onClick={this.editModeEnable}>Edit</button> + </div> + ) + } + </div> + ); + } +} + +export default connect(({tasks}) => ({tasks}), null)(Wysiwyg);