logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe git clone https://hacktivis.me/git/mastofe.git

index.js (11620B)


  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import classNames from 'classnames';
  5. import ImmutablePropTypes from 'react-immutable-proptypes';
  6. import { fetchStatus } from '../../actions/statuses';
  7. import MissingIndicator from '../../components/missing_indicator';
  8. import DetailedStatus from './components/detailed_status';
  9. import ActionBar from './components/action_bar';
  10. import Column from '../ui/components/column';
  11. import {
  12. favourite,
  13. unfavourite,
  14. reblog,
  15. unreblog,
  16. } from '../../actions/interactions';
  17. import {
  18. replyCompose,
  19. mentionCompose,
  20. directCompose,
  21. } from '../../actions/compose';
  22. import { blockAccount } from '../../actions/accounts';
  23. import {
  24. muteStatus,
  25. unmuteStatus,
  26. deleteStatus,
  27. hideStatus,
  28. revealStatus,
  29. } from '../../actions/statuses';
  30. import { initMuteModal } from '../../actions/mutes';
  31. import { initReport } from '../../actions/reports';
  32. import { makeGetStatus } from '../../selectors';
  33. import { ScrollContainer } from 'react-router-scroll-4';
  34. import ColumnBackButton from '../../components/column_back_button';
  35. import ColumnHeader from '../../components/column_header';
  36. import StatusContainer from '../../containers/status_container';
  37. import { openModal } from '../../actions/modal';
  38. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  39. import ImmutablePureComponent from 'react-immutable-pure-component';
  40. import { HotKeys } from 'react-hotkeys';
  41. import { boostModal, deleteModal } from '../../initial_state';
  42. import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen';
  43. const messages = defineMessages({
  44. deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
  45. deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
  46. blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
  47. revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
  48. hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
  49. });
  50. const makeMapStateToProps = () => {
  51. const getStatus = makeGetStatus();
  52. const mapStateToProps = (state, props) => ({
  53. status: getStatus(state, props.params.statusId),
  54. ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]),
  55. descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]),
  56. });
  57. return mapStateToProps;
  58. };
  59. @injectIntl
  60. @connect(makeMapStateToProps)
  61. export default class Status extends ImmutablePureComponent {
  62. static contextTypes = {
  63. router: PropTypes.object,
  64. };
  65. static propTypes = {
  66. params: PropTypes.object.isRequired,
  67. dispatch: PropTypes.func.isRequired,
  68. status: ImmutablePropTypes.map,
  69. ancestorsIds: ImmutablePropTypes.list,
  70. descendantsIds: ImmutablePropTypes.list,
  71. intl: PropTypes.object.isRequired,
  72. };
  73. state = {
  74. fullscreen: false,
  75. };
  76. componentWillMount () {
  77. this.props.dispatch(fetchStatus(this.props.params.statusId));
  78. }
  79. componentDidMount () {
  80. attachFullscreenListener(this.onFullScreenChange);
  81. }
  82. componentWillReceiveProps (nextProps) {
  83. if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
  84. this._scrolledIntoView = false;
  85. this.props.dispatch(fetchStatus(nextProps.params.statusId));
  86. }
  87. }
  88. handleFavouriteClick = (status) => {
  89. if (status.get('favourited')) {
  90. this.props.dispatch(unfavourite(status));
  91. } else {
  92. this.props.dispatch(favourite(status));
  93. }
  94. }
  95. handleReplyClick = (status) => {
  96. this.props.dispatch(replyCompose(status, this.context.router.history));
  97. }
  98. handleModalReblog = (status) => {
  99. this.props.dispatch(reblog(status));
  100. }
  101. handleReblogClick = (status, e) => {
  102. if (status.get('reblogged')) {
  103. this.props.dispatch(unreblog(status));
  104. } else {
  105. if (e.shiftKey || !boostModal) {
  106. this.handleModalReblog(status);
  107. } else {
  108. this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
  109. }
  110. }
  111. }
  112. handleDeleteClick = (status) => {
  113. const { dispatch, intl } = this.props;
  114. if (!deleteModal) {
  115. dispatch(deleteStatus(status.get('id')));
  116. } else {
  117. dispatch(openModal('CONFIRM', {
  118. message: intl.formatMessage(messages.deleteMessage),
  119. confirm: intl.formatMessage(messages.deleteConfirm),
  120. onConfirm: () => dispatch(deleteStatus(status.get('id'))),
  121. }));
  122. }
  123. }
  124. handleDirectClick = (account, router) => {
  125. this.props.dispatch(directCompose(account, router));
  126. }
  127. handleMentionClick = (account, router) => {
  128. this.props.dispatch(mentionCompose(account, router));
  129. }
  130. handleOpenMedia = (media, index) => {
  131. this.props.dispatch(openModal('MEDIA', { media, index }));
  132. }
  133. handleOpenVideo = (media, time) => {
  134. this.props.dispatch(openModal('VIDEO', { media, time }));
  135. }
  136. handleMuteClick = (account) => {
  137. this.props.dispatch(initMuteModal(account));
  138. }
  139. handleConversationMuteClick = (status) => {
  140. if (status.get('muted')) {
  141. this.props.dispatch(unmuteStatus(status.get('id')));
  142. } else {
  143. this.props.dispatch(muteStatus(status.get('id')));
  144. }
  145. }
  146. handleToggleHidden = (status) => {
  147. if (status.get('hidden')) {
  148. this.props.dispatch(revealStatus(status.get('id')));
  149. } else {
  150. this.props.dispatch(hideStatus(status.get('id')));
  151. }
  152. }
  153. handleToggleAll = () => {
  154. const { status, ancestorsIds, descendantsIds } = this.props;
  155. const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
  156. if (status.get('hidden')) {
  157. this.props.dispatch(revealStatus(statusIds));
  158. } else {
  159. this.props.dispatch(hideStatus(statusIds));
  160. }
  161. }
  162. handleBlockClick = (account) => {
  163. const { dispatch, intl } = this.props;
  164. dispatch(openModal('CONFIRM', {
  165. message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
  166. confirm: intl.formatMessage(messages.blockConfirm),
  167. onConfirm: () => dispatch(blockAccount(account.get('id'))),
  168. }));
  169. }
  170. handleReport = (status) => {
  171. this.props.dispatch(initReport(status.get('account'), status));
  172. }
  173. handleHotkeyMoveUp = () => {
  174. this.handleMoveUp(this.props.status.get('id'));
  175. }
  176. handleHotkeyMoveDown = () => {
  177. this.handleMoveDown(this.props.status.get('id'));
  178. }
  179. handleHotkeyReply = e => {
  180. e.preventDefault();
  181. this.handleReplyClick(this.props.status);
  182. }
  183. handleHotkeyFavourite = () => {
  184. this.handleFavouriteClick(this.props.status);
  185. }
  186. handleHotkeyBoost = () => {
  187. this.handleReblogClick(this.props.status);
  188. }
  189. handleHotkeyMention = e => {
  190. e.preventDefault();
  191. this.handleMentionClick(this.props.status);
  192. }
  193. handleHotkeyOpenProfile = () => {
  194. this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
  195. }
  196. handleMoveUp = id => {
  197. const { status, ancestorsIds, descendantsIds } = this.props;
  198. if (id === status.get('id')) {
  199. this._selectChild(ancestorsIds.size - 1);
  200. } else {
  201. let index = ancestorsIds.indexOf(id);
  202. if (index === -1) {
  203. index = descendantsIds.indexOf(id);
  204. this._selectChild(ancestorsIds.size + index);
  205. } else {
  206. this._selectChild(index - 1);
  207. }
  208. }
  209. }
  210. handleMoveDown = id => {
  211. const { status, ancestorsIds, descendantsIds } = this.props;
  212. if (id === status.get('id')) {
  213. this._selectChild(ancestorsIds.size + 1);
  214. } else {
  215. let index = ancestorsIds.indexOf(id);
  216. if (index === -1) {
  217. index = descendantsIds.indexOf(id);
  218. this._selectChild(ancestorsIds.size + index + 2);
  219. } else {
  220. this._selectChild(index + 1);
  221. }
  222. }
  223. }
  224. _selectChild (index) {
  225. const element = this.node.querySelectorAll('.focusable')[index];
  226. if (element) {
  227. element.focus();
  228. }
  229. }
  230. renderChildren (list) {
  231. return list.map(id => (
  232. <StatusContainer
  233. key={id}
  234. id={id}
  235. onMoveUp={this.handleMoveUp}
  236. onMoveDown={this.handleMoveDown}
  237. />
  238. ));
  239. }
  240. setRef = c => {
  241. this.node = c;
  242. }
  243. componentDidUpdate () {
  244. if (this._scrolledIntoView) {
  245. return;
  246. }
  247. const { status, ancestorsIds } = this.props;
  248. if (status && ancestorsIds && ancestorsIds.size > 0) {
  249. const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
  250. element.scrollIntoView(true);
  251. this._scrolledIntoView = true;
  252. }
  253. }
  254. componentWillUnmount () {
  255. detachFullscreenListener(this.onFullScreenChange);
  256. }
  257. onFullScreenChange = () => {
  258. this.setState({ fullscreen: isFullscreen() });
  259. }
  260. render () {
  261. let ancestors, descendants;
  262. const { status, ancestorsIds, descendantsIds, intl } = this.props;
  263. const { fullscreen } = this.state;
  264. if (status === null) {
  265. return (
  266. <Column>
  267. <ColumnBackButton />
  268. <MissingIndicator />
  269. </Column>
  270. );
  271. }
  272. if (ancestorsIds && ancestorsIds.size > 0) {
  273. ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
  274. }
  275. if (descendantsIds && descendantsIds.size > 0) {
  276. descendants = <div>{this.renderChildren(descendantsIds)}</div>;
  277. }
  278. const handlers = {
  279. moveUp: this.handleHotkeyMoveUp,
  280. moveDown: this.handleHotkeyMoveDown,
  281. reply: this.handleHotkeyReply,
  282. favourite: this.handleHotkeyFavourite,
  283. boost: this.handleHotkeyBoost,
  284. mention: this.handleHotkeyMention,
  285. openProfile: this.handleHotkeyOpenProfile,
  286. };
  287. return (
  288. <Column>
  289. <ColumnHeader
  290. showBackButton
  291. extraButton={(
  292. <button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><i className={`fa fa-${status.get('hidden') ? 'eye-slash' : 'eye'}`} /></button>
  293. )}
  294. />
  295. <ScrollContainer scrollKey='thread'>
  296. <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}>
  297. {ancestors}
  298. <HotKeys handlers={handlers}>
  299. <div className='focusable' tabIndex='0'>
  300. <DetailedStatus
  301. status={status}
  302. onOpenVideo={this.handleOpenVideo}
  303. onOpenMedia={this.handleOpenMedia}
  304. onToggleHidden={this.handleToggleHidden}
  305. />
  306. <ActionBar
  307. status={status}
  308. onReply={this.handleReplyClick}
  309. onFavourite={this.handleFavouriteClick}
  310. onReblog={this.handleReblogClick}
  311. onDelete={this.handleDeleteClick}
  312. onDirect={this.handleDirectClick}
  313. onMention={this.handleMentionClick}
  314. onMute={this.handleMuteClick}
  315. onMuteConversation={this.handleConversationMuteClick}
  316. onBlock={this.handleBlockClick}
  317. onReport={this.handleReport}
  318. />
  319. </div>
  320. </HotKeys>
  321. {descendants}
  322. </div>
  323. </ScrollContainer>
  324. </Column>
  325. );
  326. }
  327. }