mirror of
https://git.bsd.gay/fef/nyastodon.git
synced 2024-12-24 18:13:42 +01:00
Change about page to be mounted in the web UI (#19345)
This commit is contained in:
parent
b04633a961
commit
1bd00036c2
37 changed files with 904 additions and 1578 deletions
|
@ -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
|
||||
|
|
23
app/controllers/api/v1/instances/domain_blocks_controller.rb
Normal file
23
app/controllers/api/v1/instances/domain_blocks_controller.rb
Normal file
|
@ -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
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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,
|
||||
});
|
||||
|
|
33
app/javascript/mastodon/components/image.js
Normal file
33
app/javascript/mastodon/components/image.js
Normal file
|
@ -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 (
|
||||
<div className={classNames('image', { loaded }, className)} role='presentation'>
|
||||
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
|
||||
<img src={src} srcSet={srcSet} alt='' onLoad={this.handleLoad} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
|||
<FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
|
||||
</div>
|
||||
|
||||
<img src={server.get('thumbnail')} alt={server.get('title')} className='server-banner__hero' />
|
||||
<Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' />
|
||||
|
||||
<div className='server-banner__description'>
|
||||
{isLoading ? (
|
||||
|
@ -83,7 +85,7 @@ class ServerBanner extends React.PureComponent {
|
|||
|
||||
<hr className='spacer' />
|
||||
|
||||
<a className='button button--block button-secondary' href='/about/more' target='_blank'><FormattedMessage id='server_banner.learn_more' defaultMessage='Learn more' /></a>
|
||||
<Link className='button button--block button-secondary' to='/about'><FormattedMessage id='server_banner.learn_more' defaultMessage='Learn more' /></Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import PropTypes from 'prop-types';
|
|||
const Skeleton = ({ width, height }) => <span className='skeleton' style={{ width, height }}>‌</span>;
|
||||
|
||||
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;
|
||||
|
|
|
@ -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 (
|
||||
<div className={classNames('about__section', { active: !collapsed })}>
|
||||
<div className='about__section__title' role='button' tabIndex='0' onClick={this.handleClick}>
|
||||
<Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title}
|
||||
</div>
|
||||
|
||||
{!collapsed && (
|
||||
<div className='about__section__body'>{children}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 (
|
||||
<Column>
|
||||
<LinkFooter />
|
||||
<div className='scrollable about'>
|
||||
<div className='about__header'>
|
||||
<Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />
|
||||
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1>
|
||||
<p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank'>Mastodon</a> }} /></p>
|
||||
</div>
|
||||
|
||||
<div className='about__meta'>
|
||||
<div className='about__meta__column'>
|
||||
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
|
||||
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} />
|
||||
</div>
|
||||
|
||||
<hr className='about__meta__divider' />
|
||||
|
||||
<div className='about__meta__column'>
|
||||
<h4><FormattedMessage id='about.contact' defaultMessage='Contact:' /></h4>
|
||||
|
||||
{isLoading ? <Skeleton width='10ch' /> : <a className='about__mail' href={`mailto:${server.getIn(['contact', 'email'])}`}>{server.getIn(['contact', 'email'])}</a>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Section open title={intl.formatMessage(messages.title)}>
|
||||
{extendedDescription.get('isLoading') ? (
|
||||
<>
|
||||
<Skeleton width='100%' />
|
||||
<br />
|
||||
<Skeleton width='100%' />
|
||||
<br />
|
||||
<Skeleton width='100%' />
|
||||
<br />
|
||||
<Skeleton width='70%' />
|
||||
</>
|
||||
) : (extendedDescription.get('content')?.length > 0 ? (
|
||||
<div
|
||||
className='prose'
|
||||
dangerouslySetInnerHTML={{ __html: extendedDescription.get('content') }}
|
||||
/>
|
||||
) : (
|
||||
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<Section title={intl.formatMessage(messages.rules)}>
|
||||
{!isLoading && (server.get('rules').isEmpty() ? (
|
||||
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
||||
) : (
|
||||
<ol className='rules-list'>
|
||||
{server.get('rules').map(rule => (
|
||||
<li key={rule.get('id')}>
|
||||
<span className='rules-list__text'>{rule.get('text')}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}>
|
||||
{domainBlocks.get('isLoading') ? (
|
||||
<>
|
||||
<Skeleton width='100%' />
|
||||
<br />
|
||||
<Skeleton width='70%' />
|
||||
</>
|
||||
) : (domainBlocks.get('isAvailable') ? (
|
||||
<>
|
||||
<p><FormattedMessage id='about.domain_blocks.preamble' defaultMessage='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.' /></p>
|
||||
|
||||
<table className='about__domain-blocks'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><FormattedMessage id='about.domain_blocks.domain' defaultMessage='Domain' /></th>
|
||||
<th><FormattedMessage id='about.domain_blocks.severity' defaultMessage='Severity' /></th>
|
||||
<th><FormattedMessage id='about.domain_blocks.comment' defaultMessage='Reason' /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{domainBlocks.get('items').map(block => (
|
||||
<tr key={block.get('domain')}>
|
||||
<td><span title={`SHA-256: ${block.get('digest')}`}>{block.get('domain')}</span></td>
|
||||
<td><span title={intl.formatMessage(severityMessages[block.get('severity')].explanation)}>{intl.formatMessage(severityMessages[block.get('severity')].title)}</span></td>
|
||||
<td>{block.get('comment')}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
) : (
|
||||
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<LinkFooter />
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages.title)}</title>
|
||||
|
|
|
@ -44,7 +44,7 @@ class PrivacyPolicy extends React.PureComponent {
|
|||
</div>
|
||||
|
||||
<div
|
||||
className='privacy-policy__body'
|
||||
className='privacy-policy__body prose'
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -51,7 +51,7 @@ class LinkFooter extends React.PureComponent {
|
|||
const items = [];
|
||||
|
||||
items.push(<a key='apps' href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Get the app' /></a>);
|
||||
items.push(<a key='about' href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About' /></a>);
|
||||
items.push(<Link key='about' to='/about'><FormattedMessage id='navigation_bar.info' defaultMessage='About' /></Link>);
|
||||
items.push(<a key='mastodon' href='https://joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.what_is_mastodon' defaultMessage='About Mastodon' /></a>);
|
||||
items.push(<a key='docs' href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a>);
|
||||
items.push(<Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></Link>);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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%);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
outline: 0;
|
||||
padding: 12px 16px;
|
||||
line-height: 32px;
|
||||
font-family: $font-display, sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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%);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
15
app/models/extended_description.rb
Normal file
15
app/models/extended_description.rb
Normal file
|
@ -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
|
17
app/serializers/rest/domain_block_serializer.rb
Normal file
17
app/serializers/rest/domain_block_serializer.rb
Normal file
|
@ -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
|
13
app/serializers/rest/extended_description_serializer.rb
Normal file
13
app/serializers/rest/extended_description_serializer.rb
Normal file
|
@ -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
|
|
@ -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?
|
|
@ -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'
|
4
app/views/about/show.html.haml
Normal file
4
app/views/about/show.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
- content_for :page_title do
|
||||
= t('about.title')
|
||||
|
||||
= render partial: 'shared/web_app'
|
|
@ -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:"
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue