From 347a153b3dc73068aedf7510c395cc350a553629 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 6 Feb 2017 23:16:20 +0100 Subject: [PATCH] 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 --- .../javascripts/components/components/button.jsx | 2 +- .../features/compose/components/compose_form.jsx | 15 +++++++++++---- .../compose/containers/compose_form_container.jsx | 4 ++-- .../components/features/getting_started/index.jsx | 4 +++- .../javascripts/components/reducers/compose.jsx | 5 ++++- app/assets/stylesheets/components.scss | 10 +--------- app/assets/stylesheets/variables.scss | 4 ++-- app/controllers/api/v1/timelines_controller.rb | 4 ++-- .../settings/preferences_controller.rb | 6 ++++-- app/models/status.rb | 8 ++++++-- app/models/user.rb | 4 ++++ app/views/home/initial_state.json.rabl | 14 +++++++------- app/views/settings/preferences/show.html.haml | 2 ++ config/locales/en.yml | 6 +++++- config/locales/simple_form.en.yml | 1 + 15 files changed, 55 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/components/components/button.jsx b/app/assets/javascripts/components/components/button.jsx index 19c52550a..6790cc6cb 100644 --- a/app/assets/javascripts/components/components/button.jsx +++ b/app/assets/javascripts/components/components/button.jsx @@ -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', diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx index a8b03b3a6..48939054d 100644 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -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 = ; @@ -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 = {intl.formatMessage(messages.publish)}; + } else { + publishText = intl.formatMessage(messages.publish) + (!this.props.unlisted ? '!' : ''); + } + return (
@@ -154,7 +161,7 @@ const ComposeForm = React.createClass({ />
-
+
diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx index 8ccfce059..c027875cd 100644 --- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx +++ b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx @@ -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']), }; }; diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx index 502b5c9d1..a0bf3a694 100644 --- a/app/assets/javascripts/components/features/getting_started/index.jsx +++ b/app/assets/javascripts/components/features/getting_started/index.jsx @@ -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 }) => { {followRequests} +
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx index d3a84842f..1b903ed44 100644 --- a/app/assets/javascripts/components/reducers/compose.jsx +++ b/app/assets/javascripts/components/reducers/compose.jsx @@ -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: diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss index 85d34c531..40c39678f 100644 --- a/app/assets/stylesheets/components.scss +++ b/app/assets/stylesheets/components.scss @@ -28,15 +28,7 @@ } &.button-secondary { - background-color: $color1; - - &:hover { - background-color: $color1; - } - - &:disabled { - background-color: $color3; - } + // } } diff --git a/app/assets/stylesheets/variables.scss b/app/assets/stylesheets/variables.scss index de4157af8..cdf81c818 100644 --- a/app/assets/stylesheets/variables.scss +++ b/app/assets/stylesheets/variables.scss @@ -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 diff --git a/app/controllers/api/v1/timelines_controller.rb b/app/controllers/api/v1/timelines_controller.rb index 854ca13e6..a8cc2b288 100644 --- a/app/controllers/api/v1/timelines_controller.rb +++ b/app/controllers/api/v1/timelines_controller.rb @@ -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) diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 5ad825675..40888963e 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -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 diff --git a/app/models/status.rb b/app/models/status.rb index ab68f4e69..142dec64e 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index b34144f2c..08aac2679 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/views/home/initial_state.json.rabl b/app/views/home/initial_state.json.rabl index 0e9736f5f..71949ab0e 100644 --- a/app/views/home/initial_state.json.rabl +++ b/app/views/home/initial_state.json.rabl @@ -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 } diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index 747977f9c..aee0540d2 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -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 diff --git a/config/locales/en.yml b/config/locales/en.yml index 16b406745..b1b1e7995 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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 diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 09957c914..4d1758f82 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -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