mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-08 18:59:11 +00:00
Add first-time user education hint about quote removal on Quote notifications (#35986)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Crowdin / Upload translations / upload-translations (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
CSS Linting / lint (push) Has been cancelled
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Crowdin / Upload translations / upload-translations (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
CSS Linting / lint (push) Has been cancelled
This commit is contained in:
parent
42be0ca0eb
commit
e7c30cd072
|
@ -13,9 +13,9 @@ import { useSelectableClick } from 'mastodon/hooks/useSelectableClick';
|
||||||
const offset = [0, 4] as OffsetValue;
|
const offset = [0, 4] as OffsetValue;
|
||||||
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
||||||
|
|
||||||
export const AltTextBadge: React.FC<{
|
export const AltTextBadge: React.FC<{ description: string }> = ({
|
||||||
description: string;
|
description,
|
||||||
}> = ({ description }) => {
|
}) => {
|
||||||
const accessibilityId = useId();
|
const accessibilityId = useId();
|
||||||
const anchorRef = useRef<HTMLButtonElement>(null);
|
const anchorRef = useRef<HTMLButtonElement>(null);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
@ -56,7 +56,7 @@ export const AltTextBadge: React.FC<{
|
||||||
{({ props }) => (
|
{({ props }) => (
|
||||||
<div {...props} className='hover-card-controller'>
|
<div {...props} className='hover-card-controller'>
|
||||||
<div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
|
<div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
|
||||||
className='media-gallery__alt__popover dropdown-animation'
|
className='info-tooltip dropdown-animation'
|
||||||
role='region'
|
role='region'
|
||||||
id={accessibilityId}
|
id={accessibilityId}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call,
|
|
||||||
@typescript-eslint/no-unsafe-return,
|
|
||||||
@typescript-eslint/no-unsafe-assignment,
|
|
||||||
@typescript-eslint/no-unsafe-member-access
|
|
||||||
-- the settings store is not yet typed */
|
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { useCallback, useState, useEffect } from 'react';
|
import { useCallback, useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
@ -23,31 +18,48 @@ interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
|
export function useDismissableBannerState({ id }: Props) {
|
||||||
id,
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
children,
|
const dismissed: boolean = useAppSelector((state) =>
|
||||||
}) => {
|
/* eslint-disable-next-line */
|
||||||
const dismissed = useAppSelector((state) =>
|
|
||||||
state.settings.getIn(['dismissed_banners', id], false),
|
state.settings.getIn(['dismissed_banners', id], false),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [isVisible, setIsVisible] = useState(
|
||||||
|
!bannerSettings.get(id) && !dismissed,
|
||||||
|
);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [visible, setVisible] = useState(!bannerSettings.get(id) && !dismissed);
|
const dismiss = useCallback(() => {
|
||||||
const intl = useIntl();
|
setIsVisible(false);
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
|
||||||
setVisible(false);
|
|
||||||
bannerSettings.set(id, true);
|
bannerSettings.set(id, true);
|
||||||
dispatch(changeSetting(['dismissed_banners', id], true));
|
dispatch(changeSetting(['dismissed_banners', id], true));
|
||||||
}, [id, dispatch]);
|
}, [id, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible && !dismissed) {
|
// Store legacy localStorage setting on server
|
||||||
|
if (!isVisible && !dismissed) {
|
||||||
dispatch(changeSetting(['dismissed_banners', id], true));
|
dispatch(changeSetting(['dismissed_banners', id], true));
|
||||||
}
|
}
|
||||||
}, [id, dispatch, visible, dismissed]);
|
}, [id, dispatch, isVisible, dismissed]);
|
||||||
|
|
||||||
if (!visible) {
|
return {
|
||||||
|
isVisible,
|
||||||
|
dismiss,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
|
||||||
|
id,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { isVisible, dismiss } = useDismissableBannerState({
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +70,7 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
|
||||||
icon='times'
|
icon='times'
|
||||||
iconComponent={CloseIcon}
|
iconComponent={CloseIcon}
|
||||||
title={intl.formatMessage(messages.dismiss)}
|
title={intl.formatMessage(messages.dismiss)}
|
||||||
onClick={handleDismiss}
|
onClick={dismiss}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,12 @@ import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
||||||
import { me } from '../initial_state';
|
import { me } from '../../initial_state';
|
||||||
|
|
||||||
import { IconButton } from './icon_button';
|
import { IconButton } from '../icon_button';
|
||||||
import { isFeatureEnabled } from '../utils/environment';
|
import { isFeatureEnabled } from '../../utils/environment';
|
||||||
import { ReblogButton } from './status/reblog_button';
|
import { ReblogButton } from '../status/reblog_button';
|
||||||
|
import { RemoveQuoteHint } from './remove_quote_hint';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
|
@ -77,6 +78,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
relationship: ImmutablePropTypes.record,
|
relationship: ImmutablePropTypes.record,
|
||||||
quotedAccountId: PropTypes.string,
|
quotedAccountId: PropTypes.string,
|
||||||
|
contextType: PropTypes.string,
|
||||||
onReply: PropTypes.func,
|
onReply: PropTypes.func,
|
||||||
onFavourite: PropTypes.func,
|
onFavourite: PropTypes.func,
|
||||||
onDelete: PropTypes.func,
|
onDelete: PropTypes.func,
|
||||||
|
@ -240,7 +242,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, relationship, quotedAccountId, intl, withDismiss, withCounters, scrollKey } = this.props;
|
const { status, relationship, quotedAccountId, contextType, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||||
const { signedIn, permissions } = this.props.identity;
|
const { signedIn, permissions } = this.props.identity;
|
||||||
|
|
||||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||||
|
@ -249,6 +251,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
const account = status.get('account');
|
const account = status.get('account');
|
||||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||||
|
const isQuotingMe = quotedAccountId === me;
|
||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
|
|
||||||
|
@ -293,7 +296,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
|
||||||
if (quotedAccountId === me) {
|
if (isQuotingMe) {
|
||||||
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +363,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark);
|
const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark);
|
||||||
const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite);
|
const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite);
|
||||||
const isReply = status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
|
const isReply = status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
|
||||||
|
|
||||||
|
const shouldShowQuoteRemovalHint = isQuotingMe && contextType === 'notifications';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='status__action-bar'>
|
<div className='status__action-bar'>
|
||||||
|
@ -375,17 +380,23 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
<div className='status__action-bar__button-wrapper'>
|
<div className='status__action-bar__button-wrapper'>
|
||||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
||||||
</div>
|
</div>
|
||||||
<div className='status__action-bar__button-wrapper'>
|
<RemoveQuoteHint className='status__action-bar__button-wrapper' canShowHint={shouldShowQuoteRemovalHint}>
|
||||||
<Dropdown
|
{(dismissQuoteHint) => (
|
||||||
scrollKey={scrollKey}
|
<Dropdown
|
||||||
status={status}
|
scrollKey={scrollKey}
|
||||||
items={menu}
|
status={status}
|
||||||
icon='ellipsis-h'
|
items={menu}
|
||||||
iconComponent={MoreHorizIcon}
|
icon='ellipsis-h'
|
||||||
direction='right'
|
iconComponent={MoreHorizIcon}
|
||||||
title={intl.formatMessage(messages.more)}
|
direction='right'
|
||||||
/>
|
title={intl.formatMessage(messages.more)}
|
||||||
</div>
|
onOpen={() => {
|
||||||
|
dismissQuoteHint();
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</RemoveQuoteHint>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import Overlay from 'react-overlays/Overlay';
|
||||||
|
|
||||||
|
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
|
|
||||||
|
import { Button } from '../button';
|
||||||
|
import { useDismissableBannerState } from '../dismissable_banner';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
|
const DISMISSABLE_BANNER_ID = 'notifications/remove_quote_hint';
|
||||||
|
|
||||||
|
export const RemoveQuoteHint: React.FC<{
|
||||||
|
canShowHint: boolean;
|
||||||
|
className?: string;
|
||||||
|
children: (dismiss: () => void) => React.ReactNode;
|
||||||
|
}> = ({ canShowHint, className, children }) => {
|
||||||
|
const anchorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const { isVisible, dismiss } = useDismissableBannerState({
|
||||||
|
id: DISMISSABLE_BANNER_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} ref={anchorRef}>
|
||||||
|
{children(dismiss)}
|
||||||
|
{isVisible && canShowHint && (
|
||||||
|
<Overlay
|
||||||
|
show
|
||||||
|
flip
|
||||||
|
offset={[12, 10]}
|
||||||
|
placement='bottom-end'
|
||||||
|
target={anchorRef.current}
|
||||||
|
container={anchorRef.current}
|
||||||
|
>
|
||||||
|
{({ props, placement }) => (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={classNames(
|
||||||
|
'info-tooltip info-tooltip--solid dropdown-animation',
|
||||||
|
placement,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h4>
|
||||||
|
<FormattedMessage
|
||||||
|
id='remove_quote_hint.title'
|
||||||
|
defaultMessage='Want to remove your quoted post?'
|
||||||
|
/>
|
||||||
|
</h4>
|
||||||
|
<FormattedMessage
|
||||||
|
id='remove_quote_hint.message'
|
||||||
|
defaultMessage='You can do so from the {icon} options menu.'
|
||||||
|
values={{
|
||||||
|
icon: (
|
||||||
|
<Icon
|
||||||
|
id='ellipsis-h'
|
||||||
|
icon={MoreHorizIcon}
|
||||||
|
aria-label={intl.formatMessage({
|
||||||
|
id: 'status.more',
|
||||||
|
defaultMessage: 'More',
|
||||||
|
})}
|
||||||
|
style={{ verticalAlign: 'middle' }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(text) => <p>{text}</p>}
|
||||||
|
</FormattedMessage>
|
||||||
|
<FormattedMessage
|
||||||
|
id='remove_quote_hint.button_label'
|
||||||
|
defaultMessage='Got it'
|
||||||
|
>
|
||||||
|
{(text) => (
|
||||||
|
<Button plain compact onClick={dismiss}>
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</FormattedMessage>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Overlay>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -768,6 +768,9 @@
|
||||||
"relative_time.minutes": "{number}m",
|
"relative_time.minutes": "{number}m",
|
||||||
"relative_time.seconds": "{number}s",
|
"relative_time.seconds": "{number}s",
|
||||||
"relative_time.today": "today",
|
"relative_time.today": "today",
|
||||||
|
"remove_quote_hint.button_label": "Got it",
|
||||||
|
"remove_quote_hint.message": "You can do so from the {icon} options menu.",
|
||||||
|
"remove_quote_hint.title": "Want to remove your quoted post?",
|
||||||
"reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}",
|
"reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}",
|
||||||
"reply_indicator.cancel": "Cancel",
|
"reply_indicator.cancel": "Cancel",
|
||||||
"reply_indicator.poll": "Poll",
|
"reply_indicator.poll": "Poll",
|
||||||
|
|
|
@ -117,6 +117,7 @@ const initialState = ImmutableMap({
|
||||||
'explore/links': false,
|
'explore/links': false,
|
||||||
'explore/statuses': false,
|
'explore/statuses': false,
|
||||||
'explore/tags': false,
|
'explore/tags': false,
|
||||||
|
'notifications/remove_quote_hint': false,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -471,8 +471,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body > [data-popper-placement] {
|
[data-popper-placement] {
|
||||||
z-index: 3;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invisible {
|
.invisible {
|
||||||
|
@ -7127,7 +7127,8 @@ a.status-card {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-gallery__alt__popover {
|
.info-tooltip {
|
||||||
|
color: $white;
|
||||||
background: color.change($black, $alpha: 0.65);
|
background: color.change($black, $alpha: 0.65);
|
||||||
backdrop-filter: $backdrop-blur-filter;
|
backdrop-filter: $backdrop-blur-filter;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -7139,20 +7140,36 @@ a.status-card {
|
||||||
max-height: 30em;
|
max-height: 30em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&--solid {
|
||||||
|
color: var(--nested-card-text);
|
||||||
|
background:
|
||||||
|
/* This is a bit of a silly hack for layering two background colours
|
||||||
|
* since --nested-card-background is too transparent for a tooltip */
|
||||||
|
linear-gradient(
|
||||||
|
var(--nested-card-background),
|
||||||
|
var(--nested-card-background)
|
||||||
|
),
|
||||||
|
linear-gradient(var(--background-color), var(--background-color));
|
||||||
|
border: var(--nested-card-border);
|
||||||
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $white;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
color: color.change($white, $alpha: 0.85);
|
opacity: 0.85;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-block-start: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-list {
|
.attachment-list {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user