th: Merge remote-tracking branch 'glitch/main'
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/pr/woodpecker Pipeline was successful Details

fixes: CVE-2023-36459
fixes: CVE-2023-36460
fixes: CVE-2023-36461
fixes: CVE-2023-36462
fixes: GHSA-55j9-c3mp-6fcq
fixes: GHSA-9928-3cp5-93fm
fixes: GHSA-9pxv-6qvf-pjwc
fixes: GHSA-ccm4-vgcc-73hp
pull/62/head
kouhai dev 2023-07-06 12:09:14 -07:00
commit f26d104e75
55 changed files with 484 additions and 285 deletions

View File

@ -2,6 +2,54 @@
All notable changes to this project will be documented in this file.
## [4.1.3] - 2023-07-06
### Added
- Add fallback redirection when getting a webfinger query `LOCAL_DOMAIN@LOCAL_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23600))
### Changed
- Change OpenGraph-based embeds to allow fullscreen ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25058))
- Change AccessTokensVacuum to also delete expired tokens ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24868))
- Change profile updates to be sent to recently-mentioned servers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24852))
- Change automatic post deletion thresholds and load detection ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24614))
- Change `/api/v1/statuses/:id/history` to always return at least one item ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25510))
- Change auto-linking to allow carets in URL query params ([renchap](https://github.com/mastodon/mastodon/pull/25216))
### Removed
- Remove invalid `X-Frame-Options: ALLOWALL` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25070))
### Fixed
- Fix wrong view being displayed when a webhook fails validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25464))
- Fix soft-deleted post cleanup scheduler overwhelming the streaming server ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25519))
- Fix incorrect pagination headers in `/api/v2/admin/accounts` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25477))
- Fix multiple inefficiencies in automatic post cleanup worker ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24607), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24785), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24840))
- Fix performance of streaming by parsing message JSON once ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25278), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25361))
- Fix CSP headers when `S3_ALIAS_HOST` includes a path component ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25273))
- Fix `tootctl accounts approve --number N` not aproving N earliest registrations ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24605))
- Fix reports not being closed when performing batch suspensions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24988))
- Fix being able to vote on your own polls ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25015))
- Fix race condition when reblogging a status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25016))
- Fix “Authorized applications” inefficiently and incorrectly getting last use date ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25060))
- Fix “Authorized applications” crashing when listing apps with certain admin API scopes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25713))
- Fix multiple N+1s in ConversationsController ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25134), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25399), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25499))
- Fix user archive takeouts when using OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24431))
- Fix searching for remote content by URL not working under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25637))
- Fix inefficiencies in indexing content for search ([VyrCossont](https://github.com/mastodon/mastodon/pull/24285), [VyrCossont](https://github.com/mastodon/mastodon/pull/24342))
### Security
- Add finer permission requirements for managing webhooks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25463))
- Update dependencies
- Add hardening headers for user-uploaded files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25756))
- Fix verified links possibly hiding important parts of the URL (CVE-2023-36462)
- Fix timeout handling of outbound HTTP requests (CVE-2023-36461)
- Fix arbitrary file creation through media processing (CVE-2023-36460)
- Fix possible XSS in preview cards (CVE-2023-36459)
## [4.1.2] - 2023-04-04
### Fixed

View File

@ -34,11 +34,11 @@ class Api::V2::SearchController < Api::BaseController
params[:q],
current_account,
limit_param(RESULTS_LIMIT),
search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed))
search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed), following: truthy_param?(:following))
)
end
def search_params
params.permit(:type, :offset, :min_id, :max_id, :account_id)
params.permit(:type, :offset, :min_id, :max_id, :account_id, :following)
end
end

View File

@ -64,6 +64,10 @@ module FormattingHelper
end
def account_field_value_format(field, with_rel_me: true)
html_aware_format(field.value, field.account.local?, with_rel_me: with_rel_me, with_domains: true, multiline: false)
if field.verified? && !field.account.local?
TextFormatter.shortened_link(field.value_for_verification)
else
html_aware_format(field.value, field.account.local?, with_rel_me: with_rel_me, with_domains: true, multiline: false)
end
end
end

View File

@ -131,6 +131,10 @@ class Poll extends ImmutablePureComponent {
this.props.refresh();
};
handleReveal = () => {
this.setState({ revealed: true });
}
renderOption (option, optionIndex, showResults) {
const { poll, lang, disabled, intl } = this.props;
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
@ -206,14 +210,14 @@ class Poll extends ImmutablePureComponent {
render () {
const { poll, intl } = this.props;
const { expired } = this.state;
const { revealed, expired } = this.state;
if (!poll) {
return null;
}
const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
const showResults = poll.get('voted') || expired;
const showResults = poll.get('voted') || revealed || expired;
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
let votesCount = null;
@ -232,9 +236,10 @@ class Poll extends ImmutablePureComponent {
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
{poll.get('expires_at') && <span> · {timeRemaining}</span>}
{poll.get('expires_at') && <> · {timeRemaining}</>}
</div>
</div>
);

View File

@ -163,7 +163,7 @@ class StatusContent extends PureComponent {
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
link.setAttribute('title', mention.get('acct'));
link.setAttribute('title', `@${mention.get('acct')}`);
if (rewriteMentions !== 'no') {
while (link.firstChild) link.removeChild(link.firstChild);
link.appendChild(document.createTextNode('@'));

View File

@ -398,6 +398,7 @@ class Header extends ImmutablePureComponent {
<Helmet>
<title>{titleFromAccount(account)}</title>
<meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
<link rel='canonical' href={account.get('url')} />
</Helmet>
</div>
);

View File

@ -103,7 +103,7 @@ const Firehose = ({ feedType, multiColumn }) => {
(maxId) => {
switch(feedType) {
case 'community':
dispatch(expandCommunityTimeline({ onlyMedia }));
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
break;
case 'public':
dispatch(expandPublicTimeline({ maxId, onlyMedia, allowLocalOnly }));
@ -154,12 +154,13 @@ const Firehose = ({ feedType, multiColumn }) => {
/>
</DismissableBanner>
) : (
<DismissableBanner id='public_timeline'>
<FormattedMessage
id='dismissable_banner.public_timeline'
defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.'
/>
</DismissableBanner>
<DismissableBanner id='public_timeline'>
<FormattedMessage
id='dismissable_banner.public_timeline'
defaultMessage='These are the most recent public posts from people on the social web that people on {domain} follow.'
values={{ domain }}
/>
</DismissableBanner>
);
const emptyMessage = feedType === 'community' ? (
@ -168,10 +169,10 @@ const Firehose = ({ feedType, multiColumn }) => {
defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!'
/>
) : (
<FormattedMessage
id='empty_column.public'
defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up'
/>
<FormattedMessage
id='empty_column.public'
defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up'
/>
);
return (
@ -190,11 +191,11 @@ const Firehose = ({ feedType, multiColumn }) => {
<div className='scrollable scrollable--flex'>
<div className='account__section-headline'>
<NavLink exact to='/public/local'>
<FormattedMessage tagName='div' id='firehose.local' defaultMessage='Local' />
<FormattedMessage tagName='div' id='firehose.local' defaultMessage='This server' />
</NavLink>
<NavLink exact to='/public/remote'>
<FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Remote' />
<FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Other servers' />
</NavLink>
<NavLink exact to='/public'>

View File

@ -13,6 +13,7 @@ import { expandPublicTimeline } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { domain } from 'flavours/glitch/initial_state';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import ColumnSettingsContainer from './containers/column_settings_container';
@ -147,7 +148,7 @@ class PublicTimeline extends PureComponent {
</ColumnHeader>
<StatusListContainer
prepend={<DismissableBanner id='public_timeline'><FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' /></DismissableBanner>}
prepend={<DismissableBanner id='public_timeline'><FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on the social web that people on {domain} follow.' values={{ domain }} /></DismissableBanner>}
timelineId={`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore}
trackScroll={!pinned}

