Add API modifiers to limit returned toots from public/hashtag timelines
to only those from local users; Add link to "extended information" to getting started in the UI; Add defaults for posting privacy; Change how publish button looks depending on posting privacy chosen
This commit is contained in:
		| @@ -9,6 +9,7 @@ const Button = React.createClass({ | ||||
|     block: React.PropTypes.bool, | ||||
|     secondary: React.PropTypes.bool, | ||||
|     size: React.PropTypes.number, | ||||
|     children: React.PropTypes.node | ||||
|   }, | ||||
|  | ||||
|   getDefaultProps () { | ||||
| @@ -38,7 +39,6 @@ const Button = React.createClass({ | ||||
|       fontSize: '14px', | ||||
|       fontWeight: '500', | ||||
|       letterSpacing: '0', | ||||
|       textTransform: 'uppercase', | ||||
|       padding: `0 ${this.props.size / 2.25}px`, | ||||
|       height: `${this.props.size}px`, | ||||
|       cursor: 'pointer', | ||||
|   | ||||
| @@ -117,9 +117,10 @@ const ComposeForm = React.createClass({ | ||||
|   }, | ||||
|  | ||||
|   render () { | ||||
|     const { intl } = this.props; | ||||
|     let replyArea  = ''; | ||||
|     const disabled = this.props.is_submitting || this.props.is_uploading; | ||||
|     const { intl }  = this.props; | ||||
|     let replyArea   = ''; | ||||
|     let publishText = ''; | ||||
|     const disabled  = this.props.is_submitting || this.props.is_uploading; | ||||
|  | ||||
|     if (this.props.in_reply_to) { | ||||
|       replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />; | ||||
| @@ -127,6 +128,12 @@ const ComposeForm = React.createClass({ | ||||
|  | ||||
|     let reply_to_other = !!this.props.in_reply_to && (this.props.in_reply_to.getIn(['account', 'id']) !== this.props.me); | ||||
|  | ||||
|     if (this.props.private) { | ||||
|       publishText = <span><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; | ||||
|     } else { | ||||
|       publishText = intl.formatMessage(messages.publish) + (!this.props.unlisted ? '!' : ''); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <div style={{ padding: '10px' }}> | ||||
|         <Motion defaultStyle={{ opacity: !this.props.spoiler ? 0 : 100, height: !this.props.spoiler ? 50 : 0 }} style={{ opacity: spring(!this.props.spoiler ? 0 : 100), height: spring(!this.props.spoiler ? 0 : 50) }}> | ||||
| @@ -154,7 +161,7 @@ const ComposeForm = React.createClass({ | ||||
|         /> | ||||
|  | ||||
|         <div style={{ marginTop: '10px', overflow: 'hidden' }}> | ||||
|           <div style={{ float: 'right' }}><Button text={intl.formatMessage(messages.publish)} onClick={this.handleSubmit} disabled={disabled} /></div> | ||||
|           <div style={{ float: 'right' }}><Button text={publishText} onClick={this.handleSubmit} disabled={disabled} /></div> | ||||
|           <div style={{ float: 'right', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={[this.props.spoiler_text, this.props.text].join('')} /></div> | ||||
|           <UploadButtonContainer style={{ paddingTop: '4px' }} /> | ||||
|         </div> | ||||
|   | ||||
| @@ -26,14 +26,14 @@ const makeMapStateToProps = () => { | ||||
|       sensitive: state.getIn(['compose', 'sensitive']), | ||||
|       spoiler: state.getIn(['compose', 'spoiler']), | ||||
|       spoiler_text: state.getIn(['compose', 'spoiler_text']), | ||||
|       unlisted: state.getIn(['compose', 'unlisted']), | ||||
|       unlisted: state.getIn(['compose', 'unlisted'], ), | ||||
|       private: state.getIn(['compose', 'private']), | ||||
|       fileDropDate: state.getIn(['compose', 'fileDropDate']), | ||||
|       is_submitting: state.getIn(['compose', 'is_submitting']), | ||||
|       is_uploading: state.getIn(['compose', 'is_uploading']), | ||||
|       in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to'])), | ||||
|       media_count: state.getIn(['compose', 'media_attachments']).size, | ||||
|       me: state.getIn(['compose', 'me']) | ||||
|       me: state.getIn(['compose', 'me']), | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,8 @@ const messages = defineMessages({ | ||||
|   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, | ||||
|   sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Sign out' }, | ||||
|   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, | ||||
|   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' } | ||||
|   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, | ||||
|   info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' } | ||||
| }); | ||||
|  | ||||
| const mapStateToProps = state => ({ | ||||
| @@ -34,6 +35,7 @@ const GettingStarted = ({ intl, me }) => { | ||||
|         <ColumnLink icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' /> | ||||
|         {followRequests} | ||||
|         <ColumnLink icon='users' text={intl.formatMessage(messages.blocks)} to='/blocks' /> | ||||
|         <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' /> | ||||
|         <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> | ||||
|       </div> | ||||
|  | ||||
|   | ||||
| @@ -43,6 +43,7 @@ const initialState = Immutable.Map({ | ||||
|   suggestion_token: null, | ||||
|   suggestions: Immutable.List(), | ||||
|   me: null, | ||||
|   default_privacy: 'public', | ||||
|   resetFileKey: Math.floor((Math.random() * 0x10000)) | ||||
| }); | ||||
|  | ||||
| @@ -64,6 +65,8 @@ function clearAll(state) { | ||||
|     map.set('spoiler_text', ''); | ||||
|     map.set('is_submitting', false); | ||||
|     map.set('in_reply_to', null); | ||||
|     map.set('unlisted', state.get('default_privacy') === 'unlisted'); | ||||
|     map.set('private', state.get('default_privacy') === 'private'); | ||||
|     map.update('media_attachments', list => list.clear()); | ||||
|   }); | ||||
| }; | ||||
| @@ -97,7 +100,7 @@ const insertSuggestion = (state, position, token, completion) => { | ||||
| export default function compose(state = initialState, action) { | ||||
|   switch(action.type) { | ||||
|   case STORE_HYDRATE: | ||||
|     return state.merge(action.state.get('compose')); | ||||
|     return clearAll(state.merge(action.state.get('compose'))); | ||||
|   case COMPOSE_MOUNT: | ||||
|     return state.set('mounted', true); | ||||
|   case COMPOSE_UNMOUNT: | ||||
|   | ||||
| @@ -28,15 +28,7 @@ | ||||
|   } | ||||
|  | ||||
|   &.button-secondary { | ||||
|     background-color: $color1; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $color1; | ||||
|     } | ||||
|  | ||||
|     &:disabled { | ||||
|       background-color: $color3; | ||||
|     } | ||||
|     // | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ $color1: #282c37; // darkest | ||||
| $color2: #d9e1e8; // lightest | ||||
| $color3: #9baec8; // lighter | ||||
| $color4: #2b90d9; // vibrant | ||||
| $color5: #fff; // white | ||||
| $color5: #ffffff; // white | ||||
| $color6: #df405a; // error red | ||||
| $color7: #79bd9a; // succ green | ||||
| $color8: #000; // black | ||||
| $color8: #000000; // black | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class Api::V1::TimelinesController < ApiController | ||||
|   end | ||||
|  | ||||
|   def public | ||||
|     @statuses = Status.as_public_timeline(current_account).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) | ||||
|     @statuses = Status.as_public_timeline(current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) | ||||
|     @statuses = cache_collection(@statuses) | ||||
|  | ||||
|     set_maps(@statuses) | ||||
| @@ -40,7 +40,7 @@ class Api::V1::TimelinesController < ApiController | ||||
|  | ||||
|   def tag | ||||
|     @tag      = Tag.find_by(name: params[:id].downcase) | ||||
|     @statuses = @tag.nil? ? [] : Status.as_tag_timeline(@tag, current_account).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) | ||||
|     @statuses = @tag.nil? ? [] : Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) | ||||
|     @statuses = cache_collection(@statuses) | ||||
|  | ||||
|     set_maps(@statuses) | ||||
|   | ||||
| @@ -21,7 +21,9 @@ class Settings::PreferencesController < ApplicationController | ||||
|       must_be_following: user_params[:interactions][:must_be_following] == '1', | ||||
|     } | ||||
|  | ||||
|     if current_user.update(user_params.except(:notification_emails, :interactions)) | ||||
|     current_user.settings['default_privacy'] = user_params[:settings][:default_privacy] | ||||
|  | ||||
|     if current_user.update(user_params.except(:notification_emails, :interactions, :settings)) | ||||
|       redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg') | ||||
|     else | ||||
|       render action: :show | ||||
| @@ -31,6 +33,6 @@ class Settings::PreferencesController < ApplicationController | ||||
|   private | ||||
|  | ||||
|   def user_params | ||||
|     params.require(:user).permit(:locale, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention], interactions: [:must_be_follower, :must_be_following]) | ||||
|     params.require(:user).permit(:locale, settings: [:default_privacy], notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention], interactions: [:must_be_follower, :must_be_following]) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -102,21 +102,25 @@ class Status < ApplicationRecord | ||||
|       where(account: [account] + account.following) | ||||
|     end | ||||
|  | ||||
|     def as_public_timeline(account = nil) | ||||
|     def as_public_timeline(account = nil, local_only = false) | ||||
|       query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') | ||||
|               .where(visibility: :public) | ||||
|               .where('(statuses.in_reply_to_id IS NULL OR statuses.in_reply_to_account_id = statuses.account_id)') | ||||
|               .where('statuses.reblog_of_id IS NULL') | ||||
|  | ||||
|       query = query.where('accounts.domain IS NULL') if local_only | ||||
|  | ||||
|       account.nil? ? filter_timeline_default(query) : filter_timeline_default(filter_timeline(query, account)) | ||||
|     end | ||||
|  | ||||
|     def as_tag_timeline(tag, account = nil) | ||||
|     def as_tag_timeline(tag, account = nil, local_only = false) | ||||
|       query = tag.statuses | ||||
|                  .joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') | ||||
|                  .where(visibility: :public) | ||||
|                  .where('statuses.reblog_of_id IS NULL') | ||||
|  | ||||
|       query = query.where('accounts.domain IS NULL') if local_only | ||||
|  | ||||
|       account.nil? ? filter_timeline_default(query) : filter_timeline_default(filter_timeline(query, account)) | ||||
|     end | ||||
|  | ||||
|   | ||||
| @@ -21,4 +21,8 @@ class User < ApplicationRecord | ||||
|   def send_devise_notification(notification, *args) | ||||
|     devise_mailer.send(notification, self, *args).deliver_later | ||||
|   end | ||||
|  | ||||
|   def setting_default_privacy | ||||
|     settings.default_privacy || (account.locked? ? 'private' : 'public') | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,24 +1,24 @@ | ||||
| object false | ||||
|  | ||||
| node(:meta) { | ||||
| node(:meta) do | ||||
|   { | ||||
|     access_token: @token, | ||||
|     locale: I18n.locale, | ||||
|     me: current_account.id, | ||||
|   } | ||||
| } | ||||
| end | ||||
|  | ||||
| node(:compose) { | ||||
| node(:compose) do | ||||
|   { | ||||
|     me: current_account.id, | ||||
|     private: current_account.locked?, | ||||
|     default_privacy: current_account.user.setting_default_privacy, | ||||
|   } | ||||
| } | ||||
| end | ||||
|  | ||||
| node(:accounts) { | ||||
| node(:accounts) do | ||||
|   { | ||||
|     current_account.id => partial('api/v1/accounts/show', object: current_account), | ||||
|   } | ||||
| } | ||||
| end | ||||
|  | ||||
| node(:settings) { @web_settings } | ||||
|   | ||||
| @@ -7,6 +7,8 @@ | ||||
|   .fields-group | ||||
|     = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) } | ||||
|  | ||||
|     = f.input :setting_default_privacy, collection: Status.visibilities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |visibility| I18n.t("statuses.visibilities.#{visibility}") }, required: false | ||||
|  | ||||
|   .fields-group | ||||
|     = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff| | ||||
|       = ff.input :follow, as: :boolean, wrapper: :with_label | ||||
|   | ||||
| @@ -97,8 +97,12 @@ en: | ||||
|     settings: Settings | ||||
|     two_factor_auth: Two-factor Authentication | ||||
|   statuses: | ||||
|     over_character_limit: character limit of %{max} exceeded | ||||
|     open_in_web: Open in web | ||||
|     over_character_limit: character limit of %{max} exceeded | ||||
|     visibilities: | ||||
|       private: Only show to followers | ||||
|       public: Public | ||||
|       unlisted: Public, but do not display on the public timeline | ||||
|   stream_entries: | ||||
|     click_to_show: Click to show | ||||
|     favourited: favourited a post by | ||||
|   | ||||
| @@ -23,6 +23,7 @@ en: | ||||
|         note: Bio | ||||
|         otp_attempt: Two-factor code | ||||
|         password: Password | ||||
|         setting_default_privacy: Post privacy | ||||
|         username: Username | ||||
|       interactions: | ||||
|         must_be_follower: Block notifications from non-followers | ||||
|   | ||||
		Reference in New Issue
	
	Block a user