From c9fd6f386c5ae0938f1f9c2d1134508e66231e23 Mon Sep 17 00:00:00 2001 From: TheInventrix Date: Mon, 7 Aug 2017 17:50:15 -0600 Subject: unify short description styling (#4548) apply same style class to the About description on both the landing page and the about/more page --- app/views/about/more.html.haml | 9 +++++---- app/views/about/show.html.haml | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'app/views') diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 6342c7248..189b3bf1e 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -35,9 +35,10 @@ %i.fa.fa-external-link{ style: 'padding-left: 5px;' } .container.hero - .heading - %h3= t('about.description_headline', domain: site_hostname) - %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) + .about-short + .heading + %h3= t('about.description_headline', domain: site_hostname) + %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) .information-board .container @@ -64,4 +65,4 @@ .container %p = link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon' - = " (#{@instance_presenter.version_number})" + = " (#{@instance_presenter.version_number})" \ No newline at end of file diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index d0c9a6650..acdb12ad7 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -58,7 +58,7 @@ = @instance_presenter.closed_registrations_message.html_safe = link_to t('about.find_another_instance'), 'https://joinmastodon.org/', class: 'button button-alternative button--block' - .learn-more-cta + .about-short .container %h3= t('about.description_headline', domain: site_hostname) %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) @@ -77,4 +77,4 @@ .container %p = link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon' - = " (#{@instance_presenter.version_number})" + = " (#{@instance_presenter.version_number})" \ No newline at end of file -- cgit From dcbc1af38a3ddc289783d9e9021690692bc1438e Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 8 Aug 2017 22:49:32 +0900 Subject: Fix short description in about/more page (#4554) --- app/views/about/more.html.haml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'app/views') diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 189b3bf1e..a6fd265fa 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -35,10 +35,9 @@ %i.fa.fa-external-link{ style: 'padding-left: 5px;' } .container.hero - .about-short - .heading - %h3= t('about.description_headline', domain: site_hostname) - %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) + .heading + %h3= t('about.description_headline', domain: site_hostname) + %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) .information-board .container -- cgit From a2aeacbfeed5dc7070c37a22bb2c4bac1a58a526 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 13 Aug 2017 00:45:04 +0200 Subject: Add alternate links to ActivityPub resources from HTML/HEAD variants (#4586) --- app/controllers/concerns/account_controller_concern.rb | 8 ++++++++ app/controllers/statuses_controller.rb | 7 ++++++- app/controllers/stream_entries_controller.rb | 7 ++++++- app/views/accounts/show.html.haml | 1 + app/views/stream_entries/show.html.haml | 1 + spec/controllers/concerns/account_controller_concern_spec.rb | 2 +- spec/controllers/stream_entries_controller_spec.rb | 2 +- 7 files changed, 24 insertions(+), 4 deletions(-) (limited to 'app/views') diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb index d36fc8c93..5b9981aa2 100644 --- a/app/controllers/concerns/account_controller_concern.rb +++ b/app/controllers/concerns/account_controller_concern.rb @@ -23,6 +23,7 @@ module AccountControllerConcern [ webfinger_account_link, atom_account_url_link, + actor_url_link, ] ) end @@ -41,6 +42,13 @@ module AccountControllerConcern ] end + def actor_url_link + [ + ActivityPub::TagManager.instance.uri_for(@account), + [%w(rel alternate), %w(type application/activity+json)], + ] + end + def webfinger_account_url webfinger_url(resource: @account.to_webfinger_s) end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 8e0ce0ec3..0cce2ba23 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -36,7 +36,12 @@ class StatusesController < ApplicationController end def set_link_headers - response.headers['Link'] = LinkHeader.new([[account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]]]) + response.headers['Link'] = LinkHeader.new( + [ + [account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]], + [ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]], + ] + ) end def set_status diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 3eb91d830..ccb15495e 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -38,7 +38,12 @@ class StreamEntriesController < ApplicationController end def set_link_headers - response.headers['Link'] = LinkHeader.new([[account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]]]) + response.headers['Link'] = LinkHeader.new( + [ + [account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]], + [ActivityPub::TagManager.instance.uri_for(@stream_entry.activity), [%w(rel alternate), %w(type application/activity+json)]], + ] + ) end def set_stream_entry diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 150c14791..74e695fc3 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -7,6 +7,7 @@ %link{ rel: 'salmon', href: api_salmon_url(@account.id) }/ %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/ + %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ %meta{ property: 'og:type', content: 'profile' }/ = render 'og', account: @account, url: short_account_url(@account, only_path: false) diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml index 80ea30eb1..5ef72f804 100644 --- a/app/views/stream_entries/show.html.haml +++ b/app/views/stream_entries/show.html.haml @@ -4,6 +4,7 @@ %link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/ %link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: account_stream_entry_url(@account, @stream_entry), format: 'json') }/ + %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@stream_entry.activity) }/ %meta{ property: 'og:site_name', content: site_title }/ %meta{ property: 'og:type', content: 'article' }/ diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb index bdc181edc..ae46f9ba6 100644 --- a/spec/controllers/concerns/account_controller_concern_spec.rb +++ b/spec/controllers/concerns/account_controller_concern_spec.rb @@ -33,7 +33,7 @@ describe ApplicationController, type: :controller do it 'sets link headers' do account = Fabricate(:account, username: 'username') get 'success', params: { account_username: 'username' } - expect(response.headers['Link'].to_s).to eq '; rel="lrdd"; type="application/xrd+xml", ; rel="alternate"; type="application/atom+xml"' + expect(response.headers['Link'].to_s).to eq '; rel="lrdd"; type="application/xrd+xml", ; rel="alternate"; type="application/atom+xml", ; rel="alternate"; type="application/activity+json"' end it 'returns http success' do diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb index 2cc428e0c..808cf667c 100644 --- a/spec/controllers/stream_entries_controller_spec.rb +++ b/spec/controllers/stream_entries_controller_spec.rb @@ -21,7 +21,7 @@ RSpec.describe StreamEntriesController, type: :controller do get route, params: { account_username: alice.username, id: status.stream_entry.id } - expect(response.headers['Link'].to_s).to eq "; rel=\"alternate\"; type=\"application/atom+xml\"" + expect(response.headers['Link'].to_s).to eq "; rel=\"alternate\"; type=\"application/atom+xml\", ; rel=\"alternate\"; type=\"application/activity+json\"" end end -- cgit From 3c6503038ecad20f1b8fa0c9ea7e46087c6e3f22 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 14 Aug 2017 04:53:31 +0200 Subject: Add protocol handler. Handle follow intents (#4511) * Add protocol handler. Handle follow intents * Add share intent * Improve code in intents controller * Adjust share form CSS --- app/controllers/intents_controller.rb | 18 ++++++++++ app/controllers/shares_controller.rb | 25 ++++++++++++++ .../mastodon/containers/compose_container.js | 39 ++++++++++++++++++++++ app/javascript/mastodon/containers/mastodon.js | 5 +++ .../mastodon/features/standalone/compose/index.js | 18 ++++++++++ app/javascript/mastodon/reducers/compose.js | 12 ++++++- app/javascript/packs/share.js | 24 +++++++++++++ app/javascript/styles/containers.scss | 15 +++++++++ app/presenters/initial_state_presenter.rb | 3 +- app/serializers/initial_state_serializer.rb | 2 ++ app/views/shares/show.html.haml | 5 +++ config/routes.rb | 4 ++- 12 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 app/controllers/intents_controller.rb create mode 100644 app/controllers/shares_controller.rb create mode 100644 app/javascript/mastodon/containers/compose_container.js create mode 100644 app/javascript/mastodon/features/standalone/compose/index.js create mode 100644 app/javascript/packs/share.js create mode 100644 app/views/shares/show.html.haml (limited to 'app/views') diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb new file mode 100644 index 000000000..504befd1f --- /dev/null +++ b/app/controllers/intents_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class IntentsController < ApplicationController + def show + uri = Addressable::URI.parse(params[:uri]) + + if uri.scheme == 'web+mastodon' + case uri.host + when 'follow' + return redirect_to authorize_follow_path(acct: uri.query_values['uri'].gsub(/\Aacct:/, '')) + when 'share' + return redirect_to share_path(text: uri.query_values['text']) + end + end + + not_found + end +end diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb new file mode 100644 index 000000000..d70d66ff8 --- /dev/null +++ b/app/controllers/shares_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class SharesController < ApplicationController + layout 'public' + + before_action :authenticate_user! + + def show + serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) + @initial_state_json = serializable_resource.to_json + end + + private + + def initial_state_params + { + settings: Web::Setting.find_by(user: current_user)&.data || {}, + push_subscription: current_account.user.web_push_subscription(current_session), + current_account: current_account, + token: current_session.token, + admin: Account.find_local(Setting.site_contact_username), + text: params[:text], + } + end +end diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js new file mode 100644 index 000000000..db452d03a --- /dev/null +++ b/app/javascript/mastodon/containers/compose_container.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import PropTypes from 'prop-types'; +import configureStore from '../store/configureStore'; +import { hydrateStore } from '../actions/store'; +import { IntlProvider, addLocaleData } from 'react-intl'; +import { getLocale } from '../locales'; +import Compose from '../features/standalone/compose'; + +const { localeData, messages } = getLocale(); +addLocaleData(localeData); + +const store = configureStore(); +const initialStateContainer = document.getElementById('initial-state'); + +if (initialStateContainer !== null) { + const initialState = JSON.parse(initialStateContainer.textContent); + store.dispatch(hydrateStore(initialState)); +} + +export default class TimelineContainer extends React.PureComponent { + + static propTypes = { + locale: PropTypes.string.isRequired, + }; + + render () { + const { locale } = this.props; + + return ( + + + + + + ); + } + +} diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 87ab6023c..fe534d1c1 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -89,6 +89,11 @@ export default class Mastodon extends React.PureComponent { Notification.requestPermission(); } + if (typeof navigator.registerProtocolHandler !== 'undefined') { + const handlerUrl = window.location.protocol + '//' + window.location.host + '/intent?uri=%s'; + navigator.registerProtocolHandler('web+mastodon', handlerUrl, 'Mastodon'); + } + store.dispatch(showOnboardingOnce()); } diff --git a/app/javascript/mastodon/features/standalone/compose/index.js b/app/javascript/mastodon/features/standalone/compose/index.js new file mode 100644 index 000000000..96d07fefb --- /dev/null +++ b/app/javascript/mastodon/features/standalone/compose/index.js @@ -0,0 +1,18 @@ +import React from 'react'; +import ComposeFormContainer from '../../compose/containers/compose_form_container'; +import NotificationsContainer from '../../ui/containers/notifications_container'; +import LoadingBarContainer from '../../ui/containers/loading_bar_container'; + +export default class Compose extends React.PureComponent { + + render () { + return ( +
+ + + +
+ ); + } + +} diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index e137b774e..34f5dab7f 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -141,10 +141,20 @@ const privacyPreference = (a, b) => { } }; +const hydrate = (state, hydratedState) => { + state = clearAll(state.merge(hydratedState)); + + if (hydratedState.has('text')) { + state = state.set('text', hydratedState.get('text')); + } + + return state; +}; + export default function compose(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - return clearAll(state.merge(action.state.get('compose'))); + return hydrate(state, action.state.get('compose')); case COMPOSE_MOUNT: return state.set('mounted', true); case COMPOSE_UNMOUNT: diff --git a/app/javascript/packs/share.js b/app/javascript/packs/share.js new file mode 100644 index 000000000..51e4ae38b --- /dev/null +++ b/app/javascript/packs/share.js @@ -0,0 +1,24 @@ +import loadPolyfills from '../mastodon/load_polyfills'; + +require.context('../images/', true); + +function loaded() { + const ComposeContainer = require('../mastodon/containers/compose_container').default; + const React = require('react'); + const ReactDOM = require('react-dom'); + const mountNode = document.getElementById('mastodon-compose'); + + if (mountNode !== null) { + const props = JSON.parse(mountNode.getAttribute('data-props')); + ReactDOM.render(, mountNode); + } +} + +function main() { + const ready = require('../mastodon/ready').default; + ready(loaded); +} + +loadPolyfills().then(main).catch(error => { + console.error(error); +}); diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss index 536f4e5a1..063db44db 100644 --- a/app/javascript/styles/containers.scss +++ b/app/javascript/styles/containers.scss @@ -44,6 +44,21 @@ } } +.compose-standalone { + .compose-form { + width: 400px; + margin: 0 auto; + padding: 20px 0; + margin-top: 40px; + box-sizing: border-box; + + @media screen and (max-width: 400px) { + margin-top: 0; + padding: 20px; + } + } +} + .account-header { width: 400px; margin: 0 auto; diff --git a/app/presenters/initial_state_presenter.rb b/app/presenters/initial_state_presenter.rb index 9507aad4a..70c496be8 100644 --- a/app/presenters/initial_state_presenter.rb +++ b/app/presenters/initial_state_presenter.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true class InitialStatePresenter < ActiveModelSerializers::Model - attributes :settings, :push_subscription, :token, :current_account, :admin + attributes :settings, :push_subscription, :token, + :current_account, :admin, :text end diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 0191948b1..0ac5e8319 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -34,6 +34,8 @@ class InitialStateSerializer < ActiveModel::Serializer store[:default_sensitive] = object.current_account.user.setting_default_sensitive end + store[:text] = object.text if object.text + store end diff --git a/app/views/shares/show.html.haml b/app/views/shares/show.html.haml new file mode 100644 index 000000000..44b6f145f --- /dev/null +++ b/app/views/shares/show.html.haml @@ -0,0 +1,5 @@ +- content_for :header_tags do + %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) + = javascript_pack_tag 'share', integrity: true, crossorigin: 'anonymous' + +#mastodon-compose{ data: { props: Oj.dump(default_props) } } diff --git a/config/routes.rb b/config/routes.rb index a1b206716..f75de5304 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ Rails.application.routes.draw do get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger get 'manifest', to: 'manifests#show', defaults: { format: 'json' } + get 'intent', to: 'intents#show' devise_for :users, path: 'auth', controllers: { sessions: 'auth/sessions', @@ -86,12 +87,13 @@ Rails.application.routes.draw do # Remote follow resource :authorize_follow, only: [:show, :create] + resource :share, only: [:show, :create] namespace :admin do resources :subscriptions, only: [:index] resources :domain_blocks, only: [:index, :new, :create, :show, :destroy] resource :settings, only: [:edit, :update] - + resources :instances, only: [:index] do collection do post :resubscribe -- cgit From 5b9ae7981e2458a322f9e2fbeac9b334a15936bc Mon Sep 17 00:00:00 2001 From: unarist Date: Mon, 14 Aug 2017 21:09:00 +0900 Subject: Update /admin/accounts/:id view for ActivityPub (#4600) * Add protocol field * Switch protocol specific information according to active protocol * Hide PuSH subscription related buttons if ActivityPub is active --- app/views/admin/accounts/show.html.haml | 43 +++++++++++++++++++++------------ config/locales/en.yml | 3 +++ 2 files changed, 31 insertions(+), 15 deletions(-) (limited to 'app/views') diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 5ad1fd6ee..5c781e817 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -32,18 +32,30 @@ %th= t('admin.accounts.profile_url') %td= link_to @account.url, @account.url %tr - %th= t('admin.accounts.feed_url') - %td= link_to @account.remote_url, @account.remote_url - %tr - %th= t('admin.accounts.push_subscription_expires') - %td - - if @account.subscribed? - = l @account.subscription_expires_at - - else - = t('admin.accounts.not_subscribed') - %tr - %th= t('admin.accounts.salmon_url') - %td= link_to @account.salmon_url, @account.salmon_url + %th= t('admin.accounts.protocol') + %td= @account.protocol + + - if @account.ostatus? + %tr + %th= t('admin.accounts.feed_url') + %td= link_to @account.remote_url, @account.remote_url + %tr + %th= t('admin.accounts.push_subscription_expires') + %td + - if @account.subscribed? + = l @account.subscription_expires_at + - else + = t('admin.accounts.not_subscribed') + %tr + %th= t('admin.accounts.salmon_url') + %td= link_to @account.salmon_url, @account.salmon_url + - elsif @account.activitypub? + %tr + %th= t('admin.accounts.inbox_url') + %td= link_to @account.inbox_url, @account.inbox_url + %tr + %th= t('admin.accounts.outbox_url') + %td= link_to @account.outbox_url, @account.outbox_url %tr %th= t('admin.accounts.follows') @@ -74,9 +86,10 @@ - if @account.user&.otp_required_for_login? = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' - else - = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' - - if @account.subscribed? - = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' + - if @account.ostatus? + = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' + - if @account.subscribed? + = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' %div{ style: 'float: left' } diff --git a/config/locales/en.yml b/config/locales/en.yml index 1fa0de90b..210bfc5b4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -57,6 +57,7 @@ en: feed_url: Feed URL followers: Followers follows: Follows + inbox_url: Inbox URL ip: IP location: all: All @@ -76,8 +77,10 @@ en: alphabetic: Alphabetic most_recent: Most recent title: Order + outbox_url: Outbox URL perform_full_suspension: Perform full suspension profile_url: Profile URL + protocol: Protocol public: Public push_subscription_expires: PuSH subscription expires redownload: Refresh avatar -- cgit From ca7ea1aba92f97e93f3c49e972f686a78779fd71 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 16 Aug 2017 17:12:58 +0200 Subject: Redesign public profiles (#4608) * Redesign public profiles * Responsive design * Change public profile status filtering defaults and add options - No longer displays private/direct toots even if you are permitted access - By default omits replies - "With replies" option - "Media only" option * Redesign account grid cards * Fix style issues --- app/controllers/accounts_controller.rb | 41 +++++- app/helpers/application_helper.rb | 4 + app/javascript/styles/accounts.scss | 230 ++++++++++++++++++++++-------- app/javascript/styles/landing_strip.scss | 13 ++ app/javascript/styles/stream_entries.scss | 17 +++ app/models/account.rb | 1 + app/views/accounts/_grid_card.html.haml | 11 +- app/views/accounts/_header.html.haml | 57 +++++--- app/views/accounts/show.html.haml | 7 +- app/views/shared/_landing_strip.html.haml | 9 +- config/locales/en.yml | 6 +- config/routes.rb | 2 + 12 files changed, 310 insertions(+), 88 deletions(-) (limited to 'app/views') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 4dc0a783d..c6b98628e 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -7,8 +7,14 @@ class AccountsController < ApplicationController def show respond_to do |format| format.html do - @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id]) + if current_account && @account.blocking?(current_account) + @statuses = [] + return + end + + @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) @statuses = cache_collection(@statuses, Status) + @next_url = next_url unless @statuses.empty? end format.atom do @@ -24,7 +30,40 @@ class AccountsController < ApplicationController private + def filtered_statuses + default_statuses.tap do |statuses| + statuses.merge!(only_media_scope) if request.path.ends_with?('/media') + statuses.merge!(no_replies_scope) unless request.path.ends_with?('/with_replies') + end + end + + def default_statuses + @account.statuses.where(visibility: [:public, :unlisted]) + end + + def only_media_scope + Status.where(id: account_media_status_ids) + end + + def account_media_status_ids + @account.media_attachments.attached.reorder(nil).select(:status_id).distinct + end + + def no_replies_scope + Status.without_replies + end + def set_account @account = Account.find_local!(params[:username]) end + + def next_url + if request.path.ends_with?('/media') + short_account_media_url(@account, max_id: @statuses.last.id) + elsif request.path.ends_with?('/with_replies') + short_account_with_replies_url(@account, max_id: @statuses.last.id) + else + short_account_url(@account, max_id: @statuses.last.id) + end + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9f50d8bdb..61d4442c1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -5,6 +5,10 @@ module ApplicationHelper current_page?(path) ? 'active' : '' end + def active_link_to(label, path, options = {}) + link_to label, path, options.merge(class: active_nav_class(path)) + end + def show_landing_strip? !user_signed_in? && !single_user_mode? end diff --git a/app/javascript/styles/accounts.scss b/app/javascript/styles/accounts.scss index 66da75828..f1fbe873b 100644 --- a/app/javascript/styles/accounts.scss +++ b/app/javascript/styles/accounts.scss @@ -1,21 +1,15 @@ .card { - background: $ui-base-color; + background-color: lighten($ui-base-color, 4%); background-size: cover; background-position: center; - padding: 60px 0; - padding-bottom: 0; border-radius: 4px 4px 0 0; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); overflow: hidden; position: relative; - - @media screen and (max-width: 740px) { - border-radius: 0; - box-shadow: none; - } + display: flex; &::after { - background: linear-gradient(rgba($base-shadow-color, 0.5), rgba($base-shadow-color, 0.8)); + background: rgba(darken($ui-base-color, 8%), 0.5); display: block; content: ""; position: absolute; @@ -26,6 +20,31 @@ z-index: 1; } + @media screen and (max-width: 740px) { + border-radius: 0; + box-shadow: none; + } + + .card__illustration { + padding: 60px 0; + position: relative; + flex: 1 1 auto; + display: flex; + justify-content: center; + align-items: center; + } + + .card__bio { + max-width: 260px; + flex: 1 1 auto; + display: flex; + flex-direction: column; + justify-content: space-between; + background: rgba(darken($ui-base-color, 8%), 0.8); + position: relative; + z-index: 2; + } + &.compact { padding: 30px 0; border-radius: 4px; @@ -44,11 +63,12 @@ font-size: 20px; line-height: 18px * 1.5; color: $primary-text-color; + padding: 10px 15px; + padding-bottom: 0; font-weight: 500; - text-align: center; position: relative; z-index: 2; - text-shadow: 0 0 2px $base-shadow-color; + margin-bottom: 30px; small { display: block; @@ -61,7 +81,6 @@ .avatar { width: 120px; margin: 0 auto; - margin-bottom: 15px; position: relative; z-index: 2; @@ -70,43 +89,68 @@ height: 120px; display: block; border-radius: 120px; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); } } .controls { position: absolute; - top: 10px; - right: 10px; + top: 15px; + left: 15px; z-index: 2; + + .icon-button { + color: rgba($white, 0.8); + text-decoration: none; + font-size: 13px; + line-height: 13px; + font-weight: 500; + + .fa { + font-weight: 400; + margin-right: 5px; + } + + &:hover, + &:active, + &:focus { + color: $white; + } + } } - .details { - display: flex; - margin-top: 30px; - position: relative; - z-index: 2; - flex-direction: row; + .roles { + margin-bottom: 30px; + padding: 0 15px; } .details-counters { + margin-top: 30px; display: flex; flex-direction: row; - order: 0; + width: 100%; } .counter { - width: 80px; + width: 33.3%; + box-sizing: border-box; + flex: 0 0 auto; color: $ui-primary-color; padding: 5px 10px 0; margin-bottom: 10px; - border-right: 1px solid $ui-primary-color; + border-right: 1px solid lighten($ui-base-color, 4%); cursor: default; + text-align: center; position: relative; a { display: block; } + &:last-child { + border-right: 0; + } + &::after { display: block; content: ""; @@ -116,7 +160,7 @@ width: 100%; border-bottom: 4px solid $ui-primary-color; opacity: 0.5; - transition: all 0.8s ease; + transition: all 400ms ease; } &.active { @@ -129,7 +173,7 @@ &:hover { &::after { opacity: 1; - transition-duration: 0.2s; + transition-duration: 100ms; } } @@ -140,44 +184,40 @@ .counter-label { font-size: 12px; - text-transform: uppercase; display: block; margin-bottom: 5px; - text-shadow: 0 0 2px $base-shadow-color; } .counter-number { font-weight: 500; font-size: 18px; color: $primary-text-color; + font-family: 'mastodon-font-display', sans-serif; } } .bio { - flex: 1; font-size: 14px; line-height: 18px; - padding: 5px 10px; + padding: 0 15px; color: $ui-secondary-color; - order: 1; } @media screen and (max-width: 480px) { - .details { - display: block; - } + display: block; - .bio { - text-align: center; - margin-bottom: 20px; + .card__bio { + max-width: none; } - .counter { - flex: 1 1 auto; + .name, + .roles { + text-align: center; + margin-bottom: 15px; } - .counter:last-child { - border-right: none; + .bio { + margin-bottom: 15px; } } } @@ -264,13 +304,15 @@ .accounts-grid { box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - background: $simple-background-color; + background: darken($simple-background-color, 8%); border-radius: 0 0 4px 4px; padding: 20px 10px; padding-bottom: 10px; overflow: hidden; display: flex; flex-wrap: wrap; + z-index: 2; + position: relative; @media screen and (max-width: 740px) { border-radius: 0; @@ -280,10 +322,11 @@ .account-grid-card { box-sizing: border-box; width: 335px; - border: 1px solid $ui-secondary-color; + background: $simple-background-color; border-radius: 4px; color: $ui-base-color; margin-bottom: 10px; + position: relative; &:nth-child(odd) { margin-right: 10px; @@ -291,26 +334,52 @@ .account-grid-card__header { overflow: hidden; - padding: 10px; - border-bottom: 1px solid $ui-secondary-color; + height: 100px; + border-radius: 4px 4px 0 0; + background-color: lighten($ui-base-color, 4%); + background-size: cover; + background-position: center; + position: relative; + + &::after { + background: rgba(darken($ui-base-color, 8%), 0.5); + display: block; + content: ""; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 1; + } + } + + .account-grid-card__avatar { + box-sizing: border-box; + padding: 15px; + position: absolute; + z-index: 2; + top: 100px - (40px + 2px); + left: -2px; } .avatar { - width: 60px; - height: 60px; - float: left; - margin-right: 15px; + width: 80px; + height: 80px; img { display: block; - width: 60px; - height: 60px; - border-radius: 60px; + width: 80px; + height: 80px; + border-radius: 80px; + border: 2px solid $simple-background-color; } } .name { + padding: 15px; padding-top: 10px; + padding-left: 15px + 80px + 15px; a { display: block; @@ -318,6 +387,7 @@ text-decoration: none; text-overflow: ellipsis; overflow: hidden; + font-weight: 500; &:hover { .display_name { @@ -328,30 +398,36 @@ } .display_name { - font-size: 14px; + font-size: 16px; display: block; } .username { - color: $ui-highlight-color; + color: lighten($ui-base-color, 34%); + font-size: 14px; + font-weight: 400; } .note { - padding: 10px; + padding: 10px 15px; padding-top: 15px; - color: $ui-primary-color; + box-sizing: border-box; + color: lighten($ui-base-color, 26%); word-wrap: break-word; + min-height: 80px; } } } .nothing-here { + width: 100%; + display: block; color: $ui-primary-color; font-size: 14px; font-weight: 500; text-align: center; - padding: 15px 0; - padding-bottom: 25px; + padding: 60px 0; + padding-top: 55px; cursor: default; } @@ -416,3 +492,43 @@ color: $ui-base-color; } } + +.activity-stream-tabs { + background: $simple-background-color; + border-bottom: 1px solid $ui-secondary-color; + position: relative; + z-index: 2; + + a { + display: inline-block; + padding: 15px; + text-decoration: none; + color: $ui-highlight-color; + text-transform: uppercase; + font-weight: 500; + + &:hover, + &:active, + &:focus { + color: lighten($ui-highlight-color, 8%); + } + + &.active { + color: $ui-base-color; + cursor: default; + } + } +} + +.account-role { + display: inline-block; + padding: 4px 6px; + cursor: default; + border-radius: 3px; + font-size: 12px; + line-height: 12px; + font-weight: 500; + color: $success-green; + background-color: rgba($success-green, 0.1); + border: 1px solid rgba($success-green, 0.5); +} diff --git a/app/javascript/styles/landing_strip.scss b/app/javascript/styles/landing_strip.scss index d2ac5b822..15ff84912 100644 --- a/app/javascript/styles/landing_strip.scss +++ b/app/javascript/styles/landing_strip.scss @@ -5,6 +5,8 @@ padding: 14px; border-radius: 4px; margin-bottom: 20px; + display: flex; + align-items: center; strong, a { @@ -15,4 +17,15 @@ color: inherit; text-decoration: underline; } + + .logo { + width: 30px; + height: 30px; + flex: 0 0 auto; + margin-right: 15px; + } + + @media screen and (max-width: 740px) { + margin-bottom: 0; + } } diff --git a/app/javascript/styles/stream_entries.scss b/app/javascript/styles/stream_entries.scss index 9e062c57e..1192e2a80 100644 --- a/app/javascript/styles/stream_entries.scss +++ b/app/javascript/styles/stream_entries.scss @@ -8,6 +8,7 @@ .detailed-status.light, .status.light { border-bottom: 1px solid $ui-secondary-color; + animation: none; } &:last-child { @@ -34,6 +35,14 @@ } } } + + @media screen and (max-width: 740px) { + &, + .detailed-status.light, + .status.light { + border-radius: 0 !important; + } + } } &.with-header { @@ -44,6 +53,14 @@ .status.light { border-radius: 0; } + + &:last-child { + &, + .detailed-status.light, + .status.light { + border-radius: 0 0 4px 4px; + } + } } } } diff --git a/app/models/account.rb b/app/models/account.rb index a7264353e..c4c168160 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -105,6 +105,7 @@ class Account < ApplicationRecord :current_sign_in_ip, :current_sign_in_at, :confirmed?, + :admin?, :locale, to: :user, prefix: true, diff --git a/app/views/accounts/_grid_card.html.haml b/app/views/accounts/_grid_card.html.haml index 0571d1d5e..305eb2c44 100644 --- a/app/views/accounts/_grid_card.html.haml +++ b/app/views/accounts/_grid_card.html.haml @@ -1,8 +1,9 @@ .account-grid-card - .account-grid-card__header + .account-grid-card__header{ style: "background-image: url(#{account.header.url(:original)})" } + .account-grid-card__avatar .avatar= image_tag account.avatar.url(:original) - .name - = link_to TagManager.instance.url_for(account) do - %span.display_name.emojify= display_name(account) - %span.username @#{account.acct} + .name + = link_to TagManager.instance.url_for(account) do + %span.display_name.emojify= display_name(account) + %span.username @#{account.acct} %p.note.emojify= truncate(strip_tags(account.note), length: 150) diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 6451a5573..8009e903e 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,34 +1,51 @@ .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } - - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) - .controls - - if current_account.following?(account) - = link_to t('accounts.unfollow'), account_unfollow_path(account), data: { method: :post }, class: 'button' - - else - = link_to t('accounts.follow'), account_follow_path(account), data: { method: :post }, class: 'button' - - elsif !user_signed_in? - .controls - .remote-follow - = link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button' - .avatar= image_tag account.avatar.url(:original), class: 'u-photo' - %h1.name - %span.p-name.emojify= display_name(account) - %small - %span @#{account.username} - = fa_icon('lock') if account.locked? - .details + .card__illustration + - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) + .controls + - if current_account.following?(account) + = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-times' + = t('accounts.unfollow') + - else + = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.follow') + - elsif !user_signed_in? + .controls + .remote-follow + = link_to account_remote_follow_path(account), class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.remote_follow') + + .avatar= image_tag account.avatar.url(:original), class: 'u-photo' + + .card__bio + %h1.name + %span.p-name.emojify= display_name(account) + %small + %span @#{account.local_username_and_domain} + = fa_icon('lock') if account.locked? + + - if account.user_admin? + .roles + .account-role + = t 'accounts.roles.admin' + .bio .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account) .details-counters .counter{ class: active_nav_class(short_account_url(account)) } = link_to short_account_url(account), class: 'u-url u-uid' do - %span.counter-label= t('accounts.posts') %span.counter-number= number_with_delimiter account.statuses_count + %span.counter-label= t('accounts.posts') + .counter{ class: active_nav_class(account_following_index_url(account)) } = link_to account_following_index_url(account) do - %span.counter-label= t('accounts.following') %span.counter-number= number_with_delimiter account.following_count + %span.counter-label= t('accounts.following') + .counter{ class: active_nav_class(account_followers_url(account)) } = link_to account_followers_url(account) do - %span.counter-label= t('accounts.followers') %span.counter-number= number_with_delimiter account.followers_count + %span.counter-label= t('accounts.followers') diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 74e695fc3..ec44f4c74 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -20,6 +20,11 @@ = render 'header', account: @account + .activity-stream-tabs + = active_link_to t('accounts.posts'), short_account_url(@account) + = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) + = active_link_to t('accounts.media'), short_account_media_url(@account) + - if @statuses.empty? .accounts-grid = render 'nothing_here' @@ -29,4 +34,4 @@ - if @statuses.size == 20 .pagination - = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), short_account_url(@account, max_id: @statuses.last.id), class: 'next', rel: 'next' + = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), @next_url, class: 'next', rel: 'next' diff --git a/app/views/shared/_landing_strip.html.haml b/app/views/shared/_landing_strip.html.haml index 35461a8cb..ae26fc1ff 100644 --- a/app/views/shared/_landing_strip.html.haml +++ b/app/views/shared/_landing_strip.html.haml @@ -1,5 +1,8 @@ .landing-strip - = t('landing_strip_html', name: content_tag(:span, display_name(account), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path)) + = image_tag asset_pack_path('logo.svg'), class: 'logo' - - if open_registrations? - = t('landing_strip_signup_html', sign_up_path: new_user_registration_path) + %div + = t('landing_strip_html', name: content_tag(:span, display_name(account), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path)) + + - if open_registrations? + = t('landing_strip_signup_html', sign_up_path: new_user_registration_path) diff --git a/config/locales/en.yml b/config/locales/en.yml index 210bfc5b4..97f46c3af 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -40,7 +40,11 @@ en: nothing_here: There is nothing here! people_followed_by: People whom %{name} follows people_who_follow: People who follow %{name} - posts: Posts + posts: Toots + posts_with_replies: Toots with replies + media: Media + roles: + admin: Admin remote_follow: Remote follow reserved_username: The username is reserved unfollow: Unfollow diff --git a/config/routes.rb b/config/routes.rb index f75de5304..1a39dfeac 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,6 +56,8 @@ Rails.application.routes.draw do end get '/@:username', to: 'accounts#show', as: :short_account + get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies + get '/@:username/media', to: 'accounts#show', as: :short_account_media get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status namespace :settings do -- cgit From 075d6a1e13aa6477c656e9dbe03e6720cb4e2b32 Mon Sep 17 00:00:00 2001 From: nullkal Date: Fri, 18 Aug 2017 00:52:40 +0900 Subject: Show what protocol is used for accounts in admin/accounts#index (#4622) * Show what protocol used for in admin/accounts#index * Add frozen_string_literal --- app/helpers/account_helper.rb | 14 ++++++++++++++ app/views/admin/accounts/_account.html.haml | 3 +++ app/views/admin/accounts/index.html.haml | 1 + app/views/admin/accounts/show.html.haml | 2 +- spec/helpers/account_helper_spec.rb | 30 +++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 app/helpers/account_helper.rb create mode 100644 spec/helpers/account_helper_spec.rb (limited to 'app/views') diff --git a/app/helpers/account_helper.rb b/app/helpers/account_helper.rb new file mode 100644 index 000000000..00d4fc657 --- /dev/null +++ b/app/helpers/account_helper.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module AccountHelper + def protocol_for_display(protocol) + case protocol + when 'activitypub' + 'ActivityPub' + when 'ostatus' + 'OStatus' + else + protocol + end + end +end diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index c513776b7..a7fca6b3e 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -4,6 +4,9 @@ %td.domain - unless account.local? = link_to account.domain, admin_accounts_path(by_domain: account.domain) + %td.protocol + - unless account.local? + = protocol_for_display(account.protocol) %td.confirmed - if account.local? - if account.user_confirmed? diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 07c8d1632..1f36aeb31 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -55,6 +55,7 @@ %tr %th= t('admin.accounts.username') %th= t('admin.accounts.domain') + %th= t('admin.accounts.protocol') %th= t('admin.accounts.confirmed') %th= fa_icon 'paper-plane-o' %th diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 5c781e817..f0e4e303c 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -33,7 +33,7 @@ %td= link_to @account.url, @account.url %tr %th= t('admin.accounts.protocol') - %td= @account.protocol + %td= protocol_for_display(@account.protocol) - if @account.ostatus? %tr diff --git a/spec/helpers/account_helper_spec.rb b/spec/helpers/account_helper_spec.rb new file mode 100644 index 000000000..63e7c78b6 --- /dev/null +++ b/spec/helpers/account_helper_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the AccountHelper. For example: +# +# describe AccountHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe AccountHelper, type: :helper do + describe '#protocol_for_display' do + it "returns OStatus when the protocol is 'ostatus'" do + protocol = 'ostatus' + expect(protocol_for_display(protocol)).to eq 'OStatus' + end + + it "returns ActivityPub when the protocol is 'activitypub'" do + protocol = 'activitypub' + expect(protocol_for_display(protocol)).to eq 'ActivityPub' + end + + it "returns the same string when the protocol is unknown" do + protocol = 'wave' + expect(protocol_for_display(protocol)).to eq protocol + end + end +end -- cgit From efec02f1538adc7f75ba9ca3716ea25b3f2ef4df Mon Sep 17 00:00:00 2001 From: nightpool Date: Thu, 17 Aug 2017 17:20:50 -0400 Subject: use existing inflections instead of custom helper (#4624) * use existing inflections instead of custom helper * use ActiveSupport versions --- app/helpers/account_helper.rb | 14 -------------- app/views/admin/accounts/_account.html.haml | 2 +- app/views/admin/accounts/show.html.haml | 2 +- spec/helpers/account_helper_spec.rb | 30 ----------------------------- 4 files changed, 2 insertions(+), 46 deletions(-) delete mode 100644 app/helpers/account_helper.rb delete mode 100644 spec/helpers/account_helper_spec.rb (limited to 'app/views') diff --git a/app/helpers/account_helper.rb b/app/helpers/account_helper.rb deleted file mode 100644 index 00d4fc657..000000000 --- a/app/helpers/account_helper.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module AccountHelper - def protocol_for_display(protocol) - case protocol - when 'activitypub' - 'ActivityPub' - when 'ostatus' - 'OStatus' - else - protocol - end - end -end diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index a7fca6b3e..5265d77f6 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -6,7 +6,7 @@ = link_to account.domain, admin_accounts_path(by_domain: account.domain) %td.protocol - unless account.local? - = protocol_for_display(account.protocol) + = account.protocol.humanize %td.confirmed - if account.local? - if account.user_confirmed? diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index f0e4e303c..18bcd5e8e 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -33,7 +33,7 @@ %td= link_to @account.url, @account.url %tr %th= t('admin.accounts.protocol') - %td= protocol_for_display(@account.protocol) + %td= @account.protocol.humanize - if @account.ostatus? %tr diff --git a/spec/helpers/account_helper_spec.rb b/spec/helpers/account_helper_spec.rb deleted file mode 100644 index 63e7c78b6..000000000 --- a/spec/helpers/account_helper_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rails_helper' - -# Specs in this file have access to a helper object that includes -# the AccountHelper. For example: -# -# describe AccountHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end -RSpec.describe AccountHelper, type: :helper do - describe '#protocol_for_display' do - it "returns OStatus when the protocol is 'ostatus'" do - protocol = 'ostatus' - expect(protocol_for_display(protocol)).to eq 'OStatus' - end - - it "returns ActivityPub when the protocol is 'activitypub'" do - protocol = 'activitypub' - expect(protocol_for_display(protocol)).to eq 'ActivityPub' - end - - it "returns the same string when the protocol is unknown" do - protocol = 'wave' - expect(protocol_for_display(protocol)).to eq protocol - end - end -end -- cgit From 871c0d251a6d27c4591785ae446738a8d6c553ab Mon Sep 17 00:00:00 2001 From: Colin Mitchell Date: Tue, 22 Aug 2017 12:33:57 -0400 Subject: Application prefs section (#2758) * Add code for creating/managing apps to settings section * Add specs for app changes * Fix controller spec * Fix view file I pasted over by mistake * Add locale strings. Add 'my apps' to nav * Add Client ID/Secret to App page. Add some visual separation * Fix rubocop warnings * Fix embarrassing typo I lost an `end` statement while fixing a merge conflict. * Add code for creating/managing apps to settings section - Add specs for app changes - Add locale strings. Add 'my apps' to nav - Add Client ID/Secret to App page. Add some visual separation - Fix some bugs/warnings * Update to match code standards * Trigger notification * Add warning about not sharing API secrets * Tweak spec a bit * Cleanup fixture creation by using let! * Remove unused key * Add foreign key for application<->user --- .../settings/applications_controller.rb | 65 ++++++++ app/models/user.rb | 13 ++ app/views/settings/applications/_fields.html.haml | 4 + app/views/settings/applications/index.html.haml | 20 +++ app/views/settings/applications/new.html.haml | 9 ++ app/views/settings/applications/show.html.haml | 28 ++++ config/initializers/doorkeeper.rb | 2 +- config/locales/doorkeeper.en.yml | 7 +- config/locales/en.yml | 11 ++ config/navigation.rb | 1 + config/routes.rb | 5 + .../20170427011934_re_add_owner_to_application.rb | 8 + db/schema.rb | 7 +- .../settings/applications_controller_spec.rb | 166 +++++++++++++++++++++ spec/models/user_spec.rb | 20 +++ 15 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 app/controllers/settings/applications_controller.rb create mode 100644 app/views/settings/applications/_fields.html.haml create mode 100644 app/views/settings/applications/index.html.haml create mode 100644 app/views/settings/applications/new.html.haml create mode 100644 app/views/settings/applications/show.html.haml create mode 100644 db/migrate/20170427011934_re_add_owner_to_application.rb create mode 100644 spec/controllers/settings/applications_controller_spec.rb (limited to 'app/views') diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb new file mode 100644 index 000000000..b8f114455 --- /dev/null +++ b/app/controllers/settings/applications_controller.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +class Settings::ApplicationsController < ApplicationController + layout 'admin' + + before_action :authenticate_user! + + def index + @applications = current_user.applications.page(params[:page]) + end + + def new + @application = Doorkeeper::Application.new( + redirect_uri: Doorkeeper.configuration.native_redirect_uri, + scopes: 'read write follow' + ) + end + + def show + @application = current_user.applications.find(params[:id]) + end + + def create + @application = current_user.applications.build(application_params) + if @application.save + redirect_to settings_applications_path, notice: I18n.t('application.created') + else + render :new + end + end + + def update + @application = current_user.applications.find(params[:id]) + if @application.update_attributes(application_params) + redirect_to settings_applications_path, notice: I18n.t('generic.changes_saved_msg') + else + render :show + end + end + + def destroy + @application = current_user.applications.find(params[:id]) + @application.destroy + redirect_to settings_applications_path, notice: t('application.destroyed') + end + + def regenerate + @application = current_user.applications.find(params[:application_id]) + @access_token = current_user.token_for_app(@application) + @access_token.destroy + + redirect_to settings_application_path(@application), notice: t('access_token.regenerated') + end + + private + + def application_params + params.require(:doorkeeper_application).permit( + :name, + :redirect_uri, + :scopes, + :website + ) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 96a2d09b7..02b1b26ee 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -46,6 +46,8 @@ class User < ApplicationRecord belongs_to :account, inverse_of: :user, required: true accepts_nested_attributes_for :account + has_many :applications, class_name: 'Doorkeeper::Application', as: :owner + validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale? validates_with BlacklistedEmailValidator, if: :email_changed? @@ -108,6 +110,17 @@ class User < ApplicationRecord settings.noindex end + def token_for_app(a) + return nil if a.nil? || a.owner != self + Doorkeeper::AccessToken + .find_or_create_by(application_id: a.id, resource_owner_id: id) do |t| + + t.scopes = a.scopes + t.expires_in = Doorkeeper.configuration.access_token_expires_in + t.use_refresh_token = Doorkeeper.configuration.refresh_token_enabled? + end + end + def activate_session(request) session_activations.activate(session_id: SecureRandom.hex, user_agent: request.user_agent, diff --git a/app/views/settings/applications/_fields.html.haml b/app/views/settings/applications/_fields.html.haml new file mode 100644 index 000000000..9dbe23466 --- /dev/null +++ b/app/views/settings/applications/_fields.html.haml @@ -0,0 +1,4 @@ += f.input :name, hint: t('activerecord.attributes.doorkeeper/application.name') += f.input :website, hint: t('activerecord.attributes.doorkeeper/application.website') += f.input :redirect_uri, hint: t('activerecord.attributes.doorkeeper/application.redirect_uri') += f.input :scopes, hint: t('activerecord.attributes.doorkeeper/application.scopes') diff --git a/app/views/settings/applications/index.html.haml b/app/views/settings/applications/index.html.haml new file mode 100644 index 000000000..17035f96c --- /dev/null +++ b/app/views/settings/applications/index.html.haml @@ -0,0 +1,20 @@ +- content_for :page_title do + = t('doorkeeper.applications.index.title') + +%table.table + %thead + %tr + %th= t('doorkeeper.applications.index.application') + %th= t('doorkeeper.applications.index.scopes') + %th= t('doorkeeper.applications.index.created_at') + %th + %tbody + - @applications.each do |application| + %tr + %td= link_to application.name, settings_application_path(application) + %th= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('
').html_safe + %td= l application.created_at + %td= table_link_to 'show', t('doorkeeper.applications.index.show'), settings_application_path(application) + %td= table_link_to 'times', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') } += paginate @applications += link_to t('add_new'), new_settings_application_path, class: 'button' diff --git a/app/views/settings/applications/new.html.haml b/app/views/settings/applications/new.html.haml new file mode 100644 index 000000000..61406a31f --- /dev/null +++ b/app/views/settings/applications/new.html.haml @@ -0,0 +1,9 @@ +- content_for :page_title do + = t('doorkeeper.applications.new.title') + +.form-container + = simple_form_for @application, url: settings_applications_path do |f| + = render 'fields', f:f + + .actions + = f.button :button, t('.create'), type: :submit diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml new file mode 100644 index 000000000..9f1a11986 --- /dev/null +++ b/app/views/settings/applications/show.html.haml @@ -0,0 +1,28 @@ +- content_for :page_title do + = t('doorkeeper.applications.show.title', name: @application.name) + + +%p.hint= t('application.warning') + +%div + %h3= t('application.uid') + %code= @application.uid + +%div + %h3= t('application.secret') + %code= @application.secret + +%div + %h3= t('access_token.your_token') + %code= current_user.token_for_app(@application).token + += link_to t('access_token.regenerate'), settings_application_regenerate_path(@application), method: :put, class: 'button' + +%hr + += simple_form_for @application, url: settings_application_path(@application), method: :put do |f| + = render 'fields', f:f + + .actions + = f.button :button, t('generic.save_changes'), type: :submit + diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 056a3651a..689e2ac4a 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -50,7 +50,7 @@ Doorkeeper.configure do # Optional parameter :confirmation => true (default false) if you want to enforce ownership of # a registered application # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support - # enable_application_owner :confirmation => true + enable_application_owner # Define access token scopes for your provider # For more information go to diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index 6412b8b48..fa0a7babf 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -3,8 +3,10 @@ en: activerecord: attributes: doorkeeper/application: - name: Name + name: Application Name + website: Application Website redirect_uri: Redirect URI + scopes: Scopes errors: models: doorkeeper/application: @@ -37,9 +39,12 @@ en: name: Name new: New Application title: Your applications + show: Show + delete: Delete new: title: New Application show: + title: 'Application: %{name}' actions: Actions application_id: Application Id callback_urls: Callback urls diff --git a/config/locales/en.yml b/config/locales/en.yml index 97f46c3af..fbcef03bd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -33,6 +33,10 @@ en: user_count_after: users user_count_before: Home to what_is_mastodon: What is Mastodon? + access_token: + your_token: Your Access Token + regenerate: Regenerate Access Token + regenerated: Access Token Regenerated accounts: follow: Follow followers: Followers @@ -226,6 +230,12 @@ en: settings: 'Change e-mail preferences: %{link}' signature: Mastodon notifications from %{instance} view: 'View:' + application: + created: Application Created + destroyed: Application Destroyed + uid: Client ID + secret: Client Secret + warning: Be very careful with this data. Never share it with anyone other than authorized applications! applications: invalid_url: The provided URL is invalid auth: @@ -423,6 +433,7 @@ en: preferences: Preferences settings: Settings two_factor_authentication: Two-factor Authentication + your_apps: Your applications statuses: open_in_web: Open in web over_character_limit: character limit of %{max} exceeded diff --git a/config/navigation.rb b/config/navigation.rb index 535d033f5..6e04843ec 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -12,6 +12,7 @@ SimpleNavigation::Configuration.run do |navigation| settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url + settings.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url settings.item :follower_domains, safe_join([fa_icon('users fw'), t('settings.followers')]), settings_follower_domains_url end diff --git a/config/routes.rb b/config/routes.rb index 1a39dfeac..e8bc968f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,6 +79,11 @@ Rails.application.routes.draw do end resource :follower_domains, only: [:show, :update] + + resources :applications do + put :regenerate + end + resource :delete, only: [:show, :destroy] resources :sessions, only: [:destroy] diff --git a/db/migrate/20170427011934_re_add_owner_to_application.rb b/db/migrate/20170427011934_re_add_owner_to_application.rb new file mode 100644 index 000000000..a41d71d2a --- /dev/null +++ b/db/migrate/20170427011934_re_add_owner_to_application.rb @@ -0,0 +1,8 @@ +class ReAddOwnerToApplication < ActiveRecord::Migration[5.0] + def change + add_column :oauth_applications, :owner_id, :integer, null: true + add_column :oauth_applications, :owner_type, :string, null: true + add_index :oauth_applications, [:owner_id, :owner_type] + add_foreign_key :oauth_applications, :users, column: :owner_id, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 2501e451d..929a5fd01 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -216,8 +216,11 @@ ActiveRecord::Schema.define(version: 20170720000000) do t.string "scopes", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "superapp", default: false, null: false - t.string "website" + t.boolean "superapp", default: false, null: false + t.string "website" + t.integer "owner_id" + t.string "owner_type" + t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb new file mode 100644 index 000000000..fa27e6ec6 --- /dev/null +++ b/spec/controllers/settings/applications_controller_spec.rb @@ -0,0 +1,166 @@ +require 'rails_helper' + +describe Settings::ApplicationsController do + render_views + + let!(:user) { Fabricate(:user) } + let!(:app) { Fabricate(:application, owner: user) } + + before do + sign_in user, scope: :user + end + + describe 'GET #index' do + let!(:other_app) { Fabricate(:application) } + + it 'shows apps' do + get :index + expect(response).to have_http_status(:success) + expect(assigns(:applications)).to include(app) + expect(assigns(:applications)).to_not include(other_app) + end + end + + + describe 'GET #show' do + it 'returns http success' do + get :show, params: { id: app.id } + expect(response).to have_http_status(:success) + expect(assigns[:application]).to eql(app) + end + + it 'returns 404 if you dont own app' do + app.update!(owner: nil) + + get :show, params: { id: app.id } + expect(response.status).to eq 404 + end + end + + describe 'GET #new' do + it 'works' do + get :new + expect(response).to have_http_status(:success) + end + end + + describe 'POST #create' do + context 'success' do + def call_create + post :create, params: { + doorkeeper_application: { + name: 'My New App', + redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', + website: 'http://google.com', + scopes: 'read write follow' + } + } + response + end + + it 'creates an entry in the database' do + expect { call_create }.to change(Doorkeeper::Application, :count) + end + + it 'redirects back to applications page' do + expect(call_create).to redirect_to(settings_applications_path) + end + end + + context 'failure' do + before do + post :create, params: { + doorkeeper_application: { + name: '', + redirect_uri: '', + website: '', + scopes: '' + } + } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'renders form again' do + expect(response).to render_template(:new) + end + end + end + + describe 'PATCH #update' do + context 'success' do + let(:opts) { + { + website: 'https://foo.bar/' + } + } + + def call_update + patch :update, params: { + id: app.id, + doorkeeper_application: opts + } + response + end + + it 'updates existing application' do + call_update + expect(app.reload.website).to eql(opts[:website]) + end + + it 'redirects back to applications page' do + expect(call_update).to redirect_to(settings_applications_path) + end + end + + context 'failure' do + before do + patch :update, params: { + id: app.id, + doorkeeper_application: { + name: '', + redirect_uri: '', + website: '', + scopes: '' + } + } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'renders form again' do + expect(response).to render_template(:show) + end + end + end + + describe 'destroy' do + before do + post :destroy, params: { id: app.id } + end + + it 'redirects back to applications page' do + expect(response).to redirect_to(settings_applications_path) + end + + it 'removes the app' do + expect(Doorkeeper::Application.find_by(id: app.id)).to be_nil + end + end + + describe 'regenerate' do + let(:token) { user.token_for_app(app) } + before do + expect(token).to_not be_nil + put :regenerate, params: { application_id: app.id } + end + + it 'should create new token' do + expect(user.token_for_app(app)).to_not eql(token) + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ef45818b9..99aeca01b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -286,4 +286,24 @@ RSpec.describe User, type: :model do Fabricate(:user) end end + + describe 'token_for_app' do + let(:user) { Fabricate(:user) } + let(:app) { Fabricate(:application, owner: user) } + + it 'returns a token' do + expect(user.token_for_app(app)).to be_a(Doorkeeper::AccessToken) + end + + it 'persists a token' do + t = user.token_for_app(app) + expect(user.token_for_app(app)).to eql(t) + end + + it 'is nil if user does not own app' do + app.update!(owner: nil) + + expect(user.token_for_app(app)).to be_nil + end + end end -- cgit From 696c2c6f2f3338df121cf17389478da9ecab11af Mon Sep 17 00:00:00 2001 From: Daigo 3 Dango Date: Tue, 22 Aug 2017 20:54:19 +0000 Subject: Add Mastodon::Source.url (#4643) * Add Mastodon::Source.url * Update spec * Refactor Move things frmo Mastodon::Source to Mastodon::Version --- app/presenters/instance_presenter.rb | 4 ++++ app/views/about/more.html.haml | 4 ++-- app/views/about/show.html.haml | 4 ++-- lib/mastodon/version.rb | 17 +++++++++++++++++ spec/views/about/show.html.haml_spec.rb | 1 + 5 files changed, 26 insertions(+), 4 deletions(-) (limited to 'app/views') diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb index 5d5be58ba..8104b7531 100644 --- a/app/presenters/instance_presenter.rb +++ b/app/presenters/instance_presenter.rb @@ -31,4 +31,8 @@ class InstancePresenter def version_number Mastodon::Version end + + def source_url + Mastodon::Version.source_url + end end diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index a6fd265fa..094188472 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -63,5 +63,5 @@ .footer-links .container %p - = link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon' - = " (#{@instance_presenter.version_number})" \ No newline at end of file + = link_to t('about.source_code'), @instance_presenter.source_url + = " (#{@instance_presenter.version_number})" diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index acdb12ad7..93270fe3d 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -76,5 +76,5 @@ .footer-links .container %p - = link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon' - = " (#{@instance_presenter.version_number})" \ No newline at end of file + = link_to t('about.source_code'), @instance_presenter.source_url + = " (#{@instance_presenter.version_number})" diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 381e9aac9..fcca875d9 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -31,5 +31,22 @@ module Mastodon def to_s [to_a.join('.'), flags].join end + + def source_base_url + 'https://github.com/tootsuite/mastodon' + end + + # specify git tag or commit hash here + def source_tag + nil + end + + def source_url + if source_tag + "#{source_base_url}/tree/#{source_tag}" + else + source_base_url + end + end end end diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb index c0ead6349..aa151dd27 100644 --- a/spec/views/about/show.html.haml_spec.rb +++ b/spec/views/about/show.html.haml_spec.rb @@ -13,6 +13,7 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do site_title: 'something', site_description: 'something', version_number: '1.0', + source_url: 'https://github.com/tootsuite/mastodon', open_registrations: false, closed_registrations_message: 'yes') assign(:instance_presenter, instance_presenter) -- cgit From c1b086a538d128e9fbceab4fc6686611a4f2710f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 23 Aug 2017 00:59:35 +0200 Subject: Fix up the applications area (#4664) - Section it into "Development" area - Improve UI of application form, index, and details --- .../settings/applications_controller.rb | 21 ++++++------ app/views/settings/applications/_fields.html.haml | 15 +++++--- app/views/settings/applications/index.html.haml | 11 +++--- app/views/settings/applications/new.html.haml | 11 +++--- app/views/settings/applications/show.html.haml | 40 ++++++++++++---------- config/locales/doorkeeper.en.yml | 19 +++++----- config/locales/en.yml | 23 ++++++------- config/locales/ja.yml | 6 ++-- config/locales/oc.yml | 16 ++++----- config/locales/pl.yml | 8 ++--- config/navigation.rb | 5 ++- config/routes.rb | 6 ++-- db/schema.rb | 11 +++--- .../settings/applications_controller_spec.rb | 2 +- 14 files changed, 102 insertions(+), 92 deletions(-) (limited to 'app/views') diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb index b8f114455..894222c2a 100644 --- a/app/controllers/settings/applications_controller.rb +++ b/app/controllers/settings/applications_controller.rb @@ -4,6 +4,7 @@ class Settings::ApplicationsController < ApplicationController layout 'admin' before_action :authenticate_user! + before_action :set_application, only: [:show, :update, :destroy, :regenerate] def index @applications = current_user.applications.page(params[:page]) @@ -16,22 +17,20 @@ class Settings::ApplicationsController < ApplicationController ) end - def show - @application = current_user.applications.find(params[:id]) - end + def show; end def create @application = current_user.applications.build(application_params) + if @application.save - redirect_to settings_applications_path, notice: I18n.t('application.created') + redirect_to settings_applications_path, notice: I18n.t('applications.created') else render :new end end def update - @application = current_user.applications.find(params[:id]) - if @application.update_attributes(application_params) + if @application.update(application_params) redirect_to settings_applications_path, notice: I18n.t('generic.changes_saved_msg') else render :show @@ -39,21 +38,23 @@ class Settings::ApplicationsController < ApplicationController end def destroy - @application = current_user.applications.find(params[:id]) @application.destroy - redirect_to settings_applications_path, notice: t('application.destroyed') + redirect_to settings_applications_path, notice: I18n.t('applications.destroyed') end def regenerate - @application = current_user.applications.find(params[:application_id]) @access_token = current_user.token_for_app(@application) @access_token.destroy - redirect_to settings_application_path(@application), notice: t('access_token.regenerated') + redirect_to settings_application_path(@application), notice: I18n.t('applications.token_regenerated') end private + def set_application + @application = current_user.applications.find(params[:id]) + end + def application_params params.require(:doorkeeper_application).permit( :name, diff --git a/app/views/settings/applications/_fields.html.haml b/app/views/settings/applications/_fields.html.haml index 9dbe23466..536f69e04 100644 --- a/app/views/settings/applications/_fields.html.haml +++ b/app/views/settings/applications/_fields.html.haml @@ -1,4 +1,11 @@ -= f.input :name, hint: t('activerecord.attributes.doorkeeper/application.name') -= f.input :website, hint: t('activerecord.attributes.doorkeeper/application.website') -= f.input :redirect_uri, hint: t('activerecord.attributes.doorkeeper/application.redirect_uri') -= f.input :scopes, hint: t('activerecord.attributes.doorkeeper/application.scopes') +.fields-group + = f.input :name, placeholder: t('activerecord.attributes.doorkeeper/application.name') + = f.input :website, placeholder: t('activerecord.attributes.doorkeeper/application.website') + +.fields-group + = f.input :redirect_uri, wrapper: :with_block_label, label: t('activerecord.attributes.doorkeeper/application.redirect_uri'), hint: t('doorkeeper.applications.help.redirect_uri') + + %p.hint= t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: Doorkeeper.configuration.native_redirect_uri) + +.fields-group + = f.input :scopes, wrapper: :with_label, label: t('activerecord.attributes.doorkeeper/application.scopes'), hint: t('doorkeeper.applications.help.scopes') diff --git a/app/views/settings/applications/index.html.haml b/app/views/settings/applications/index.html.haml index 17035f96c..eea550388 100644 --- a/app/views/settings/applications/index.html.haml +++ b/app/views/settings/applications/index.html.haml @@ -6,15 +6,14 @@ %tr %th= t('doorkeeper.applications.index.application') %th= t('doorkeeper.applications.index.scopes') - %th= t('doorkeeper.applications.index.created_at') %th %tbody - @applications.each do |application| %tr %td= link_to application.name, settings_application_path(application) - %th= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('
').html_safe - %td= l application.created_at - %td= table_link_to 'show', t('doorkeeper.applications.index.show'), settings_application_path(application) - %td= table_link_to 'times', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') } + %th= application.scopes + %td + = table_link_to 'times', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') } + = paginate @applications -= link_to t('add_new'), new_settings_application_path, class: 'button' += link_to t('doorkeeper.applications.index.new'), new_settings_application_path, class: 'button' diff --git a/app/views/settings/applications/new.html.haml b/app/views/settings/applications/new.html.haml index 61406a31f..5274a430c 100644 --- a/app/views/settings/applications/new.html.haml +++ b/app/views/settings/applications/new.html.haml @@ -1,9 +1,8 @@ - content_for :page_title do = t('doorkeeper.applications.new.title') + += simple_form_for @application, url: settings_applications_path do |f| + = render 'fields', f: f -.form-container - = simple_form_for @application, url: settings_applications_path do |f| - = render 'fields', f:f - - .actions - = f.button :button, t('.create'), type: :submit + .actions + = f.button :button, t('doorkeeper.applications.buttons.submit'), type: :submit diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml index 9f1a11986..4d8555111 100644 --- a/app/views/settings/applications/show.html.haml +++ b/app/views/settings/applications/show.html.haml @@ -1,27 +1,29 @@ - content_for :page_title do = t('doorkeeper.applications.show.title', name: @application.name) - -%p.hint= t('application.warning') - -%div - %h3= t('application.uid') - %code= @application.uid - -%div - %h3= t('application.secret') - %code= @application.secret - -%div - %h3= t('access_token.your_token') - %code= current_user.token_for_app(@application).token - -= link_to t('access_token.regenerate'), settings_application_regenerate_path(@application), method: :put, class: 'button' - -%hr +%p.hint= t('applications.warning') + +%table.table + %tbody + %tr + %th= t('doorkeeper.applications.show.application_id') + %td + %code= @application.uid + %tr + %th= t('doorkeeper.applications.show.secret') + %td + %code= @application.secret + %tr + %th{ rowspan: 2}= t('applications.your_token') + %td + %code= current_user.token_for_app(@application).token + %tr + %td= table_link_to 'refresh', t('applications.regenerate_token'), regenerate_settings_application_path(@application), method: :post + +%hr/ = simple_form_for @application, url: settings_application_path(@application), method: :put do |f| - = render 'fields', f:f + = render 'fields', f: f .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index fa0a7babf..788d1bb40 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -3,10 +3,10 @@ en: activerecord: attributes: doorkeeper/application: - name: Application Name - website: Application Website + name: Application name redirect_uri: Redirect URI scopes: Scopes + website: Application website errors: models: doorkeeper/application: @@ -36,20 +36,19 @@ en: scopes: Separate scopes with spaces. Leave blank to use the default scopes. index: callback_url: Callback URL + delete: Delete name: Name - new: New Application - title: Your applications + new: New application show: Show - delete: Delete + title: Your applications new: - title: New Application + title: New application show: - title: 'Application: %{name}' actions: Actions - application_id: Application Id - callback_urls: Callback urls + application_id: Client key + callback_urls: Callback URLs scopes: Scopes - secret: Secret + secret: Client secret title: 'Application: %{name}' authorizations: buttons: diff --git a/config/locales/en.yml b/config/locales/en.yml index fbcef03bd..97bb14186 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -33,24 +33,20 @@ en: user_count_after: users user_count_before: Home to what_is_mastodon: What is Mastodon? - access_token: - your_token: Your Access Token - regenerate: Regenerate Access Token - regenerated: Access Token Regenerated accounts: follow: Follow followers: Followers following: Following + media: Media nothing_here: There is nothing here! people_followed_by: People whom %{name} follows people_who_follow: People who follow %{name} posts: Toots posts_with_replies: Toots with replies - media: Media - roles: - admin: Admin remote_follow: Remote follow reserved_username: The username is reserved + roles: + admin: Admin unfollow: Unfollow admin: accounts: @@ -230,14 +226,14 @@ en: settings: 'Change e-mail preferences: %{link}' signature: Mastodon notifications from %{instance} view: 'View:' - application: - created: Application Created - destroyed: Application Destroyed - uid: Client ID - secret: Client Secret - warning: Be very careful with this data. Never share it with anyone other than authorized applications! applications: + created: Application successfully created + destroyed: Application successfully deleted invalid_url: The provided URL is invalid + regenerate_token: Regenerate access token + token_regenerated: Access token successfully regenerated + warning: Be very careful with this data. Never share it with anyone! + your_token: Your access token auth: agreement_html: By signing up you agree to our terms of service and privacy policy. change_password: Security @@ -426,6 +422,7 @@ en: authorized_apps: Authorized apps back: Back to Mastodon delete: Account deletion + development: Development edit_profile: Edit profile export: Data export followers: Authorized followers diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 0f0b0ad4a..2ee99db45 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -37,16 +37,16 @@ ja: follow: フォロー followers: フォロワー following: フォロー中 + media: メディア nothing_here: 何もありません people_followed_by: "%{name} さんがフォロー中のアカウント" people_who_follow: "%{name} さんをフォロー中のアカウント" posts: トゥート posts_with_replies: トゥートと返信 - media: メディア - roles: - admin: Admin remote_follow: リモートフォロー reserved_username: このユーザー名は予約されています。 + roles: + admin: Admin unfollow: フォロー解除 admin: accounts: diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 9038d887a..65ea4525a 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -37,16 +37,16 @@ oc: follow: Sègre followers: Seguidors following: Abonaments + media: Mèdias nothing_here: I a pas res aquí ! people_followed_by: Lo mond que %{name} sèc people_who_follow: Lo mond que sègon %{name} posts: Tuts posts_with_replies: Tuts amb responsas - media: Mèdias - roles: - admin: Admin remote_follow: Sègre a distància reserved_username: Aqueste nom d’utilizaire es reservat + roles: + admin: Admin unfollow: Quitar de sègre admin: accounts: @@ -221,7 +221,7 @@ oc: body: "%{reporter} a senhalat %{target}" subject: Novèl senhalament per %{instance} (#%{id}) application_mailer: - salutation: '%{name},' + salutation: "%{name}," settings: 'Cambiar las preferéncias de corrièl : %{link}' signature: Notificacion de Mastodon sus %{instance} view: 'Veire :' @@ -234,13 +234,13 @@ oc: delete_account_html: Se volètz suprimir vòstre compte, podètz o far aquí. Vos demandarem que confirmetz. didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ? forgot_password: Senhal oblidat ? + invalid_reset_password_token: Lo geton de reïnicializacion es invalid o acabat. Tornatz demandar un geton se vos plai. login: Se connectar logout: Se desconnectar register: Se marcar resend_confirmation: Tornar mandar las instruccions de confirmacion reset_password: Reïnicializar lo senhal set_new_password: Picar un nòu senhal - invalid_reset_password_token: Lo geton de reïnicializacion es invalid o acabat. Tornatz demandar un geton se vos plai. authorize_follow: error: O planhèm, i a agut una error al moment de cercar lo compte follow: Sègre @@ -337,12 +337,12 @@ oc: x_months: one: Fa un mes other: Fa %{count} meses - x_years: - one: Fa un an - other: Fa %{count} ans x_seconds: one: Fa una segonda other: Fa %{count} segondas + x_years: + one: Fa un an + other: Fa %{count} ans deletes: bad_password_msg: Ben ensajat pirata ! Senhal incorrècte confirm_password: Picatz vòstre senhal actual per verificar vòstra identitat diff --git a/config/locales/pl.yml b/config/locales/pl.yml index c005cdb01..b7f4898b0 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -37,16 +37,16 @@ pl: follow: Śledź followers: Śledzących following: Śledzi + media: Zawartość multimedialna nothing_here: Niczego tu nie ma! people_followed_by: Konta śledzone przez %{name} people_who_follow: Osoby, które śledzą konto %{name} posts: Wpisy posts_with_replies: Wpisy z odpowiedziami - media: Zawartość multimedialna - roles: - admin: Administrator remote_follow: Śledź zdalnie reserved_username: Ta nazwa użytkownika jest zarezerwowana. + roles: + admin: Administrator unfollow: Przestań śledzić admin: accounts: @@ -126,8 +126,8 @@ pl: severity: Priorytet show: affected_accounts: - one: Dotyczy jednego konta w bazie danych many: Dotyczy %{count} kont w bazie danych + one: Dotyczy jednego konta w bazie danych other: Dotyczy %{count} kont w bazie danych retroactive: silence: Odwołaj wyciszenie wszystkich kont w tej domenie diff --git a/config/navigation.rb b/config/navigation.rb index 6e04843ec..4b454b3fc 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -12,10 +12,13 @@ SimpleNavigation::Configuration.run do |navigation| settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url - settings.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url settings.item :follower_domains, safe_join([fa_icon('users fw'), t('settings.followers')]), settings_follower_domains_url end + primary.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_url do |development| + development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications} + end + primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.admin? } do |admin| admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports} admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts} diff --git a/config/routes.rb b/config/routes.rb index e8bc968f4..94a4ac88e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -80,8 +80,10 @@ Rails.application.routes.draw do resource :follower_domains, only: [:show, :update] - resources :applications do - put :regenerate + resources :applications, except: [:edit] do + member do + post :regenerate + end end resource :delete, only: [:show, :destroy] diff --git a/db/schema.rb b/db/schema.rb index 929a5fd01..98b07e282 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -216,11 +216,11 @@ ActiveRecord::Schema.define(version: 20170720000000) do t.string "scopes", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "superapp", default: false, null: false - t.string "website" - t.integer "owner_id" - t.string "owner_type" - t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree + t.boolean "superapp", default: false, null: false + t.string "website" + t.integer "owner_id" + t.string "owner_type" + t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type" t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end @@ -423,6 +423,7 @@ ActiveRecord::Schema.define(version: 20170720000000) do add_foreign_key "oauth_access_grants", "users", column: "resource_owner_id", on_delete: :cascade add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id", on_delete: :cascade add_foreign_key "oauth_access_tokens", "users", column: "resource_owner_id", on_delete: :cascade + add_foreign_key "oauth_applications", "users", column: "owner_id", on_delete: :cascade add_foreign_key "preview_cards", "statuses", on_delete: :cascade add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", on_delete: :nullify add_foreign_key "reports", "accounts", column: "target_account_id", on_delete: :cascade diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb index fa27e6ec6..7902a4334 100644 --- a/spec/controllers/settings/applications_controller_spec.rb +++ b/spec/controllers/settings/applications_controller_spec.rb @@ -156,7 +156,7 @@ describe Settings::ApplicationsController do let(:token) { user.token_for_app(app) } before do expect(token).to_not be_nil - put :regenerate, params: { application_id: app.id } + post :regenerate, params: { id: app.id } end it 'should create new token' do -- cgit From 80393a23d0a0c296d4356a2a21cf8504435265bf Mon Sep 17 00:00:00 2001 From: nullkal Date: Wed, 23 Aug 2017 22:16:20 +0900 Subject: Use checkboxes for application scope setting (#4671) --- .../settings/applications_controller.rb | 6 +++++ app/views/settings/applications/_fields.html.haml | 14 ++++++++-- .../settings/applications_controller_spec.rb | 30 +++++++++++++++++++--- 3 files changed, 44 insertions(+), 6 deletions(-) (limited to 'app/views') diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb index 894222c2a..8fc9a0fa9 100644 --- a/app/controllers/settings/applications_controller.rb +++ b/app/controllers/settings/applications_controller.rb @@ -5,6 +5,7 @@ class Settings::ApplicationsController < ApplicationController before_action :authenticate_user! before_action :set_application, only: [:show, :update, :destroy, :regenerate] + before_action :prepare_scopes, only: [:create, :update] def index @applications = current_user.applications.page(params[:page]) @@ -63,4 +64,9 @@ class Settings::ApplicationsController < ApplicationController :website ) end + + def prepare_scopes + scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil) + params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array + end end diff --git a/app/views/settings/applications/_fields.html.haml b/app/views/settings/applications/_fields.html.haml index 536f69e04..83297a1ae 100644 --- a/app/views/settings/applications/_fields.html.haml +++ b/app/views/settings/applications/_fields.html.haml @@ -7,5 +7,15 @@ %p.hint= t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: Doorkeeper.configuration.native_redirect_uri) -.fields-group - = f.input :scopes, wrapper: :with_label, label: t('activerecord.attributes.doorkeeper/application.scopes'), hint: t('doorkeeper.applications.help.scopes') +.field-group + = f.input :scopes, + label: t('activerecord.attributes.doorkeeper/application.scopes'), + collection: Doorkeeper.configuration.scopes, + wrapper: :with_label, + include_blank: false, + selected: f.object.scopes.all, + required: false, + as: :check_boxes, + collection_wrapper_tag: 'ul', + item_wrapper_tag: 'li' + diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb index 7902a4334..ca66f8d23 100644 --- a/spec/controllers/settings/applications_controller_spec.rb +++ b/spec/controllers/settings/applications_controller_spec.rb @@ -45,7 +45,7 @@ describe Settings::ApplicationsController do end describe 'POST #create' do - context 'success' do + context 'success (passed scopes as a String)' do def call_create post :create, params: { doorkeeper_application: { @@ -61,7 +61,29 @@ describe Settings::ApplicationsController do it 'creates an entry in the database' do expect { call_create }.to change(Doorkeeper::Application, :count) end - + + it 'redirects back to applications page' do + expect(call_create).to redirect_to(settings_applications_path) + end + end + + context 'success (passed scopes as an Array)' do + def call_create + post :create, params: { + doorkeeper_application: { + name: 'My New App', + redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', + website: 'http://google.com', + scopes: [ 'read', 'write', 'follow' ] + } + } + response + end + + it 'creates an entry in the database' do + expect { call_create }.to change(Doorkeeper::Application, :count) + end + it 'redirects back to applications page' do expect(call_create).to redirect_to(settings_applications_path) end @@ -74,7 +96,7 @@ describe Settings::ApplicationsController do name: '', redirect_uri: '', website: '', - scopes: '' + scopes: [] } } end @@ -123,7 +145,7 @@ describe Settings::ApplicationsController do name: '', redirect_uri: '', website: '', - scopes: '' + scopes: [] } } end -- cgit From 9caa90025fd9f1ef46a74f31cefd19335e291e76 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 25 Aug 2017 01:41:18 +0200 Subject: Pinned statuses (#4675) * Pinned statuses * yarn manage:translations --- app/controllers/accounts_controller.rb | 25 +++++-- .../api/v1/accounts/statuses_controller.rb | 5 ++ app/controllers/api/v1/statuses/pins_controller.rb | 28 ++++++++ app/javascript/mastodon/actions/interactions.js | 78 ++++++++++++++++++++++ app/javascript/mastodon/components/status.js | 1 + .../mastodon/components/status_action_bar.js | 11 +++ .../mastodon/containers/status_container.js | 10 +++ .../features/status/components/action_bar.js | 11 +++ app/javascript/mastodon/features/status/index.js | 11 +++ app/javascript/mastodon/locales/ar.json | 2 + app/javascript/mastodon/locales/bg.json | 2 + app/javascript/mastodon/locales/ca.json | 2 + app/javascript/mastodon/locales/de.json | 2 + .../mastodon/locales/defaultMessages.json | 16 +++++ app/javascript/mastodon/locales/en.json | 2 + app/javascript/mastodon/locales/eo.json | 2 + app/javascript/mastodon/locales/es.json | 2 + app/javascript/mastodon/locales/fa.json | 2 + app/javascript/mastodon/locales/fi.json | 2 + app/javascript/mastodon/locales/fr.json | 2 + app/javascript/mastodon/locales/he.json | 2 + app/javascript/mastodon/locales/hr.json | 2 + app/javascript/mastodon/locales/hu.json | 2 + app/javascript/mastodon/locales/id.json | 2 + app/javascript/mastodon/locales/io.json | 2 + app/javascript/mastodon/locales/it.json | 2 + app/javascript/mastodon/locales/ja.json | 2 + app/javascript/mastodon/locales/ko.json | 2 + app/javascript/mastodon/locales/nl.json | 2 + app/javascript/mastodon/locales/no.json | 2 + app/javascript/mastodon/locales/oc.json | 2 + app/javascript/mastodon/locales/pl.json | 2 + app/javascript/mastodon/locales/pt-BR.json | 2 + app/javascript/mastodon/locales/pt.json | 2 + app/javascript/mastodon/locales/ru.json | 2 + app/javascript/mastodon/locales/th.json | 2 + app/javascript/mastodon/locales/tr.json | 2 + app/javascript/mastodon/locales/uk.json | 2 + app/javascript/mastodon/locales/zh-CN.json | 2 + app/javascript/mastodon/locales/zh-HK.json | 2 + app/javascript/mastodon/locales/zh-TW.json | 2 + app/javascript/mastodon/reducers/statuses.js | 4 ++ app/models/account.rb | 4 ++ app/models/concerns/account_interactions.rb | 4 ++ app/models/status.rb | 4 ++ app/models/status_pin.rb | 16 +++++ app/presenters/status_relationships_presenter.rb | 19 ++++-- app/serializers/rest/status_serializer.rb | 16 +++++ app/validators/status_pin_validator.rb | 9 +++ app/views/accounts/show.html.haml | 3 + app/views/stream_entries/_status.html.haml | 7 ++ config/locales/en.yml | 5 ++ config/routes.rb | 13 ++-- db/migrate/20170823162448_create_status_pins.rb | 10 +++ db/schema.rb | 12 +++- .../api/v1/accounts/statuses_controller_spec.rb | 36 +++++++--- .../api/v1/statuses/pins_controller_spec.rb | 57 ++++++++++++++++ spec/fabricators/status_pin_fabricator.rb | 4 ++ spec/models/status_pin_spec.rb | 41 ++++++++++++ 59 files changed, 493 insertions(+), 29 deletions(-) create mode 100644 app/controllers/api/v1/statuses/pins_controller.rb create mode 100644 app/models/status_pin.rb create mode 100644 app/validators/status_pin_validator.rb create mode 100644 db/migrate/20170823162448_create_status_pins.rb create mode 100644 spec/controllers/api/v1/statuses/pins_controller_spec.rb create mode 100644 spec/fabricators/status_pin_fabricator.rb create mode 100644 spec/models/status_pin_spec.rb (limited to 'app/views') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index c6b98628e..f4ca239ba 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -7,14 +7,17 @@ class AccountsController < ApplicationController def show respond_to do |format| format.html do + @pinned_statuses = [] + if current_account && @account.blocking?(current_account) @statuses = [] return end - @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) - @statuses = cache_collection(@statuses, Status) - @next_url = next_url unless @statuses.empty? + @pinned_statuses = cache_collection(@account.pinned_statuses.limit(1), Status) unless media_requested? + @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) + @statuses = cache_collection(@statuses, Status) + @next_url = next_url unless @statuses.empty? end format.atom do @@ -32,8 +35,8 @@ class AccountsController < ApplicationController def filtered_statuses default_statuses.tap do |statuses| - statuses.merge!(only_media_scope) if request.path.ends_with?('/media') - statuses.merge!(no_replies_scope) unless request.path.ends_with?('/with_replies') + statuses.merge!(only_media_scope) if media_requested? + statuses.merge!(no_replies_scope) unless replies_requested? end end @@ -58,12 +61,20 @@ class AccountsController < ApplicationController end def next_url - if request.path.ends_with?('/media') + if media_requested? short_account_media_url(@account, max_id: @statuses.last.id) - elsif request.path.ends_with?('/with_replies') + elsif replies_requested? short_account_with_replies_url(@account, max_id: @statuses.last.id) else short_account_url(@account, max_id: @statuses.last.id) end end + + def media_requested? + request.path.ends_with?('/media') + end + + def replies_requested? + request.path.ends_with?('/with_replies') + end end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index d9ae5c089..095f6937b 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -29,6 +29,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def account_statuses default_statuses.tap do |statuses| statuses.merge!(only_media_scope) if params[:only_media] + statuses.merge!(pinned_scope) if params[:pinned] statuses.merge!(no_replies_scope) if params[:exclude_replies] end end @@ -53,6 +54,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController @account.media_attachments.attached.reorder(nil).select(:status_id).distinct end + def pinned_scope + @account.pinned_statuses + end + def no_replies_scope Status.without_replies end diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb new file mode 100644 index 000000000..3de1009b8 --- /dev/null +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::PinsController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write } + before_action :require_user! + before_action :set_status + + respond_to :json + + def create + StatusPin.create!(account: current_account, status: @status) + render json: @status, serializer: REST::StatusSerializer + end + + def destroy + pin = StatusPin.find_by(account: current_account, status: @status) + pin&.destroy! + render json: @status, serializer: REST::StatusSerializer + end + + private + + def set_status + @status = Status.find(params[:status_id]) + end +end diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index 36eec4934..7b5f4bd9c 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -24,6 +24,14 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST'; export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; +export const PIN_REQUEST = 'PIN_REQUEST'; +export const PIN_SUCCESS = 'PIN_SUCCESS'; +export const PIN_FAIL = 'PIN_FAIL'; + +export const UNPIN_REQUEST = 'UNPIN_REQUEST'; +export const UNPIN_SUCCESS = 'UNPIN_SUCCESS'; +export const UNPIN_FAIL = 'UNPIN_FAIL'; + export function reblog(status) { return function (dispatch, getState) { dispatch(reblogRequest(status)); @@ -233,3 +241,73 @@ export function fetchFavouritesFail(id, error) { error, }; }; + +export function pin(status) { + return (dispatch, getState) => { + dispatch(pinRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { + dispatch(pinSuccess(status, response.data)); + }).catch(error => { + dispatch(pinFail(status, error)); + }); + }; +}; + +export function pinRequest(status) { + return { + type: PIN_REQUEST, + status, + }; +}; + +export function pinSuccess(status, response) { + return { + type: PIN_SUCCESS, + status, + response, + }; +}; + +export function pinFail(status, error) { + return { + type: PIN_FAIL, + status, + error, + }; +}; + +export function unpin (status) { + return (dispatch, getState) => { + dispatch(unpinRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { + dispatch(unpinSuccess(status, response.data)); + }).catch(error => { + dispatch(unpinFail(status, error)); + }); + }; +}; + +export function unpinRequest(status) { + return { + type: UNPIN_REQUEST, + status, + }; +}; + +export function unpinSuccess(status, response) { + return { + type: UNPIN_SUCCESS, + status, + response, + }; +}; + +export function unpinFail(status, error) { + return { + type: UNPIN_FAIL, + status, + error, + }; +}; diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 38a4aafc1..b4f523f72 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -31,6 +31,7 @@ export default class Status extends ImmutablePureComponent { onFavourite: PropTypes.func, onReblog: PropTypes.func, onDelete: PropTypes.func, + onPin: PropTypes.func, onOpenMedia: PropTypes.func, onOpenVideo: PropTypes.func, onBlock: PropTypes.func, diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 0d8c9add4..6436d6ebe 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -21,6 +21,8 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, + pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, + unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, }); @injectIntl @@ -41,6 +43,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onBlock: PropTypes.func, onReport: PropTypes.func, onMuteConversation: PropTypes.func, + onPin: PropTypes.func, me: PropTypes.number, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -77,6 +80,10 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onDelete(this.props.status); } + handlePinClick = () => { + this.props.onPin(this.props.status); + } + handleMentionClick = () => { this.props.onMention(this.props.status.get('account'), this.context.router.history); } @@ -121,6 +128,10 @@ export default class StatusActionBar extends ImmutablePureComponent { } if (status.getIn(['account', 'id']) === me) { + if (['public', 'unlisted'].indexOf(status.get('visibility')) !== -1) { + menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + } + menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index b150165aa..c488b6ce7 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -11,6 +11,8 @@ import { favourite, unreblog, unfavourite, + pin, + unpin, } from '../actions/interactions'; import { blockAccount, @@ -72,6 +74,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onPin (status) { + if (status.get('pinned')) { + dispatch(unpin(status)); + } else { + dispatch(pin(status)); + } + }, + onDelete (status) { if (!this.deleteModal) { dispatch(deleteStatus(status.get('id'))); diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 91ac64de2..c4a614677 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -14,6 +14,8 @@ const messages = defineMessages({ favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, report: { id: 'status.report', defaultMessage: 'Report @{name}' }, share: { id: 'status.share', defaultMessage: 'Share' }, + pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, + unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, }); @injectIntl @@ -31,6 +33,7 @@ export default class ActionBar extends React.PureComponent { onDelete: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, onReport: PropTypes.func, + onPin: PropTypes.func, me: PropTypes.number.isRequired, intl: PropTypes.object.isRequired, }; @@ -59,6 +62,10 @@ export default class ActionBar extends React.PureComponent { this.props.onReport(this.props.status); } + handlePinClick = () => { + this.props.onPin(this.props.status); + } + handleShare = () => { navigator.share({ text: this.props.status.get('search_index'), @@ -72,6 +79,10 @@ export default class ActionBar extends React.PureComponent { let menu = []; if (me === status.getIn(['account', 'id'])) { + if (['public', 'unlisted'].indexOf(status.get('visibility')) !== -1) { + menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + } + menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index cbabdd5bc..84e717a12 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -12,6 +12,8 @@ import { unfavourite, reblog, unreblog, + pin, + unpin, } from '../../actions/interactions'; import { replyCompose, @@ -87,6 +89,14 @@ export default class Status extends ImmutablePureComponent { } } + handlePin = (status) => { + if (status.get('pinned')) { + this.props.dispatch(unpin(status)); + } else { + this.props.dispatch(pin(status)); + } + } + handleReplyClick = (status) => { this.props.dispatch(replyCompose(status, this.context.router.history)); } @@ -187,6 +197,7 @@ export default class Status extends ImmutablePureComponent { onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} + onPin={this.handlePin} /> {descendants} diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index f5cf77f92..fa8cda97d 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -168,6 +168,7 @@ "status.mention": "أذكُر @{name}", "status.mute_conversation": "Mute conversation", "status.open": "وسع هذه المشاركة", + "status.pin": "Pin on profile", "status.reblog": "رَقِّي", "status.reblogged_by": "{name} رقى", "status.reply": "ردّ", @@ -179,6 +180,7 @@ "status.show_less": "إعرض أقلّ", "status.show_more": "أظهر المزيد", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "تحرير", "tabs_bar.federated_timeline": "الموحَّد", "tabs_bar.home": "الرئيسية", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index e6788f9eb..4aa097d31 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -168,6 +168,7 @@ "status.mention": "Споменаване", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Споделяне", "status.reblogged_by": "{name} сподели", "status.reply": "Отговор", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Съставяне", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Начало", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 95b3c60bf..d9cb7c7a3 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -168,6 +168,7 @@ "status.mention": "Esmentar @{name}", "status.mute_conversation": "Silenciar conversació", "status.open": "Ampliar aquest estat", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} ha retootejat", "status.reply": "Respondre", @@ -179,6 +180,7 @@ "status.show_less": "Mostra menys", "status.show_more": "Mostra més", "status.unmute_conversation": "Activar conversació", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compondre", "tabs_bar.federated_timeline": "Federada", "tabs_bar.home": "Inici", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 67a99b765..a5232552f 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -168,6 +168,7 @@ "status.mention": "Erwähnen", "status.mute_conversation": "Mute conversation", "status.open": "Öffnen", + "status.pin": "Pin on profile", "status.reblog": "Teilen", "status.reblogged_by": "{name} teilte", "status.reply": "Antworten", @@ -179,6 +180,7 @@ "status.show_less": "Weniger anzeigen", "status.show_more": "Mehr anzeigen", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Schreiben", "tabs_bar.federated_timeline": "Föderation", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index ef76f6e5b..fdb8aefe1 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -189,6 +189,14 @@ { "defaultMessage": "Unmute conversation", "id": "status.unmute_conversation" + }, + { + "defaultMessage": "Pin on profile", + "id": "status.pin" + }, + { + "defaultMessage": "Unpin from profile", + "id": "status.unpin" } ], "path": "app/javascript/mastodon/components/status_action_bar.json" @@ -1035,6 +1043,14 @@ { "defaultMessage": "Share", "id": "status.share" + }, + { + "defaultMessage": "Pin on profile", + "id": "status.pin" + }, + { + "defaultMessage": "Unpin from profile", + "id": "status.unpin" } ], "path": "app/javascript/mastodon/features/status/components/action_bar.json" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 2ea2062d3..595063888 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -168,6 +168,7 @@ "status.mention": "Mention @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} boosted", "status.reply": "Reply", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compose", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 960d747ec..ed323f406 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -168,6 +168,7 @@ "status.mention": "Mencii @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Diskonigi", "status.reblogged_by": "{name} diskonigita", "status.reply": "Respondi", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Ekskribi", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Hejmo", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 212d16639..2fee29148 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar", "status.mute_conversation": "Mute conversation", "status.open": "Expandir estado", + "status.pin": "Pin on profile", "status.reblog": "Retoot", "status.reblogged_by": "Retooteado por {name}", "status.reply": "Responder", @@ -179,6 +180,7 @@ "status.show_less": "Mostrar menos", "status.show_more": "Mostrar más", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Redactar", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Inicio", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 5ada62f93..89fa014e4 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -168,6 +168,7 @@ "status.mention": "نام‌بردن از @{name}", "status.mute_conversation": "بی‌صداکردن گفتگو", "status.open": "این نوشته را باز کن", + "status.pin": "Pin on profile", "status.reblog": "بازبوقیدن", "status.reblogged_by": "‫{name}‬ بازبوقید", "status.reply": "پاسخ", @@ -179,6 +180,7 @@ "status.show_less": "نهفتن", "status.show_more": "نمایش", "status.unmute_conversation": "باصداکردن گفتگو", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "بنویسید", "tabs_bar.federated_timeline": "همگانی", "tabs_bar.home": "خانه", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index cb9e9c2a6..1c1334899 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -168,6 +168,7 @@ "status.mention": "Mainitse @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Buustaa", "status.reblogged_by": "{name} buustasi", "status.reply": "Vastaa", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Luo", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Koti", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 34a89a69f..479b8de7d 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -168,6 +168,7 @@ "status.mention": "Mentionner", "status.mute_conversation": "Masquer la conversation", "status.open": "Déplier ce statut", + "status.pin": "Pin on profile", "status.reblog": "Partager", "status.reblogged_by": "{name} a partagé :", "status.reply": "Répondre", @@ -179,6 +180,7 @@ "status.show_less": "Replier", "status.show_more": "Déplier", "status.unmute_conversation": "Ne plus masquer la conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Composer", "tabs_bar.federated_timeline": "Fil public global", "tabs_bar.home": "Accueil", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 34266d8e1..1e221af9c 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -168,6 +168,7 @@ "status.mention": "פניה אל @{name}", "status.mute_conversation": "השתקת שיחה", "status.open": "הרחבת הודעה", + "status.pin": "Pin on profile", "status.reblog": "הדהוד", "status.reblogged_by": "הודהד על ידי {name}", "status.reply": "תגובה", @@ -179,6 +180,7 @@ "status.show_less": "הראה פחות", "status.show_more": "הראה יותר", "status.unmute_conversation": "הסרת השתקת שיחה", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "חיבור", "tabs_bar.federated_timeline": "ציר זמן בין-קהילתי", "tabs_bar.home": "בבית", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index f69b096d4..2effecb1e 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -168,6 +168,7 @@ "status.mention": "Spomeni @{name}", "status.mute_conversation": "Utišaj razgovor", "status.open": "Proširi ovaj status", + "status.pin": "Pin on profile", "status.reblog": "Podigni", "status.reblogged_by": "{name} je podigao", "status.reply": "Odgovori", @@ -179,6 +180,7 @@ "status.show_less": "Pokaži manje", "status.show_more": "Pokaži više", "status.unmute_conversation": "Poništi utišavanje razgovora", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Sastavi", "tabs_bar.federated_timeline": "Federalni", "tabs_bar.home": "Dom", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 4d2a50963..59a7b8deb 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -168,6 +168,7 @@ "status.mention": "Említés", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Reblog", "status.reblogged_by": "{name} reblogolta", "status.reply": "Válasz", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Összeállítás", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Kezdőlap", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 532739e3c..9dd66b6cd 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -168,6 +168,7 @@ "status.mention": "Balasan @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Tampilkan status ini", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "di-boost {name}", "status.reply": "Balas", @@ -179,6 +180,7 @@ "status.show_less": "Tampilkan lebih sedikit", "status.show_more": "Tampilkan semua", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Tulis", "tabs_bar.federated_timeline": "Gabungan", "tabs_bar.home": "Beranda", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index a5e363e40..07184ae81 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Detaligar ca mesajo", + "status.pin": "Pin on profile", "status.reblog": "Repetar", "status.reblogged_by": "{name} repetita", "status.reply": "Respondar", @@ -179,6 +180,7 @@ "status.show_less": "Montrar mine", "status.show_more": "Montrar plue", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Kompozar", "tabs_bar.federated_timeline": "Federata", "tabs_bar.home": "Hemo", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 329eb82ca..369ae7f32 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -168,6 +168,7 @@ "status.mention": "Nomina @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Espandi questo post", + "status.pin": "Pin on profile", "status.reblog": "Condividi", "status.reblogged_by": "{name} ha condiviso", "status.reply": "Rispondi", @@ -179,6 +180,7 @@ "status.show_less": "Mostra meno", "status.show_more": "Mostra di più", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Scrivi", "tabs_bar.federated_timeline": "Federazione", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 757190c90..c35b0def3 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -168,6 +168,7 @@ "status.mention": "返信", "status.mute_conversation": "会話をミュート", "status.open": "詳細を表示", + "status.pin": "Pin on profile", "status.reblog": "ブースト", "status.reblogged_by": "{name}さんにブーストされました", "status.reply": "返信", @@ -179,6 +180,7 @@ "status.show_less": "隠す", "status.show_more": "もっと見る", "status.unmute_conversation": "会話のミュートを解除", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "投稿", "tabs_bar.federated_timeline": "連合", "tabs_bar.home": "ホーム", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 47d0d4087..52ba1e70f 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -168,6 +168,7 @@ "status.mention": "답장", "status.mute_conversation": "이 대화를 뮤트", "status.open": "상세 정보 표시", + "status.pin": "Pin on profile", "status.reblog": "부스트", "status.reblogged_by": "{name}님이 부스트 했습니다", "status.reply": "답장", @@ -179,6 +180,7 @@ "status.show_less": "숨기기", "status.show_more": "더 보기", "status.unmute_conversation": "이 대화의 뮤트 해제하기", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "포스트", "tabs_bar.federated_timeline": "연합", "tabs_bar.home": "홈", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 4d68c7992..fb4127831 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -168,6 +168,7 @@ "status.mention": "Vermeld @{name}", "status.mute_conversation": "Negeer conversatie", "status.open": "Toot volledig tonen", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} boostte", "status.reply": "Reageren", @@ -179,6 +180,7 @@ "status.show_less": "Minder tonen", "status.show_more": "Meer tonen", "status.unmute_conversation": "Conversatie niet meer negeren", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Schrijven", "tabs_bar.federated_timeline": "Globaal", "tabs_bar.home": "Start", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 9453e65ff..2d6224c48 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -168,6 +168,7 @@ "status.mention": "Nevn @{name}", "status.mute_conversation": "Demp samtale", "status.open": "Utvid denne statusen", + "status.pin": "Pin on profile", "status.reblog": "Fremhev", "status.reblogged_by": "Fremhevd av {name}", "status.reply": "Svar", @@ -179,6 +180,7 @@ "status.show_less": "Vis mindre", "status.show_more": "Vis mer", "status.unmute_conversation": "Ikke demp samtale", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Komponer", "tabs_bar.federated_timeline": "Felles", "tabs_bar.home": "Hjem", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 5e5e28af0..34e1a8c47 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar", "status.mute_conversation": "Rescondre la conversacion", "status.open": "Desplegar aqueste estatut", + "status.pin": "Pin on profile", "status.reblog": "Partejar", "status.reblogged_by": "{name} a partejat :", "status.reply": "Respondre", @@ -179,6 +180,7 @@ "status.show_less": "Tornar plegar", "status.show_more": "Desplegar", "status.unmute_conversation": "Conversacions amb silenci levat", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compausar", "tabs_bar.federated_timeline": "Flux public global", "tabs_bar.home": "Acuèlh", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index af38bbb6c..8a8d0f38a 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -168,6 +168,7 @@ "status.mention": "Wspomnij o @{name}", "status.mute_conversation": "Wycisz konwersację", "status.open": "Rozszerz ten status", + "status.pin": "Pin on profile", "status.reblog": "Podbij", "status.reblogged_by": "{name} podbił", "status.reply": "Odpowiedz", @@ -179,6 +180,7 @@ "status.show_less": "Pokaż mniej", "status.show_more": "Pokaż więcej", "status.unmute_conversation": "Cofnij wyciszenie konwersacji", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Napisz", "tabs_bar.federated_timeline": "Globalne", "tabs_bar.home": "Strona główna", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 55d2f05de..8a299e272 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expandir", + "status.pin": "Pin on profile", "status.reblog": "Partilhar", "status.reblogged_by": "{name} partilhou", "status.reply": "Responder", @@ -179,6 +180,7 @@ "status.show_less": "Mostrar menos", "status.show_more": "Mostrar mais", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Criar", "tabs_bar.federated_timeline": "Global", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 55d2f05de..8a299e272 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -168,6 +168,7 @@ "status.mention": "Mencionar @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expandir", + "status.pin": "Pin on profile", "status.reblog": "Partilhar", "status.reblogged_by": "{name} partilhou", "status.reply": "Responder", @@ -179,6 +180,7 @@ "status.show_less": "Mostrar menos", "status.show_more": "Mostrar mais", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Criar", "tabs_bar.federated_timeline": "Global", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index af38fc723..822f116c7 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -168,6 +168,7 @@ "status.mention": "Упомянуть @{name}", "status.mute_conversation": "Заглушить тред", "status.open": "Развернуть статус", + "status.pin": "Pin on profile", "status.reblog": "Продвинуть", "status.reblogged_by": "{name} продвинул(а)", "status.reply": "Ответить", @@ -179,6 +180,7 @@ "status.show_less": "Свернуть", "status.show_more": "Развернуть", "status.unmute_conversation": "Снять глушение с треда", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Написать", "tabs_bar.federated_timeline": "Глобальная", "tabs_bar.home": "Главная", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index aa0929f82..9c985eec9 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -168,6 +168,7 @@ "status.mention": "Mention @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", + "status.pin": "Pin on profile", "status.reblog": "Boost", "status.reblogged_by": "{name} boosted", "status.reply": "Reply", @@ -179,6 +180,7 @@ "status.show_less": "Show less", "status.show_more": "Show more", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Compose", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 37ce8597e..41c9d44a7 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -168,6 +168,7 @@ "status.mention": "Bahset @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Bu gönderiyi genişlet", + "status.pin": "Pin on profile", "status.reblog": "Boost'la", "status.reblogged_by": "{name} boost etti", "status.reply": "Cevapla", @@ -179,6 +180,7 @@ "status.show_less": "Daha azı", "status.show_more": "Daha fazlası", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Oluştur", "tabs_bar.federated_timeline": "Federe", "tabs_bar.home": "Ana sayfa", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index fea7bd94e..6087e3a1e 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -168,6 +168,7 @@ "status.mention": "Згадати", "status.mute_conversation": "Заглушити діалог", "status.open": "Розгорнути допис", + "status.pin": "Pin on profile", "status.reblog": "Передмухнути", "status.reblogged_by": "{name} передмухнув(-ла)", "status.reply": "Відповісти", @@ -179,6 +180,7 @@ "status.show_less": "Згорнути", "status.show_more": "Розгорнути", "status.unmute_conversation": "Зняти глушення з діалогу", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "Написати", "tabs_bar.federated_timeline": "Глобальна", "tabs_bar.home": "Головна", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index e7c431454..2e3b4b0b8 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -168,6 +168,7 @@ "status.mention": "提及 @{name}", "status.mute_conversation": "Mute conversation", "status.open": "展开嘟文", + "status.pin": "Pin on profile", "status.reblog": "转嘟", "status.reblogged_by": "{name} 转嘟", "status.reply": "回应", @@ -179,6 +180,7 @@ "status.show_less": "减少显示", "status.show_more": "显示更多", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "撰写", "tabs_bar.federated_timeline": "跨站", "tabs_bar.home": "主页", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 7312aae82..1ab3b3f9d 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -168,6 +168,7 @@ "status.mention": "提及 @{name}", "status.mute_conversation": "Mute conversation", "status.open": "展開文章", + "status.pin": "Pin on profile", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推", "status.reply": "回應", @@ -179,6 +180,7 @@ "status.show_less": "減少顯示", "status.show_more": "顯示更多", "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "撰寫", "tabs_bar.federated_timeline": "跨站", "tabs_bar.home": "主頁", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 1c2e35272..571a2383d 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -168,6 +168,7 @@ "status.mention": "提到 @{name}", "status.mute_conversation": "消音對話", "status.open": "展開這個狀態", + "status.pin": "Pin on profile", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推了", "status.reply": "回應", @@ -179,6 +180,7 @@ "status.show_less": "看少點", "status.show_more": "看更多", "status.unmute_conversation": "不消音對話", + "status.unpin": "Unpin from profile", "tabs_bar.compose": "編輯", "tabs_bar.federated_timeline": "聯盟", "tabs_bar.home": "家", diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 3e40b0b42..38691dc43 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -7,6 +7,8 @@ import { FAVOURITE_SUCCESS, FAVOURITE_FAIL, UNFAVOURITE_SUCCESS, + PIN_SUCCESS, + UNPIN_SUCCESS, } from '../actions/interactions'; import { STATUS_FETCH_SUCCESS, @@ -114,6 +116,8 @@ export default function statuses(state = initialState, action) { case UNREBLOG_SUCCESS: case FAVOURITE_SUCCESS: case UNFAVOURITE_SUCCESS: + case PIN_SUCCESS: + case UNPIN_SUCCESS: return normalizeStatus(state, action.response); case FAVOURITE_REQUEST: return state.setIn([action.status.get('id'), 'favourited'], true); diff --git a/app/models/account.rb b/app/models/account.rb index 0c9c6aed4..b83aa1159 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -77,6 +77,10 @@ class Account < ApplicationRecord has_many :mentions, inverse_of: :account, dependent: :destroy has_many :notifications, inverse_of: :account, dependent: :destroy + # Pinned statuses + has_many :status_pins, inverse_of: :account, dependent: :destroy + has_many :pinned_statuses, through: :status_pins, class_name: 'Status', source: :status + # Media has_many :media_attachments, dependent: :destroy diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index 9ffed2910..b26520f5b 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -138,4 +138,8 @@ module AccountInteractions def reblogged?(status) status.proper.reblogs.where(account: self).exists? end + + def pinned?(status) + status_pins.where(status: status).exists? + end end diff --git a/app/models/status.rb b/app/models/status.rb index 24eaf7071..3dc83ad1f 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -164,6 +164,10 @@ class Status < ApplicationRecord ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).map { |m| [m.conversation_id, true] }.to_h end + def pins_map(status_ids, account_id) + StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |p| [p.status_id, true] }.to_h + end + def reload_stale_associations!(cached_items) account_ids = [] diff --git a/app/models/status_pin.rb b/app/models/status_pin.rb new file mode 100644 index 000000000..c9a669344 --- /dev/null +++ b/app/models/status_pin.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: status_pins +# +# id :integer not null, primary key +# account_id :integer not null +# status_id :integer not null +# + +class StatusPin < ApplicationRecord + belongs_to :account, required: true + belongs_to :status, required: true + + validates_with StatusPinValidator +end diff --git a/app/presenters/status_relationships_presenter.rb b/app/presenters/status_relationships_presenter.rb index 03294015f..10b449504 100644 --- a/app/presenters/status_relationships_presenter.rb +++ b/app/presenters/status_relationships_presenter.rb @@ -1,19 +1,24 @@ # frozen_string_literal: true class StatusRelationshipsPresenter - attr_reader :reblogs_map, :favourites_map, :mutes_map + attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map - def initialize(statuses, current_account_id = nil, reblogs_map: {}, favourites_map: {}, mutes_map: {}) + def initialize(statuses, current_account_id = nil, options = {}) if current_account_id.nil? @reblogs_map = {} @favourites_map = {} @mutes_map = {} + @pins_map = {} else - status_ids = statuses.compact.flat_map { |s| [s.id, s.reblog_of_id] }.uniq - conversation_ids = statuses.compact.map(&:conversation_id).compact.uniq - @reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(reblogs_map) - @favourites_map = Status.favourites_map(status_ids, current_account_id).merge(favourites_map) - @mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(mutes_map) + statuses = statuses.compact + status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq + conversation_ids = statuses.map(&:conversation_id).compact.uniq + pinnable_status_ids = statuses.map(&:proper).select { |s| s.account_id == current_account_id && %w(public unlisted).include?(s.visibility) }.map(&:id) + + @reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {}) + @favourites_map = Status.favourites_map(status_ids, current_account_id).merge(options[:favourites_map] || {}) + @mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {}) + @pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {}) end end end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 246b12a90..298a3bb40 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -8,6 +8,7 @@ class REST::StatusSerializer < ActiveModel::Serializer attribute :favourited, if: :current_user? attribute :reblogged, if: :current_user? attribute :muted, if: :current_user? + attribute :pinned, if: :pinnable? belongs_to :reblog, serializer: REST::StatusSerializer belongs_to :application @@ -57,6 +58,21 @@ class REST::StatusSerializer < ActiveModel::Serializer end end + def pinned + if instance_options && instance_options[:relationships] + instance_options[:relationships].pins_map[object.id] || false + else + current_user.account.pinned?(object) + end + end + + def pinnable? + current_user? && + current_user.account_id == object.account_id && + !object.reblog? && + %w(public unlisted).include?(object.visibility) + end + class ApplicationSerializer < ActiveModel::Serializer attributes :name, :website end diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb new file mode 100644 index 000000000..f557df6af --- /dev/null +++ b/app/validators/status_pin_validator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class StatusPinValidator < ActiveModel::Validator + def validate(pin) + pin.errors.add(:status, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? + pin.errors.add(:status, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id + pin.errors.add(:status, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) + end +end diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index ec44f4c74..e0f9f869a 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -30,6 +30,9 @@ = render 'nothing_here' - else .activity-stream.with-header + - if params[:page].to_i.zero? + = render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true } + = render partial: 'stream_entries/status', collection: @statuses, as: :status - if @statuses.size == 20 diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 50a373743..e2e1fdd12 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -1,4 +1,5 @@ :ruby + pinned ||= false include_threads ||= false is_predecessor ||= false is_successor ||= false @@ -25,6 +26,12 @@ = link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do %strong.emojify= display_name(status.account) = t('stream_entries.reblogged') + - elsif pinned + .pre-header + .pre-header__icon + = fa_icon('thumb-tack fw') + %span + = t('stream_entries.pinned') = render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper diff --git a/config/locales/en.yml b/config/locales/en.yml index 97bb14186..96d08e6b2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -434,6 +434,10 @@ en: statuses: open_in_web: Open in web over_character_limit: character limit of %{max} exceeded + pin_errors: + ownership: Someone else's toot cannot be pinned + private: Non-public toot cannot be pinned + reblog: A boost cannot be pinned show_more: Show more visibilities: private: Followers-only @@ -444,6 +448,7 @@ en: unlisted_long: Everyone can see, but not listed on public timelines stream_entries: click_to_show: Click to show + pinned: Pinned toot reblogged: boosted sensitive_content: Sensitive content terms: diff --git a/config/routes.rb b/config/routes.rb index 94a4ac88e..7588805c0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -162,6 +162,9 @@ Rails.application.routes.draw do resource :mute, only: :create post :unmute, to: 'mutes#destroy' + + resource :pin, only: :create + post :unpin, to: 'pins#destroy' end member do @@ -175,7 +178,8 @@ Rails.application.routes.draw do resource :public, only: :show, controller: :public resources :tag, only: :show end - resources :streaming, only: [:index] + + resources :streaming, only: [:index] get '/search', to: 'search#index', as: :search @@ -210,6 +214,7 @@ Rails.application.routes.draw do resource :search, only: :show, controller: :search resources :relationships, only: :index end + resources :accounts, only: [:show] do resources :statuses, only: :index, controller: 'accounts/statuses' resources :followers, only: :index, controller: 'accounts/follower_accounts' @@ -245,7 +250,7 @@ Rails.application.routes.draw do root 'home#index' match '*unmatched_route', - via: :all, - to: 'application#raise_not_found', - format: false + via: :all, + to: 'application#raise_not_found', + format: false end diff --git a/db/migrate/20170823162448_create_status_pins.rb b/db/migrate/20170823162448_create_status_pins.rb new file mode 100644 index 000000000..9a6d4a7b9 --- /dev/null +++ b/db/migrate/20170823162448_create_status_pins.rb @@ -0,0 +1,10 @@ +class CreateStatusPins < ActiveRecord::Migration[5.1] + def change + create_table :status_pins do |t| + t.belongs_to :account, foreign_key: { on_delete: :cascade }, null: false + t.belongs_to :status, foreign_key: { on_delete: :cascade }, null: false + end + + add_index :status_pins, [:account_id, :status_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 98b07e282..d0e72be0f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170720000000) do +ActiveRecord::Schema.define(version: 20170823162448) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -282,6 +282,14 @@ ActiveRecord::Schema.define(version: 20170720000000) do t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true end + create_table "status_pins", force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "status_id", null: false + t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true + t.index ["account_id"], name: "index_status_pins_on_account_id" + t.index ["status_id"], name: "index_status_pins_on_status_id" + end + create_table "statuses", force: :cascade do |t| t.string "uri" t.integer "account_id", null: false @@ -430,6 +438,8 @@ ActiveRecord::Schema.define(version: 20170720000000) do add_foreign_key "reports", "accounts", on_delete: :cascade add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade add_foreign_key "session_activations", "users", on_delete: :cascade + add_foreign_key "status_pins", "accounts", on_delete: :cascade + add_foreign_key "status_pins", "statuses", on_delete: :cascade add_foreign_key "statuses", "accounts", column: "in_reply_to_account_id", on_delete: :nullify add_foreign_key "statuses", "accounts", on_delete: :cascade add_foreign_key "statuses", "statuses", column: "in_reply_to_id", on_delete: :nullify diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb index 8b4fd6a5b..c49a77ac3 100644 --- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb @@ -18,21 +18,37 @@ describe Api::V1::Accounts::StatusesController do expect(response).to have_http_status(:success) expect(response.headers['Link'].links.size).to eq(2) end - end - describe 'GET #index with only media' do - it 'returns http success' do - get :index, params: { account_id: user.account.id, only_media: true } + context 'with only media' do + it 'returns http success' do + get :index, params: { account_id: user.account.id, only_media: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(:success) + end end - end - describe 'GET #index with exclude replies' do - it 'returns http success' do - get :index, params: { account_id: user.account.id, exclude_replies: true } + context 'with exclude replies' do + before do + Fabricate(:status, account: user.account, thread: Fabricate(:status)) + end - expect(response).to have_http_status(:success) + it 'returns http success' do + get :index, params: { account_id: user.account.id, exclude_replies: true } + + expect(response).to have_http_status(:success) + end + end + + context 'with only pinned' do + before do + Fabricate(:status_pin, account: user.account, status: Fabricate(:status, account: user.account)) + end + + it 'returns http success' do + get :index, params: { account_id: user.account.id, pinned: true } + + expect(response).to have_http_status(:success) + end end end end diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb new file mode 100644 index 000000000..2e170da24 --- /dev/null +++ b/spec/controllers/api/v1/statuses/pins_controller_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::Statuses::PinsController do + render_views + + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write', application: app) } + + context 'with an oauth token' do + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'POST #create' do + let(:status) { Fabricate(:status, account: user.account) } + + before do + post :create, params: { status_id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'updates the pinned attribute' do + expect(user.account.pinned?(status)).to be true + end + + it 'return json with updated attributes' do + hash_body = body_as_json + + expect(hash_body[:id]).to eq status.id + expect(hash_body[:pinned]).to be true + end + end + + describe 'POST #destroy' do + let(:status) { Fabricate(:status, account: user.account) } + + before do + Fabricate(:status_pin, status: status, account: user.account) + post :destroy, params: { status_id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'updates the pinned attribute' do + expect(user.account.pinned?(status)).to be false + end + end + end +end diff --git a/spec/fabricators/status_pin_fabricator.rb b/spec/fabricators/status_pin_fabricator.rb new file mode 100644 index 000000000..6a9006c9f --- /dev/null +++ b/spec/fabricators/status_pin_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:status_pin) do + account + status +end diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb new file mode 100644 index 000000000..6f54f80f9 --- /dev/null +++ b/spec/models/status_pin_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe StatusPin, type: :model do + describe 'validations' do + it 'allows pins of own statuses' do + account = Fabricate(:account) + status = Fabricate(:status, account: account) + + expect(StatusPin.new(account: account, status: status).save).to be true + end + + it 'does not allow pins of statuses by someone else' do + account = Fabricate(:account) + status = Fabricate(:status) + + expect(StatusPin.new(account: account, status: status).save).to be false + end + + it 'does not allow pins of reblogs' do + account = Fabricate(:account) + status = Fabricate(:status, account: account) + reblog = Fabricate(:status, reblog: status) + + expect(StatusPin.new(account: account, status: reblog).save).to be false + end + + it 'does not allow pins of private statuses' do + account = Fabricate(:account) + status = Fabricate(:status, account: account, visibility: :private) + + expect(StatusPin.new(account: account, status: status).save).to be false + end + + it 'does not allow pins of direct statuses' do + account = Fabricate(:account) + status = Fabricate(:status, account: account, visibility: :direct) + + expect(StatusPin.new(account: account, status: status).save).to be false + end + end +end -- cgit From b88635202f49c9d6f2770c70984cb232b508de52 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 25 Aug 2017 20:03:26 +0900 Subject: Add label for application scopes (#4691) * Add label for application scopes * hint --- app/views/settings/applications/_fields.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/views') diff --git a/app/views/settings/applications/_fields.html.haml b/app/views/settings/applications/_fields.html.haml index 83297a1ae..b21f3cca6 100644 --- a/app/views/settings/applications/_fields.html.haml +++ b/app/views/settings/applications/_fields.html.haml @@ -13,9 +13,9 @@ collection: Doorkeeper.configuration.scopes, wrapper: :with_label, include_blank: false, + label_method: lambda { |scope| safe_join([scope, content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) }, selected: f.object.scopes.all, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' - -- cgit From 00f9f16f94feeaf2fae9a5a1a7bcb3a06b86a036 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 26 Aug 2017 00:21:00 +0900 Subject: Change timezone of the datetime to what browser specifies (#4688) --- app/views/admin/accounts/show.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'app/views') diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 18bcd5e8e..85841d7a1 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -24,7 +24,8 @@ %th= t('admin.accounts.most_recent_activity') %td - if @account.user_current_sign_in_at - = l @account.user_current_sign_in_at + %time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) } + = l @account.user_current_sign_in_at - else Never - else @@ -43,7 +44,8 @@ %th= t('admin.accounts.push_subscription_expires') %td - if @account.subscribed? - = l @account.subscription_expires_at + %time.formatted{ datetime: account.subscription_expires_at.iso8601, title: l(account.subscription_expires_at) } + = l @account.subscription_expires_at - else = t('admin.accounts.not_subscribed') %tr -- cgit From 3ac7b353f8ba74bce9e20974bcc1832930424ef2 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 26 Aug 2017 19:39:26 +0900 Subject: Fix missing at-sign (regression from #4688) (#4705) --- app/views/admin/accounts/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/views') diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 85841d7a1..dc2f16cc9 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -44,7 +44,7 @@ %th= t('admin.accounts.push_subscription_expires') %td - if @account.subscribed? - %time.formatted{ datetime: account.subscription_expires_at.iso8601, title: l(account.subscription_expires_at) } + %time.formatted{ datetime: @account.subscription_expires_at.iso8601, title: l(@account.subscription_expires_at) } = l @account.subscription_expires_at - else = t('admin.accounts.not_subscribed') -- cgit From 15093f9113e0eda8a66a7c12bbd92e7e8670da15 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 27 Aug 2017 17:04:45 +0200 Subject: Shorten display of large numbers on public profiles (#4711) --- app/views/accounts/_header.html.haml | 6 +++--- config/i18n-tasks.yml | 4 ++-- config/locales/en.yml | 11 +++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'app/views') diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 8009e903e..a6366b0f3 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -37,15 +37,15 @@ .details-counters .counter{ class: active_nav_class(short_account_url(account)) } = link_to short_account_url(account), class: 'u-url u-uid' do - %span.counter-number= number_with_delimiter account.statuses_count + %span.counter-number= number_to_human account.statuses_count %span.counter-label= t('accounts.posts') .counter{ class: active_nav_class(account_following_index_url(account)) } = link_to account_following_index_url(account) do - %span.counter-number= number_with_delimiter account.following_count + %span.counter-number= number_to_human account.following_count %span.counter-label= t('accounts.following') .counter{ class: active_nav_class(account_followers_url(account)) } = link_to account_followers_url(account) do - %span.counter-number= number_with_delimiter account.followers_count + %span.counter-number= number_to_human account.followers_count %span.counter-label= t('accounts.followers') diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 849e8116a..b51cf46df 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -36,7 +36,7 @@ ignore_missing: - 'activerecord.attributes.*' - 'activerecord.errors.*' - '{devise,pagination,doorkeeper}.*' - - '{date,datetime,time}.*' + - '{date,datetime,time,number}.*' - 'simple_form.{yes,no}' - 'simple_form.{placeholders,hints,labels}.*' - 'simple_form.{error_notification,required}.:' @@ -50,7 +50,7 @@ ignore_unused: - 'activerecord.attributes.*' - 'activerecord.errors.*' - '{devise,pagination,doorkeeper}.*' - - '{date,datetime,time}.*' + - '{date,datetime,time,number}.*' - 'simple_form.{yes,no}' - 'simple_form.{placeholders,hints,labels}.*' - 'simple_form.{error_notification,required}.:' diff --git a/config/locales/en.yml b/config/locales/en.yml index 96d08e6b2..1a9926edd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -353,6 +353,17 @@ en: reblog: body: 'Your status was boosted by %{name}:' subject: "%{name} boosted your status" + number: + human: + decimal_units: + format: "%n%u" + units: + billion: B + million: M + quadrillion: Q + thousand: K + trillion: T + unit: '' pagination: next: Next prev: Prev -- cgit From e95bdec7c5da63930fc2e08e67e4358fec19296d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 Aug 2017 10:23:43 +0200 Subject: Update status embeds (#4742) - Use statuses controller for embeds instead of stream entries controller - Prefer /@:username/:id/embed URL for embeds - Use /@:username as author_url in OEmbed - Add follow link to embeds which opens web intent in new window - Use redis cache in development - Cache entire embed --- app/controllers/api/oembed_controller.rb | 8 ++-- app/controllers/statuses_controller.rb | 5 ++ app/controllers/stream_entries_controller.rb | 5 +- app/helpers/stream_entries_helper.rb | 2 +- app/javascript/packs/public.js | 7 +++ app/javascript/styles/stream_entries.scss | 30 ++++++++++++ app/lib/status_finder.rb | 34 +++++++++++++ app/lib/stream_entry_finder.rb | 34 ------------- app/serializers/oembed_serializer.rb | 4 +- .../stream_entries/_detailed_status.html.haml | 5 ++ app/views/stream_entries/embed.html.haml | 5 +- config/brakeman.ignore | 50 ++++++++++---------- config/environments/development.rb | 5 +- config/routes.rb | 2 + spec/controllers/stream_entries_controller_spec.rb | 6 +-- spec/lib/status_finder_spec.rb | 55 ++++++++++++++++++++++ spec/lib/stream_entry_finder_spec.rb | 55 ---------------------- 17 files changed, 179 insertions(+), 133 deletions(-) create mode 100644 app/lib/status_finder.rb delete mode 100644 app/lib/stream_entry_finder.rb create mode 100644 spec/lib/status_finder_spec.rb delete mode 100644 spec/lib/stream_entry_finder_spec.rb (limited to 'app/views') diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb index f8c87dd16..37a163cd3 100644 --- a/app/controllers/api/oembed_controller.rb +++ b/app/controllers/api/oembed_controller.rb @@ -4,14 +4,14 @@ class Api::OEmbedController < Api::BaseController respond_to :json def show - @stream_entry = find_stream_entry.stream_entry - render json: @stream_entry, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default + @status = status_finder.status + render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default end private - def find_stream_entry - StreamEntryFinder.new(params[:url]) + def status_finder + StatusFinder.new(params[:url]) end def maxwidth_or_default diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index a9768d092..65206ea96 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -30,6 +30,11 @@ class StatusesController < ApplicationController render json: @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end + def embed + response.headers['X-Frame-Options'] = 'ALLOWALL' + render 'stream_entries/embed', layout: 'embedded' + end + private def set_account diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index ccb15495e..cc579dbc8 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -25,10 +25,7 @@ class StreamEntriesController < ApplicationController end def embed - response.headers['X-Frame-Options'] = 'ALLOWALL' - return gone if @stream_entry.activity.nil? - - render layout: 'embedded' + redirect_to embed_short_account_status_url(@account, @stream_entry.activity), status: 301 end private diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 4ef7cffb0..445114985 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module StreamEntriesHelper - EMBEDDED_CONTROLLER = 'stream_entries' + EMBEDDED_CONTROLLER = 'statuses' EMBEDDED_ACTION = 'embed' def display_name(account) diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index d8a0f4eee..ce12041e6 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -38,6 +38,13 @@ function main() { content.title = dateTimeFormat.format(datetime); content.textContent = relativeFormat.format(datetime); }); + + [].forEach.call(document.querySelectorAll('.logo-button'), (content) => { + content.addEventListener('click', (e) => { + e.preventDefault(); + window.open(e.target.href, 'mastodon-intent', 'width=400,height=400,resizable=no,menubar=no,status=no,scrollbars=yes'); + }); + }); }); delegate(document, '.video-player video', 'click', ({ target }) => { diff --git a/app/javascript/styles/stream_entries.scss b/app/javascript/styles/stream_entries.scss index 1192e2a80..7048ab110 100644 --- a/app/javascript/styles/stream_entries.scss +++ b/app/javascript/styles/stream_entries.scss @@ -421,3 +421,33 @@ } } } + +.button.button-secondary.logo-button { + position: absolute; + right: 14px; + top: 14px; + font-size: 14px; + + svg { + width: 20px; + height: auto; + vertical-align: middle; + margin-right: 5px; + + path:first-child { + fill: $ui-primary-color; + } + + path:last-child { + fill: $simple-background-color; + } + } + + &:active, + &:focus, + &:hover { + svg path:first-child { + fill: lighten($ui-primary-color, 4%); + } + } +} diff --git a/app/lib/status_finder.rb b/app/lib/status_finder.rb new file mode 100644 index 000000000..bd910f12b --- /dev/null +++ b/app/lib/status_finder.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class StatusFinder + attr_reader :url + + def initialize(url) + @url = url + end + + def status + verify_action! + + case recognized_params[:controller] + when 'stream_entries' + StreamEntry.find(recognized_params[:id]).status + when 'statuses' + Status.find(recognized_params[:id]) + else + raise ActiveRecord::RecordNotFound + end + end + + private + + def recognized_params + Rails.application.routes.recognize_path(url) + end + + def verify_action! + unless recognized_params[:action] == 'show' + raise ActiveRecord::RecordNotFound + end + end +end diff --git a/app/lib/stream_entry_finder.rb b/app/lib/stream_entry_finder.rb deleted file mode 100644 index 0ea33229c..000000000 --- a/app/lib/stream_entry_finder.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -class StreamEntryFinder - attr_reader :url - - def initialize(url) - @url = url - end - - def stream_entry - verify_action! - - case recognized_params[:controller] - when 'stream_entries' - StreamEntry.find(recognized_params[:id]) - when 'statuses' - Status.find(recognized_params[:id]).stream_entry - else - raise ActiveRecord::RecordNotFound - end - end - - private - - def recognized_params - Rails.application.routes.recognize_path(url) - end - - def verify_action! - unless recognized_params[:action] == 'show' - raise ActiveRecord::RecordNotFound - end - end -end diff --git a/app/serializers/oembed_serializer.rb b/app/serializers/oembed_serializer.rb index 78376d253..0c2ced859 100644 --- a/app/serializers/oembed_serializer.rb +++ b/app/serializers/oembed_serializer.rb @@ -21,7 +21,7 @@ class OEmbedSerializer < ActiveModel::Serializer end def author_url - account_url(object.account) + short_account_url(object.account) end def provider_name @@ -38,7 +38,7 @@ class OEmbedSerializer < ActiveModel::Serializer def html tag :iframe, - src: embed_account_stream_entry_url(object.account, object), + src: embed_short_account_status_url(object.account, object), style: 'width: 100%; overflow: hidden', frameborder: '0', scrolling: 'no', diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index 193cc6470..107202b75 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -1,4 +1,9 @@ .detailed-status.light + - if embedded_view? + = link_to "web+mastodon://follow?uri=#{status.account.local_username_and_domain}", class: 'button button-secondary logo-button', target: '_new' do + = render file: Rails.root.join('app', 'javascript', 'images', 'logo.svg') + = t('accounts.follow') + = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do %div .avatar diff --git a/app/views/stream_entries/embed.html.haml b/app/views/stream_entries/embed.html.haml index 5df82528b..b703c15d2 100644 --- a/app/views/stream_entries/embed.html.haml +++ b/app/views/stream_entries/embed.html.haml @@ -1,2 +1,3 @@ -.activity-stream.activity-stream-headless - = render @type, @type.to_sym => @stream_entry.activity, centered: true +- cache @stream_entry.activity do + .activity-stream.activity-stream-headless + = render "stream_entries/#{@type}", @type.to_sym => @stream_entry.activity, centered: true diff --git a/config/brakeman.ignore b/config/brakeman.ignore index f9bc77069..dbb59dd07 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,5 +1,24 @@ { "ignored_warnings": [ + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "44d3f14e05d8fbb5b23e13ac02f15aa38b2a2f0f03b9ba76bab7f98e155a4a4e", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/stream_entries/embed.html.haml", + "line": 3, + "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :centered => true })", + "render_path": [{"type":"controller","class":"StatusesController","method":"embed","line":35,"file":"app/controllers/statuses_controller.rb"}], + "location": { + "type": "template", + "template": "stream_entries/embed" + }, + "user_input": "params[:id]", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -7,10 +26,10 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/admin/accounts/index.html.haml", - "line": 32, + "line": 63, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_accounts.page(params[:page]), {})", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":7,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":10,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/index" @@ -39,25 +58,6 @@ "confidence": "High", "note": "" }, - { - "warning_type": "Dynamic Render Path", - "warning_code": 15, - "fingerprint": "c417f9d44ab05dd9cf3d5ec9df2324a5036774c151181787b32c4c940623191b", - "check_name": "Render", - "message": "Render path contains parameter value", - "file": "app/views/stream_entries/embed.html.haml", - "line": 2, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", - "code": "render(action => Account.find_local!(params[:account_username]).stream_entries.where(:activity_type => \"Status\").find(params[:id]).activity_type.downcase, { Account.find_local!(params[:account_username]).stream_entries.where(:activity_type => \"Status\").find(params[:id]).activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).stream_entries.where(:activity_type => \"Status\").find(params[:id]).activity, :centered => true })", - "render_path": [{"type":"controller","class":"StreamEntriesController","method":"embed","line":32,"file":"app/controllers/stream_entries_controller.rb"}], - "location": { - "type": "template", - "template": "stream_entries/embed" - }, - "user_input": "params[:id]", - "confidence": "Weak", - "note": "" - }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -84,10 +84,10 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/stream_entries/show.html.haml", - "line": 19, + "line": 23, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(partial => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { :locals => ({ Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :include_threads => true }) })", - "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":15,"file":"app/controllers/statuses_controller.rb"}], + "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":20,"file":"app/controllers/statuses_controller.rb"}], "location": { "type": "template", "template": "stream_entries/show" @@ -97,6 +97,6 @@ "note": "" } ], - "updated": "2017-05-07 08:26:06 +0900", - "brakeman_version": "3.6.1" + "updated": "2017-08-30 05:14:04 +0200", + "brakeman_version": "3.7.2" } diff --git a/config/environments/development.rb b/config/environments/development.rb index 4c60965c8..59bc2c3e2 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -16,9 +16,10 @@ Rails.application.configure do if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true - config.cache_store = :memory_store + config.cache_store = :redis_store, ENV['REDIS_URL'], REDIS_CACHE_PARAMS + config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}", } else config.action_controller.perform_caching = false diff --git a/config/routes.rb b/config/routes.rb index 7588805c0..f8f145e1d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,6 +44,7 @@ Rails.application.routes.draw do resources :statuses, only: [:show] do member do get :activity + get :embed end end @@ -59,6 +60,7 @@ Rails.application.routes.draw do get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies get '/@:username/media', to: 'accounts#show', as: :short_account_media get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status + get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status namespace :settings do resource :profile, only: [:show, :update] diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb index 808cf667c..f81e2be7b 100644 --- a/spec/controllers/stream_entries_controller_spec.rb +++ b/spec/controllers/stream_entries_controller_spec.rb @@ -88,14 +88,12 @@ RSpec.describe StreamEntriesController, type: :controller do describe 'GET #embed' do include_examples 'before_action', :embed - it 'returns embedded view of status' do + it 'redirects to new embed page' do status = Fabricate(:status) get :embed, params: { account_username: status.account.username, id: status.stream_entry.id } - expect(response).to have_http_status(:success) - expect(response.headers['X-Frame-Options']).to eq 'ALLOWALL' - expect(response).to render_template(layout: 'embedded') + expect(response).to redirect_to(embed_short_account_status_url(status.account, status)) end end end diff --git a/spec/lib/status_finder_spec.rb b/spec/lib/status_finder_spec.rb new file mode 100644 index 000000000..5c2f2dbe8 --- /dev/null +++ b/spec/lib/status_finder_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe StatusFinder do + include RoutingHelper + + describe '#status' do + context 'with a status url' do + let(:status) { Fabricate(:status) } + let(:url) { short_account_status_url(account_username: status.account.username, id: status.id) } + subject { described_class.new(url) } + + it 'finds the stream entry' do + expect(subject.status).to eq(status) + end + + it 'raises an error if action is not :show' do + recognized = Rails.application.routes.recognize_path(url) + expect(recognized).to receive(:[]).with(:action).and_return(:create) + expect(Rails.application.routes).to receive(:recognize_path).with(url).and_return(recognized) + + expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with a stream entry url' do + let(:stream_entry) { Fabricate(:stream_entry) } + let(:url) { account_stream_entry_url(stream_entry.account, stream_entry) } + subject { described_class.new(url) } + + it 'finds the stream entry' do + expect(subject.status).to eq(stream_entry.status) + end + end + + context 'with a plausible url' do + let(:url) { 'https://example.com/users/test/updates/123/embed' } + subject { described_class.new(url) } + + it 'raises an error' do + expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with an unrecognized url' do + let(:url) { 'https://example.com/about' } + subject { described_class.new(url) } + + it 'raises an error' do + expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end diff --git a/spec/lib/stream_entry_finder_spec.rb b/spec/lib/stream_entry_finder_spec.rb deleted file mode 100644 index 64e03c36a..000000000 --- a/spec/lib/stream_entry_finder_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe StreamEntryFinder do - include RoutingHelper - - describe '#stream_entry' do - context 'with a status url' do - let(:status) { Fabricate(:status) } - let(:url) { short_account_status_url(account_username: status.account.username, id: status.id) } - subject { described_class.new(url) } - - it 'finds the stream entry' do - expect(subject.stream_entry).to eq(status.stream_entry) - end - - it 'raises an error if action is not :show' do - recognized = Rails.application.routes.recognize_path(url) - expect(recognized).to receive(:[]).with(:action).and_return(:create) - expect(Rails.application.routes).to receive(:recognize_path).with(url).and_return(recognized) - - expect { subject.stream_entry }.to raise_error(ActiveRecord::RecordNotFound) - end - end - - context 'with a stream entry url' do - let(:stream_entry) { Fabricate(:stream_entry) } - let(:url) { account_stream_entry_url(stream_entry.account, stream_entry) } - subject { described_class.new(url) } - - it 'finds the stream entry' do - expect(subject.stream_entry).to eq(stream_entry) - end - end - - context 'with a plausible url' do - let(:url) { 'https://example.com/users/test/updates/123/embed' } - subject { described_class.new(url) } - - it 'raises an error' do - expect { subject.stream_entry }.to raise_error(ActiveRecord::RecordNotFound) - end - end - - context 'with an unrecognized url' do - let(:url) { 'https://example.com/about' } - subject { described_class.new(url) } - - it 'raises an error' do - expect { subject.stream_entry }.to raise_error(ActiveRecord::RecordNotFound) - end - end - end -end -- cgit From 2305f7c391325c7abf8746ebb2bb560c13df4437 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 1 Sep 2017 23:13:31 +0900 Subject: Use system's default font on non web UI pages (#4553) * Use system's default font on non web UI pages * Remove import for Redirect --- app/javascript/mastodon/features/ui/index.js | 9 +------ app/javascript/styles/basics.scss | 36 +++++++++++----------------- app/serializers/initial_state_serializer.rb | 1 - app/views/layouts/application.html.haml | 1 + 4 files changed, 16 insertions(+), 31 deletions(-) (limited to 'app/views') diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 34b59fcc5..8f971ae67 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -1,5 +1,4 @@ import React from 'react'; -import classNames from 'classnames'; import NotificationsContainer from './containers/notifications_container'; import PropTypes from 'prop-types'; import LoadingBarContainer from './containers/loading_bar_container'; @@ -43,7 +42,6 @@ import { import '../../components/status'; const mapStateToProps = state => ({ - systemFontUi: state.getIn(['meta', 'system_font_ui']), isComposing: state.getIn(['compose', 'is_composing']), }); @@ -58,7 +56,6 @@ export default class UI extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, children: PropTypes.node, - systemFontUi: PropTypes.bool, isComposing: PropTypes.bool, location: PropTypes.object, }; @@ -197,12 +194,8 @@ export default class UI extends React.PureComponent { const { width, draggingOver } = this.state; const { children } = this.props; - const className = classNames('ui', { - 'system-font': this.props.systemFontUi, - }); - return ( -
+
diff --git a/app/javascript/styles/basics.scss b/app/javascript/styles/basics.scss index 6e87157ec..f18a32201 100644 --- a/app/javascript/styles/basics.scss +++ b/app/javascript/styles/basics.scss @@ -1,4 +1,5 @@ body { + font-family: 'mastodon-font-sans-serif', sans-serif; background: $ui-base-color; background-size: cover; background-attachment: fixed; @@ -13,9 +14,19 @@ body { -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-tap-highlight-color: transparent; - // This is done because we want to use mastodon-font-sans-serif (a.k.a Roboto) on the `.ui` element in the app UI - &:not(.app-body) { - font-family: 'mastodon-font-sans-serif', sans-serif; + &.system-font { + // system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+) + // -apple-system => Safari <11 specific + // BlinkMacSystemFont => Chrome <56 on macOS specific + // Segoe UI => Windows 7/8/10 + // Oxygen => KDE + // Ubuntu => Unity/Ubuntu + // Cantarell => GNOME + // Fira Sans => Firefox OS + // Droid Sans => Older Androids (<4.0) + // Helvetica Neue => Older macOS <10.11 + // mastodon-font-sans-serif => web-font (Roboto) fallback and newer Androids (>=4.0) + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", mastodon-font-sans-serif, sans-serif; } &.app-body { @@ -72,22 +83,3 @@ button { align-items: center; justify-content: center; } - -.ui.system-font { - // system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+) - // -apple-system => Safari <11 specific - // BlinkMacSystemFont => Chrome <56 on macOS specific - // Segoe UI => Windows 7/8/10 - // Oxygen => KDE - // Ubuntu => Unity/Ubuntu - // Cantarell => GNOME - // Fira Sans => Firefox OS - // Droid Sans => Older Androids (<4.0) - // Helvetica Neue => Older macOS <10.11 - // mastodon-font-sans-serif => web-font (Roboto) fallback and newer Androids (>=4.0) - font-family: system-ui, -apple-system,BlinkMacSystemFont, "Segoe UI","Oxygen", "Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",mastodon-font-sans-serif, sans-serif; -} - -.ui:not(.system-font) { - font-family: 'mastodon-font-sans-serif', sans-serif; -} diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 0ac5e8319..32ffcc688 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -19,7 +19,6 @@ class InitialStateSerializer < ActiveModel::Serializer store[:boost_modal] = object.current_account.user.setting_boost_modal store[:delete_modal] = object.current_account.user.setting_delete_modal store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif - store[:system_font_ui] = object.current_account.user.setting_system_font_ui end store diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 82b20810a..0ba0ffc37 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -35,6 +35,7 @@ = yield :header_tags - body_classes ||= @body_classes + - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui %body{ class: add_rtl_body_class(body_classes) } = content_for?(:content) ? yield(:content) : yield -- cgit From 0b32338e3f276e2aa6bd55162753f1b418283dbc Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Sat, 2 Sep 2017 01:52:28 +0200 Subject: Add link to 'noscript' message (#4561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add link to 'noscript' message Signed-off-by: Marcin Mikołajczak * remove indent --- app/views/home/index.html.haml | 2 +- config/locales/ca.yml | 2 +- config/locales/en.yml | 2 +- config/locales/fa.yml | 2 +- config/locales/fr.yml | 2 +- config/locales/ja.yml | 2 +- config/locales/ko.yml | 2 +- config/locales/nl.yml | 2 +- config/locales/oc.yml | 2 +- config/locales/pl.yml | 2 +- config/locales/ru.yml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) (limited to 'app/views') diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 75fe59f00..6c93281db 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -9,4 +9,4 @@ = image_tag asset_pack_path('logo.svg'), alt: 'Mastodon' %div - = t('errors.noscript') + = t('errors.noscript_html') diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 089d6dfa6..b6bff8288 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -275,7 +275,7 @@ ca: content: La verificació de seguretat ha fallat. Bloquejes les galetes? title: La verificació de seguretat ha fallat '429': Estrangulat - noscript: Per utilitzar Mastodon si us plau activa JavaScript. + noscript_html: Per utilitzar Mastodon si us plau activa JavaScript. exports: blocks: Persones que has bloquejat csv: CSV diff --git a/config/locales/en.yml b/config/locales/en.yml index 2599b6078..4160745f8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -288,7 +288,7 @@ en: content: Security verification failed. Are you blocking cookies? title: Security verification failed '429': Throttled - noscript: To use the Mastodon web application, please enable JavaScript. Alternatively, find a native app for Mastodon for your platform. + noscript_html: To use the Mastodon web application, please enable JavaScript. Alternatively, try one of the native apps for Mastodon for your platform. exports: blocks: You block csv: CSV diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 6959134a6..08ffb4484 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -274,7 +274,7 @@ fa: content: تأیید امنیتی انجام نشد. آیا مرورگر شما کوکی‌ها را مسدود می‌کند؟ title: تأیید امنیتی شکست خورد '429': درخواست‌های بیش از حد - noscript: برای استفاده از نسخهٔ تحت وب ماستدون، لطفاً جاوااسکریپت را فعال کنید. یا به جایش می‌توانید یک اپ ماستدون را به‌کار ببرید. + noscript_html: برای استفاده از نسخهٔ تحت وب ماستدون، لطفاً جاوااسکریپت را فعال کنید. یا به جایش می‌توانید یک اپ ماستدون را به‌کار ببرید. exports: blocks: حساب‌های مسدودشده csv: CSV diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 2c289471d..8029d8bd5 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -293,7 +293,7 @@ fr: content: Vérification de sécurité échouée. Bloquez-vous les cookies ? title: Vérification de sécurité échouée '429': Trop de requêtes émises dans un délai donné. - noscript: Pour utiliser Mastodon, veuillez activer JavaScript + noscript_html: Pour utiliser Mastodon, veuillez activer JavaScript exports: blocks: Vous bloquez csv: CSV diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 4f6f92866..8a0fb5f52 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -287,7 +287,7 @@ ja: content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか? title: セキュリティ認証に失敗 '429': リクエストの制限に達しました。 - noscript: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けのMastodonネイティブアプリを探すことができます。 + noscript_html: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けのMastodonネイティブアプリを探すことができます。 exports: blocks: ブロック csv: CSV diff --git a/config/locales/ko.yml b/config/locales/ko.yml index f3bde5bbb..f98059526 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -220,7 +220,7 @@ ko: content: 보안 인증에 실패했습니다. Cookie를 차단하고 있진 않습니까? title: 보안 인증 실패 '429': 요청 횟수 제한에 도달했습니다. - noscript: Mastodon을 사용하기 위해서는 JavaScript를 켜 주십시오. + noscript_html: Mastodon을 사용하기 위해서는 JavaScript를 켜 주십시오. exports: blocks: 차단 csv: CSV diff --git a/config/locales/nl.yml b/config/locales/nl.yml index e738d5662..50ae5508b 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -272,7 +272,7 @@ nl: content: Veiligheidsverificatie mislukt. Blokkeer je toevallig cookies? title: Veiligheidsverificatie mislukt '429': Te veel verbindingsaanvragen. - noscript: Schakel JavaScript in om de webapplicatie van Mastodon te gebruiken. Als alternatief kan je een Mastodon-app zoeken voor jouw platform. + noscript_html: Schakel JavaScript in om de webapplicatie van Mastodon te gebruiken. Als alternatief kan je een Mastodon-app zoeken voor jouw platform. exports: blocks: Jij blokkeert csv: CSV diff --git a/config/locales/oc.yml b/config/locales/oc.yml index ba7993d7c..019d3b196 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -365,7 +365,7 @@ oc: content: Verificacion de seguretat fracassada. Blocatz los cookies ? title: Verificacion de seguretat fracassada '429': Lo servidor mòla (subrecargada) - noscript: Per utilizar l’aplicacion Mastodon, mercés d’activar JavaScript. Autrament podètz utilizar una aplicacion nativa Mastodon per vòstra plataforma. + noscript_html: Per utilizar l’aplicacion web de Mastodon, mercés d’activar JavaScript. O podètz utilizar una aplicacion per vòstra plataforma coma alernativa. exports: blocks: Personas que blocatz csv: CSV diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 2b2cbb26b..bf6d19db0 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -286,7 +286,7 @@ pl: content: Sprawdzanie bezpieczeństwa nie powiodło się. Czy blokujesz pliki cookie? title: Sprawdzanie bezpieczeństwa nie powiodło się '429': Uduszono - noscript: Aby korzystać z aplikacji Mastodon, włącz JavaScript. Możesz też skorzystać z natywnej aplikacji obsługującej Twoje urządzenie. + noscript_html: Aby korzystać z aplikacji Mastodon, włącz JavaScript. Możesz też skorzystać z jednej z natywnych aplikacji obsługującej Twoje urządzenie. exports: blocks: Blokujesz csv: CSV diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 5c87ebf26..52cb71c60 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -193,7 +193,7 @@ ru: content: Проверка безопасности не удалась. Возможно, Вы блокируете cookies? title: Проверка безопасности не удалась. '429': Слишком много запросов - noscript: Для работы с Mastodon, пожалуйста, включите JavaScript. + noscript_html: Для работы с Mastodon, пожалуйста, включите JavaScript. exports: blocks: Список блокировки csv: CSV -- cgit From 6fd2e8c3c5e3be75b2bcc423bca72a8b3b931f04 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 2 Sep 2017 21:01:59 +0900 Subject: Fix profile page when use system's font (#4774) --- app/views/layouts/application.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/views') diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 0ba0ffc37..e21fb1ce1 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -34,7 +34,7 @@ = yield :header_tags - - body_classes ||= @body_classes + - body_classes ||= @body_classes || '' - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui %body{ class: add_rtl_body_class(body_classes) } -- cgit From cfe39fb58d42ef649dc1ebb6f39e77814bbfbe96 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 2 Sep 2017 22:49:28 +0900 Subject: Adjust settings pages (#4765) * views: Adjust heading positions * Adjust settings pages Adjust label. Adjust tables. Adjust admin/reports/* pages. Fix 2FA QR code style for narrow devices. Widen several pages. Increase contrast. * Remove trailing whitespace --- app/javascript/styles/admin.scss | 59 +++++++++++++++++++---------- app/javascript/styles/forms.scss | 39 +++++++++++++++---- app/javascript/styles/tables.scss | 2 +- app/views/auth/registrations/edit.html.haml | 2 +- 4 files changed, 73 insertions(+), 29 deletions(-) (limited to 'app/views') diff --git a/app/javascript/styles/admin.scss b/app/javascript/styles/admin.scss index 4c3bbdfc5..b86de75b6 100644 --- a/app/javascript/styles/admin.scss +++ b/app/javascript/styles/admin.scss @@ -32,7 +32,7 @@ a { display: block; - padding: 15px 25px; + padding: 15px; color: rgba($primary-text-color, 0.7); text-decoration: none; transition: all 200ms linear; @@ -61,6 +61,7 @@ a { border: 0; + padding: 15px 35px; &.selected { color: $primary-text-color; @@ -98,7 +99,7 @@ h6 { font-size: 16px; - color: $ui-primary-color; + color: $ui-secondary-color; line-height: 28px; font-weight: 400; } @@ -123,10 +124,10 @@ } .muted-hint { - color: lighten($ui-base-color, 27%); + color: $ui-primary-color; a { - color: $ui-primary-color; + color: $ui-highlight-color; } } @@ -139,15 +140,23 @@ .simple_form { max-width: 400px; - .label_input { - label.select { - width: 50%; - } + &.edit_user, + &.new_form_admin_settings, + &.new_form_two_factor_confirmation, + &.new_form_delete_confirmation, + &.new_import, + &.new_domain_block, + &.edit_domain_block { + max-width: none; + } - select { - width: 50%; - float: right; - } + .form_two_factor_confirmation_code, + .form_delete_confirmation_password { + max-width: 400px; + } + + .actions { + max-width: 400px; } } @@ -227,27 +236,25 @@ .report-accounts { display: flex; + flex-wrap: wrap; margin-bottom: 20px; } .report-accounts__item { - flex: 1 1 0; display: flex; + flex: 250px; flex-direction: column; + margin: 0 5px; & > strong { display: block; - margin-bottom: 10px; + margin: 0 0 10px -5px; font-weight: 500; font-size: 14px; line-height: 18px; color: $ui-secondary-color; } - &:first-child { - margin-right: 10px; - } - .account-card { flex: 1 1 auto; } @@ -261,6 +268,11 @@ .activity-stream { flex: 2 0 0; margin-right: 20px; + max-width: calc(100% - 60px); + + .entry { + border-radius: 4px; + } } } @@ -280,18 +292,25 @@ .batch-form-box { display: flex; - margin-bottom: 10px; + flex-wrap: wrap; + margin-bottom: 5px; #form_status_batch_action { - margin-right: 5px; + margin: 0 5px 5px 0; font-size: 14px; } + input.button { + margin: 0 5px 5px 0; + } + .media-spoiler-toggle-buttons { margin-left: auto; .button { overflow: visible; + margin: 0 0 5px 5px; + float: right; } } } diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index 78f13270a..747610237 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -24,7 +24,7 @@ code { p.hint { margin-bottom: 15px; - color: lighten($ui-base-color, 32%); + color: $ui-primary-color; &.subtle-hint { text-align: center; @@ -53,7 +53,6 @@ code { label { flex: 0 0 auto; - width: 100px; } input { @@ -65,12 +64,37 @@ code { padding: 15px 0; margin-bottom: 0; + .label_input { + flex-wrap: wrap; + align-items: flex-start; + } + + &.select .label_input { + align-items: initial; + } + .label_input > label { font-family: inherit; font-size: 16px; color: $primary-text-color; display: block; padding-top: 5px; + margin-bottom: 5px; + flex: 1; + min-width: 150px; + word-wrap: break-word; + + &.select { + flex: 0; + } + + & ~ * { + margin-left: 10px; + } + } + + ul { + flex: 390px; } &.boolean { @@ -367,13 +391,15 @@ code { .qr-wrapper { display: flex; + flex-wrap: wrap; + align-items: flex-start; } .qr-code { flex: 0 0 auto; background: $simple-background-color; padding: 4px; - margin-bottom: 20px; + margin: 0 10px 20px 0; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); display: inline-block; @@ -384,8 +410,9 @@ code { } .qr-alternative { - margin-left: 10px; - color: $ui-primary-color; + margin-bottom: 20px; + color: $ui-secondary-color; + flex: 150px; samp { display: block; @@ -395,7 +422,6 @@ code { .table-form { p { - max-width: 400px; margin-bottom: 15px; strong { @@ -407,7 +433,6 @@ code { .simple_form, .table-form { .warning { - max-width: 400px; box-sizing: border-box; background: rgba($error-value-color, 0.5); color: $primary-text-color; diff --git a/app/javascript/styles/tables.scss b/app/javascript/styles/tables.scss index 6e54c59c0..f6e57e196 100644 --- a/app/javascript/styles/tables.scss +++ b/app/javascript/styles/tables.scss @@ -46,7 +46,7 @@ &.inline-table { td, th { - padding: 8px 0; + padding: 8px 2px; } & > tbody > tr:nth-child(odd) > td, diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index f016a4883..145f5cd9e 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -1,7 +1,7 @@ - content_for :page_title do = t('auth.change_password') -= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| += simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f| = render 'shared/error_messages', object: resource = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') } -- cgit From 09cffaaf042f9d7d37c24427246abbf2303ebccb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Sep 2017 02:14:12 +0200 Subject: Fix #4551 - Use correct syntax for content preloading (#4798) --- app/views/layouts/application.html.haml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'app/views') diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index e21fb1ce1..88eff7d17 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -21,13 +21,13 @@ = stylesheet_pack_tag 'common', media: 'all' = javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous' - = javascript_pack_tag 'features/getting_started', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' - = javascript_pack_tag 'features/compose', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' - = javascript_pack_tag 'features/home_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' - = javascript_pack_tag 'features/notifications', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' - = javascript_pack_tag 'features/community_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' - = javascript_pack_tag 'features/public_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' - = javascript_pack_tag 'emojione_picker', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' + %link{ href: asset_pack_path('features/getting_started.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ + %link{ href: asset_pack_path('features/compose.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ + %link{ href: asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ + %link{ href: asset_pack_path('features/notifications.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ + %link{ href: asset_pack_path('features/community_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ + %link{ href: asset_pack_path('features/public_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ + %link{ href: asset_pack_path('emojione_picker.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = csrf_meta_tags -- cgit From 4c3dd0b25472b4d291f607979d255dd406856bef Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 5 Sep 2017 19:31:24 +0900 Subject: Adjust status embeds (#4808) * Adjust status embeds Adjust styles of embed code. Adjust styles of embed pages. Fix overflow of embed-modal. * Remove trailing whitespace * Using width from the variable --- app/javascript/styles/components.scss | 95 +++++++++++----------- app/javascript/styles/stream_entries.scss | 71 ++++++++-------- app/serializers/oembed_serializer.rb | 3 +- .../stream_entries/_detailed_status.html.haml | 10 +-- 4 files changed, 92 insertions(+), 87 deletions(-) (limited to 'app/views') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 0fbaeeea0..1b9763e90 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -3966,41 +3966,10 @@ noscript { } } -.embed-modal__html { - color: $ui-secondary-color; - outline: 0; - box-sizing: border-box; - display: block; - width: 100%; - border: none; - padding: 10px; - font-family: 'mastodon-font-monospace', monospace; - background: $ui-base-color; - color: $ui-primary-color; - font-size: 14px; - margin: 0; - margin-bottom: 15px; - - &::-moz-focus-inner { - border: 0; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } - - &:focus { - background: lighten($ui-base-color, 4%); - } - - @media screen and (max-width: 600px) { - font-size: 16px; - } -} - .embed-modal { + max-width: 80vw; + max-height: 80vh; + h4 { padding: 30px; font-weight: 500; @@ -4008,18 +3977,52 @@ noscript { text-align: center; } - .hint { - margin-bottom: 15px; - } -} + .embed-modal__container { + padding: 10px; -.embed-modal__container { - padding: 10px; -} + .hint { + margin-bottom: 15px; + } -.embed-modal__iframe { - width: 100%; - min-width: 400px; - overflow: hidden; - border: 0; + .embed-modal__html { + color: $ui-secondary-color; + outline: 0; + box-sizing: border-box; + display: block; + width: 100%; + border: none; + padding: 10px; + font-family: 'mastodon-font-monospace', monospace; + background: $ui-base-color; + color: $ui-primary-color; + font-size: 14px; + margin: 0; + margin-bottom: 15px; + + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus, + &:active { + outline: 0 !important; + } + + &:focus { + background: lighten($ui-base-color, 4%); + } + + @media screen and (max-width: 600px) { + font-size: 16px; + } + } + + .embed-modal__iframe { + width: 400px; + max-width: 100%; + overflow: hidden; + border: 0; + } + } } diff --git a/app/javascript/styles/stream_entries.scss b/app/javascript/styles/stream_entries.scss index 7048ab110..8ed4c0b25 100644 --- a/app/javascript/styles/stream_entries.scss +++ b/app/javascript/styles/stream_entries.scss @@ -403,51 +403,54 @@ .embed { .activity-stream { - border-radius: 4px; box-shadow: none; .entry { - &:last-child { - border-radius: 0 0 4px 4px; - } - &:first-child { - border-radius: 4px 4px 0 0; + .detailed-status.light { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: flex-start; - &:last-child { - border-radius: 4px; + .detailed-status__display-name { + flex: 1; + margin: 0 5px 15px 0; } - } - } - } -} -.button.button-secondary.logo-button { - position: absolute; - right: 14px; - top: 14px; - font-size: 14px; + .button.button-secondary.logo-button { + flex: 0 auto; + font-size: 14px; - svg { - width: 20px; - height: auto; - vertical-align: middle; - margin-right: 5px; + svg { + width: 20px; + height: auto; + vertical-align: middle; + margin-right: 5px; - path:first-child { - fill: $ui-primary-color; - } + path:first-child { + fill: $ui-primary-color; + } - path:last-child { - fill: $simple-background-color; - } - } + path:last-child { + fill: $simple-background-color; + } + } - &:active, - &:focus, - &:hover { - svg path:first-child { - fill: lighten($ui-primary-color, 4%); + &:active, + &:focus, + &:hover { + svg path:first-child { + fill: lighten($ui-primary-color, 4%); + } + } + } + + .status__content, + .detailed-status__meta { + flex: 100%; + } + } } } } diff --git a/app/serializers/oembed_serializer.rb b/app/serializers/oembed_serializer.rb index 4f9293043..bd05da585 100644 --- a/app/serializers/oembed_serializer.rb +++ b/app/serializers/oembed_serializer.rb @@ -40,8 +40,7 @@ class OEmbedSerializer < ActiveModel::Serializer attributes = { src: embed_short_account_status_url(object.account, object), class: 'mastodon-embed', - frameborder: '0', - scrolling: 'no', + style: 'max-width: 100%; border: none;', width: width, height: height, } diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index 107202b75..466087b6a 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -1,9 +1,4 @@ .detailed-status.light - - if embedded_view? - = link_to "web+mastodon://follow?uri=#{status.account.local_username_and_domain}", class: 'button button-secondary logo-button', target: '_new' do - = render file: Rails.root.join('app', 'javascript', 'images', 'logo.svg') - = t('accounts.follow') - = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do %div .avatar @@ -12,6 +7,11 @@ %strong.p-name.emojify= display_name(status.account) %span= acct(status.account) + - if embedded_view? + = link_to "web+mastodon://follow?uri=#{status.account.local_username_and_domain}", class: 'button button-secondary logo-button', target: '_new' do + = render file: Rails.root.join('app', 'javascript', 'images', 'logo.svg') + = t('accounts.follow') + .status__content.p-name.emojify< - if status.spoiler_text? %p{ style: 'margin-bottom: 0' }< -- cgit