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/javascript/styles/about.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index d409c8214..62143246f 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -484,7 +484,7 @@ padding: 0; } - .learn-more-cta { + .about-short { background: darken($ui-base-color, 4%); padding: 50px 0; } -- cgit From d9a1fb134a407aaa7fcff5be285d7414a0cb43ed Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 10 Aug 2017 20:41:12 +0900 Subject: Fix emoji picker scrollbar style (#4572) --- app/javascript/styles/components.scss | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 77b06b2d0..f66be5111 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -394,6 +394,11 @@ bottom: -1px; right: 8px; } + + ::-webkit-scrollbar-track:hover, + ::-webkit-scrollbar-track:active { + background-color: rgba($base-overlay-background, 0.3); + } } } -- 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/javascript/styles') 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 f814661fcaed99221bf0b250dbe14349cb702833 Mon Sep 17 00:00:00 2001 From: Clworld Date: Wed, 16 Aug 2017 23:48:44 +0900 Subject: Make share intent modal to make "signed in as" shown. (#4611) * Make share intent modal to make "signed in as" shown. * fix glitch on mobile. --- app/controllers/shares_controller.rb | 7 ++++++- app/javascript/styles/containers.scss | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'app/javascript/styles') diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index d70d66ff8..994742c3d 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class SharesController < ApplicationController - layout 'public' + layout 'modal' before_action :authenticate_user! + before_action :set_body_classes def show serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) @@ -22,4 +23,8 @@ class SharesController < ApplicationController text: params[:text], } end + + def set_body_classes + @body_classes = 'compose-standalone' + end end diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss index 063db44db..cfe8ea643 100644 --- a/app/javascript/styles/containers.scss +++ b/app/javascript/styles/containers.scss @@ -53,6 +53,7 @@ box-sizing: border-box; @media screen and (max-width: 400px) { + width: 100%; margin-top: 0; padding: 20px; } -- 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/javascript/styles') 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 f26758dc019a24cd7e87078e2a19350d0a2d083c Mon Sep 17 00:00:00 2001 From: unarist Date: Mon, 21 Aug 2017 03:45:44 +0900 Subject: Fix .information-board style for Safari (#4602) flex-basis: 0 allows make flexbox smaller than its contents on Safari <10. https://github.com/philipwalton/flexbugs#1-minimum-content-sizing-of-flex-items-not-honored --- app/javascript/styles/about.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index 62143246f..9477335be 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -171,7 +171,7 @@ } .section { - flex: 1 0 0; + flex: 1 0 auto; font: 16px/28px 'mastodon-font-sans-serif', sans-serif; text-align: right; padding: 10px 15px; -- cgit From 4cbb6386049f4037c146ed3cf52c852cc3d8f9d5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 21 Aug 2017 17:59:34 +0200 Subject: Fix visual line-break glitch with .invisible parts of links (#4655) --- app/javascript/styles/components.scss | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index f66be5111..ef1797e72 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -237,6 +237,8 @@ line-height: 0; display: inline-block; width: 0; + height: 0; + position: absolute; } .ellipsis { -- cgit From 143b77e10d984d3790382758c0b797215850b024 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 22 Aug 2017 04:59:03 +0900 Subject: Increase contrast in landing pages (#4567) * Increase contrast in about and about/more page * Lighten em color in landing pages * Increase contrast in landing pages Fix about.scss --- app/javascript/styles/about.scss | 16 ++++++++-------- app/javascript/styles/forms.scss | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index 9477335be..020842f7d 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -139,7 +139,7 @@ font-size: 14px; line-height: 24px; font-weight: 500; - color: $ui-base-lighter-color; + color: $ui-primary-color; padding-bottom: 5px; margin-bottom: 15px; border-bottom: 1px solid lighten($ui-base-color, 4%); @@ -150,7 +150,7 @@ a, span { font-weight: 400; - color: lighten($ui-base-color, 34%); + color: darken($ui-primary-color, 10%); } a { @@ -262,11 +262,11 @@ .text { font-size: 16px; line-height: 30px; - color: $ui-base-lighter-color; + color: $ui-primary-color; h6 { font-weight: 500; - color: $ui-primary-color; + color: $ui-secondary-color; } } } @@ -516,7 +516,7 @@ font: 16px/28px 'mastodon-font-sans-serif', sans-serif; font-weight: 400; margin-bottom: 12px; - color: $ui-base-lighter-color; + color: $ui-primary-color; a { color: $ui-highlight-color; @@ -531,13 +531,13 @@ line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $ui-primary-color; + color: $ui-secondary-color; } p { font-size: 16px; line-height: 30px; - color: $ui-base-lighter-color; + color: $ui-primary-color; } .features { @@ -623,7 +623,7 @@ font-family: inherit; font-size: inherit; line-height: inherit; - color: $ui-primary-color; + color: lighten($ui-primary-color, 10%); } h1 { diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index 62094e98e..8e41bb002 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -32,10 +32,10 @@ code { line-height: 18px; margin-top: 15px; margin-bottom: 0; - color: $ui-base-lighter-color; + color: $ui-primary-color; a { - color: $ui-primary-color; + color: $ui-highlight-color; } } } -- cgit From f72ed21cd62541881c0ad19bac1b78e82f514413 Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Thu, 24 Aug 2017 10:28:49 -0700 Subject: Don't load Roboto webfont when system font is used in the app (#4591) * Don't load Roboto webfont when system font is used in the app * remove trailing whitespace --- app/javascript/styles/basics.scss | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/basics.scss b/app/javascript/styles/basics.scss index 4e51b555c..e524b7f26 100644 --- a/app/javascript/styles/basics.scss +++ b/app/javascript/styles/basics.scss @@ -1,5 +1,4 @@ body { - font-family: 'mastodon-font-sans-serif', sans-serif; background: $ui-base-color; background-size: cover; background-attachment: fixed; @@ -14,6 +13,11 @@ 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; + } + &.app-body { position: fixed; width: 100%; @@ -69,7 +73,7 @@ button { justify-content: center; } -.system-font { +.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 @@ -83,3 +87,7 @@ button { // 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; +} -- cgit From 18f69fb964f73dce1e921b3fd1a391167d638e8a Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 Aug 2017 00:19:35 +0900 Subject: Adjust styles of landing pages. (#4682) * Adjust about.scss * Delete trailing whitespace. --- app/javascript/styles/about.scss | 856 ++++++++++++++++++--------------------- 1 file changed, 395 insertions(+), 461 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index 020842f7d..28924738a 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -1,52 +1,96 @@ -.about-body { - .wrapper { - max-width: 600px; - margin: 0 auto; +.landing-page { + p, + li { + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; + line-height: 30px; + margin-bottom: 12px; color: $ui-primary-color; - padding-top: 50px; - padding-bottom: 50px; - &.thicc { - max-width: 800px; + a { + color: $ui-highlight-color; + text-decoration: underline; } } + em { + display: inline; + margin: 0; + padding: 0; + font-weight: 500; + background: transparent; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: lighten($ui-primary-color, 10%); + } + h1 { - font: 46px/52px 'mastodon-font-sans-serif', sans-serif; - font-weight: 600; + font-family: 'mastodon-font-display', sans-serif; + font-size: 26px; + line-height: 30px; + font-weight: 500; margin-bottom: 20px; - color: $ui-highlight-color; - padding: 20px 0; + color: $ui-secondary-color; - img { - margin-bottom: -5px; - margin-right: 5px; - width: 46px; - height: 46px; + small { + font-family: 'mastodon-font-sans-serif', sans-serif; + display: block; + font-size: 18px; + font-weight: 400; + color: $ui-base-lighter-color; } } h2 { font-family: 'mastodon-font-display', sans-serif; - font-size: 24px; - line-height: 28px; - font-weight: 400; + font-size: 22px; + line-height: 26px; + font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $ui-secondary-color; } h3 { font-family: 'mastodon-font-display', sans-serif; - font-size: 20px; - line-height: 28px; - font-weight: 400; + font-size: 18px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $ui-secondary-color; + } + + h4 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $ui-secondary-color; + } + + h5 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 14px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $ui-secondary-color; + } + + h6 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 12px; + line-height: 24px; + font-weight: 500; margin-bottom: 20px; color: $ui-secondary-color; } ul, ol { - list-style: inherit; margin-left: 20px; &[type='a'] { @@ -58,220 +102,30 @@ } } - li > ol, - li > ul { - margin-top: 20px; + ul { + list-style: disc; } - p, - li { - font: 16px/28px 'mastodon-font-sans-serif', sans-serif; - font-weight: 400; - margin-bottom: 12px; - - a { - color: $ui-highlight-color; - text-decoration: underline; - } - } - - em { - display: inline-block; - padding: 7px 7px 5px; - margin: 0 2px; - background: $ui-primary-color; - color: $ui-base-color; - font: 16px/16px 'mastodon-font-sans-serif', sans-serif; - font-weight: 300; - } - - .screenshot { - box-shadow: 0 0 15px rgba($base-shadow-color, 0.4); - margin-bottom: 26px; - - img { - max-width: 100%; - height: auto; - display: block; - } - } - - .actions { - overflow: hidden; - margin-bottom: 20px; - - .info { - float: right; - text-align: right; - line-height: 36px; - - a { - color: $ui-primary-color; - text-decoration: underline; - } - } + ol { + list-style: decimal; } - @media screen and (max-width: 625px) { - .wrapper { - padding: 20px; - } + li > ol, + li > ul { + margin-top: 6px; } -} -.information-board { - background: darken($ui-base-color, 4%); - padding: 20px 0; - - .panel { - position: absolute; - width: 280px; - box-sizing: border-box; - background: darken($ui-base-color, 8%); - padding: 20px; - padding-top: 10px; - border-radius: 4px 4px 0 0; - right: 0; - bottom: -40px; - - .panel-header { - font-family: 'mastodon-font-display', sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - color: $ui-primary-color; - padding-bottom: 5px; - margin-bottom: 15px; - border-bottom: 1px solid lighten($ui-base-color, 4%); - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - - a, - span { - font-weight: 400; - color: darken($ui-primary-color, 10%); - } - - a { - text-decoration: none; - } - } + hr { + border-color: rgba($ui-base-lighter-color, .6); } .container { - position: relative; - padding-right: 280px + 15px; - } - - .information-board-sections { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - } - - .section { - flex: 1 0 auto; - font: 16px/28px 'mastodon-font-sans-serif', sans-serif; - text-align: right; - padding: 10px 15px; - - span, - strong { - display: block; - } - - span { - font-size: 16px; - - &:last-child { - color: $ui-secondary-color; - } - } - - strong { - font-weight: 500; - font-size: 32px; - line-height: 48px; - color: $primary-text-color; - } - } -} - -.owner { - text-align: center; - - .avatar { - width: 80px; - height: 80px; + width: 100%; + box-sizing: border-box; + max-width: 800px; margin: 0 auto; - margin-bottom: 15px; - - img { - display: block; - width: 80px; - height: 80px; - border-radius: 48px; - } - } - - .name { - font-size: 14px; - - a { - display: block; - color: $primary-text-color; - text-decoration: none; - - &:hover { - .display_name { - text-decoration: underline; - } - } - } - - .username { - display: block; - color: $ui-primary-color; - } - } -} - -.features-list__row { - display: flex; - padding: 10px 0; - justify-content: space-between; - - &:first-child { - padding-top: 0; - } - - .visual { - flex: 0 0 auto; - display: flex; - align-items: center; - margin-left: 15px; - - .fa { - display: block; - color: $ui-primary-color; - font-size: 48px; - } } - .text { - font-size: 16px; - line-height: 30px; - color: $ui-primary-color; - - h6 { - font-weight: 500; - color: $ui-secondary-color; - } - } -} - -.landing-page { .header-wrapper { padding-top: 15px; background: $ui-base-color; @@ -284,6 +138,17 @@ .hero .heading { padding-bottom: 30px; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; + line-height: 30px; + color: $ui-primary-color; + + a { + color: $ui-highlight-color; + text-decoration: underline; + } } } @@ -307,17 +172,6 @@ } } - p, - li { - font: inherit; - font-weight: inherit; - margin-bottom: 0; - } - - hr { - border-color: rgba($ui-base-lighter-color, .6); - } - .header { line-height: 30px; overflow: hidden; @@ -327,6 +181,62 @@ justify-content: space-between; } + .links { + position: relative; + z-index: 4; + + a { + display: flex; + justify-content: center; + align-items: center; + color: $ui-primary-color; + text-decoration: none; + padding: 12px 16px; + line-height: 32px; + font-family: 'mastodon-font-display', sans-serif; + font-weight: 500; + font-size: 14px; + + &:hover { + color: $ui-secondary-color; + } + } + + .brand { + a { + padding-left: 0; + padding-right: 0; + color: $white; + } + + img { + height: 32px; + position: relative; + top: 4px; + left: -10px; + } + } + + ul { + list-style: none; + margin: 0; + + li { + display: inline-block; + vertical-align: bottom; + margin: 0; + + &:first-child a { + padding-left: 0; + } + + &:last-child a { + padding-right: 0; + } + } + } + } + .hero { margin-top: 50px; align-items: center; @@ -379,6 +289,12 @@ } } + .heading { + position: relative; + z-index: 4; + padding-bottom: 150px; + } + .simple_form, .closed-registrations-message { background: darken($ui-base-color, 4%); @@ -400,12 +316,6 @@ } } - .heading { - position: relative; - z-index: 4; - padding-bottom: 150px; - } - .closed-registrations-message { min-height: 330px; display: flex; @@ -413,233 +323,263 @@ justify-content: space-between; } } + } - ul { - list-style: none; - margin: 0; + .about-short { + background: darken($ui-base-color, 4%); + padding: 50px 0; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; + line-height: 30px; + color: $ui-primary-color; - li { - display: inline-block; - vertical-align: bottom; - margin: 0; + a { + color: $ui-highlight-color; + text-decoration: underline; + } + } - &:first-child a { - padding-left: 0; - } + .information-board { + background: darken($ui-base-color, 4%); + padding: 20px 0; - &:last-child a { - padding-right: 0; - } - } + .container { + position: relative; + padding-right: 280px + 15px; } - .links { - position: relative; - z-index: 4; + .information-board-sections { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + } - a { - display: flex; - justify-content: center; - align-items: center; - color: $ui-primary-color; - text-decoration: none; - padding: 12px 16px; - line-height: 32px; - font-family: 'mastodon-font-display', sans-serif; - font-weight: 500; - font-size: 14px; + .section { + flex: 1 0 0; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + line-height: 28px; + color: $primary-text-color; + text-align: right; + padding: 10px 15px; - &:hover { - color: $ui-secondary-color; - } + span, + strong { + display: block; } - .brand { - a { - padding-left: 0; - padding-right: 0; - color: $white; + span { + &:last-child { + color: $ui-secondary-color; } + } - img { - height: 32px; - position: relative; - top: 4px; - left: -10px; - } + strong { + font-weight: 500; + font-size: 32px; + line-height: 48px; } } - } - .container { - width: 100%; - box-sizing: border-box; - max-width: 800px; - margin: 0 auto; - } - - .wrapper { - max-width: 800px; - margin: 0 auto; - padding: 0; - } + .panel { + position: absolute; + width: 280px; + box-sizing: border-box; + background: darken($ui-base-color, 8%); + padding: 20px; + padding-top: 10px; + border-radius: 4px 4px 0 0; + right: 0; + bottom: -40px; - .about-short { - background: darken($ui-base-color, 4%); - padding: 50px 0; - } + .panel-header { + font-family: 'mastodon-font-display', sans-serif; + font-size: 14px; + line-height: 24px; + font-weight: 500; + color: $ui-primary-color; + padding-bottom: 5px; + margin-bottom: 15px; + border-bottom: 1px solid lighten($ui-base-color, 4%); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + a, + span { + font-weight: 400; + color: darken($ui-primary-color, 10%); + } - .extended-description { - padding: 50px 0; + a { + text-decoration: none; + } + } + } - ul, - ol { - list-style: inherit; - margin-left: 20px; + .owner { + text-align: center; - &[type='a'] { - list-style-type: lower-alpha; - } + .avatar { + width: 80px; + height: 80px; + margin: 0 auto; + margin-bottom: 15px; - &[type='i'] { - list-style-type: lower-roman; + img { + display: block; + width: 80px; + height: 80px; + border-radius: 48px; + } } - } - li > ol, - li > ul { - margin-top: 20px; - } + .name { + font-size: 14px; - p, - li { - font: 16px/28px 'mastodon-font-sans-serif', sans-serif; - font-weight: 400; - margin-bottom: 12px; - color: $ui-primary-color; + a { + display: block; + color: $primary-text-color; + text-decoration: none; + + &:hover { + .display_name { + text-decoration: underline; + } + } + } - a { - color: $ui-highlight-color; - text-decoration: underline; + .username { + display: block; + color: $ui-primary-color; + } } } } - h3 { - font-family: 'mastodon-font-display', sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $ui-secondary-color; - } - - p { - font-size: 16px; - line-height: 30px; - color: $ui-primary-color; - } - .features { padding: 50px 0; .container { display: flex; } - } - #mastodon-timeline { - display: flex; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - font-family: 'mastodon-font-sans-serif', sans-serif; - font-size: 13px; - line-height: 18px; - font-weight: 400; - color: $primary-text-color; - width: 330px; - margin-right: 30px; - flex: 0 0 auto; - background: $ui-base-color; - overflow: hidden; - box-shadow: 0 0 6px rgba($black, 0.1); + #mastodon-timeline { + display: flex; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 13px; + line-height: 18px; + font-weight: 400; + color: $primary-text-color; + width: 330px; + margin-right: 30px; + flex: 0 0 auto; + background: $ui-base-color; + overflow: hidden; + box-shadow: 0 0 6px rgba($black, 0.1); + + .column-header { + color: inherit; + font-family: inherit; + font-size: 16px; + line-height: inherit; + font-weight: inherit; + margin: 0; + padding: 15px; + } - .column-header { - color: inherit; - font-family: inherit; - font-size: 16px; - line-height: inherit; - font-weight: inherit; - margin: 0; - padding: 15px; - } + .column { + padding: 0; + border-radius: 4px; + overflow: hidden; + } - .column { - padding: 0; - border-radius: 4px; - overflow: hidden; - } + .scrollable { + height: 400px; + } - .scrollable { - height: 400px; - } + p { + font-size: inherit; + line-height: inherit; + font-weight: inherit; + color: $primary-text-color; + margin-bottom: 20px; - p { - font-size: inherit; - line-height: inherit; - font-weight: inherit; - color: $primary-text-color; - margin-bottom: 20px; + &:last-child { + margin-bottom: 0; + } - &:last-child { - margin-bottom: 0; + a { + color: $ui-secondary-color; + text-decoration: none; + } } + } - a { - color: $ui-secondary-color; - text-decoration: none; + .about-mastodon { + max-width: 675px; + + p { + margin-bottom: 20px; } - } - } - .about-mastodon { - max-width: 675px; + .features-list { + margin-top: 20px; - p { - margin-bottom: 20px; - } + .features-list__row { + display: flex; + padding: 10px 0; + justify-content: space-between; - .features-list { - margin-top: 20px; - } - } + &:first-child { + padding-top: 0; + } - em { - display: inline; - margin: 0; - padding: 0; - font-weight: 500; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; - color: lighten($ui-primary-color, 10%); + .visual { + flex: 0 0 auto; + display: flex; + align-items: center; + margin-left: 15px; + + .fa { + display: block; + color: $ui-primary-color; + font-size: 48px; + } + } + + .text { + font-size: 16px; + line-height: 30px; + color: $ui-primary-color; + + h6 { + font-size: inherit; + line-height: inherit; + margin-bottom: 0; + } + } + } + } + } } - h1 { - font-family: 'mastodon-font-display', sans-serif; - font-size: 26px; + .extended-description { + padding: 50px 0; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 16px; + font-weight: 400; + font-size: 16px; line-height: 30px; - margin-bottom: 0; - font-weight: 500; - color: $ui-secondary-color; + color: $ui-primary-color; - small { - font-family: 'mastodon-font-sans-serif', sans-serif; - display: block; - font-size: 18px; - font-weight: 400; - color: $ui-base-lighter-color; + a { + color: $ui-highlight-color; + text-decoration: underline; } } @@ -663,8 +603,15 @@ padding: 0 20px; } - .information-board .container { - padding-right: 20px; + .information-board { + + .container { + padding-right: 20px; + } + + .section { + text-align: center; + } .panel { position: static; @@ -678,10 +625,6 @@ } } - .information-board .section { - text-align: center; - } - .header-wrapper .mascot { left: 20px; } @@ -699,6 +642,7 @@ &.compact .hero .heading { padding-bottom: 20px; + text-align: initial; } } @@ -707,51 +651,41 @@ display: block; } - .links { - padding-top: 15px; - background: darken($ui-base-color, 4%); - } - .header { - .hero { - margin-top: 30px; - padding: 0; + .links { + padding-top: 15px; + background: darken($ui-base-color, 4%); - .heading { - padding: 0 20px 20px; + a { + padding: 12px 8px; } - } - .floats { - display: none; - } - - .heading, - .nav { - text-align: center; - } + .nav { + display: flex; + flex-flow: row wrap; + justify-content: space-around; + } - .nav { - display: flex; - flex-flow: row wrap; - justify-content: space-around; + .brand img { + left: 0; + top: 0; + } } - .links a { - padding: 12px 8px; - } + .hero { + margin-top: 30px; + padding: 0; - .heading h1 { - padding: 30px 0; - } + .floats { + display: none; + } - .links .brand img { - left: 0; - top: 0; - } + .heading { + padding: 30px 20px; + text-align: center; + } - .hero { .simple_form, .closed-registrations-message { background: darken($ui-base-color, 8%); @@ -762,7 +696,7 @@ } } - #mastodon-timeline { + .features #mastodon-timeline { height: 70vh; width: 100%; margin-bottom: 50px; -- cgit From 8ecfdd8795624b74d14df27d5468580734e5aede Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 Aug 2017 21:23:20 +0900 Subject: Set margin between character-counter and compose-form__buttons (#4698) For some languages publish translation is long. --- app/javascript/styles/components.scss | 2 +- app/javascript/styles/rtl.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index ef1797e72..32cf450b7 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1872,7 +1872,7 @@ .character-counter__wrapper { line-height: 36px; - margin-right: 16px; + margin: 0 16px 0 8px; padding-top: 10px; } diff --git a/app/javascript/styles/rtl.scss b/app/javascript/styles/rtl.scss index 4966fbc21..c8872c732 100644 --- a/app/javascript/styles/rtl.scss +++ b/app/javascript/styles/rtl.scss @@ -8,7 +8,7 @@ body.rtl { } .character-counter__wrapper { - margin-right: 0; + margin-right: 8px; margin-left: 16px; } -- cgit From 26402ee2cb57b4c0695c584fa24acfe429c14547 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 27 Aug 2017 13:35:18 +0200 Subject: Adjust RTL styles (#4712) --- app/javascript/styles/rtl.scss | 127 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 119 insertions(+), 8 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/rtl.scss b/app/javascript/styles/rtl.scss index c8872c732..6c003d69a 100644 --- a/app/javascript/styles/rtl.scss +++ b/app/javascript/styles/rtl.scss @@ -32,6 +32,11 @@ body.rtl { right: auto; } + .column-header__back-button { + padding-left: 5px; + padding-right: 0; + } + .column-header__setting-arrows { float: left; } @@ -54,25 +59,64 @@ body.rtl { right: 10px; } - .status { + .status, + .activity-stream .status.light { padding-left: 10px; padding-right: 68px; } - .status__info .status__display-name { + .status__info .status__display-name, + .activity-stream .status.light .status__display-name { padding-left: 25px; padding-right: 0; } + .activity-stream .pre-header { + padding-right: 68px; + padding-left: 0; + } + + .status__prepend { + margin-left: 0; + margin-right: 68px; + } + + .status__prepend-icon-wrapper { + left: auto; + right: -26px; + } + + .activity-stream .pre-header .pre-header__icon { + left: auto; + right: 42px; + } + + .account__avatar-overlay-overlay { + right: auto; + left: 0; + } + .column-back-button--slim-button { right: auto; left: 0; } - .status__relative-time { + .status__relative-time, + .activity-stream .status.light .status__header .status__meta { float: left; } + .activity-stream .detailed-status.light .detailed-status__display-name > div { + float: right; + margin-right: 0; + margin-left: 10px; + } + + .activity-stream .detailed-status.light .detailed-status__meta span > span { + margin-left: 0; + margin-right: 6px; + } + .status__action-bar-button { float: right; margin-right: 0; @@ -129,6 +173,78 @@ body.rtl { right: -2.14285714em; } + .admin-wrapper .sidebar ul a i.fa, + a.table-action-link i.fa { + margin-right: 0; + margin-left: 5px; + } + + .simple_form .check_boxes .checkbox label, + .simple_form .input.with_label.boolean label.checkbox { + padding-left: 0; + padding-right: 25px; + } + + .simple_form .check_boxes .checkbox input[type="checkbox"], + .simple_form .input.boolean input[type="checkbox"] { + left: auto; + right: 0; + } + + .simple_form .input-with-append .input input { + padding-left: 127px; + padding-right: 0; + } + + .simple_form .input-with-append .append { + right: auto; + left: 0; + } + + .table th, + .table td { + text-align: right; + } + + .filters .filter-subset { + margin-right: 0; + margin-left: 45px; + } + + .landing-page .header-wrapper .mascot { + right: 60px; + left: auto; + } + + .landing-page .header .hero .floats .float-1 { + left: -120px; + right: auto; + } + + .landing-page .header .hero .floats .float-2 { + left: 210px; + right: auto; + } + + .landing-page .header .hero .floats .float-3 { + left: 110px; + right: auto; + } + + .landing-page .header .links .brand img { + left: 0; + } + + .landing-page .fa-external-link { + padding-right: 5px; + padding-left: 0 !important; + } + + .landing-page .features #mastodon-timeline { + margin-right: 0; + margin-left: 30px; + } + @media screen and (min-width: 1025px) { .column, .drawer { @@ -139,11 +255,6 @@ body.rtl { padding-left: 5px; padding-right: 10px; } - - &:last-child { - padding-right: 0; - padding-left: 10px; - } } .columns-area > div { -- cgit From bab9afaa092e0eb8b3f11fdcdaf7a75d3ce94566 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Mon, 28 Aug 2017 06:59:51 +0900 Subject: Adjust public profile pages (#4713) * Adjust account-grid in public profiles Full-width card on mobile UI. Set break-word for long name and ID. Fix margin. * Reduce padding-bottom of public profiles * Revive next prev buttons in mobile public profiles In followers followees pages. * Revert break-word for username * Fix overflow of display_name Need re-setting text-overflow and overflow in display: block; --- app/javascript/styles/accounts.scss | 14 +++++++++----- app/javascript/styles/basics.scss | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/accounts.scss b/app/javascript/styles/accounts.scss index f1fbe873b..744650554 100644 --- a/app/javascript/styles/accounts.scss +++ b/app/javascript/styles/accounts.scss @@ -296,7 +296,9 @@ } .next, - .prev { + .prev, + .next a, + .prev a { display: inline-block; } } @@ -306,7 +308,7 @@ box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); background: darken($simple-background-color, 8%); border-radius: 0 0 4px 4px; - padding: 20px 10px; + padding: 20px 5px; padding-bottom: 10px; overflow: hidden; display: flex; @@ -325,11 +327,11 @@ background: $simple-background-color; border-radius: 4px; color: $ui-base-color; - margin-bottom: 10px; + margin: 0 5px 10px; position: relative; - &:nth-child(odd) { - margin-right: 10px; + @media screen and (max-width: 740px) { + width: calc(100% - 10px); } .account-grid-card__header { @@ -400,6 +402,8 @@ .display_name { font-size: 16px; display: block; + text-overflow: ellipsis; + overflow: hidden; } .username { diff --git a/app/javascript/styles/basics.scss b/app/javascript/styles/basics.scss index e524b7f26..6e87157ec 100644 --- a/app/javascript/styles/basics.scss +++ b/app/javascript/styles/basics.scss @@ -6,7 +6,7 @@ body { line-height: 18px; font-weight: 400; color: $primary-text-color; - padding-bottom: 140px; + padding-bottom: 40px; text-rendering: optimizelegibility; font-feature-settings: "kern"; text-size-adjust: none; -- cgit From 07994eed002375025f0c377079500d25e87cb641 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Mon, 28 Aug 2017 07:01:07 +0900 Subject: Adjust "signed in as" pages (#4720) * Adjust "signed in as" pages Fix min-width Set width of .account-header .name To apply text-overflow and overflow settings Set overflow for detailed-status__display-name * Remove trailing whitespace --- app/javascript/styles/components.scss | 2 ++ app/javascript/styles/containers.scss | 6 +++++- app/javascript/styles/forms.scss | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 32cf450b7..3a6672b9f 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1057,6 +1057,8 @@ strong, span { display: block; + text-overflow: ellipsis; + overflow: hidden; } strong { diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss index cfe8ea643..af2589e23 100644 --- a/app/javascript/styles/containers.scss +++ b/app/javascript/styles/containers.scss @@ -72,7 +72,7 @@ margin-bottom: -30px; margin-top: 40px; - @media screen and (max-width: 400px) { + @media screen and (max-width: 440px) { width: 100%; margin: 0; margin-bottom: 10px; @@ -97,10 +97,13 @@ .name { flex: 1 1 auto; color: $ui-secondary-color; + width: calc(100% - 88px); .username { display: block; font-weight: 500; + text-overflow: ellipsis; + overflow: hidden; } } @@ -108,5 +111,6 @@ display: block; font-size: 32px; line-height: 40px; + margin-left: 8px; } } diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index 8e41bb002..78f13270a 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -359,6 +359,10 @@ code { color: $ui-secondary-color; font-weight: 500; } + + @media screen and (max-width: 740px) and (min-width: 441px) { + margin-top: 40px; + } } .qr-wrapper { -- 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/javascript/styles') 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 d1a78eba1558004f69ab8933b08ffe0093671546 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 31 Aug 2017 03:38:35 +0200 Subject: Embed modal (#4748) * Embed modal * Proxy OEmbed requests from web UI --- app/controllers/api/web/embeds_controller.rb | 17 +++++ .../features/status/components/action_bar.js | 8 +++ app/javascript/mastodon/features/status/index.js | 5 ++ .../mastodon/features/ui/components/embed_modal.js | 84 ++++++++++++++++++++++ .../mastodon/features/ui/components/modal_root.js | 2 + .../mastodon/features/ui/util/async-components.js | 4 ++ app/javascript/packs/public.js | 4 ++ app/javascript/styles/components.scss | 61 +++++++++++++++- app/serializers/oembed_serializer.rb | 2 +- config/routes.rb | 1 + 10 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 app/controllers/api/web/embeds_controller.rb create mode 100644 app/javascript/mastodon/features/ui/components/embed_modal.js (limited to 'app/javascript/styles') diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb new file mode 100644 index 000000000..2ed516161 --- /dev/null +++ b/app/controllers/api/web/embeds_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Api::Web::EmbedsController < Api::BaseController + respond_to :json + + before_action :require_user! + + def create + status = StatusFinder.new(params[:url]).status + render json: status, serializer: OEmbedSerializer, width: 400 + rescue ActiveRecord::RecordNotFound + oembed = OEmbed::Providers.get(params[:url]) + render json: Oj.dump(oembed.fields) + rescue OEmbed::NotFound + render json: {}, status: :not_found + end +end diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index c4a614677..9431b11c1 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -16,6 +16,7 @@ const messages = defineMessages({ share: { id: 'status.share', defaultMessage: 'Share' }, pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, + embed: { id: 'status.embed', defaultMessage: 'Embed' }, }); @injectIntl @@ -34,6 +35,7 @@ export default class ActionBar extends React.PureComponent { onMention: PropTypes.func.isRequired, onReport: PropTypes.func, onPin: PropTypes.func, + onEmbed: PropTypes.func, me: PropTypes.number.isRequired, intl: PropTypes.object.isRequired, }; @@ -73,11 +75,17 @@ export default class ActionBar extends React.PureComponent { }); } + handleEmbed = () => { + this.props.onEmbed(this.props.status); + } + render () { const { status, me, intl } = this.props; let menu = []; + menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); + 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 }); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 84e717a12..c614f6acb 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -147,6 +147,10 @@ export default class Status extends ImmutablePureComponent { this.props.dispatch(initReport(status.get('account'), status)); } + handleEmbed = (status) => { + this.props.dispatch(openModal('EMBED', { url: status.get('url') })); + } + renderChildren (list) { return list.map(id => ); } @@ -198,6 +202,7 @@ export default class Status extends ImmutablePureComponent { onMention={this.handleMentionClick} onReport={this.handleReport} onPin={this.handlePin} + onEmbed={this.handleEmbed} /> {descendants} diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.js b/app/javascript/mastodon/features/ui/components/embed_modal.js new file mode 100644 index 000000000..992aed8a3 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/embed_modal.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { FormattedMessage, injectIntl } from 'react-intl'; +import axios from 'axios'; + +@injectIntl +export default class EmbedModal extends ImmutablePureComponent { + + static propTypes = { + url: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + } + + state = { + loading: false, + oembed: null, + }; + + componentDidMount () { + const { url } = this.props; + + this.setState({ loading: true }); + + axios.post('/api/web/embed', { url }).then(res => { + this.setState({ loading: false, oembed: res.data }); + + const iframeDocument = this.iframe.contentWindow.document; + + iframeDocument.open(); + iframeDocument.write(res.data.html); + iframeDocument.close(); + + iframeDocument.body.style.margin = 0; + this.iframe.height = iframeDocument.body.scrollHeight + 'px'; + }); + } + + setIframeRef = c => { + this.iframe = c; + } + + handleTextareaClick = (e) => { + e.target.select(); + } + + render () { + const { oembed } = this.state; + + return ( +
+

+ +
+

+ +

+ + + +

+ +

+ +