The code powering m.abunchtell.com https://m.abunchtell.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

164 lines
5.5 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import ImmutablePureComponent from 'react-immutable-pure-component';
  5. import StatusContent from 'mastodon/components/status_content';
  6. import AttachmentList from 'mastodon/components/attachment_list';
  7. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  8. import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
  9. import AvatarComposite from 'mastodon/components/avatar_composite';
  10. import Permalink from 'mastodon/components/permalink';
  11. import IconButton from 'mastodon/components/icon_button';
  12. import RelativeTimestamp from 'mastodon/components/relative_timestamp';
  13. import { HotKeys } from 'react-hotkeys';
  14. const messages = defineMessages({
  15. more: { id: 'status.more', defaultMessage: 'More' },
  16. open: { id: 'conversation.open', defaultMessage: 'View conversation' },
  17. reply: { id: 'status.reply', defaultMessage: 'Reply' },
  18. markAsRead: { id: 'conversation.mark_as_read', defaultMessage: 'Mark as read' },
  19. delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' },
  20. muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
  21. unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
  22. });
  23. export default @injectIntl
  24. class Conversation extends ImmutablePureComponent {
  25. static contextTypes = {
  26. router: PropTypes.object,
  27. };
  28. static propTypes = {
  29. conversationId: PropTypes.string.isRequired,
  30. accounts: ImmutablePropTypes.list.isRequired,
  31. lastStatus: ImmutablePropTypes.map,
  32. unread:PropTypes.bool.isRequired,
  33. onMoveUp: PropTypes.func,
  34. onMoveDown: PropTypes.func,
  35. markRead: PropTypes.func.isRequired,
  36. delete: PropTypes.func.isRequired,
  37. intl: PropTypes.object.isRequired,
  38. };
  39. handleClick = () => {
  40. if (!this.context.router) {
  41. return;
  42. }
  43. const { lastStatus, unread, markRead } = this.props;
  44. if (unread) {
  45. markRead();
  46. }
  47. this.context.router.history.push(`/statuses/${lastStatus.get('id')}`);
  48. }
  49. handleMarkAsRead = () => {
  50. this.props.markRead();
  51. }
  52. handleReply = () => {
  53. this.props.reply(this.props.lastStatus, this.context.router.history);
  54. }
  55. handleDelete = () => {
  56. this.props.delete();
  57. }
  58. handleHotkeyMoveUp = () => {
  59. this.props.onMoveUp(this.props.conversationId);
  60. }
  61. handleHotkeyMoveDown = () => {
  62. this.props.onMoveDown(this.props.conversationId);
  63. }
  64. handleConversationMute = () => {
  65. this.props.onMute(this.props.lastStatus);
  66. }
  67. handleShowMore = () => {
  68. this.props.onToggleHidden(this.props.lastStatus);
  69. }
  70. render () {
  71. const { accounts, lastStatus, unread, intl } = this.props;
  72. if (lastStatus === null) {
  73. return null;
  74. }
  75. const menu = [
  76. { text: intl.formatMessage(messages.open), action: this.handleClick },
  77. null,
  78. ];
  79. menu.push({ text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute });
  80. if (unread) {
  81. menu.push({ text: intl.formatMessage(messages.markAsRead), action: this.handleMarkAsRead });
  82. menu.push(null);
  83. }
  84. menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
  85. const names = accounts.map(a => <Permalink to={`/accounts/${a.get('id')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]);
  86. const handlers = {
  87. reply: this.handleReply,
  88. open: this.handleClick,
  89. moveUp: this.handleHotkeyMoveUp,
  90. moveDown: this.handleHotkeyMoveDown,
  91. toggleHidden: this.handleShowMore,
  92. };
  93. return (
  94. <HotKeys handlers={handlers}>
  95. <div className='conversation focusable muted' tabIndex='0'>
  96. <div className='conversation__avatar'>
  97. <AvatarComposite accounts={accounts} size={48} />
  98. </div>
  99. <div className='conversation__content'>
  100. <div className='conversation__content__info'>
  101. <div className='conversation__content__relative-time'>
  102. <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
  103. </div>
  104. <div className='conversation__content__names'>
  105. <FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
  106. </div>
  107. </div>
  108. <StatusContent
  109. status={lastStatus}
  110. onClick={this.handleClick}
  111. expanded={!lastStatus.get('hidden')}
  112. onExpandedToggle={this.handleShowMore}
  113. collapsable
  114. />
  115. {lastStatus.get('media_attachments').size > 0 && (
  116. <AttachmentList
  117. compact
  118. media={lastStatus.get('media_attachments')}
  119. />
  120. )}
  121. <div className='status__action-bar'>
  122. <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} />
  123. <div className='status__action-bar-dropdown'>
  124. <DropdownMenuContainer status={lastStatus} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} />
  125. </div>
  126. </div>
  127. </div>
  128. </div>
  129. </HotKeys>
  130. );
  131. }
  132. }