about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/flavours/glitch/actions/compose.js9
-rw-r--r--app/javascript/flavours/glitch/actions/statuses.js5
-rw-r--r--app/javascript/flavours/glitch/containers/poll_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.js56
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/options_container.js10
-rw-r--r--app/javascript/flavours/glitch/features/compose/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js8
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js11
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js1
-rw-r--r--app/javascript/flavours/glitch/util/initial_state.js1
-rw-r--r--app/javascript/mastodon/components/poll.js14
-rw-r--r--app/javascript/mastodon/components/status.js2
-rw-r--r--app/javascript/mastodon/features/compose/containers/compose_form_container.js2
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js2
14 files changed, 110 insertions, 15 deletions
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index f117ce771..2fb97fa17 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -46,6 +46,7 @@ export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
 export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
 export const COMPOSE_VISIBILITY_CHANGE  = 'COMPOSE_VISIBILITY_CHANGE';
 export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
+export const COMPOSE_CONTENT_TYPE_CHANGE = 'COMPOSE_CONTENT_TYPE_CHANGE';
 
 export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
 
@@ -147,6 +148,7 @@ export function submitCompose(routerHistory) {
     }
     api(getState).post('/api/v1/statuses', {
       status,
+      content_type: getState().getIn(['compose', 'content_type']),
       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
       media_ids: media.map(item => item.get('id')),
       sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0),
@@ -517,6 +519,13 @@ export function changeComposeVisibility(value) {
   };
 };
 
