Thread.jsx 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import React from 'react'
  2. import i18n from '../i18n.js'
  3. import { debug } from '../helper.js'
  4. import {
  5. handleFetchResult,
  6. PopinFixed,
  7. PopinFixedHeader,
  8. PopinFixedOption,
  9. PopinFixedContent,
  10. Timeline,
  11. SelectStatus,
  12. ArchiveDeleteContent
  13. } from 'tracim_lib'
  14. import {
  15. getThreadContent,
  16. getThreadComment,
  17. postThreadNewComment,
  18. putThreadStatus
  19. } from '../action.async.js'
  20. class Thread extends React.Component {
  21. constructor (props) {
  22. super(props)
  23. this.state = {
  24. appName: 'thread',
  25. isVisible: true,
  26. config: props.data ? props.data.config : debug.config,
  27. loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
  28. content: props.data ? props.data.content : debug.content,
  29. listMessage: props.data ? [] : [], // debug.listMessage,
  30. newComment: '',
  31. timelineWysiwyg: false
  32. }
  33. document.addEventListener('appCustomEvent', this.customEventReducer)
  34. }
  35. customEventReducer = ({ detail: action }) => { // action: { type: '', data: {} }
  36. switch (action.type) {
  37. case 'Thread_showApp':
  38. this.setState({isVisible: true})
  39. break
  40. case 'Thread_hideApp':
  41. this.setState({isVisible: false})
  42. break
  43. }
  44. }
  45. componentDidMount () {
  46. console.log('Thread did Mount')
  47. this.loadContent()
  48. }
  49. componentDidUpdate (prevProps, prevState) {
  50. const { state } = this
  51. console.log('Thread did Update', prevState, state)
  52. if (!prevState.content || !state.content) return
  53. if (prevState.content.content_id !== state.content.content_id) this.loadContent()
  54. if (!prevState.timelineWysiwyg && state.timelineWysiwyg) wysiwyg('#wysiwygTimelineComment', this.handleChangeNewComment)
  55. else if (prevState.timelineWysiwyg && !state.timelineWysiwyg) tinymce.remove('#wysiwygTimelineComment')
  56. }
  57. loadContent = async () => {
  58. const { content, config } = this.state
  59. if (content.content_id === '-1') return // debug case
  60. const fetchResultThread = getThreadContent(config.apiUrl, content.workspace_id, content.content_id)
  61. const fetchResultThreadComment = getThreadComment(config.apiUrl, content.workspace_id, content.content_id)
  62. Promise.all([
  63. handleFetchResult(await fetchResultThread),
  64. handleFetchResult(await fetchResultThreadComment)
  65. ])
  66. .then(([resThread, resComment]) => this.setState({
  67. content: resThread.body,
  68. listMessage: resComment.body.map(c => ({
  69. ...c,
  70. timelineType: 'comment',
  71. created: (new Date(c.created)).toLocaleString()
  72. }))
  73. }))
  74. .catch(e => console.log('Error loading Thread data.', e))
  75. }
  76. handleClickBtnCloseApp = () => {
  77. this.setState({ isVisible: false })
  78. GLOBAL_dispatchEvent({type: 'appClosed', data: {}}) // handled by tracim_front::src/container/WorkspaceContent.jsx
  79. }
  80. handleChangeNewComment = e => {
  81. const newComment = e.target.value
  82. this.setState({newComment})
  83. }
  84. handleClickValidateNewCommentBtn = async () => {
  85. const { config, content, newComment } = this.state
  86. const fetchResultSaveNewComment = await postThreadNewComment(config.apiUrl, content.workspace_id, content.content_id, newComment)
  87. handleFetchResult(await fetchResultSaveNewComment)
  88. .then(resSave => {
  89. if (resSave.apiResponse.status === 200) {
  90. this.setState({newComment: ''})
  91. if (this.state.timelineWysiwyg) tinymce.get('wysiwygTimelineComment').setContent('')
  92. this.loadContent()
  93. } else {
  94. console.warn('Error saving thread comment. Result:', resSave, 'content:', content, 'config:', config)
  95. }
  96. })
  97. }
  98. handleToggleWysiwyg = () => this.setState(prev => ({timelineWysiwyg: !prev.timelineWysiwyg}))
  99. handleChangeStatus = async newStatus => {
  100. const { config, content } = this.state
  101. const fetchResultSaveEditStatus = putThreadStatus(config.apiUrl, content.workspace_id, content.content_id, newStatus)
  102. handleFetchResult(await fetchResultSaveEditStatus)
  103. .then(resSave => {
  104. if (resSave.apiResponse.status !== 204) { // 204 no content so dont take status from resSave.apiResponse.status
  105. console.warn('Error saving thread comment. Result:', resSave, 'content:', content, 'config:', config)
  106. } else {
  107. this.loadContent()
  108. }
  109. })
  110. }
  111. handleClickArchive = () => console.log('archive nyi')
  112. handleClickDelete = () => console.log('delete nyi')
  113. render () {
  114. const { config, isVisible, loggedUser, content, listMessage, newComment, timelineWysiwyg } = this.state
  115. if (!isVisible) return null
  116. return (
  117. <PopinFixed customClass={`wsContentThread`}>
  118. <PopinFixedHeader
  119. customClass={`wsContentThread`}
  120. faIcon={config.faIcon}
  121. title={content.label}
  122. onClickCloseBtn={this.handleClickBtnCloseApp}
  123. />
  124. <PopinFixedOption customClass={`wsContentThread`} i18n={i18n}>
  125. <div className='justify-content-end'>
  126. <SelectStatus
  127. selectedStatus={config.availableStatuses.find(s => s.slug === content.status)}
  128. availableStatus={config.availableStatuses}
  129. onChangeStatus={this.handleChangeStatus}
  130. disabled={false}
  131. />
  132. <ArchiveDeleteContent
  133. onClickArchiveBtn={this.handleClickArchive}
  134. onClickDeleteBtn={this.handleClickDelete}
  135. disabled={false}
  136. />
  137. </div>
  138. </PopinFixedOption>
  139. <PopinFixedContent customClass={`${config.customClass}__contentpage`}>
  140. <Timeline
  141. customClass={`${config.slug}__contentpage`}
  142. loggedUser={loggedUser}
  143. timelineData={listMessage}
  144. newComment={newComment}
  145. disableComment={false}
  146. wysiwyg={timelineWysiwyg}
  147. onChangeNewComment={this.handleChangeNewComment}
  148. onClickValidateNewCommentBtn={this.handleClickValidateNewCommentBtn}
  149. onClickWysiwygBtn={this.handleToggleWysiwyg}
  150. onClickRevisionBtn={() => {}}
  151. shouldScrollToBottom={false}
  152. showHeader={false}
  153. />
  154. </PopinFixedContent>
  155. </PopinFixed>
  156. )
  157. }
  158. }
  159. export default Thread