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.

317 lines
8.4 KiB

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 { defineMessages, injectIntl } from 'react-intl';
  6. import spring from 'react-motion/lib/spring';
  7. import Toggle from 'react-toggle';
  8. import { connect } from 'react-redux';
  9. // Components.
  10. import IconButton from 'flavours/glitch/components/icon_button';
  11. import TextIconButton from './text_icon_button';
  12. import DropdownContainer from '../containers/dropdown_container';
  13. import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
  14. import LanguageDropdown from '../containers/language_dropdown_container';
  15. import ImmutablePureComponent from 'react-immutable-pure-component';
  16. // Utils.
  17. import Motion from '../../ui/util/optional_motion';
  18. import { pollLimits } from 'flavours/glitch/initial_state';
  19. // Messages.
  20. const messages = defineMessages({
  21. advanced_options_icon_title: {
  22. defaultMessage: 'Advanced options',
  23. id: 'advanced_options.icon_title',
  24. },
  25. attach: {
  26. defaultMessage: 'Attach...',
  27. id: 'compose.attach',
  28. },
  29. content_type: {
  30. defaultMessage: 'Content type',
  31. id: 'content-type.change',
  32. },
  33. doodle: {
  34. defaultMessage: 'Draw something',
  35. id: 'compose.attach.doodle',
  36. },
  37. html: {
  38. defaultMessage: 'HTML',
  39. id: 'compose.content-type.html',
  40. },
  41. local_only_long: {
  42. defaultMessage: 'Do not post to other instances',
  43. id: 'advanced_options.local-only.long',
  44. },
  45. local_only_short: {
  46. defaultMessage: 'Local-only',
  47. id: 'advanced_options.local-only.short',
  48. },
  49. markdown: {
  50. defaultMessage: 'Markdown',
  51. id: 'compose.content-type.markdown',
  52. },
  53. plain: {
  54. defaultMessage: 'Plain text',
  55. id: 'compose.content-type.plain',
  56. },
  57. spoiler: {
  58. defaultMessage: 'Hide text behind warning',
  59. id: 'compose_form.spoiler',
  60. },
  61. threaded_mode_long: {
  62. defaultMessage: 'Automatically opens a reply on posting',
  63. id: 'advanced_options.threaded_mode.long',
  64. },
  65. threaded_mode_short: {
  66. defaultMessage: 'Threaded mode',
  67. id: 'advanced_options.threaded_mode.short',
  68. },
  69. upload: {
  70. defaultMessage: 'Upload a file',
  71. id: 'compose.attach.upload',
  72. },
  73. add_poll: {
  74. defaultMessage: 'Add a poll',
  75. id: 'poll_button.add_poll',
  76. },
  77. remove_poll: {
  78. defaultMessage: 'Remove poll',
  79. id: 'poll_button.remove_poll',
  80. },
  81. });
  82. const mapStateToProps = (state, { name }) => ({
  83. checked: state.getIn(['compose', 'advanced_options', name]),
  84. });
  85. class ToggleOptionImpl extends ImmutablePureComponent {
  86. static propTypes = {
  87. name: PropTypes.string.isRequired,
  88. checked: PropTypes.bool,
  89. onChangeAdvancedOption: PropTypes.func.isRequired,
  90. };
  91. handleChange = () => {
  92. this.props.onChangeAdvancedOption(this.props.name);
  93. };
  94. render() {
  95. const { meta, text, checked } = this.props;
  96. return (
  97. <React.Fragment>
  98. <Toggle checked={checked} onChange={this.handleChange} />
  99. <div className='privacy-dropdown__option__content'>
  100. <strong>{text}</strong>
  101. {meta}
  102. </div>
  103. </React.Fragment>
  104. );
  105. }
  106. }
  107. const ToggleOption = connect(mapStateToProps)(ToggleOptionImpl);
  108. class ComposerOptions extends ImmutablePureComponent {
  109. static propTypes = {
  110. acceptContentTypes: PropTypes.string,
  111. advancedOptions: ImmutablePropTypes.map,
  112. disabled: PropTypes.bool,
  113. allowMedia: PropTypes.bool,
  114. hasMedia: PropTypes.bool,
  115. allowPoll: PropTypes.bool,
  116. hasPoll: PropTypes.bool,
  117. intl: PropTypes.object.isRequired,
  118. onChangeAdvancedOption: PropTypes.func,
  119. onChangeContentType: PropTypes.func,
  120. onTogglePoll: PropTypes.func,
  121. onDoodleOpen: PropTypes.func,
  122. onToggleSpoiler: PropTypes.func,
  123. onUpload: PropTypes.func,
  124. contentType: PropTypes.string,
  125. resetFileKey: PropTypes.number,
  126. spoiler: PropTypes.bool,
  127. showContentTypeChoice: PropTypes.bool,
  128. isEditing: PropTypes.bool,
  129. };
  130. // Handles file selection.
  131. handleChangeFiles = ({ target: { files } }) => {
  132. const { onUpload } = this.props;
  133. if (files.length && onUpload) {
  134. onUpload(files);
  135. }
  136. };
  137. // Handles attachment clicks.
  138. handleClickAttach = (name) => {
  139. const { fileElement } = this;
  140. const { onDoodleOpen } = this.props;
  141. // We switch over the name of the option.
  142. switch (name) {
  143. case 'upload':
  144. if (fileElement) {
  145. fileElement.click();
  146. }
  147. return;
  148. case 'doodle':
  149. if (onDoodleOpen) {
  150. onDoodleOpen();
  151. }
  152. return;
  153. }
  154. };
  155. // Handles a ref to the file input.
  156. handleRefFileElement = (fileElement) => {
  157. this.fileElement = fileElement;
  158. };
  159. renderToggleItemContents = (item) => {
  160. const { onChangeAdvancedOption } = this.props;
  161. const { name, meta, text } = item;
  162. return <ToggleOption name={name} text={text} meta={meta} onChangeAdvancedOption={onChangeAdvancedOption} />;
  163. };
  164. // Rendering.
  165. render () {
  166. const {
  167. acceptContentTypes,
  168. advancedOptions,
  169. contentType,
  170. disabled,
  171. allowMedia,
  172. hasMedia,
  173. allowPoll,
  174. hasPoll,
  175. onChangeAdvancedOption,
  176. onChangeContentType,
  177. onTogglePoll,
  178. onToggleSpoiler,
  179. resetFileKey,
  180. spoiler,
  181. showContentTypeChoice,
  182. isEditing,
  183. intl: { formatMessage },
  184. } = this.props;
  185. const contentTypeItems = {
  186. plain: {
  187. icon: 'file-text',
  188. name: 'text/plain',
  189. text: formatMessage(messages.plain),
  190. },
  191. html: {
  192. icon: 'code',
  193. name: 'text/html',
  194. text: formatMessage(messages.html),
  195. },
  196. markdown: {
  197. icon: 'arrow-circle-down',
  198. name: 'text/markdown',
  199. text: formatMessage(messages.markdown),
  200. },
  201. };
  202. // The result.
  203. return (
  204. <div className='compose-form__buttons'>
  205. <input
  206. accept={acceptContentTypes}
  207. disabled={disabled || !allowMedia}
  208. key={resetFileKey}
  209. onChange={this.handleChangeFiles}
  210. ref={this.handleRefFileElement}
  211. type='file'
  212. multiple
  213. style={{ display: 'none' }}
  214. />
  215. <DropdownContainer
  216. disabled={disabled || !allowMedia}
  217. icon='paperclip'
  218. items={[
  219. {
  220. icon: 'cloud-upload',
  221. name: 'upload',
  222. text: formatMessage(messages.upload),
  223. },
  224. {
  225. icon: 'paint-brush',
  226. name: 'doodle',
  227. text: formatMessage(messages.doodle),
  228. },
  229. ]}
  230. onChange={this.handleClickAttach}
  231. title={formatMessage(messages.attach)}
  232. />
  233. {!!pollLimits && (
  234. <IconButton
  235. active={hasPoll}
  236. disabled={disabled || !allowPoll}
  237. icon='tasks'
  238. inverted
  239. onClick={onTogglePoll}
  240. size={18}
  241. style={{
  242. height: null,
  243. lineHeight: null,
  244. }}
  245. title={formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
  246. />
  247. )}
  248. <hr />
  249. <PrivacyDropdownContainer disabled={disabled || isEditing} />
  250. {showContentTypeChoice && (
  251. <DropdownContainer
  252. disabled={disabled}
  253. icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
  254. items={[
  255. contentTypeItems.plain,
  256. contentTypeItems.html,
  257. contentTypeItems.markdown,
  258. ]}
  259. onChange={onChangeContentType}
  260. title={formatMessage(messages.content_type)}
  261. value={contentType}
  262. />
  263. )}
  264. {onToggleSpoiler && (
  265. <TextIconButton
  266. active={spoiler}
  267. ariaControls='glitch.composer.spoiler.input'
  268. label='CW'
  269. onClick={onToggleSpoiler}
  270. title={formatMessage(messages.spoiler)}
  271. />
  272. )}
  273. <DropdownContainer
  274. disabled={disabled || isEditing}
  275. icon='ellipsis-h'
  276. items={advancedOptions ? [
  277. {
  278. meta: formatMessage(messages.local_only_long),
  279. name: 'do_not_federate',
  280. text: formatMessage(messages.local_only_short),
  281. },
  282. ] : null}
  283. onChange={onChangeAdvancedOption}
  284. renderItemContents={this.renderToggleItemContents}
  285. title={formatMessage(messages.advanced_options_icon_title)}
  286. closeOnChange={false}
  287. />
  288. </div>
  289. );
  290. }
  291. }
  292. export default injectIntl(ComposerOptions);