diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index eae7de8c89e..0fbc6a800d2 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -1,63 +1,11 @@ # frozen_string_literal: true class AboutController < ApplicationController - include RegistrationSpamConcern + include WebAppControllerConcern - layout 'public' + skip_before_action :require_functional! - before_action :require_open_federation!, only: [:more] - before_action :set_body_classes, only: :show - before_action :set_instance_presenter - before_action :set_expires_in, only: [:more] - - skip_before_action :require_functional!, only: [:more] - - def more - flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor] - - toc_generator = TOCGenerator.new(@instance_presenter.extended_description) - - @rules = Rule.ordered - @contents = toc_generator.html - @table_of_contents = toc_generator.toc - @blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks? - end - - helper_method :display_blocks? - helper_method :display_blocks_rationale? - helper_method :public_fetch_mode? - helper_method :new_user - - private - - def require_open_federation! - not_found if whitelist_mode? - end - - def display_blocks? - Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?) - end - - def display_blocks_rationale? - Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?) - end - - def new_user - User.new.tap do |user| - user.build_account - user.build_invite_request - end - end - - def set_instance_presenter - @instance_presenter = InstancePresenter.new - end - - def set_body_classes - @hide_navbar = true - end - - def set_expires_in - expires_in 0, public: true + def show + expires_in 0, public: true unless user_signed_in? end end diff --git a/app/controllers/api/v1/instances/domain_blocks_controller.rb b/app/controllers/api/v1/instances/domain_blocks_controller.rb new file mode 100644 index 00000000000..37a6906fb6b --- /dev/null +++ b/app/controllers/api/v1/instances/domain_blocks_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class Api::V1::Instances::DomainBlocksController < Api::BaseController + skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + + before_action :require_enabled_api! + before_action :set_domain_blocks + + def index + expires_in 3.minutes, public: true + render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)) + end + + private + + def require_enabled_api! + head 404 unless Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?) + end + + def set_domain_blocks + @domain_blocks = DomainBlock.with_user_facing_limitations.by_severity + end +end diff --git a/app/controllers/api/v1/instances/extended_descriptions_controller.rb b/app/controllers/api/v1/instances/extended_descriptions_controller.rb new file mode 100644 index 00000000000..c72e16cff2c --- /dev/null +++ b/app/controllers/api/v1/instances/extended_descriptions_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController + skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + + before_action :set_extended_description + + def show + expires_in 3.minutes, public: true + render json: @extended_description, serializer: REST::ExtendedDescriptionSerializer + end + + private + + def set_extended_description + @extended_description = ExtendedDescription.current + end +end diff --git a/app/javascript/fonts/montserrat/Montserrat-Medium.ttf b/app/javascript/fonts/montserrat/Montserrat-Medium.ttf deleted file mode 100644 index 88d70b89c3f..00000000000 Binary files a/app/javascript/fonts/montserrat/Montserrat-Medium.ttf and /dev/null differ diff --git a/app/javascript/fonts/montserrat/Montserrat-Regular.ttf b/app/javascript/fonts/montserrat/Montserrat-Regular.ttf deleted file mode 100644 index 29ca85d4a8b..00000000000 Binary files a/app/javascript/fonts/montserrat/Montserrat-Regular.ttf and /dev/null differ diff --git a/app/javascript/fonts/montserrat/Montserrat-Regular.woff b/app/javascript/fonts/montserrat/Montserrat-Regular.woff deleted file mode 100644 index af3b5ec44a9..00000000000 Binary files a/app/javascript/fonts/montserrat/Montserrat-Regular.woff and /dev/null differ diff --git a/app/javascript/fonts/montserrat/Montserrat-Regular.woff2 b/app/javascript/fonts/montserrat/Montserrat-Regular.woff2 deleted file mode 100644 index 3d75434ddf9..00000000000 Binary files a/app/javascript/fonts/montserrat/Montserrat-Regular.woff2 and /dev/null differ diff --git a/app/javascript/mastodon/actions/server.js b/app/javascript/mastodon/actions/server.js index af8fef780f3..31d4aea1000 100644 --- a/app/javascript/mastodon/actions/server.js +++ b/app/javascript/mastodon/actions/server.js @@ -5,6 +5,14 @@ export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST'; export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS'; export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL'; +export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST'; +export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS'; +export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL'; + +export const SERVER_DOMAIN_BLOCKS_FETCH_REQUEST = 'SERVER_DOMAIN_BLOCKS_FETCH_REQUEST'; +export const SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS = 'SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS'; +export const SERVER_DOMAIN_BLOCKS_FETCH_FAIL = 'SERVER_DOMAIN_BLOCKS_FETCH_FAIL'; + export const fetchServer = () => (dispatch, getState) => { dispatch(fetchServerRequest()); @@ -28,3 +36,56 @@ const fetchServerFail = error => ({ type: SERVER_FETCH_FAIL, error, }); + +export const fetchExtendedDescription = () => (dispatch, getState) => { + dispatch(fetchExtendedDescriptionRequest()); + + api(getState) + .get('/api/v1/instance/extended_description') + .then(({ data }) => dispatch(fetchExtendedDescriptionSuccess(data))) + .catch(err => dispatch(fetchExtendedDescriptionFail(err))); +}; + +const fetchExtendedDescriptionRequest = () => ({ + type: EXTENDED_DESCRIPTION_REQUEST, +}); + +const fetchExtendedDescriptionSuccess = description => ({ + type: EXTENDED_DESCRIPTION_SUCCESS, + description, +}); + +const fetchExtendedDescriptionFail = error => ({ + type: EXTENDED_DESCRIPTION_FAIL, + error, +}); + +export const fetchDomainBlocks = () => (dispatch, getState) => { + dispatch(fetchDomainBlocksRequest()); + + api(getState) + .get('/api/v1/instance/domain_blocks') + .then(({ data }) => dispatch(fetchDomainBlocksSuccess(true, data))) + .catch(err => { + if (err.response.status === 404) { + dispatch(fetchDomainBlocksSuccess(false, [])); + } else { + dispatch(fetchDomainBlocksFail(err)); + } + }); +}; + +const fetchDomainBlocksRequest = () => ({ + type: SERVER_DOMAIN_BLOCKS_FETCH_REQUEST, +}); + +const fetchDomainBlocksSuccess = (isAvailable, blocks) => ({ + type: SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS, + isAvailable, + blocks, +}); + +const fetchDomainBlocksFail = error => ({ + type: SERVER_DOMAIN_BLOCKS_FETCH_FAIL, + error, +}); diff --git a/app/javascript/mastodon/components/image.js b/app/javascript/mastodon/components/image.js new file mode 100644 index 00000000000..6e81ddf082c --- /dev/null +++ b/app/javascript/mastodon/components/image.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Blurhash from './blurhash'; +import classNames from 'classnames'; + +export default class Image extends React.PureComponent { + + static propTypes = { + src: PropTypes.string, + srcSet: PropTypes.string, + blurhash: PropTypes.string, + className: PropTypes.string, + }; + + state = { + loaded: false, + }; + + handleLoad = () => this.setState({ loaded: true }); + + render () { + const { src, srcSet, blurhash, className } = this.props; + const { loaded } = this.state; + + return ( +
+ {blurhash && } + +
+ ); + } + +} diff --git a/app/javascript/mastodon/components/server_banner.js b/app/javascript/mastodon/components/server_banner.js index ae4e200c340..c2336e43d35 100644 --- a/app/javascript/mastodon/components/server_banner.js +++ b/app/javascript/mastodon/components/server_banner.js @@ -7,13 +7,15 @@ import ShortNumber from 'mastodon/components/short_number'; import Skeleton from 'mastodon/components/skeleton'; import Account from 'mastodon/containers/account_container'; import { domain } from 'mastodon/initial_state'; +import Image from 'mastodon/components/image'; +import { Link } from 'react-router-dom'; const messages = defineMessages({ aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' }, }); const mapStateToProps = state => ({ - server: state.get('server'), + server: state.getIn(['server', 'server']), }); export default @connect(mapStateToProps) @@ -41,7 +43,7 @@ class ServerBanner extends React.PureComponent { {domain}, mastodon: Mastodon }} /> - {server.get('title')} +
{isLoading ? ( @@ -83,7 +85,7 @@ class ServerBanner extends React.PureComponent {
- +
); } diff --git a/app/javascript/mastodon/components/skeleton.js b/app/javascript/mastodon/components/skeleton.js index 09093e99c75..6a17ffb261e 100644 --- a/app/javascript/mastodon/components/skeleton.js +++ b/app/javascript/mastodon/components/skeleton.js @@ -4,8 +4,8 @@ import PropTypes from 'prop-types'; const Skeleton = ({ width, height }) => ; Skeleton.propTypes = { - width: PropTypes.number, - height: PropTypes.number, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), }; export default Skeleton; diff --git a/app/javascript/mastodon/features/about/index.js b/app/javascript/mastodon/features/about/index.js index bc8d3a41b07..e9212565acd 100644 --- a/app/javascript/mastodon/features/about/index.js +++ b/app/javascript/mastodon/features/about/index.js @@ -1,27 +1,214 @@ import React from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Column from 'mastodon/components/column'; import LinkFooter from 'mastodon/features/ui/components/link_footer'; import { Helmet } from 'react-helmet'; +import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server'; +import Account from 'mastodon/containers/account_container'; +import Skeleton from 'mastodon/components/skeleton'; +import Icon from 'mastodon/components/icon'; +import classNames from 'classnames'; +import Image from 'mastodon/components/image'; const messages = defineMessages({ title: { id: 'column.about', defaultMessage: 'About' }, + rules: { id: 'about.rules', defaultMessage: 'Server rules' }, + blocks: { id: 'about.blocks', defaultMessage: 'Moderated servers' }, + silenced: { id: 'about.domain_blocks.silenced.title', defaultMessage: 'Limited' }, + silencedExplanation: { id: 'about.domain_blocks.silenced.explanation', defaultMessage: 'You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.' }, + suspended: { id: 'about.domain_blocks.suspended.title', defaultMessage: 'Suspended' }, + suspendedExplanation: { id: 'about.domain_blocks.suspended.explanation', defaultMessage: 'No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.' }, }); -export default @injectIntl +const severityMessages = { + silence: { + title: messages.silenced, + explanation: messages.silencedExplanation, + }, + + suspend: { + title: messages.suspended, + explanation: messages.suspendedExplanation, + }, +}; + +const mapStateToProps = state => ({ + server: state.getIn(['server', 'server']), + extendedDescription: state.getIn(['server', 'extendedDescription']), + domainBlocks: state.getIn(['server', 'domainBlocks']), +}); + +class Section extends React.PureComponent { + + static propTypes = { + title: PropTypes.string, + children: PropTypes.node, + open: PropTypes.bool, + onOpen: PropTypes.func, + }; + + state = { + collapsed: !this.props.open, + }; + + handleClick = () => { + const { onOpen } = this.props; + const { collapsed } = this.state; + + this.setState({ collapsed: !collapsed }, () => onOpen && onOpen()); + } + + render () { + const { title, children } = this.props; + const { collapsed } = this.state; + + return ( +
+
+ {title} +
+ + {!collapsed && ( +
{children}
+ )} +
+ ); + } + +} + +export default @connect(mapStateToProps) +@injectIntl class About extends React.PureComponent { static propTypes = { + server: ImmutablePropTypes.map, + extendedDescription: ImmutablePropTypes.map, + domainBlocks: ImmutablePropTypes.contains({ + isLoading: PropTypes.bool, + isAvailable: PropTypes.bool, + items: ImmutablePropTypes.list, + }), + dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchServer()); + dispatch(fetchExtendedDescription()); + } + + handleDomainBlocksOpen = () => { + const { dispatch } = this.props; + dispatch(fetchDomainBlocks()); + } + render () { - const { intl } = this.props; + const { intl, server, extendedDescription, domainBlocks } = this.props; + const isLoading = server.get('isLoading'); return ( - +
+
+ `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> +

{isLoading ? : server.get('domain')}

+

Mastodon }} />

+
+ +
+
+

+ + +
+ +
+ +
+

+ + {isLoading ? : {server.getIn(['contact', 'email'])}} +
+
+ +
+ {extendedDescription.get('isLoading') ? ( + <> + +
+ +
+ +
+ + + ) : (extendedDescription.get('content')?.length > 0 ? ( +
+ ) : ( +

+ ))} +
+ +
+ {!isLoading && (server.get('rules').isEmpty() ? ( +

+ ) : ( +
    + {server.get('rules').map(rule => ( +
  1. + {rule.get('text')} +
  2. + ))} +
+ ))} +
+ +
+ {domainBlocks.get('isLoading') ? ( + <> + +
+ + + ) : (domainBlocks.get('isAvailable') ? ( + <> +

+ + + + + + + + + + + + {domainBlocks.get('items').map(block => ( + + + + + + ))} + +
{block.get('domain')}{intl.formatMessage(severityMessages[block.get('severity')].title)}{block.get('comment')}
+ + ) : ( +

+ ))} +
+ + +
{intl.formatMessage(messages.title)} diff --git a/app/javascript/mastodon/features/privacy_policy/index.js b/app/javascript/mastodon/features/privacy_policy/index.js index 5fbe340c05c..eee4255f471 100644 --- a/app/javascript/mastodon/features/privacy_policy/index.js +++ b/app/javascript/mastodon/features/privacy_policy/index.js @@ -44,7 +44,7 @@ class PrivacyPolicy extends React.PureComponent {
diff --git a/app/javascript/mastodon/features/report/rules.js b/app/javascript/mastodon/features/report/rules.js index 2cb4a95b5a9..920da68d638 100644 --- a/app/javascript/mastodon/features/report/rules.js +++ b/app/javascript/mastodon/features/report/rules.js @@ -7,7 +7,7 @@ import Button from 'mastodon/components/button'; import Option from './components/option'; const mapStateToProps = state => ({ - rules: state.getIn(['server', 'rules']), + rules: state.getIn(['server', 'server', 'rules']), }); export default @connect(mapStateToProps) diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js index c4ce2a98551..cc3d8357270 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.js +++ b/app/javascript/mastodon/features/ui/components/link_footer.js @@ -51,7 +51,7 @@ class LinkFooter extends React.PureComponent { const items = []; items.push(); - items.push(); + items.push(); items.push(); items.push(); items.push(); diff --git a/app/javascript/mastodon/reducers/server.js b/app/javascript/mastodon/reducers/server.js index 68131c6ddfb..db9f2b5e6b6 100644 --- a/app/javascript/mastodon/reducers/server.js +++ b/app/javascript/mastodon/reducers/server.js @@ -1,18 +1,52 @@ -import { SERVER_FETCH_REQUEST, SERVER_FETCH_SUCCESS, SERVER_FETCH_FAIL } from 'mastodon/actions/server'; -import { Map as ImmutableMap, fromJS } from 'immutable'; +import { + SERVER_FETCH_REQUEST, + SERVER_FETCH_SUCCESS, + SERVER_FETCH_FAIL, + EXTENDED_DESCRIPTION_REQUEST, + EXTENDED_DESCRIPTION_SUCCESS, + EXTENDED_DESCRIPTION_FAIL, + SERVER_DOMAIN_BLOCKS_FETCH_REQUEST, + SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS, + SERVER_DOMAIN_BLOCKS_FETCH_FAIL, +} from 'mastodon/actions/server'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; const initialState = ImmutableMap({ - isLoading: true, + server: ImmutableMap({ + isLoading: true, + }), + + extendedDescription: ImmutableMap({ + isLoading: true, + }), + + domainBlocks: ImmutableMap({ + isLoading: true, + isAvailable: true, + items: ImmutableList(), + }), }); export default function server(state = initialState, action) { switch (action.type) { case SERVER_FETCH_REQUEST: - return state.set('isLoading', true); + return state.setIn(['server', 'isLoading'], true); case SERVER_FETCH_SUCCESS: - return fromJS(action.server).set('isLoading', false); + return state.set('server', fromJS(action.server)).setIn(['server', 'isLoading'], false); case SERVER_FETCH_FAIL: - return state.set('isLoading', false); + return state.setIn(['server', 'isLoading'], false); + case EXTENDED_DESCRIPTION_REQUEST: + return state.setIn(['extendedDescription', 'isLoading'], true); + case EXTENDED_DESCRIPTION_SUCCESS: + return state.set('extendedDescription', fromJS(action.description)).setIn(['extendedDescription', 'isLoading'], false); + case EXTENDED_DESCRIPTION_FAIL: + return state.setIn(['extendedDescription', 'isLoading'], false); + case SERVER_DOMAIN_BLOCKS_FETCH_REQUEST: + return state.setIn(['domainBlocks', 'isLoading'], true); + case SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS: + return state.setIn(['domainBlocks', 'items'], fromJS(action.blocks)).setIn(['domainBlocks', 'isLoading'], false).setIn(['domainBlocks', 'isAvailable'], action.isAvailable); + case SERVER_DOMAIN_BLOCKS_FETCH_FAIL: + return state.setIn(['domainBlocks', 'isLoading'], false); default: return state; } diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss index bbea061957e..e9f596e2fd1 100644 --- a/app/javascript/styles/application.scss +++ b/app/javascript/styles/application.scss @@ -2,7 +2,6 @@ @import 'mastodon/variables'; @import 'fonts/roboto'; @import 'fonts/roboto-mono'; -@import 'fonts/montserrat'; @import 'mastodon/reset'; @import 'mastodon/basics'; @@ -10,7 +9,6 @@ @import 'mastodon/containers'; @import 'mastodon/lists'; @import 'mastodon/footer'; -@import 'mastodon/compact_header'; @import 'mastodon/widgets'; @import 'mastodon/forms'; @import 'mastodon/accounts'; diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss index 841ed66480c..22f5bcc94bb 100644 --- a/app/javascript/styles/contrast/diff.scss +++ b/app/javascript/styles/contrast/diff.scss @@ -13,10 +13,6 @@ } } -.rich-formatting a, -.rich-formatting p a, -.rich-formatting li a, -.landing-page__short-description p a, .status__content a, .reply-indicator__content a { color: lighten($ui-highlight-color, 12%); diff --git a/app/javascript/styles/fonts/montserrat.scss b/app/javascript/styles/fonts/montserrat.scss deleted file mode 100644 index 170fe654294..00000000000 --- a/app/javascript/styles/fonts/montserrat.scss +++ /dev/null @@ -1,21 +0,0 @@ -@font-face { - font-family: mastodon-font-display; - src: - local('Montserrat'), - url('../fonts/montserrat/Montserrat-Regular.woff2') format('woff2'), - url('../fonts/montserrat/Montserrat-Regular.woff') format('woff'), - url('../fonts/montserrat/Montserrat-Regular.ttf') format('truetype'); - font-weight: 400; - font-display: swap; - font-style: normal; -} - -@font-face { - font-family: mastodon-font-display; - src: - local('Montserrat Medium'), - url('../fonts/montserrat/Montserrat-Medium.ttf') format('truetype'); - font-weight: 500; - font-display: swap; - font-style: normal; -} diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 0bc6247ef2c..4b27e6b4f3c 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -36,6 +36,20 @@ html { border-top: 0; } +.column > .scrollable.about { + border-top: 1px solid lighten($ui-base-color, 8%); +} + +.about__meta, +.about__section__title { + background: $white; + border: 1px solid lighten($ui-base-color, 8%); +} + +.rules-list li::before { + background: $ui-highlight-color; +} + .directory__card__img { background: lighten($ui-base-color, 12%); } @@ -45,10 +59,6 @@ html { border-bottom: 1px solid lighten($ui-base-color, 8%); } -.table-of-contents { - border: 1px solid lighten($ui-base-color, 8%); -} - .column-back-button, .column-header { background: $white; @@ -138,11 +148,6 @@ html { .compose-form__poll-wrapper select, .search__input, .setting-text, -.box-widget input[type="text"], -.box-widget input[type="email"], -.box-widget input[type="password"], -.box-widget textarea, -.statuses-grid .detailed-status, .report-dialog-modal__textarea, .audio-player { border: 1px solid lighten($ui-base-color, 8%); @@ -480,52 +485,16 @@ html { background: $white; } -.tabs-bar { - background: $white; - border: 1px solid lighten($ui-base-color, 8%); - border-bottom: 0; - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 0; - } - - &__link { - padding-bottom: 14px; - border-bottom-width: 1px; - border-bottom-color: lighten($ui-base-color, 8%); - - &:hover, - &:active, - &:focus { - background: $ui-base-color; - } - - &.active { - &:hover, - &:active, - &:focus { - background: transparent; - border-bottom-color: $ui-highlight-color; - } - } - } -} - // Change the default colors used on some parts of the profile pages .activity-stream-tabs { background: $account-background-color; border-bottom-color: lighten($ui-base-color, 8%); } -.box-widget, .nothing-here, .page-header, .directory__tag > a, -.directory__tag > div, -.landing-page__call-to-action, -.contact-widget, -.landing .hero-widget__text, -.landing-page__information.contact-widget { +.directory__tag > div { background: $white; border: 1px solid lighten($ui-base-color, 8%); @@ -536,11 +505,6 @@ html { } } -.landing .hero-widget__text { - border-top: 0; - border-bottom: 0; -} - .simple_form { input[type="text"], input[type="number"], @@ -553,26 +517,12 @@ html { } } -.landing .hero-widget__footer { - background: $white; - border: 1px solid lighten($ui-base-color, 8%); - border-top: 0; - - @media screen and (max-width: $no-gap-breakpoint) { - border: 0; - } -} - .picture-in-picture-placeholder { background: $white; border-color: lighten($ui-base-color, 8%); color: lighten($ui-base-color, 8%); } -.brand__tagline { - color: $ui-secondary-color; -} - .directory__tag > a { &:hover, &:active, @@ -666,8 +616,7 @@ html { } } -.simple_form, -.table-form { +.simple_form { .warning { box-shadow: none; background: rgba($error-red, 0.5); @@ -801,9 +750,6 @@ html { } .hero-widget, -.box-widget, -.contact-widget, -.landing-page__information.contact-widget, .moved-account-widget, .memoriam-widget, .activity-stream, diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 8893e3cf09b..0183c43d5e7 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -1,7 +1,5 @@ $maximum-width: 1235px; $fluid-breakpoint: $maximum-width + 20px; -$column-breakpoint: 700px; -$small-breakpoint: 960px; .container { box-sizing: border-box; @@ -15,892 +13,44 @@ $small-breakpoint: 960px; } } -.rich-formatting { - font-family: $font-sans-serif, sans-serif; - font-size: 14px; - font-weight: 400; - line-height: 1.7; - word-wrap: break-word; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - - &:hover, - &:focus, - &:active { - text-decoration: none; - } - } - - p, - li { - color: $darker-text-color; - } - - p { - margin-top: 0; - margin-bottom: 0.85em; - - &:last-child { - margin-bottom: 0; - } - } - - strong { - font-weight: 700; - color: $secondary-text-color; - } - - em { - font-style: italic; - color: $secondary-text-color; - } - - code { - font-size: 0.85em; - background: darken($ui-base-color, 8%); - border-radius: 4px; - padding: 0.2em 0.3em; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - font-family: $font-display, sans-serif; - margin-top: 1.275em; - margin-bottom: 0.85em; - font-weight: 500; - color: $secondary-text-color; - } - - h1 { - font-size: 2em; - } - - h2 { - font-size: 1.75em; - } - - h3 { - font-size: 1.5em; - } - - h4 { - font-size: 1.25em; - } - - h5, - h6 { - font-size: 1em; - } - - ul { - list-style: disc; - } - - ol { - list-style: decimal; - } - - ul, - ol { - margin: 0; - padding: 0; - padding-left: 2em; - margin-bottom: 0.85em; - - &[type='a'] { - list-style-type: lower-alpha; - } - - &[type='i'] { - list-style-type: lower-roman; - } - } - - hr { - width: 100%; - height: 0; - border: 0; - border-bottom: 1px solid lighten($ui-base-color, 4%); - margin: 1.7em 0; - - &.spacer { - height: 1px; - border: 0; - } - } - - table { - width: 100%; - border-collapse: collapse; - break-inside: auto; - margin-top: 24px; - margin-bottom: 32px; - - thead tr, - tbody tr { - border-bottom: 1px solid lighten($ui-base-color, 4%); - font-size: 1em; - line-height: 1.625; - font-weight: 400; - text-align: left; - color: $darker-text-color; - } - - thead tr { - border-bottom-width: 2px; - line-height: 1.5; - font-weight: 500; - color: $dark-text-color; - } - - th, - td { - padding: 8px; - align-self: flex-start; - align-items: flex-start; - word-break: break-all; - - &.nowrap { - width: 25%; - position: relative; - - &::before { - content: ' '; - visibility: hidden; - } - - span { - position: absolute; - left: 8px; - right: 8px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - } - - & > :first-child { - margin-top: 0; - } -} - -.information-board { - background: darken($ui-base-color, 4%); - padding: 20px 0; - - .container-alt { - position: relative; - padding-right: 280px + 15px; - } - - &__sections { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - } - - &__section { - flex: 1 0 0; - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - line-height: 28px; - color: $primary-text-color; - text-align: right; - padding: 10px 15px; - - span, - strong { - display: block; - } - - span { - &:last-child { - color: $secondary-text-color; - } - } - - strong { - font-family: $font-display, sans-serif; - font-weight: 500; - font-size: 32px; - line-height: 48px; - } - - @media screen and (max-width: $column-breakpoint) { - text-align: center; - } - } - - .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: $font-display, sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - color: $darker-text-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($darker-text-color, 10%); - } - - a { - text-decoration: none; - } - } - } - - .owner { - text-align: center; - - .avatar { - width: 80px; - height: 80px; - 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: $darker-text-color; - } - } - } -} - -.landing-page { - p, - li { - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 30px; - margin-bottom: 12px; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - - em { - display: inline; - margin: 0; - padding: 0; - font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; - color: lighten($darker-text-color, 10%); - } - - h1 { - font-family: $font-display, sans-serif; - font-size: 26px; - line-height: 30px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - - small { - font-family: $font-sans-serif, sans-serif; - display: block; - font-size: 18px; - font-weight: 400; - color: lighten($darker-text-color, 10%); - } - } - - h2 { - font-family: $font-display, sans-serif; - font-size: 22px; - line-height: 26px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h3 { - font-family: $font-display, sans-serif; - font-size: 18px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h4 { - font-family: $font-display, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h5 { - font-family: $font-display, sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h6 { - font-family: $font-display, sans-serif; - font-size: 12px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - ul, - ol { - margin-left: 20px; - - &[type='a'] { - list-style-type: lower-alpha; - } - - &[type='i'] { - list-style-type: lower-roman; - } - } - - ul { - list-style: disc; - } - - ol { - list-style: decimal; - } - - li > ol, - li > ul { - margin-top: 6px; - } - - hr { - width: 100%; - height: 0; - border: 0; - border-bottom: 1px solid rgba($ui-base-lighter-color, 0.6); - margin: 20px 0; - - &.spacer { - height: 1px; - border: 0; - } - } - - &__information, - &__forms { - padding: 20px; - } - - &__call-to-action { - background: $ui-base-color; - border-radius: 4px; - padding: 25px 40px; - overflow: hidden; - box-sizing: border-box; - - .row { - width: 100%; - display: flex; - flex-direction: row-reverse; - flex-wrap: nowrap; - justify-content: space-between; - align-items: center; - } - - .row__information-board { - display: flex; - justify-content: flex-end; - align-items: flex-end; - - .information-board__section { - flex: 1 0 auto; - padding: 0 10px; - } - - @media screen and (max-width: $no-gap-breakpoint) { - width: 100%; - justify-content: space-between; - } - } - - .row__mascot { - flex: 1; - margin: 10px -50px 0 0; - - @media screen and (max-width: $no-gap-breakpoint) { - display: none; - } - } - } - - &__logo { - margin-right: 20px; - - img { - height: 50px; - width: auto; - mix-blend-mode: lighten; - } - } - - &__information { - padding: 45px 40px; - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - - strong { - font-weight: 500; - color: lighten($darker-text-color, 10%); - } - - .account { - border-bottom: 0; - padding: 0; - - &__display-name { - align-items: center; - display: flex; - margin-right: 5px; - } - - div.account__display-name { - &:hover { - .display-name strong { - text-decoration: none; - } - } - - .account__avatar { - cursor: default; - } - } - - &__avatar-wrapper { - margin-left: 0; - flex: 0 0 auto; - } - - .display-name { - font-size: 15px; - - &__account { - font-size: 14px; - } - } - } - - @media screen and (max-width: $small-breakpoint) { - .contact { - margin-top: 30px; - } - } - - @media screen and (max-width: $column-breakpoint) { - padding: 25px 20px; - } - } - - &__information, - &__forms, - #mastodon-timeline { - box-sizing: border-box; - background: $ui-base-color; - border-radius: 4px; - box-shadow: 0 0 6px rgba($black, 0.1); - } - - &__mascot { - height: 104px; - position: relative; - left: -40px; - bottom: 25px; - - img { - height: 190px; - width: auto; - } - } - - &__short-description { - .row { - display: flex; - flex-wrap: wrap; - align-items: center; - margin-bottom: 40px; - } - - @media screen and (max-width: $column-breakpoint) { - .row { - margin-bottom: 20px; - } - } - - p a { - color: $secondary-text-color; - } - - h1 { - font-weight: 500; - color: $primary-text-color; - margin-bottom: 0; - - small { - color: $darker-text-color; - - span { - color: $secondary-text-color; - } - } - } - - p:last-child { - margin-bottom: 0; - } - } - - &__hero { - margin-bottom: 10px; - - img { - display: block; - margin: 0; - max-width: 100%; - height: auto; - border-radius: 4px; - } - } - - @media screen and (max-width: 840px) { - .information-board { - .container-alt { - padding-right: 20px; - } - - .panel { - position: static; - margin-top: 20px; - width: 100%; - border-radius: 4px; - - .panel-header { - text-align: center; - } - } - } - } - - @media screen and (max-width: 675px) { - .header-wrapper { - padding-top: 0; - - &.compact { - padding-bottom: 0; - } - - &.compact .hero .heading { - text-align: initial; - } - } - - .header .container-alt, - .features .container-alt { - display: block; - } - } - - .cta { - margin: 20px; - } -} - -.landing { - margin-bottom: 100px; - - @media screen and (max-width: 738px) { - margin-bottom: 0; - } - - &__brand { - display: flex; - justify-content: center; - align-items: center; - padding: 50px; - - .logo { - fill: $primary-text-color; - height: 52px; - } - - @media screen and (max-width: $no-gap-breakpoint) { - padding: 0; - margin-bottom: 30px; - } - } - - .directory { - margin-top: 30px; - background: transparent; - box-shadow: none; - border-radius: 0; - } - - .hero-widget { - margin-top: 30px; - margin-bottom: 0; - - h4 { - padding: 10px; - text-transform: uppercase; - font-weight: 700; - font-size: 13px; - color: $darker-text-color; - } - - &__text { - border-radius: 0; - padding-bottom: 0; - } - - &__footer { - background: $ui-base-color; - padding: 10px; - border-radius: 0 0 4px 4px; - display: flex; - - &__column { - flex: 1 1 50%; - overflow-x: hidden; - } - } - - .account { - padding: 10px 0; - border-bottom: 0; - - .account__display-name { - display: flex; - align-items: center; - } - } - - &__counters__wrapper { - display: flex; - } - - &__counter { - padding: 10px; - width: 50%; - - strong { - font-family: $font-display, sans-serif; - font-size: 15px; - font-weight: 700; - display: block; - } - - span { - font-size: 14px; - color: $darker-text-color; - } - } - } - - .simple_form .user_agreement .label_input > label { - font-weight: 400; - color: $darker-text-color; - } - - .simple_form p.lead { - color: $darker-text-color; - font-size: 15px; - line-height: 20px; - font-weight: 400; - margin-bottom: 25px; - } - - &__grid { - max-width: 960px; - margin: 0 auto; - display: grid; - grid-template-columns: minmax(0, 50%) minmax(0, 50%); - grid-gap: 30px; - - @media screen and (max-width: 738px) { - grid-template-columns: minmax(0, 100%); - grid-gap: 10px; - - &__column-login { - grid-row: 1; - display: flex; - flex-direction: column; - - .box-widget { - order: 2; - flex: 0 0 auto; - } - - .hero-widget { - margin-top: 0; - margin-bottom: 10px; - order: 1; - flex: 0 0 auto; - } - } - - &__column-registration { - grid-row: 2; - } - - .directory { - margin-top: 10px; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - grid-gap: 0; - - .hero-widget { - display: block; - margin-bottom: 0; - box-shadow: none; - - &__img, - &__img img, - &__footer { - border-radius: 0; - } - } - - .hero-widget, - .box-widget, - .directory__tag { - border-bottom: 1px solid lighten($ui-base-color, 8%); - } - - .directory { - margin-top: 0; - - &__tag { - margin-bottom: 0; - - & > a, - & > div { - border-radius: 0; - box-shadow: none; - } - - &:last-child { - border-bottom: 0; - } - } - } - } - } -} - .brand { position: relative; text-decoration: none; } -.brand__tagline { - display: block; - position: absolute; - bottom: -10px; - left: 50px; - width: 300px; - color: $ui-primary-color; - text-decoration: none; - font-size: 14px; - - @media screen and (max-width: $no-gap-breakpoint) { - position: static; - width: auto; - margin-top: 20px; - color: $dark-text-color; - } -} - .rules-list { - background: darken($ui-base-color, 2%); - border: 1px solid darken($ui-base-color, 8%); - border-radius: 4px; - padding: 0.5em 2.5em !important; - margin-top: 1.85em !important; + font-size: 15px; + line-height: 22px; + color: $primary-text-color; + counter-reset: list-counter; li { - border-bottom: 1px solid lighten($ui-base-color, 4%); - color: $dark-text-color; - padding: 1em; + position: relative; + border-bottom: 1px solid lighten($ui-base-color, 8%); + padding: 1em 1.75em; + padding-left: 3em; + font-weight: 500; + counter-increment: list-counter; + + &::before { + content: counter(list-counter); + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + background: $highlight-text-color; + color: $ui-base-color; + border-radius: 50%; + width: 4ch; + height: 4ch; + font-weight: 500; + display: flex; + justify-content: center; + align-items: center; + } &:last-child { border-bottom: 0; } } - - &__text { - color: $primary-text-color; - } } diff --git a/app/javascript/styles/mastodon/compact_header.scss b/app/javascript/styles/mastodon/compact_header.scss deleted file mode 100644 index 4980ab5f1ac..00000000000 --- a/app/javascript/styles/mastodon/compact_header.scss +++ /dev/null @@ -1,34 +0,0 @@ -.compact-header { - h1 { - font-size: 24px; - line-height: 28px; - color: $darker-text-color; - font-weight: 500; - margin-bottom: 20px; - padding: 0 10px; - word-wrap: break-word; - - @media screen and (max-width: 740px) { - text-align: center; - padding: 20px 10px 0; - } - - a { - color: inherit; - text-decoration: none; - } - - small { - font-weight: 400; - color: $secondary-text-color; - } - - img { - display: inline-block; - margin-bottom: -5px; - margin-right: 15px; - width: 36px; - height: 36px; - } - } -} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index a8919b9cbd1..d4657d18043 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3261,6 +3261,7 @@ $ui-header-height: 55px; padding: 10px; padding-top: 20px; z-index: 1; + font-size: 13px; ul { margin-bottom: 10px; @@ -3272,7 +3273,6 @@ $ui-header-height: 55px; p { color: $dark-text-color; - font-size: 13px; margin-bottom: 20px; a { @@ -8266,78 +8266,201 @@ noscript { &__body { margin-top: 20px; - color: $secondary-text-color; - font-size: 15px; - line-height: 22px; + } +} - h1, - p, - ul, - ol { - margin-bottom: 20px; +.prose { + color: $secondary-text-color; + font-size: 15px; + line-height: 22px; + + p, + ul, + ol { + margin-top: 1.25em; + margin-bottom: 1.25em; + } + + img { + margin-top: 2em; + margin-bottom: 2em; + } + + video { + margin-top: 2em; + margin-bottom: 2em; + } + + figure { + margin-top: 2em; + margin-bottom: 2em; + + figcaption { + font-size: 0.875em; + line-height: 1.4285714; + margin-top: 0.8571429em; } + } - ul { - list-style: disc; + figure > * { + margin-top: 0; + margin-bottom: 0; + } + + h1 { + font-size: 1.5em; + margin-top: 0; + margin-bottom: 1em; + line-height: 1.33; + } + + h2 { + font-size: 1.25em; + margin-top: 1.6em; + margin-bottom: 0.6em; + line-height: 1.6; + } + + h3, + h4, + h5, + h6 { + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.5; + } + + ol { + counter-reset: list-counter; + } + + li { + margin-top: 0.5em; + margin-bottom: 0.5em; + } + + ol > li { + counter-increment: list-counter; + + &::before { + content: counter(list-counter) "."; + position: absolute; + left: 0; } + } - ol { - list-style: decimal; + ul > li::before { + content: ""; + position: absolute; + background-color: $darker-text-color; + border-radius: 50%; + width: 0.375em; + height: 0.375em; + top: 0.5em; + left: 0.25em; + } + + ul > li, + ol > li { + position: relative; + padding-left: 1.75em; + } + + & > ul > li p { + margin-top: 0.75em; + margin-bottom: 0.75em; + } + + & > ul > li > *:first-child { + margin-top: 1.25em; + } + + & > ul > li > *:last-child { + margin-bottom: 1.25em; + } + + & > ol > li > *:first-child { + margin-top: 1.25em; + } + + & > ol > li > *:last-child { + margin-bottom: 1.25em; + } + + ul ul, + ul ol, + ol ul, + ol ol { + margin-top: 0.75em; + margin-bottom: 0.75em; + } + + h1, + h2, + h3, + h4, + h5, + h6, + strong, + b { + color: $primary-text-color; + font-weight: 700; + } + + em, + i { + font-style: italic; + } + + a { + color: $highlight-text-color; + text-decoration: underline; + + &:focus, + &:hover, + &:active { + text-decoration: none; } + } - ul, - ol { - padding-left: 1em; - } + code { + font-size: 0.875em; + background: darken($ui-base-color, 8%); + border-radius: 4px; + padding: 0.2em 0.3em; + } - li { - margin-bottom: 10px; + hr { + border: 0; + border-top: 1px solid lighten($ui-base-color, 4%); + margin-top: 3em; + margin-bottom: 3em; + } - &::marker { - color: $darker-text-color; - } + hr + * { + margin-top: 0; + } - &:last-child { - margin-bottom: 0; - } - } + h2 + * { + margin-top: 0; + } - h1 { - color: $primary-text-color; - font-size: 19px; - line-height: 24px; - font-weight: 700; - margin-top: 30px; + h3 + * { + margin-top: 0; + } - &:first-child { - margin-top: 0; - } - } + h4 + *, + h5 + *, + h6 + * { + margin-top: 0; + } - strong { - font-weight: 700; - color: $primary-text-color; - } + & > :first-child { + margin-top: 0; + } - em { - font-style: italic; - } - - a { - color: $highlight-text-color; - text-decoration: underline; - - &:focus, - &:hover, - &:active { - text-decoration: none; - } - } - - hr { - border: 1px solid lighten($ui-base-color, 4%); - margin: 30px 0; - } + & > :last-child { + margin-bottom: 0; } } @@ -8365,3 +8488,242 @@ noscript { justify-content: center; } } + +.image { + position: relative; + overflow: hidden; + + &__preview { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + + &.loaded &__preview { + display: none; + } + + img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + border: 0; + background: transparent; + opacity: 0; + } + + &.loaded img { + opacity: 1; + } +} + +.about { + padding: 20px; + + @media screen and (min-width: $no-gap-breakpoint) { + border-radius: 4px; + } + + &__header { + margin-bottom: 30px; + + &__hero { + width: 100%; + height: auto; + aspect-ratio: 1.9; + background: lighten($ui-base-color, 4%); + border-radius: 8px; + margin-bottom: 30px; + } + + h1, + p { + text-align: center; + } + + h1 { + font-size: 24px; + line-height: 1.5; + font-weight: 700; + margin-bottom: 10px; + } + + p { + font-size: 16px; + line-height: 24px; + font-weight: 400; + color: $darker-text-color; + } + } + + &__meta { + background: lighten($ui-base-color, 4%); + border-radius: 4px; + display: flex; + margin-bottom: 30px; + font-size: 15px; + + &__column { + box-sizing: border-box; + width: 50%; + padding: 20px; + } + + &__divider { + width: 0; + border: 0; + border-style: solid; + border-color: lighten($ui-base-color, 8%); + border-left-width: 1px; + min-height: calc(100% - 60px); + flex: 0 0 auto; + } + + h4 { + font-size: 15px; + text-transform: uppercase; + color: $darker-text-color; + font-weight: 500; + margin-bottom: 20px; + } + + @media screen and (max-width: 600px) { + display: block; + + h4 { + text-align: center; + } + + &__column { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + } + + &__divider { + min-height: 0; + width: 100%; + border-left-width: 0; + border-top-width: 1px; + } + } + + .layout-multiple-columns & { + display: block; + + h4 { + text-align: center; + } + + &__column { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + } + + &__divider { + min-height: 0; + width: 100%; + border-left-width: 0; + border-top-width: 1px; + } + } + } + + &__mail { + color: $primary-text-color; + text-decoration: none; + font-weight: 500; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + .getting-started__footer { + padding: 0; + margin-top: 60px; + text-align: center; + font-size: 15px; + line-height: 22px; + + @media screen and (min-width: $no-gap-breakpoint) { + display: none; + } + } + + .account { + padding: 0; + border: 0; + } + + .account__avatar-wrapper { + margin-left: 0; + } + + .account__relationship { + display: none; + } + + &__section { + margin-bottom: 10px; + + &__title { + font-size: 17px; + font-weight: 600; + line-height: 22px; + padding: 20px; + border-radius: 4px; + background: lighten($ui-base-color, 4%); + color: $highlight-text-color; + cursor: pointer; + } + + &.active &__title { + border-radius: 4px 4px 0 0; + } + + &__body { + border: 1px solid lighten($ui-base-color, 4%); + border-top: 0; + padding: 20px; + font-size: 15px; + line-height: 22px; + } + } + + &__domain-blocks { + margin-top: 30px; + width: 100%; + border-collapse: collapse; + break-inside: auto; + + th { + text-align: left; + font-weight: 500; + color: $darker-text-color; + } + + thead tr, + tbody tr { + border-bottom: 1px solid lighten($ui-base-color, 8%); + } + + tbody tr:last-child { + border-bottom: 0; + } + + th, + td { + padding: 8px; + } + } +} diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 01ee5621960..8e5ed03f07e 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -30,7 +30,6 @@ outline: 0; padding: 12px 16px; line-height: 32px; - font-family: $font-display, sans-serif; font-weight: 500; font-size: 14px; } diff --git a/app/javascript/styles/mastodon/dashboard.scss b/app/javascript/styles/mastodon/dashboard.scss index c21fc9eba84..f25765d1da9 100644 --- a/app/javascript/styles/mastodon/dashboard.scss +++ b/app/javascript/styles/mastodon/dashboard.scss @@ -38,7 +38,6 @@ font-weight: 500; font-size: 24px; color: $primary-text-color; - font-family: $font-display, sans-serif; margin-bottom: 20px; line-height: 30px; } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 3d67f3b567c..69a0b22d6e8 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -139,18 +139,9 @@ code { } .rules-list { - list-style: decimal; font-size: 17px; line-height: 22px; - font-weight: 500; - background: transparent; - border: 0; - padding: 0.5em 1em !important; margin-bottom: 30px; - - li { - border-color: lighten($ui-base-color, 8%); - } } .hint { @@ -603,41 +594,6 @@ code { } } } - - &__overlay-area { - position: relative; - - &__blurred form { - filter: blur(2px); - } - - &__overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - background: rgba($ui-base-color, 0.65); - border-radius: 4px; - margin-left: -4px; - margin-top: -4px; - padding: 4px; - - &__content { - text-align: center; - - &.rich-formatting { - &, - p { - color: $primary-text-color; - } - } - } - } - } } .block-icon { @@ -908,24 +864,7 @@ code { } } -.table-form { - p { - margin-bottom: 15px; - - strong { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - } -} - -.simple_form, -.table-form { +.simple_form { .warning { box-sizing: border-box; background: rgba($error-value-color, 0.5); diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 43284eb482b..260a97c6d22 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -112,13 +112,6 @@ } } -.box-widget { - padding: 20px; - border-radius: 4px; - background: $ui-base-color; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); -} - .placeholder-widget { padding: 16px; border-radius: 4px; @@ -128,47 +121,6 @@ margin-bottom: 10px; } -.contact-widget { - min-height: 100%; - font-size: 15px; - color: $darker-text-color; - line-height: 20px; - word-wrap: break-word; - font-weight: 400; - padding: 0; - - h4 { - padding: 10px; - text-transform: uppercase; - font-weight: 700; - font-size: 13px; - color: $darker-text-color; - } - - .account { - border-bottom: 0; - padding: 10px 0; - padding-top: 5px; - } - - & > a { - display: inline-block; - padding: 10px; - padding-top: 0; - color: $darker-text-color; - text-decoration: none; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &:hover, - &:focus, - &:active { - text-decoration: underline; - } - } -} - .moved-account-widget { padding: 15px; padding-bottom: 20px; @@ -249,37 +201,6 @@ margin-bottom: 10px; } -.page-header { - background: lighten($ui-base-color, 8%); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - padding: 60px 15px; - text-align: center; - margin: 10px 0; - - h1 { - color: $primary-text-color; - font-size: 36px; - line-height: 1.1; - font-weight: 700; - margin-bottom: 10px; - } - - p { - font-size: 15px; - color: $darker-text-color; - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin-top: 0; - background: lighten($ui-base-color, 4%); - - h1 { - font-size: 24px; - } - } -} - .directory { background: $ui-base-color; border-radius: 4px; @@ -366,34 +287,6 @@ } } -.avatar-stack { - display: flex; - justify-content: flex-end; - - .account__avatar { - flex: 0 0 auto; - width: 36px; - height: 36px; - border-radius: 50%; - position: relative; - margin-left: -10px; - background: darken($ui-base-color, 8%); - border: 2px solid $ui-base-color; - - &:nth-child(1) { - z-index: 1; - } - - &:nth-child(2) { - z-index: 2; - } - - &:nth-child(3) { - z-index: 3; - } - } -} - .accounts-table { width: 100%; @@ -495,11 +388,7 @@ .moved-account-widget, .memoriam-widget, -.box-widget, -.contact-widget, -.landing-page__information.contact-widget, -.directory, -.page-header { +.directory { @media screen and (max-width: $no-gap-breakpoint) { margin-bottom: 0; box-shadow: none; @@ -507,88 +396,6 @@ } } -$maximum-width: 1235px; -$fluid-breakpoint: $maximum-width + 20px; - -.statuses-grid { - min-height: 600px; - - @media screen and (max-width: 640px) { - width: 100% !important; // Masonry layout is unnecessary at this width - } - - &__item { - width: math.div(960px - 20px, 3); - - @media screen and (max-width: $fluid-breakpoint) { - width: math.div(940px - 20px, 3); - } - - @media screen and (max-width: 640px) { - width: 100%; - } - - @media screen and (max-width: $no-gap-breakpoint) { - width: 100vw; - } - } - - .detailed-status { - border-radius: 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 1px solid lighten($ui-base-color, 16%); - } - - &.compact { - .detailed-status__meta { - margin-top: 15px; - } - - .status__content { - font-size: 15px; - line-height: 20px; - - .emojione { - width: 20px; - height: 20px; - margin: -3px 0 0; - } - - .status__content__spoiler-link { - line-height: 20px; - margin: 0; - } - } - - .media-gallery, - .status-card, - .video-player { - margin-top: 15px; - } - } - } -} - -.notice-widget { - margin-bottom: 10px; - color: $darker-text-color; - - p { - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - } - - a { - font-size: 14px; - line-height: 20px; - } -} - -.notice-widget, .placeholder-widget { a { text-decoration: none; @@ -602,37 +409,3 @@ $fluid-breakpoint: $maximum-width + 20px; } } } - -.table-of-contents { - background: darken($ui-base-color, 4%); - min-height: 100%; - font-size: 14px; - border-radius: 4px; - - li a { - display: block; - font-weight: 500; - padding: 15px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-decoration: none; - color: $primary-text-color; - border-bottom: 1px solid lighten($ui-base-color, 4%); - - &:hover, - &:focus, - &:active { - text-decoration: underline; - } - } - - li:last-child a { - border-bottom: 0; - } - - li ul { - padding-left: 20px; - border-bottom: 1px solid lighten($ui-base-color, 4%); - } -} diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index b08687787ca..ad1dc2a38e9 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -28,8 +28,8 @@ class DomainBlock < ApplicationRecord delegate :count, to: :accounts, prefix: true scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } - scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) } - scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain')) } + scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]) } + scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), domain')) } def to_log_human_identifier domain diff --git a/app/models/extended_description.rb b/app/models/extended_description.rb new file mode 100644 index 00000000000..6e5c0d1b6a7 --- /dev/null +++ b/app/models/extended_description.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ExtendedDescription < ActiveModelSerializers::Model + attributes :updated_at, :text + + def self.current + custom = Setting.find_by(var: 'site_extended_description') + + if custom&.value.present? + new(text: custom.value, updated_at: custom.updated_at) + else + new + end + end +end diff --git a/app/serializers/rest/domain_block_serializer.rb b/app/serializers/rest/domain_block_serializer.rb new file mode 100644 index 00000000000..678463e13b3 --- /dev/null +++ b/app/serializers/rest/domain_block_serializer.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class REST::DomainBlockSerializer < ActiveModel::Serializer + attributes :domain, :digest, :severity, :comment + + def domain + object.public_domain + end + + def digest + object.domain_digest + end + + def comment + object.public_comment if instance_options[:with_comment] + end +end diff --git a/app/serializers/rest/extended_description_serializer.rb b/app/serializers/rest/extended_description_serializer.rb new file mode 100644 index 00000000000..0c364903331 --- /dev/null +++ b/app/serializers/rest/extended_description_serializer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class REST::ExtendedDescriptionSerializer < ActiveModel::Serializer + attributes :updated_at, :content + + def updated_at + object.updated_at&.iso8601 + end + + def content + object.text + end +end diff --git a/app/views/about/_domain_blocks.html.haml b/app/views/about/_domain_blocks.html.haml deleted file mode 100644 index 35a30f16eef..00000000000 --- a/app/views/about/_domain_blocks.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%table - %thead - %tr - %th= t('about.unavailable_content_description.domain') - %th= t('about.unavailable_content_description.reason') - %tbody - - domain_blocks.each do |domain_block| - %tr - %td.nowrap - %span{ title: "SHA-256: #{domain_block.domain_digest}" }= domain_block.public_domain - %td - = domain_block.public_comment if display_blocks_rationale? diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml deleted file mode 100644 index a5a10b62059..00000000000 --- a/app/views/about/more.html.haml +++ /dev/null @@ -1,96 +0,0 @@ -- content_for :page_title do - = site_hostname - -- content_for :header_tags do - = javascript_pack_tag 'public', crossorigin: 'anonymous' - = render partial: 'shared/og' - -.grid-4 - .column-0 - .public-account-header.public-account-header--no-bar - .public-account-header__image - = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.png'), alt: @instance_presenter.title, class: 'parallax' - - .column-1 - .landing-page__call-to-action{ dir: 'ltr' } - .row - .row__information-board - .information-board__section - %span= t 'about.user_count_before' - %strong= friendly_number_to_human @instance_presenter.user_count - %span= t 'about.user_count_after', count: @instance_presenter.user_count - .information-board__section - %span= t 'about.status_count_before' - %strong= friendly_number_to_human @instance_presenter.status_count - %span= t 'about.status_count_after', count: @instance_presenter.status_count - .row__mascot - .landing-page__mascot - = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: '' - - .column-2 - .contact-widget - %h4= t 'about.administered_by' - - = account_link_to(@instance_presenter.contact.account) - - - if @instance_presenter.contact.email.present? - %h4 - = succeed ':' do - = t 'about.contact' - - = mail_to @instance_presenter.contact.email, nil, title: @instance_presenter.contact.email - - .column-3 - = render 'application/flashes' - - - if @contents.blank? && @rules.empty? && (!display_blocks? || @blocks&.empty?) - = nothing_here - - else - .box-widget - .rich-formatting - - unless @rules.empty? - %h2#rules= t('about.rules') - - %p= t('about.rules_html') - - %ol.rules-list - - @rules.each do |rule| - %li - .rules-list__text= rule.text - - = @contents.html_safe - - - if display_blocks? && !@blocks.empty? - %h2#unavailable-content= t('about.unavailable_content') - - %p= t('about.unavailable_content_html') - - - if (blocks = @blocks.select(&:reject_media?)) && !blocks.empty? - %h3= t('about.unavailable_content_description.rejecting_media_title') - %p= t('about.unavailable_content_description.rejecting_media') - = render partial: 'domain_blocks', locals: { domain_blocks: blocks } - - if (blocks = @blocks.select(&:silence?)) && !blocks.empty? - %h3= t('about.unavailable_content_description.silenced_title') - %p= t('about.unavailable_content_description.silenced') - = render partial: 'domain_blocks', locals: { domain_blocks: blocks } - - if (blocks = @blocks.select(&:suspend?)) && !blocks.empty? - %h3= t('about.unavailable_content_description.suspended_title') - %p= t('about.unavailable_content_description.suspended') - = render partial: 'domain_blocks', locals: { domain_blocks: blocks } - - .column-4 - %ul.table-of-contents - - unless @rules.empty? - %li= link_to t('about.rules'), '#rules' - - - @table_of_contents.each do |item| - %li - = link_to item.title, "##{item.anchor}" - - - unless item.children.empty? - %ul - - item.children.each do |sub_item| - %li= link_to sub_item.title, "##{sub_item.anchor}" - - - if display_blocks? && !@blocks.empty? - %li= link_to t('about.unavailable_content'), '#unavailable-content' diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml new file mode 100644 index 00000000000..aff28b9a921 --- /dev/null +++ b/app/views/about/show.html.haml @@ -0,0 +1,4 @@ +- content_for :page_title do + = t('about.title') + += render partial: 'shared/web_app' diff --git a/config/locales/en.yml b/config/locales/en.yml index 8a70bd8caca..11716234ef3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2,41 +2,15 @@ en: about: about_mastodon_html: 'The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!' - about_this: About - administered_by: 'Administered by:' api: API apps: Mobile apps - contact: Contact contact_missing: Not set contact_unavailable: N/A documentation: Documentation hosted_on: Mastodon hosted on %{domain} - instance_actor_flash: | - This account is a virtual actor used to represent the server itself and not any individual user. - It is used for federation purposes and should not be blocked unless you want to block the whole instance, in which case you should use a domain block. privacy_policy: Privacy Policy - rules: Server rules - rules_html: 'Below is a summary of rules you need to follow if you want to have an account on this server of Mastodon:' source_code: Source code - status_count_after: - one: post - other: posts - status_count_before: Who published - unavailable_content: Moderated servers - unavailable_content_description: - domain: Server - reason: Reason - rejecting_media: 'Media files from these servers will not be processed or stored, and no thumbnails will be displayed, requiring manual click-through to the original file:' - rejecting_media_title: Filtered media - silenced: 'Posts from these servers will be hidden in public timelines and conversations, and no notifications will be generated from their users interactions, unless you are following them:' - silenced_title: Limited servers - suspended: 'No data from these servers will be processed, stored or exchanged, making any interaction or communication with users from these servers impossible:' - suspended_title: Suspended servers - unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server. - user_count_after: - one: user - other: users - user_count_before: Home to + title: About what_is_mastodon: What is Mastodon? accounts: choices_html: "%{name}'s choices:" diff --git a/config/routes.rb b/config/routes.rb index e6098cd1723..29ec0f8a56f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -487,7 +487,9 @@ Rails.application.routes.draw do resource :instance, only: [:show] do resources :peers, only: [:index], controller: 'instances/peers' resources :rules, only: [:index], controller: 'instances/rules' + resources :domain_blocks, only: [:index], controller: 'instances/domain_blocks' resource :privacy_policy, only: [:show], controller: 'instances/privacy_policies' + resource :extended_description, only: [:show], controller: 'instances/extended_descriptions' resource :activity, only: [:show], controller: 'instances/activity' end @@ -642,8 +644,8 @@ Rails.application.routes.draw do get '/web/(*any)', to: 'home#index', as: :web - get '/about', to: redirect('/') - get '/about/more', to: 'about#more' + get '/about', to: 'about#show' + get '/about/more', to: redirect('/about') get '/privacy-policy', to: 'privacy#show', as: :privacy_policy get '/terms', to: redirect('/privacy-policy') diff --git a/spec/controllers/about_controller_spec.rb b/spec/controllers/about_controller_spec.rb index 20069e4137e..97143ec4372 100644 --- a/spec/controllers/about_controller_spec.rb +++ b/spec/controllers/about_controller_spec.rb @@ -3,13 +3,9 @@ require 'rails_helper' RSpec.describe AboutController, type: :controller do render_views - describe 'GET #more' do + describe 'GET #show' do before do - get :more - end - - it 'assigns @instance_presenter' do - expect(assigns(:instance_presenter)).to be_kind_of InstancePresenter + get :show end it 'returns http success' do