View File

@ -771,6 +771,7 @@ class Status extends ImmutablePureComponent {
<Helmet>
<title>{titleFromStatus(intl, status)}</title>
<meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
<link rel='canonical' href={status.get('url')} />
</Helmet>
</Column>
);

View File

@ -128,7 +128,6 @@ $content-width: 840px;
}
&.selected {
background: darken($ui-base-color, 2%);
border-radius: 4px 0 0;
}
}
@ -146,13 +145,9 @@ $content-width: 840px;
.simple-navigation-active-leaf a {
color: $primary-text-color;
background-color: darken($ui-highlight-color, 2%);
background-color: $ui-highlight-color;
border-bottom: 0;
border-radius: 0;
&:hover {
background-color: $ui-highlight-color;
}
}
}
@ -246,12 +241,6 @@ $content-width: 840px;
font-weight: 700;
color: $primary-text-color;
background: $ui-highlight-color;
&:hover,
&:focus,
&:active {
background: lighten($ui-highlight-color, 4%);
}
}
}
}

View File

@ -38,11 +38,11 @@
}
.button {
background-color: darken($ui-highlight-color, 3%);
background-color: $ui-button-background-color;
border: 10px none;
border-radius: 4px;
box-sizing: border-box;
color: $primary-text-color;
color: $ui-button-color;
cursor: pointer;
display: inline-block;
font-family: inherit;
@ -62,14 +62,14 @@
&:active,
&:focus,
&:hover {
background-color: $ui-highlight-color;
background-color: $ui-button-focus-background-color;
}
&--destructive {
&:active,
&:focus,
&:hover {
background-color: $error-red;
background-color: $ui-button-destructive-focus-background-color;
transition: none;
}
}
@ -79,43 +79,22 @@
cursor: default;
}
&.button-alternative {
color: $inverted-text-color;
background: $ui-primary-color;
&:active,
&:focus,
&:hover {
background-color: lighten($ui-primary-color, 4%);
}
}
&.button-alternative-2 {
background: $ui-base-lighter-color;
&:active,
&:focus,
&:hover {
background-color: lighten($ui-base-lighter-color, 4%);
}
}
&.button-secondary {
font-size: 16px;
line-height: 36px;
height: auto;
color: $darker-text-color;
color: $ui-button-secondary-color;
text-transform: none;
background: transparent;
padding: 6px 17px;
border: 1px solid lighten($ui-base-color, 12%);
border: 1px solid $ui-button-secondary-border-color;
&:active,
&:focus,
&:hover {
background: lighten($ui-base-color, 4%);
border-color: lighten($ui-base-color, 16%);
color: lighten($darker-text-color, 4%);
border-color: $ui-button-secondary-focus-background-color;
color: $ui-button-secondary-focus-color;
background-color: $ui-button-secondary-focus-background-color;
text-decoration: none;
}
@ -127,14 +106,14 @@
&.button-tertiary {
background: transparent;
padding: 6px 17px;
color: $highlight-text-color;
border: 1px solid $highlight-text-color;
color: $ui-button-tertiary-color;
border: 1px solid $ui-button-tertiary-border-color;
&:active,
&:focus,
&:hover {
background: $ui-highlight-color;
color: $primary-text-color;
background-color: $ui-button-tertiary-focus-background-color;
color: $ui-button-tertiary-focus-color;
border: 0;
padding: 7px 18px;
}

View File

@ -718,15 +718,15 @@
}
.button.button-secondary {
border-color: $inverted-text-color;
color: $inverted-text-color;
border-color: $ui-button-secondary-border-color;
color: $ui-button-secondary-color;
flex: 0 0 auto;
&:hover,
&:focus,
&:active {
border-color: lighten($inverted-text-color, 15%);
color: lighten($inverted-text-color, 15%);
border-color: $ui-button-secondary-focus-background-color;
color: $ui-button-secondary-focus-color;
}
}

View File

@ -81,7 +81,7 @@
display: flex;
align-items: baseline;
border-radius: 4px;
background: darken($ui-highlight-color, 2%);
background: $ui-button-background-color;
color: $primary-text-color;
transition: all 100ms ease-in;
font-size: 14px;
@ -94,7 +94,7 @@
&:active,
&:focus,
&:hover {
background-color: $ui-highlight-color;
background-color: $ui-button-focus-background-color;
transition: all 200ms ease-out;
}

View File

@ -512,8 +512,8 @@ code {
width: 100%;
border: 0;
border-radius: 4px;
background: darken($ui-highlight-color, 2%);
color: $primary-text-color;
background: $ui-button-background-color;
color: $ui-button-color;
font-size: 18px;
line-height: inherit;
height: auto;
@ -535,7 +535,7 @@ code {
&:active,
&:focus,
&:hover {
background-color: $ui-highlight-color;
background-color: $ui-button-focus-background-color;
}
&:disabled:hover {
@ -543,15 +543,12 @@ code {
}
&.negative {
background: $error-value-color;
&:hover {
background-color: lighten($error-value-color, 5%);
}
background: $ui-button-destructive-background-color;
&:hover,
&:active,
&:focus {
background-color: darken($error-value-color, 5%);
background-color: $ui-button-destructive-focus-background-color;
}
}
}

View File

@ -5,19 +5,6 @@ html {
scrollbar-color: $ui-base-color rgba($ui-base-color, 0.25);
}
// Change the colors of button texts
.button {
color: $white;
&.button-alternative-2 {
color: $white;
}
&.button-tertiary {
color: $highlight-text-color;
}
}
.simple_form .button.button-tertiary {
color: $highlight-text-color;
}
@ -437,26 +424,6 @@ html {
color: $white;
}
.button.button-tertiary {
&:hover,
&:focus,
&:active {
color: $white;
}
}
.button.button-secondary {
border-color: $darker-text-color;
color: $darker-text-color;
&:hover,
&:focus,
&:active {
border-color: darken($darker-text-color, 8%);
color: darken($darker-text-color, 8%);
}
}
.flash-message.warning {
color: lighten($gold-star, 16%);
}

View File

@ -7,6 +7,12 @@ $classic-primary-color: #9baec8;
$classic-secondary-color: #d9e1e8;
$classic-highlight-color: #6364ff;
$blurple-600: #563acc; // Iris
$blurple-500: #6364ff; // Brand purple
$blurple-300: #858afa; // Faded Blue
$grey-600: #4e4c5a; // Trout
$grey-100: #dadaf3; // Topaz
// Differences
$success-green: lighten(#3c754d, 8%);
@ -19,6 +25,13 @@ $ui-primary-color: #9bcbed;
$ui-secondary-color: $classic-base-color !default;
$ui-highlight-color: $classic-highlight-color !default;
$ui-button-secondary-color: $grey-600 !default;
$ui-button-secondary-border-color: $grey-600 !default;
$ui-button-secondary-focus-color: $white !default;
$ui-button-tertiary-color: $blurple-500 !default;
$ui-button-tertiary-border-color: $blurple-500 !default;
$primary-text-color: $black !default;
$darker-text-color: $classic-base-color !default;
$highlight-text-color: darken($ui-highlight-color, 8%) !default;

View File

@ -1,10 +1,18 @@
// Commonly used web colors
$black: #000000; // Black
$white: #ffffff; // White
$success-green: #79bd9a; // Padua
$error-red: #df405a; // Cerise
$warning-red: #ff5050; // Sunset Orange
$gold-star: #ca8f04; // Dark Goldenrod
$red-600: #b7253d !default; // Deep Carmine
$red-500: #df405a !default; // Cerise
$blurple-600: #563acc; // Iris
$blurple-500: #6364ff; // Brand purple
$blurple-300: #858afa; // Faded Blue
$grey-600: #4e4c5a; // Trout
$grey-100: #dadaf3; // Topaz
$success-green: #79bd9a !default; // Padua
$error-red: $red-500 !default; // Cerise
$warning-red: #ff5050 !default; // Sunset Orange
$gold-star: #ca8f04 !default; // Dark Goldenrod
$red-bookmark: $warning-red;
@ -31,6 +39,22 @@ $ui-base-lighter-color: lighten(
$ui-primary-color: $classic-primary-color !default; // Lighter
$ui-secondary-color: $classic-secondary-color !default; // Lightest
$ui-highlight-color: $classic-highlight-color !default;
$ui-button-color: $white !default;
$ui-button-background-color: $blurple-500 !default;
$ui-button-focus-background-color: $blurple-600 !default;
$ui-button-secondary-color: $grey-100 !default;
$ui-button-secondary-border-color: $grey-100 !default;
$ui-button-secondary-focus-background-color: $grey-600 !default;
$ui-button-secondary-focus-color: $white !default;
$ui-button-tertiary-color: $blurple-300 !default;
$ui-button-tertiary-border-color: $blurple-300 !default;
$ui-button-tertiary-focus-background-color: $blurple-600 !default;
$ui-button-tertiary-focus-color: $white !default;
$ui-button-destructive-background-color: $red-500 !default;
$ui-button-destructive-focus-background-color: $red-600 !default;
// Variables for texts
$primary-text-color: $white !default;
@ -39,6 +63,7 @@ $dark-text-color: $ui-base-lighter-color !default;
$secondary-text-color: $ui-secondary-color !default;
$highlight-text-color: lighten($ui-highlight-color, 8%) !default;
$action-button-color: $ui-base-lighter-color !default;
$action-button-focus-color: lighten($ui-base-lighter-color, 4%) !default;
$passive-text-color: $gold-star !default;
$active-passive-text-color: $success-green !default;

View File

@ -130,6 +130,10 @@ class Poll extends ImmutablePureComponent {
this.props.refresh();
};
handleReveal = () => {
this.setState({ revealed: true });
}
renderOption (option, optionIndex, showResults) {
const { poll, lang, disabled, intl } = this.props;
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
@ -205,14 +209,14 @@ class Poll extends ImmutablePureComponent {
render () {
const { poll, intl } = this.props;
const { expired } = this.state;
const { revealed, expired } = this.state;
if (!poll) {
return null;
}
const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
const showResults = poll.get('voted') || expired;
const showResults = poll.get('voted') || revealed || expired;
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
let votesCount = null;
@ -231,9 +235,10 @@ class Poll extends ImmutablePureComponent {
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
{poll.get('expires_at') && <span> · {timeRemaining}</span>}
{poll.get('expires_at') && <> · {timeRemaining}</>}
</div>
</div>
);

View File

@ -104,7 +104,7 @@ class StatusContent extends PureComponent {
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
link.setAttribute('title', mention.get('acct'));
link.setAttribute('title', `@${mention.get('acct')}`);
link.setAttribute('href', `/@${mention.get('acct')}`);
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);

View File

@ -476,6 +476,7 @@ class Header extends ImmutablePureComponent {
<Helmet>
<title>{titleFromAccount(account)}</title>
<meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
<link rel='canonical' href={account.get('url')} />
</Helmet>
</div>
);

View File

@ -84,7 +84,7 @@ const Firehose = ({ feedType, multiColumn }) => {
(maxId) => {
switch(feedType) {
case 'community':
dispatch(expandCommunityTimeline({ onlyMedia }));
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
break;
case 'public':
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
@ -135,12 +135,13 @@ const Firehose = ({ feedType, multiColumn }) => {
/>
</DismissableBanner>
) : (
<DismissableBanner id='public_timeline'>
<FormattedMessage
id='dismissable_banner.public_timeline'
defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.'
/>
</DismissableBanner>
<DismissableBanner id='public_timeline'>
<FormattedMessage
id='dismissable_banner.public_timeline'
defaultMessage='These are the most recent public posts from people on the social web that people on {domain} follow.'
values={{ domain }}
/>
</DismissableBanner>
);
const emptyMessage = feedType === 'community' ? (
@ -149,10 +150,10 @@ const Firehose = ({ feedType, multiColumn }) => {
defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!'
/>
) : (
<FormattedMessage
id='empty_column.public'
defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up'
/>
<FormattedMessage
id='empty_column.public'
defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up'
/>
);
return (
@ -171,11 +172,11 @@ const Firehose = ({ feedType, multiColumn }) => {
<div className='scrollable scrollable--flex'>
<div className='account__section-headline'>
<NavLink exact to='/public/local'>
<FormattedMessage tagName='div' id='firehose.local' defaultMessage='Local' />
<FormattedMessage tagName='div' id='firehose.local' defaultMessage='This server' />
</NavLink>
<NavLink exact to='/public/remote'>
<FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Remote' />
<FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Other servers' />
</NavLink>
<NavLink exact to='/public'>

View File

@ -8,6 +8,7 @@ import { Helmet } from 'react-helmet';
import { connect } from 'react-redux';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { connectPublicStream } from '../../actions/streaming';
@ -143,7 +144,7 @@ class PublicTimeline extends PureComponent {
</ColumnHeader>
<StatusListContainer
prepend={<DismissableBanner id='public_timeline'><FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' /></DismissableBanner>}
prepend={<DismissableBanner id='public_timeline'><FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on the social web that people on {domain} follow.' values={{ domain }} /></DismissableBanner>}
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore}
trackScroll={!pinned}

View File

@ -716,6 +716,7 @@ class Status extends ImmutablePureComponent {
<Helmet>
<title>{titleFromStatus(intl, status)}</title>
<meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
<link rel='canonical' href={status.get('url')} />
</Helmet>
</Column>
);

View File

@ -202,7 +202,7 @@
"dismissable_banner.explore_links": "These are news stories being shared the most on the social web today. Newer news stories posted by more different people are ranked higher.",
"dismissable_banner.explore_statuses": "These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favourites are ranked higher.",
"dismissable_banner.explore_tags": "These are hashtags that are gaining traction on the social web today. Hashtags that are used by more different people are ranked higher.",
"dismissable_banner.public_timeline": "These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.",
"dismissable_banner.public_timeline": "These are the most recent public posts from people on the social web that people on {domain} follow.",
"embed.instructions": "Embed this post on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
@ -269,8 +269,8 @@
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"firehose.all": "All",
"firehose.local": "Local",
"firehose.remote": "Remote",
"firehose.local": "This server",
"firehose.remote": "Other servers",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
@ -487,6 +487,7 @@
"picture_in_picture.restore": "Put it back",
"poll.closed": "Closed",
"poll.refresh": "Refresh",
"poll.reveal": "See results",
"poll.total_people": "{count, plural, one {# person} other {# people}}",
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
"poll.vote": "Vote",

View File

@ -5,19 +5,6 @@ html {
scrollbar-color: $ui-base-color rgba($ui-base-color, 0.25);
}
// Change the colors of button texts
.button {
color: $white;
&.button-alternative-2 {
color: $white;
}
&.button-tertiary {
color: $highlight-text-color;
}
}
.simple_form .button.button-tertiary {
color: $highlight-text-color;
}
@ -436,26 +423,6 @@ html {
color: $white;
}
.button.button-tertiary {
&:hover,
&:focus,
&:active {
color: $white;
}
}
.button.button-secondary {
border-color: $darker-text-color;
color: $darker-text-color;
&:hover,
&:focus,
&:active {
border-color: darken($darker-text-color, 8%);
color: darken($darker-text-color, 8%);
}
}
.flash-message.warning {
color: lighten($gold-star, 16%);
}

View File

@ -7,6 +7,12 @@ $classic-primary-color: #9baec8;
$classic-secondary-color: #d9e1e8;
$classic-highlight-color: #6364ff;
$blurple-600: #563acc; // Iris
$blurple-500: #6364ff; // Brand purple
$blurple-300: #858afa; // Faded Blue
$grey-600: #4e4c5a; // Trout
$grey-100: #dadaf3; // Topaz
// Differences
$success-green: lighten(#3c754d, 8%);
@ -19,6 +25,13 @@ $ui-primary-color: #9bcbed;
$ui-secondary-color: $classic-base-color !default;
$ui-highlight-color: $classic-highlight-color !default;
$ui-button-secondary-color: $grey-600 !default;
$ui-button-secondary-border-color: $grey-600 !default;
$ui-button-secondary-focus-color: $white !default;
$ui-button-tertiary-color: $blurple-500 !default;
$ui-button-tertiary-border-color: $blurple-500 !default;
$primary-text-color: $black !default;
$darker-text-color: $classic-base-color !default;
$highlight-text-color: darken($ui-highlight-color, 8%) !default;

View File

@ -128,7 +128,6 @@ $content-width: 840px;
}
&.selected {
background: darken($ui-base-color, 2%);
border-radius: 4px 0 0;
}
}
@ -146,13 +145,9 @@ $content-width: 840px;
.simple-navigation-active-leaf a {
color: $primary-text-color;
background-color: darken($ui-highlight-color, 2%);
background-color: $ui-highlight-color;
border-bottom: 0;
border-radius: 0;
&:hover {
background-color: $ui-highlight-color;
}
}
}
@ -246,12 +241,6 @@ $content-width: 840px;
font-weight: 700;
color: $primary-text-color;
background: $ui-highlight-color;
&:hover,
&:focus,
&:active {
background: lighten($ui-highlight-color, 4%);
}
}
}
}

View File

@ -47,11 +47,11 @@
}
.button {
background-color: darken($ui-highlight-color, 2%);
background-color: $ui-button-background-color;
border: 10px none;
border-radius: 4px;
box-sizing: border-box;
color: $primary-text-color;
color: $ui-button-color;
cursor: pointer;
display: inline-block;
font-family: inherit;
@ -71,14 +71,14 @@
&:active,
&:focus,
&:hover {
background-color: $ui-highlight-color;
background-color: $ui-button-focus-background-color;
}
&--destructive {
&:active,
&:focus,
&:hover {
background-color: $error-red;
background-color: $ui-button-destructive-focus-background-color;
transition: none;
}
}
@ -108,39 +108,18 @@
outline: 0 !important;
}
&.button-alternative {
color: $inverted-text-color;
background: $ui-primary-color;
&:active,
&:focus,
&:hover {
background-color: lighten($ui-primary-color, 4%);
}
}
&.button-alternative-2 {
background: $ui-base-lighter-color;
&:active,
&:focus,
&:hover {
background-color: lighten($ui-base-lighter-color, 4%);
}
}
&.button-secondary {
color: $darker-text-color;
color: $ui-button-secondary-color;
background: transparent;
padding: 6px 17px;
border: 1px solid lighten($ui-base-color, 12%);
border: 1px solid $ui-button-secondary-border-color;
&:active,
&:focus,
&:hover {
background: lighten($ui-base-color, 4%);
border-color: lighten($ui-base-color, 16%);
color: lighten($darker-text-color, 4%);
border-color: $ui-button-secondary-focus-background-color;
color: $ui-button-secondary-focus-color;
background-color: $ui-button-secondary-focus-background-color;
text-decoration: none;
}
@ -152,14 +131,14 @@
&.button-tertiary {
background: transparent;
padding: 6px 17px;
color: $highlight-text-color;
border: 1px solid $highlight-text-color;
color: $ui-button-tertiary-color;
border: 1px solid $ui-button-tertiary-border-color;
&:active,
&:focus,
&:hover {
background: $ui-highlight-color;
color: $primary-text-color;
background-color: $ui-button-tertiary-focus-background-color;
color: $ui-button-tertiary-focus-color;
border: 0;
padding: 7px 18px;
}
@ -1148,6 +1127,8 @@ body > [data-popper-placement] {
}
&--in-thread {
$thread-margin: 46px + 10px;
border-bottom: 0;
.status__content,
@ -1158,8 +1139,12 @@ body > [data-popper-placement] {
.attachment-list,
.picture-in-picture-placeholder,
.status-card {
margin-inline-start: 46px + 10px;
width: calc(100% - (46px + 10px));
margin-inline-start: $thread-margin;
width: calc(100% - ($thread-margin));
}
.status__content__read-more-button {
margin-inline-start: $thread-margin;
}
}
@ -5810,15 +5795,15 @@ a.status-card.compact:hover {
}
.button.button-secondary {
border-color: $inverted-text-color;
color: $inverted-text-color;
border-color: $ui-button-secondary-border-color;
color: $ui-button-secondary-color;
flex: 0 0 auto;
&:hover,
&:focus,
&:active {
border-color: lighten($inverted-text-color, 15%);
color: lighten($inverted-text-color, 15%);
border-color: $ui-button-secondary-focus-background-color;
color: $ui-button-secondary-focus-color;
}
}

View File

@ -81,7 +81,7 @@
display: flex;
align-items: baseline;
border-radius: 4px;
background: darken($ui-highlight-color, 2%);
background: $ui-button-background-color;
color: $primary-text-color;
transition: all 100ms ease-in;
font-size: 14px;
@ -94,7 +94,7 @@
&:active,
&:focus,
&:hover {
background-color: $ui-highlight-color;
background-color: $ui-button-focus-background-color;
transition: all 200ms ease-out;
}

View File

@ -511,8 +511,8 @@ code {
width: 100%;
border: 0;
border-radius: 4px;
background: darken($ui-highlight-color, 2%);
color: $primary-text-color;
background: $ui-button-background-color;
color: $ui-button-color;
font-size: 18px;
line-height: inherit;
height: auto;
@ -534,7 +534,7 @@ code {
&:active,
&:focus,
&:hover {
background-color: $ui-highlight-color;
background-color: $ui-button-focus-background-color;
}
&:disabled:hover {
@ -542,15 +542,12 @@ code {
}
&.negative {
background: $error-value-color;
&:hover {
background-color: lighten($error-value-color, 5%);
}
background: $ui-button-destructive-background-color;
&:hover,
&:active,
&:focus {
background-color: darken($error-value-color, 5%);
background-color: $ui-button-destructive-focus-background-color;
}
}
}

View File

@ -1,8 +1,16 @@
// Commonly used web colors
$black: #000000; // Black
$white: #ffffff; // White
$red-600: #b7253d !default; // Deep Carmine
$red-500: #df405a !default; // Cerise
$blurple-600: #563acc; // Iris
$blurple-500: #6364ff; // Brand purple
$blurple-300: #858afa; // Faded Blue
$grey-600: #4e4c5a; // Trout
$grey-100: #dadaf3; // Topaz
$success-green: #79bd9a !default; // Padua
$error-red: #df405a !default; // Cerise
$error-red: $red-500 !default; // Cerise
$warning-red: #ff5050 !default; // Sunset Orange
$gold-star: #ca8f04 !default; // Dark Goldenrod
@ -31,6 +39,22 @@ $ui-base-lighter-color: lighten(
$ui-primary-color: $classic-primary-color !default; // Lighter
$ui-secondary-color: $classic-secondary-color !default; // Lightest
$ui-highlight-color: $classic-highlight-color !default;
$ui-button-color: $white !default;
$ui-button-background-color: $blurple-500 !default;
$ui-button-focus-background-color: $blurple-600 !default;
$ui-button-secondary-color: $grey-100 !default;
$ui-button-secondary-border-color: $grey-100 !default;
$ui-button-secondary-focus-background-color: $grey-600 !default;
$ui-button-secondary-focus-color: $white !default;
$ui-button-tertiary-color: $blurple-300 !default;
$ui-button-tertiary-border-color: $blurple-300 !default;
$ui-button-tertiary-focus-background-color: $blurple-600 !default;
$ui-button-tertiary-focus-color: $white !default;
$ui-button-destructive-background-color: $red-500 !default;
$ui-button-destructive-focus-background-color: $red-600 !default;
// Variables for texts
$primary-text-color: $white !default;
@ -39,6 +63,7 @@ $dark-text-color: $ui-base-lighter-color !default;
$secondary-text-color: $ui-secondary-color !default;
$highlight-text-color: lighten($ui-highlight-color, 8%) !default;
$action-button-color: $ui-base-lighter-color !default;
$action-button-focus-color: lighten($ui-base-lighter-color, 4%) !default;
$passive-text-color: $gold-star !default;
$active-passive-text-color: $success-green !default;

View File

@ -7,11 +7,48 @@ require 'resolv'
# Monkey-patch the HTTP.rb timeout class to avoid using a timeout block
# around the Socket#open method, since we use our own timeout blocks inside
# that method
#
# Also changes how the read timeout behaves so that it is cumulative (closer
# to HTTP::Timeout::Global, but still having distinct timeouts for other
# operation types)
class HTTP::Timeout::PerOperation
def connect(socket_class, host, port, nodelay = false)
@socket = socket_class.open(host, port)
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
end
# Reset deadline when the connection is re-used for different requests
def reset_counter
@deadline = nil
end
# Read data from the socket
def readpartial(size, buffer = nil)
@deadline ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) + @read_timeout
timeout = false
loop do
result = @socket.read_nonblock(size, buffer, exception: false)
return :eof if result.nil?
remaining_time = @deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
raise HTTP::TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout || remaining_time <= 0
return result if result != :wait_readable
# marking the socket for timeout. Why is this not being raised immediately?
# it seems there is some race-condition on the network level between calling
# #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting
# for reads, and when waiting for x seconds, it returns nil suddenly without completing
# the x seconds. In a normal case this would be a timeout on wait/read, but it can
# also mean that the socket has been closed by the server. Therefore we "mark" the
# socket for timeout and try to read more bytes. If it returns :eof, it's all good, no
# timeout. Else, the first timeout was a proper timeout.
# This hack has to be done because io/wait#wait_readable doesn't provide a value for when
# the socket is closed by the server, and HTTP::Parser doesn't provide the limit for the chunks.
timeout = true unless @socket.to_io.wait_readable(remaining_time)
end
end
end
class Request

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class ScopeParser < Parslet::Parser
rule(:term) { match('[a-z]').repeat(1).as(:term) }