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.

344 lines
9.0 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. // Package imports.
  2. import PropTypes from 'prop-types';
  3. import React from 'react';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import {
  6. FormattedMessage,
  7. defineMessages,
  8. } from 'react-intl';
  9. import spring from 'react-motion/lib/spring';
  10. // Components.
  11. import IconButton from 'flavours/glitch/components/icon_button';
  12. import TextIconButton from 'flavours/glitch/components/text_icon_button';
  13. import Dropdown from './dropdown';
  14. // Utils.
  15. import Motion from 'flavours/glitch/util/optional_motion';
  16. import {
  17. assignHandlers,
  18. hiddenComponent,
  19. } from 'flavours/glitch/util/react_helpers';
  20. // Messages.
  21. const messages = defineMessages({
  22. advanced_options_icon_title: {
  23. defaultMessage: 'Advanced options',
  24. id: 'advanced_options.icon_title',
  25. },
  26. attach: {
  27. defaultMessage: 'Attach...',
  28. id: 'compose.attach',
  29. },
  30. change_privacy: {
  31. defaultMessage: 'Adjust status privacy',
  32. id: 'privacy.change',
  33. },
  34. direct_long: {
  35. defaultMessage: 'Post to mentioned users only',
  36. id: 'privacy.direct.long',
  37. },
  38. direct_short: {
  39. defaultMessage: 'Direct',
  40. id: 'privacy.direct.short',
  41. },
  42. doodle: {
  43. defaultMessage: 'Draw something',
  44. id: 'compose.attach.doodle',
  45. },
  46. local_only_long: {
  47. defaultMessage: 'Do not post to other instances',
  48. id: 'advanced_options.local-only.long',
  49. },
  50. local_only_short: {
  51. defaultMessage: 'Local-only',
  52. id: 'advanced_options.local-only.short',
  53. },
  54. private_long: {
  55. defaultMessage: 'Post to followers only',
  56. id: 'privacy.private.long',
  57. },
  58. private_short: {
  59. defaultMessage: 'Followers-only',
  60. id: 'privacy.private.short',
  61. },
  62. public_long: {
  63. defaultMessage: 'Post to public timelines',
  64. id: 'privacy.public.long',
  65. },
  66. public_short: {
  67. defaultMessage: 'Public',
  68. id: 'privacy.public.short',
  69. },
  70. sensitive: {
  71. defaultMessage: 'Mark media as sensitive',
  72. id: 'compose_form.sensitive',
  73. },
  74. spoiler: {
  75. defaultMessage: 'Hide text behind warning',
  76. id: 'compose_form.spoiler',
  77. },
  78. threaded_mode_long: {
  79. defaultMessage: 'Automatically opens a reply on posting',
  80. id: 'advanced_options.threaded_mode.long',
  81. },
  82. threaded_mode_short: {
  83. defaultMessage: 'Threaded mode',
  84. id: 'advanced_options.threaded_mode.short',
  85. },
  86. unlisted_long: {
  87. defaultMessage: 'Do not show in public timelines',
  88. id: 'privacy.unlisted.long',
  89. },
  90. unlisted_short: {
  91. defaultMessage: 'Unlisted',
  92. id: 'privacy.unlisted.short',
  93. },
  94. upload: {
  95. defaultMessage: 'Upload a file',
  96. id: 'compose.attach.upload',
  97. },
  98. });
  99. // Handlers.
  100. const handlers = {
  101. // Handles file selection.
  102. handleChangeFiles ({ target: { files } }) {
  103. const { onUpload } = this.props;
  104. if (files.length && onUpload) {
  105. onUpload(files);
  106. }
  107. },
  108. // Handles attachment clicks.
  109. handleClickAttach (name) {
  110. const { fileElement } = this;
  111. const { onDoodleOpen } = this.props;
  112. // We switch over the name of the option.
  113. switch (name) {
  114. case 'upload':
  115. if (fileElement) {
  116. fileElement.click();
  117. }
  118. return;
  119. case 'doodle':
  120. if (onDoodleOpen) {
  121. onDoodleOpen();
  122. }
  123. return;
  124. }
  125. },
  126. // Handles a ref to the file input.
  127. handleRefFileElement (fileElement) {
  128. this.fileElement = fileElement;
  129. },
  130. };
  131. // The component.
  132. export default class ComposerOptions extends React.PureComponent {
  133. // Constructor.
  134. constructor (props) {
  135. super(props);
  136. assignHandlers(this, handlers);
  137. // Instance variables.
  138. this.fileElement = null;
  139. }
  140. // Rendering.
  141. render () {
  142. const {
  143. handleChangeFiles,
  144. handleClickAttach,
  145. handleRefFileElement,
  146. } = this.handlers;
  147. const {
  148. acceptContentTypes,
  149. advancedOptions,
  150. disabled,
  151. full,
  152. hasMedia,
  153. intl,
  154. onChangeAdvancedOption,
  155. onChangeSensitivity,
  156. onChangeVisibility,
  157. onModalClose,
  158. onModalOpen,
  159. onToggleSpoiler,
  160. privacy,
  161. resetFileKey,
  162. sensitive,
  163. spoiler,
  164. } = this.props;
  165. // We predefine our privacy items so that we can easily pick the
  166. // dropdown icon later.
  167. const privacyItems = {
  168. direct: {
  169. icon: 'envelope',
  170. meta: <FormattedMessage {...messages.direct_long} />,
  171. name: 'direct',
  172. text: <FormattedMessage {...messages.direct_short} />,
  173. },
  174. private: {
  175. icon: 'lock',
  176. meta: <FormattedMessage {...messages.private_long} />,
  177. name: 'private',
  178. text: <FormattedMessage {...messages.private_short} />,
  179. },
  180. public: {
  181. icon: 'globe',
  182. meta: <FormattedMessage {...messages.public_long} />,
  183. name: 'public',
  184. text: <FormattedMessage {...messages.public_short} />,
  185. },
  186. unlisted: {
  187. icon: 'unlock-alt',
  188. meta: <FormattedMessage {...messages.unlisted_long} />,
  189. name: 'unlisted',
  190. text: <FormattedMessage {...messages.unlisted_short} />,
  191. },
  192. };
  193. // The result.
  194. return (
  195. <div className='composer--options'>
  196. <input
  197. accept={acceptContentTypes}
  198. disabled={disabled || full}
  199. key={resetFileKey}
  200. onChange={handleChangeFiles}
  201. ref={handleRefFileElement}
  202. type='file'
  203. {...hiddenComponent}
  204. />
  205. <Dropdown
  206. disabled={disabled || full}
  207. icon='paperclip'
  208. items={[
  209. {
  210. icon: 'cloud-upload',
  211. name: 'upload',
  212. text: <FormattedMessage {...messages.upload} />,
  213. },
  214. {
  215. icon: 'paint-brush',
  216. name: 'doodle',
  217. text: <FormattedMessage {...messages.doodle} />,
  218. },
  219. ]}
  220. onChange={handleClickAttach}
  221. onModalClose={onModalClose}
  222. onModalOpen={onModalOpen}
  223. title={intl.formatMessage(messages.attach)}
  224. />
  225. <Motion
  226. defaultStyle={{ scale: 0.87 }}
  227. style={{
  228. scale: spring(hasMedia ? 1 : 0.87, {
  229. stiffness: 200,
  230. damping: 3,
  231. }),
  232. }}
  233. >
  234. {({ scale }) => (
  235. <div
  236. style={{
  237. display: hasMedia ? null : 'none',
  238. transform: `scale(${scale})`,
  239. }}
  240. >
  241. <IconButton
  242. active={sensitive}
  243. className='sensitive'
  244. disabled={spoiler}
  245. icon={sensitive ? 'eye-slash' : 'eye'}
  246. inverted
  247. onClick={onChangeSensitivity}
  248. size={18}
  249. style={{
  250. height: null,
  251. lineHeight: null,
  252. }}
  253. title={intl.formatMessage(messages.sensitive)}
  254. />
  255. </div>
  256. )}
  257. </Motion>
  258. <hr />
  259. <Dropdown
  260. disabled={disabled}
  261. icon={(privacyItems[privacy] || {}).icon}
  262. items={[
  263. privacyItems.public,
  264. privacyItems.unlisted,
  265. privacyItems.private,
  266. privacyItems.direct,
  267. ]}
  268. onChange={onChangeVisibility}
  269. onModalClose={onModalClose}
  270. onModalOpen={onModalOpen}
  271. title={intl.formatMessage(messages.change_privacy)}
  272. value={privacy}
  273. />
  274. <TextIconButton
  275. active={spoiler}
  276. ariaControls='glitch.composer.spoiler.input'
  277. label='CW'
  278. onClick={onToggleSpoiler}
  279. title={intl.formatMessage(messages.spoiler)}
  280. />
  281. <Dropdown
  282. active={advancedOptions && advancedOptions.some(value => !!value)}
  283. disabled={disabled}
  284. icon='ellipsis-h'
  285. items={advancedOptions ? [
  286. {
  287. meta: <FormattedMessage {...messages.local_only_long} />,
  288. name: 'do_not_federate',
  289. on: advancedOptions.get('do_not_federate'),
  290. text: <FormattedMessage {...messages.local_only_short} />,
  291. },
  292. {
  293. meta: <FormattedMessage {...messages.threaded_mode_long} />,
  294. name: 'threaded_mode',
  295. on: advancedOptions.get('threaded_mode'),
  296. text: <FormattedMessage {...messages.threaded_mode_short} />,
  297. },
  298. ] : null}
  299. onChange={onChangeAdvancedOption}
  300. onModalClose={onModalClose}
  301. onModalOpen={onModalOpen}
  302. title={intl.formatMessage(messages.advanced_options_icon_title)}
  303. />
  304. </div>
  305. );
  306. }
  307. }
  308. // Props.
  309. ComposerOptions.propTypes = {
  310. acceptContentTypes: PropTypes.string,
  311. advancedOptions: ImmutablePropTypes.map,
  312. disabled: PropTypes.bool,
  313. full: PropTypes.bool,
  314. hasMedia: PropTypes.bool,
  315. intl: PropTypes.object.isRequired,
  316. onChangeAdvancedOption: PropTypes.func,
  317. onChangeSensitivity: PropTypes.func,
  318. onChangeVisibility: PropTypes.func,
  319. onDoodleOpen: PropTypes.func,
  320. onModalClose: PropTypes.func,
  321. onModalOpen: PropTypes.func,
  322. onToggleSpoiler: PropTypes.func,
  323. onUpload: PropTypes.func,
  324. privacy: PropTypes.string,
  325. resetFileKey: PropTypes.number,
  326. sensitive: PropTypes.bool,
  327. spoiler: PropTypes.bool,
  328. };