+export function changeComposeContentType(value) {
+  return {
+    type: COMPOSE_CONTENT_TYPE_CHANGE,
+    value,
+  };
+};
+
 export function insertEmojiCompose(position, emoji) {
   return {
     type: COMPOSE_EMOJI_INSERT,
diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js
index 550fe510f..7e22a7f98 100644
--- a/app/javascript/flavours/glitch/actions/statuses.js
+++ b/app/javascript/flavours/glitch/actions/statuses.js
@@ -71,11 +71,12 @@ export function fetchStatusFail(id, error, skipLoading) {
   };
 };
 
-export function redraft(status, raw_text) {
+export function redraft(status, raw_text, content_type) {
   return {
     type: REDRAFT,
     status,
     raw_text,
+    content_type,
   };
 };
 
@@ -94,7 +95,7 @@ export function deleteStatus(id, router, withRedraft = false) {
       dispatch(deleteFromTimelines(id));
 
       if (withRedraft) {
-        dispatch(redraft(status, response.data.text));
+        dispatch(redraft(status, response.data.text, response.data.content_type));
 
         if (!getState().getIn(['compose', 'mounted'])) {
           router.push('/statuses/new');
diff --git a/app/javascript/flavours/glitch/containers/poll_container.js b/app/javascript/flavours/glitch/containers/poll_container.js
index cd7216de7..da93cc905 100644
--- a/app/javascript/flavours/glitch/containers/poll_container.js
+++ b/app/javascript/flavours/glitch/containers/poll_container.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import Poll from 'mastodon/components/poll';
+import Poll from 'flavours/glitch/components/poll';
 
 const mapStateToProps = (state, { pollId }) => ({
   poll: state.getIn(['polls', pollId]),
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index ee9730961..0c94f5514 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -29,6 +29,10 @@ const messages = defineMessages({
     defaultMessage: 'Adjust status privacy',
     id: 'privacy.change',
   },
+  content_type: {
+    defaultMessage: 'Content type',
+    id: 'content-type.change',
+  },
   direct_long: {
     defaultMessage: 'Post to mentioned users only',
     id: 'privacy.direct.long',
@@ -41,6 +45,10 @@ const messages = defineMessages({
     defaultMessage: 'Draw something',
     id: 'compose.attach.doodle',
   },
+  html: {
+    defaultMessage: 'HTML',
+    id: 'compose.content-type.html',
+  },
   local_only_long: {
     defaultMessage: 'Do not post to other instances',
     id: 'advanced_options.local-only.long',
@@ -49,6 +57,14 @@ const messages = defineMessages({
     defaultMessage: 'Local-only',
     id: 'advanced_options.local-only.short',
   },
+  markdown: {
+    defaultMessage: 'Markdown',
+    id: 'compose.content-type.markdown',
+  },
+  plain: {
+    defaultMessage: 'Plain text',
+    id: 'compose.content-type.plain',
+  },
   private_long: {
     defaultMessage: 'Post to followers only',
     id: 'privacy.private.long',
@@ -113,6 +129,7 @@ class ComposerOptions extends ImmutablePureComponent {
     intl: PropTypes.object.isRequired,
     onChangeAdvancedOption: PropTypes.func,
     onChangeVisibility: PropTypes.func,
+    onChangeContentType: PropTypes.func,
     onTogglePoll: PropTypes.func,
     onDoodleOpen: PropTypes.func,
     onModalClose: PropTypes.func,
@@ -120,8 +137,10 @@ class ComposerOptions extends ImmutablePureComponent {
     onToggleSpoiler: PropTypes.func,
     onUpload: PropTypes.func,
     privacy: PropTypes.string,
+    contentType: PropTypes.string,
     resetFileKey: PropTypes.number,
     spoiler: PropTypes.bool,
+    showContentTypeChoice: PropTypes.bool,
   };
 
   //  Handles file selection.
@@ -162,6 +181,7 @@ class ComposerOptions extends ImmutablePureComponent {
     const {
       acceptContentTypes,
       advancedOptions,
+      contentType,
       disabled,
       allowMedia,
       hasMedia,
@@ -169,6 +189,7 @@ class ComposerOptions extends ImmutablePureComponent {
       hasPoll,
       intl,
       onChangeAdvancedOption,
+      onChangeContentType,
       onChangeVisibility,
       onTogglePoll,
       onModalClose,
@@ -177,6 +198,7 @@ class ComposerOptions extends ImmutablePureComponent {
       privacy,
       resetFileKey,
       spoiler,
+      showContentTypeChoice,
     } = this.props;
 
     //  We predefine our privacy items so that we can easily pick the
@@ -208,6 +230,24 @@ class ComposerOptions extends ImmutablePureComponent {
       },
     };
 
+    const contentTypeItems = {
+      plain: {
+        icon: 'align-left',
+        name: 'text/plain',
+        text: <FormattedMessage {...messages.plain} />,
+      },
+      html: {
+        icon: 'code',
+        name: 'text/html',
+        text: <FormattedMessage {...messages.html} />,
+      },
+      markdown: {
+        icon: 'arrow-circle-down',
+        name: 'text/markdown',
+        text: <FormattedMessage {...messages.markdown} />,
+      },
+    };
+
     //  The result.
     return (
       <div className='composer--options'>
@@ -272,6 +312,22 @@ class ComposerOptions extends ImmutablePureComponent {
           title={intl.formatMessage(messages.change_privacy)}
           value={privacy}
         />
+        {showContentTypeChoice && (
+          <Dropdown
+            disabled={disabled}
+            icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
+            items={[
+              contentTypeItems.plain,
+              contentTypeItems.html,
+              contentTypeItems.markdown,
+            ]}
+            onChange={onChangeContentType}
+            onModalClose={onModalClose}
+            onModalOpen={onModalOpen}
+            title={intl.formatMessage(messages.content_type)}
+            value={contentType}
+          />
+        )}
         {onToggleSpoiler && (
           <TextIconButton
             active={spoiler}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
index 2ac7ab8d8..c8c7ecd43 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/options_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
@@ -2,8 +2,10 @@ import { connect } from 'react-redux';
 import Options from '../components/options';
 import {
   changeComposeAdvancedOption,
+  changeComposeContentType,
+  addPoll,
+  removePoll,
 } from 'flavours/glitch/actions/compose';
-import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
 import { closeModal, openModal } from 'flavours/glitch/actions/modal';
 
 function mapStateToProps (state) {
@@ -17,6 +19,8 @@ function mapStateToProps (state) {
     allowMedia: !poll && (media ? media.size < 4 && !media.some(item => item.get('type') === 'video') : true),
     hasMedia: media && !!media.size,
     allowPoll: !(media && !!media.size),
+    showContentTypeChoice: state.getIn(['local_settings', 'show_content_type_choice']),
+    contentType: state.getIn(['compose', 'content_type']),
   };
 };
 
@@ -26,6 +30,10 @@ const mapDispatchToProps = (dispatch) => ({
     dispatch(changeComposeAdvancedOption(option, value));
   },
 
+  onChangeContentType(value) {
+    dispatch(changeComposeContentType(value));
+  },
+
   onTogglePoll() {
     dispatch((_, getState) => {
       if (getState().getIn(['compose', 'poll'])) {
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index a7795a04d..e60eedfd9 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -29,7 +29,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   },
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
+export default @connect(mapStateToProps)
 @injectIntl
 class Compose extends React.PureComponent {
   static propTypes = {
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js
index a13bffa3a..cd2d86713 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -153,6 +153,14 @@ export default class LocalSettingsPage extends React.PureComponent {
         </LocalSettingsPageItem>
         <LocalSettingsPageItem
           settings={settings}
+          item={['show_content_type_choice']}
+          id='mastodon-settings--show_content_type_choice'
+          onChange={onChange}
+        >
+          <FormattedMessage id='settings.show_content_type_choice' defaultMessage='Show content-type choice when authoring toots' />
+        </LocalSettingsPageItem>
+        <LocalSettingsPageItem
+          settings={settings}
           item={['side_arm']}
           id='mastodon-settings--side_arm'
           options={[
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index bc1785a48..c0c2fc547 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -25,6 +25,7 @@ import {
   COMPOSE_SPOILERNESS_CHANGE,
   COMPOSE_SPOILER_TEXT_CHANGE,
   COMPOSE_VISIBILITY_CHANGE,
+  COMPOSE_CONTENT_TYPE_CHANGE,
   COMPOSE_EMOJI_INSERT,
   COMPOSE_UPLOAD_CHANGE_REQUEST,
   COMPOSE_UPLOAD_CHANGE_SUCCESS,
@@ -44,7 +45,7 @@ import { REDRAFT } from 'flavours/glitch/actions/statuses';
 import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
 import uuid from 'flavours/glitch/util/uuid';
 import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
-import { me } from 'flavours/glitch/util/initial_state';
+import { me, defaultContentType } from 'flavours/glitch/util/initial_state';
 import { overwrite } from 'flavours/glitch/util/js_helpers';
 import { unescapeHTML } from 'flavours/glitch/util/html';
 import { recoverHashtags } from 'flavours/glitch/util/hashtag';
@@ -66,6 +67,7 @@ const initialState = ImmutableMap({
   spoiler: false,
   spoiler_text: '',
   privacy: null,
+  content_type: defaultContentType || 'text/plain',
   text: '',
   focusDate: null,
   caretPosition: null,
@@ -141,6 +143,7 @@ function apiStatusToTextHashtags (state, status) {
 function clearAll(state) {
   return state.withMutations(map => {
     map.set('text', '');
+    if (defaultContentType) map.set('content_type', defaultContentType);
     map.set('spoiler', false);
     map.set('spoiler_text', '');
     map.set('is_submitting', false);
@@ -310,6 +313,10 @@ export default function compose(state = initialState, action) {
     return state
       .set('privacy', action.value)
       .set('idempotencyKey', uuid());
+  case COMPOSE_CONTENT_TYPE_CHANGE:
+    return state
+      .set('content_type', action.value)
+      .set('idempotencyKey', uuid());
   case COMPOSE_CHANGE:
     return state
       .set('text', action.text)
@@ -348,6 +355,7 @@ export default function compose(state = initialState, action) {
   case COMPOSE_RESET:
     return state.withMutations(map => {
       map.set('in_reply_to', null);
+      if (defaultContentType) map.set('content_type', defaultContentType);
       map.set('text', '');
       map.set('spoiler', false);
       map.set('spoiler_text', '');
@@ -427,6 +435,7 @@ export default function compose(state = initialState, action) {
   case REDRAFT:
     return state.withMutations(map => {
       map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status)));
+      map.set('content_type', action.content_type || 'text/plain');
       map.set('in_reply_to', action.status.get('in_reply_to_id'));
       map.set('privacy', action.status.get('visibility'));
       map.set('media_attachments', action.status.get('media_attachments'));
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 8dea4d8f6..5716c5982 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -19,6 +19,7 @@ const initialState = ImmutableMap({
   preselect_on_reply: true,
   inline_preview_cards: true,
   hicolor_privacy_icons: false,
+  show_content_type_choice: false,
   content_warnings : ImmutableMap({
     auto_unfold : false,
     filter      : null,
diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js
index 62588eeaa..99d8a4dbc 100644
--- a/app/javascript/flavours/glitch/util/initial_state.js
+++ b/app/javascript/flavours/glitch/util/initial_state.js
@@ -27,5 +27,6 @@ export const invitesEnabled = getMeta('invites_enabled');
 export const version = getMeta('version');
 export const mascot = getMeta('mascot');
 export const isStaff = getMeta('is_staff');
+export const defaultContentType = getMeta('default_content_type');
 
 export default initialState;
diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js
index 690f9ae5a..acab107a1 100644
--- a/app/javascript/mastodon/components/poll.js
+++ b/app/javascript/mastodon/components/poll.js
@@ -28,6 +28,7 @@ class Poll extends ImmutablePureComponent {
     intl: PropTypes.object.isRequired,
     dispatch: PropTypes.func,
     disabled: PropTypes.bool,
+    visible: PropTypes.bool,
   };
 
   state = {
@@ -69,13 +70,14 @@ class Poll extends ImmutablePureComponent {
   };
 
   renderOption (option, optionIndex) {
-    const { poll, disabled } = this.props;
-    const percent            = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
-    const leading            = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
-    const active             = !!this.state.selected[`${optionIndex}`];
-    const showResults        = poll.get('voted') || poll.get('expired');
+    const { poll, disabled, visible } = this.props;
+    const percent     = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
+    const leading     = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
+    const active      = !!this.state.selected[`${optionIndex}`];
+    const showResults = poll.get('voted') || poll.get('expired');
 
     let titleEmojified = option.get('title_emojified');
+
     if (!titleEmojified) {
       const emojiMap = makeEmojiMap(poll);
       titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
@@ -104,7 +106,7 @@ class Poll extends ImmutablePureComponent {
           {!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />}
           {showResults && <span className='poll__number'>{Math.round(percent)}%</span>}
 
-          <span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
+          {visible ? <span dangerouslySetInnerHTML={{ __html: titleEmojified }} /> : <span>{String.fromCharCode(64 + optionIndex + 1)}</span>}
         </label>
       </li>
     );
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 42535ea68..6f66a4260 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -272,7 +272,7 @@ class Status extends ImmutablePureComponent {
     }
 
     if (status.get('poll')) {
-      media = <PollContainer pollId={status.get('poll')} />;
+      media = <PollContainer pollId={status.get('poll')} visible={!status.get('hidden')} />;
     } else if (status.get('media_attachments').size > 0) {
       if (this.props.muted) {
         media = (
diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
index 93a468388..37a0e8845 100644
--- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
@@ -20,7 +20,7 @@ const mapStateToProps = state => ({
   focusDate: state.getIn(['compose', 'focusDate']),
   caretPosition: state.getIn(['compose', 'caretPosition']),
   preselectDate: state.getIn(['compose', 'preselectDate']),
-  is_submitting: state.getIn(['compose', 'is_submitting']),
+  isSubmitting: state.getIn(['compose', 'is_submitting']),
   isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
   isUploading: state.getIn(['compose', 'is_uploading']),
   showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 84471f9a3..059ecd979 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -106,7 +106,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
     }
 
     if (status.get('poll')) {
-      media = <PollContainer pollId={status.get('poll')} />;
+      media = <PollContainer pollId={status.get('poll')} visible={!status.get('hidden')} />;
     } else if (status.get('media_attachments').size > 0) {
       if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
         const video = status.getIn(['media_attachments', 0]);