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.

248 lines
6.2 KiB

  1. /*
  2. `<StatusHeader>`
  3. ================
  4. Originally a part of `<Status>`, but extracted into a separate
  5. component for better documentation and maintainance by
  6. @kibi@glitch.social as a part of glitch-soc/mastodon.
  7. */
  8. /* * * * */
  9. /*
  10. Imports:
  11. --------
  12. */
  13. // Package imports //
  14. import React from 'react';
  15. import PropTypes from 'prop-types';
  16. import ImmutablePropTypes from 'react-immutable-proptypes';
  17. import { defineMessages, injectIntl } from 'react-intl';
  18. // Mastodon imports //
  19. import Avatar from '../../../mastodon/components/avatar';
  20. import AvatarOverlay from '../../../mastodon/components/avatar_overlay';
  21. import DisplayName from '../../../mastodon/components/display_name';
  22. import IconButton from '../../../mastodon/components/icon_button';
  23. /* * * * */
  24. /*
  25. Inital setup:
  26. -------------
  27. The `messages` constant is used to define any messages that we need
  28. from inside props. In our case, these are the `collapse` and
  29. `uncollapse` messages used with our collapse/uncollapse buttons.
  30. */
  31. const messages = defineMessages({
  32. collapse: { id: 'status.collapse', defaultMessage: 'Collapse' },
  33. uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' },
  34. public: { id: 'privacy.public.short', defaultMessage: 'Public' },
  35. unlisted: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
  36. private: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
  37. direct: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
  38. });
  39. /* * * * */
  40. /*
  41. The `<StatusHeader>` component:
  42. -------------------------------
  43. The `<StatusHeader>` component wraps together the header information
  44. (avatar, display name) and upper buttons and icons (collapsing, media
  45. icons) into a single `<header>` element.
  46. ### Props
  47. - __`account`, `friend` (`ImmutablePropTypes.map`) :__
  48. These give the accounts associated with the status. `account` is
  49. the author of the post; `friend` will have their avatar appear
  50. in the overlay if provided.
  51. - __`mediaIcon` (`PropTypes.string`) :__
  52. If a mediaIcon should be placed in the header, this string
  53. specifies it.
  54. - __`collapsible`, `collapsed` (`PropTypes.bool`) :__
  55. These props tell whether a post can be, and is, collapsed.
  56. - __`parseClick` (`PropTypes.func`) :__
  57. This function will be called when the user clicks inside the header
  58. information.
  59. - __`setExpansion` (`PropTypes.func`) :__
  60. This function is used to set the expansion state of the post.
  61. - __`intl` (`PropTypes.object`) :__
  62. This is our internationalization object, provided by
  63. `injectIntl()`.
  64. */
  65. @injectIntl
  66. export default class StatusHeader extends React.PureComponent {
  67. static propTypes = {
  68. account: ImmutablePropTypes.map.isRequired,
  69. friend: ImmutablePropTypes.map,
  70. mediaIcon: PropTypes.string,
  71. collapsible: PropTypes.bool,
  72. collapsed: PropTypes.bool,
  73. parseClick: PropTypes.func.isRequired,
  74. setExpansion: PropTypes.func.isRequired,
  75. intl: PropTypes.object.isRequired,
  76. visibility: PropTypes.string,
  77. };
  78. /*
  79. ### Implementation
  80. #### `handleCollapsedClick()`.
  81. `handleCollapsedClick()` is just a simple callback for our collapsing
  82. button. It calls `setExpansion` to set the collapsed state of the
  83. status.
  84. */
  85. handleCollapsedClick = (e) => {
  86. const { collapsed, setExpansion } = this.props;
  87. if (e.button === 0) {
  88. setExpansion(collapsed ? null : false);
  89. e.preventDefault();
  90. }
  91. }
  92. /*
  93. #### `handleAccountClick()`.
  94. `handleAccountClick()` handles any clicks on the header info. It calls
  95. `parseClick()` with our `account` as the anticipatory `destination`.
  96. */
  97. handleAccountClick = (e) => {
  98. const { account, parseClick } = this.props;
  99. parseClick(e, `/accounts/${+account.get('id')}`);
  100. }
  101. /*
  102. #### `render()`.
  103. `render()` actually puts our element on the screen. `<StatusHeader>`
  104. has a very straightforward rendering process.
  105. */
  106. render () {
  107. const {
  108. account,
  109. friend,
  110. mediaIcon,
  111. collapsible,
  112. collapsed,
  113. intl,
  114. visibility,
  115. } = this.props;
  116. const visibilityClass = {
  117. public: 'globe',
  118. unlisted: 'unlock-alt',
  119. private: 'lock',
  120. direct: 'envelope',
  121. }[visibility];
  122. return (
  123. <header className='status__info'>
  124. {
  125. /*
  126. We have to include the status icons before the header content because
  127. it is rendered as a float.
  128. */
  129. }
  130. <div className='status__info__icons'>
  131. {mediaIcon ? (
  132. <i
  133. className={`fa fa-fw fa-${mediaIcon}`}
  134. aria-hidden='true'
  135. />
  136. ) : null}
  137. {(
  138. <i
  139. className={`status__visibility-icon fa fa-fw fa-${visibilityClass}`}
  140. title={intl.formatMessage(messages[visibility])}
  141. aria-hidden='true'
  142. />
  143. )}
  144. {collapsible ? (
  145. <IconButton
  146. className='status__collapse-button'
  147. animate flip
  148. active={collapsed}
  149. title={
  150. collapsed ?
  151. intl.formatMessage(messages.uncollapse) :
  152. intl.formatMessage(messages.collapse)
  153. }
  154. icon='angle-double-up'
  155. onClick={this.handleCollapsedClick}
  156. />
  157. ) : null}
  158. </div>
  159. {
  160. /*
  161. This begins our header content. It is all wrapped inside of a link
  162. which gets handled by `handleAccountClick`. We use an `<AvatarOverlay>`
  163. if we have a `friend` and a normal `<Avatar>` if we don't.
  164. */
  165. }
  166. <a
  167. href={account.get('url')}
  168. target='_blank'
  169. className='status__display-name'
  170. onClick={this.handleAccountClick}
  171. >
  172. <div className='status__avatar'>{
  173. friend ? (
  174. <AvatarOverlay
  175. staticSrc={account.get('avatar_static')}
  176. overlaySrc={friend.get('avatar_static')}
  177. />
  178. ) : (
  179. <Avatar
  180. src={account.get('avatar')}
  181. staticSrc={account.get('avatar_static')}
  182. size={48}
  183. />
  184. )
  185. }</div>
  186. <DisplayName account={account} />
  187. </a>
  188. </header>
  189. );
  190. }
  191. }