Merge commit 'ce1f35d7e213327549b960bb64f63c67a141ea40' into glitch-soc/merge-upstream

Conflicts:
- `db/schema.rb`:
  Upstream regenerated the schema file using Rails 7, the conflicts are
  caused by our extra columns.
  Applied upstream's changes, but keeping our extra columns.
local
Claire 10 months ago
commit bd349cb850
  1. 4
      .github/workflows/test-ruby.yml
  2. 8
      Gemfile.lock
  3. 39
      app/controllers/auth/omniauth_callbacks_controller.rb
  4. 2
      app/controllers/auth/sessions_controller.rb
  5. 15
      app/javascript/mastodon/components/media_gallery.jsx
  6. 5
      app/javascript/mastodon/components/picture_in_picture_placeholder.jsx
  7. 50
      app/javascript/mastodon/components/status.jsx
  8. 6
      app/javascript/mastodon/features/audio/index.jsx
  9. 1
      app/javascript/mastodon/features/bookmarked_statuses/index.jsx
  10. 17
      app/javascript/mastodon/features/explore/components/story.jsx
  11. 6
      app/javascript/mastodon/features/explore/links.jsx
  12. 1
      app/javascript/mastodon/features/favourited_statuses/index.jsx
  13. 2
      app/javascript/mastodon/features/lists/index.jsx
  14. 49
      app/javascript/mastodon/features/status/components/card.jsx
  15. 28
      app/javascript/mastodon/features/status/components/detailed_status.jsx
  16. 1
      app/javascript/mastodon/features/ui/components/media_modal.jsx
  17. 1
      app/javascript/mastodon/features/ui/components/video_modal.jsx
  18. 179
      app/javascript/mastodon/features/video/index.jsx
  19. 2
      app/javascript/mastodon/initial_state.js
  20. 1
      app/javascript/mastodon/locales/en.json
  21. 11
      app/javascript/styles/mastodon-light/diff.scss
  22. 2
      app/javascript/styles/mastodon-light/variables.scss
  23. 4
      app/javascript/styles/mastodon/basics.scss
  24. 196
      app/javascript/styles/mastodon/components.scss
  25. 26
      app/javascript/styles/mastodon/polls.scss
  26. 5
      app/lib/link_details_extractor.rb
  27. 4
      app/models/concerns/has_user_settings.rb
  28. 1
      app/models/preview_card.rb
  29. 9
      app/models/report.rb
  30. 1
      app/models/user_settings.rb
  31. 3
      app/models/webhook.rb
  32. 2
      app/serializers/initial_state_serializer.rb
  33. 2
      app/serializers/rest/preview_card_serializer.rb
  34. 5
      app/views/settings/preferences/appearance/show.html.haml
  35. 1
      config/locales/an.yml
  36. 1
      config/locales/ar.yml
  37. 1
      config/locales/ast.yml
  38. 1
      config/locales/be.yml
  39. 1
      config/locales/bg.yml
  40. 1
      config/locales/ca.yml
  41. 1
      config/locales/ckb.yml
  42. 1
      config/locales/co.yml
  43. 1
      config/locales/cs.yml
  44. 1
      config/locales/cy.yml
  45. 1
      config/locales/da.yml
  46. 1
      config/locales/de.yml
  47. 1
      config/locales/el.yml
  48. 1
      config/locales/en-GB.yml
  49. 1
      config/locales/en.yml
  50. 1
      config/locales/eo.yml
  51. 1
      config/locales/es-AR.yml
  52. 1
      config/locales/es-MX.yml
  53. 1
      config/locales/es.yml
  54. 1
      config/locales/et.yml
  55. 1
      config/locales/eu.yml
  56. 1
      config/locales/fa.yml
  57. 1
      config/locales/fi.yml
  58. 1
      config/locales/fo.yml
  59. 1
      config/locales/fr-QC.yml
  60. 1
      config/locales/fr.yml
  61. 1
      config/locales/fy.yml
  62. 1
      config/locales/gd.yml
  63. 1
      config/locales/gl.yml
  64. 1
      config/locales/he.yml
  65. 1
      config/locales/hu.yml
  66. 1
      config/locales/id.yml
  67. 1
      config/locales/io.yml
  68. 1
      config/locales/is.yml
  69. 1
      config/locales/it.yml
  70. 1
      config/locales/ja.yml
  71. 1
      config/locales/kk.yml
  72. 1
      config/locales/ko.yml
  73. 1
      config/locales/ku.yml
  74. 1
      config/locales/lv.yml
  75. 1
      config/locales/my.yml
  76. 1
      config/locales/nl.yml
  77. 1
      config/locales/nn.yml
  78. 1
      config/locales/no.yml
  79. 1
      config/locales/oc.yml
  80. 1
      config/locales/pl.yml
  81. 1
      config/locales/pt-BR.yml
  82. 1
      config/locales/pt-PT.yml
  83. 1
      config/locales/ro.yml
  84. 1
      config/locales/ru.yml
  85. 1
      config/locales/sc.yml
  86. 1
      config/locales/sco.yml
  87. 1
      config/locales/si.yml
  88. 1
      config/locales/simple_form.an.yml
  89. 1
      config/locales/simple_form.ar.yml
  90. 1
      config/locales/simple_form.ast.yml
  91. 1
      config/locales/simple_form.be.yml
  92. 1
      config/locales/simple_form.bg.yml
  93. 1
      config/locales/simple_form.ca.yml
  94. 1
      config/locales/simple_form.ckb.yml
  95. 1
      config/locales/simple_form.co.yml
  96. 1
      config/locales/simple_form.cs.yml
  97. 1
      config/locales/simple_form.cy.yml
  98. 1
      config/locales/simple_form.da.yml
  99. 1
      config/locales/simple_form.de.yml
  100. 1
      config/locales/simple_form.el.yml
  101. Some files were not shown because too many files have changed in this diff Show More

@ -107,6 +107,10 @@ jobs:
PAM_ENABLED: true
PAM_DEFAULT_SERVICE: pam_test
PAM_CONTROLLED_SERVICE: pam_test_controlled
OIDC_ENABLED: true
OIDC_SCOPE: read
SAML_ENABLED: true
CAS_ENABLED: true
BUNDLE_WITH: 'pam_authentication test'
CI_JOBS: ${{ matrix.ci_job }}/4

@ -103,7 +103,7 @@ GEM
attr_required (1.0.1)
awrence (1.2.1)
aws-eventstream (1.2.0)
aws-partitions (1.786.0)
aws-partitions (1.791.0)
aws-sdk-core (3.178.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
@ -112,7 +112,7 @@ GEM
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.130.0)
aws-sdk-s3 (1.131.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
@ -144,7 +144,7 @@ GEM
blurhash (0.1.7)
bootsnap (1.16.0)
msgpack (~> 1.2)
brakeman (6.0.0)
brakeman (6.0.1)
browser (5.3.1)
brpoplpush-redis_script (0.1.3)
concurrent-ruby (~> 1.0, >= 1.0.5)
@ -569,7 +569,7 @@ GEM
rake (13.0.6)
rdf (3.2.11)
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.6.0)
rdf-normalize (0.6.1)
rdf (~> 3.2)
redcarpet (3.6.0)
redis (4.8.1)

@ -5,21 +5,13 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
define_method provider do
@provider = provider
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
if @user.persisted?
LoginActivity.create(
user: @user,
success: true,
authentication_method: :omniauth,
provider: provider,
ip: request.remote_ip,
user_agent: request.user_agent
)
record_login_activity
sign_in_and_redirect @user, event: :authentication
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
set_flash_message(:notice, :success, kind: label) if is_navigational_format?
set_flash_message(:notice, :success, kind: label_for_provider) if is_navigational_format?
else
session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url
@ -38,4 +30,29 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
auth_setup_path(missing_email: '1')
end
end
private
def record_login_activity
LoginActivity.create(
user: @user,
success: true,
authentication_method: :omniauth,
provider: @provider,
ip: request.remote_ip,
user_agent: request.user_agent
)
end
def label_for_provider
provider_display_name || configured_provider_name
end
def provider_display_name
Devise.omniauth_configs[@provider]&.strategy&.display_name.presence
end
def configured_provider_name
I18n.t("auth.providers.#{@provider}", default: @provider.to_s.chomp('_oauth2').capitalize)
end
end

@ -113,7 +113,7 @@ class Auth::SessionsController < Devise::SessionsController
end
def home_paths(resource)
paths = [about_path]
paths = [about_path, '/explore']
paths << short_account_path(username: resource.account) if single_user_mode? && resource.is_a?(User)

@ -12,7 +12,7 @@ import { debounce } from 'lodash';
import { Blurhash } from 'mastodon/components/blurhash';
import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state';
import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
import { IconButton } from './icon_button';
@ -209,7 +209,6 @@ class MediaGallery extends PureComponent {
static propTypes = {
sensitive: PropTypes.bool,
standalone: PropTypes.bool,
media: ImmutablePropTypes.list.isRequired,
lang: PropTypes.string,
size: PropTypes.object,
@ -223,10 +222,6 @@ class MediaGallery extends PureComponent {
onToggleVisibility: PropTypes.func,
};
static defaultProps = {
standalone: false,
};
state = {
visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
width: this.props.defaultWidth,
@ -295,7 +290,7 @@ class MediaGallery extends PureComponent {
}
render () {
const { media, lang, intl, sensitive, defaultWidth, standalone, autoplay } = this.props;
const { media, lang, intl, sensitive, defaultWidth, autoplay } = this.props;
const { visible } = this.state;
const width = this.state.width || defaultWidth;
@ -303,16 +298,16 @@ class MediaGallery extends PureComponent {
const style = {};
if (this.isFullSizeEligible() && (standalone || !cropImages)) {
if (this.isFullSizeEligible()) {
style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`;
} else {
style.aspectRatio = '16 / 9';
style.aspectRatio = '3 / 2';
}
const size = media.take(4).size;
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
if (standalone && this.isFullSizeEligible()) {
if (this.isFullSizeEligible()) {
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
} else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);

@ -12,6 +12,7 @@ class PictureInPicturePlaceholder extends PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
aspectRatio: PropTypes.string,
};
handleClick = () => {
@ -20,8 +21,10 @@ class PictureInPicturePlaceholder extends PureComponent {
};
render () {
const { aspectRatio } = this.props;
return (
<div className='picture-in-picture-placeholder' role='button' tabIndex={0} onClick={this.handleClick}>
<div className='picture-in-picture-placeholder' style={{ aspectRatio }} role='button' tabIndex={0} onClick={this.handleClick}>
<Icon id='window-restore' />
<FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
</div>

@ -19,7 +19,6 @@ import Bundle from '../features/ui/components/bundle';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { displayMedia } from '../initial_state';
import AttachmentList from './attachment_list';
import { Avatar } from './avatar';
import { AvatarOverlay } from './avatar_overlay';
import { DisplayName } from './display_name';
@ -191,17 +190,35 @@ class Status extends ImmutablePureComponent {
this.props.onTranslate(this._properStatus());
};
renderLoadingMediaGallery () {
return <div className='media-gallery' style={{ height: '110px' }} />;
}
getAttachmentAspectRatio () {
const attachments = this._properStatus().get('media_attachments');
renderLoadingVideoPlayer () {
return <div className='video-player' style={{ height: '110px' }} />;
if (attachments.getIn([0, 'type']) === 'video') {
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
} else if (attachments.getIn([0, 'type']) === 'audio') {
return '16 / 9';
} else {
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
}
}
renderLoadingAudioPlayer () {
return <div className='audio-player' style={{ height: '110px' }} />;
}
renderLoadingMediaGallery = () => {
return (
<div className='media-gallery' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
);
};
renderLoadingVideoPlayer = () => {
return (
<div className='video-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
);
};
renderLoadingAudioPlayer = () => {
return (
<div className='audio-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
);
};
handleOpenVideo = (options) => {
const status = this._properStatus();
@ -426,18 +443,11 @@ class Status extends ImmutablePureComponent {
}
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder />;
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
} else if (status.get('media_attachments').size > 0) {
const language = status.getIn(['translation', 'language']) || status.get('language');
if (this.props.muted) {
media = (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
@ -475,11 +485,11 @@ class Status extends ImmutablePureComponent {
<Component
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={description}
lang={language}
inline
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
@ -508,7 +518,7 @@ class Status extends ImmutablePureComponent {
</Bundle>
);
}
} else if (status.get('spoiler_text').length === 0 && status.get('card') && !this.props.muted) {
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
media = (
<Card
onOpenMedia={this.handleOpenMedia}

@ -470,6 +470,7 @@ class Audio extends PureComponent {
const progress = Math.min((currentTime / duration) * 100, 100);
let warning;
if (sensitive) {
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
} else {
@ -515,7 +516,10 @@ class Audio extends PureComponent {
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
<span className='spoiler-button__overlay__label'>{warning}</span>
<span className='spoiler-button__overlay__label'>
{warning}
<span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
</span>
</button>
</div>

@ -86,7 +86,6 @@ class Bookmarks extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
showBackButton
/>
<StatusList

@ -1,10 +1,13 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Blurhash } from 'mastodon/components/blurhash';
import { accountsCountRenderer } from 'mastodon/components/hashtag';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
@ -13,10 +16,14 @@ export default class Story extends PureComponent {
static propTypes = {
url: PropTypes.string,
title: PropTypes.string,
lang: PropTypes.string,
publisher: PropTypes.string,
publishedAt: PropTypes.string,
author: PropTypes.string,
sharedTimes: PropTypes.number,
thumbnail: PropTypes.string,
blurhash: PropTypes.string,
expanded: PropTypes.bool,
};
state = {
@ -26,16 +33,16 @@ export default class Story extends PureComponent {
handleImageLoad = () => this.setState({ thumbnailLoaded: true });
render () {
const { url, title, publisher, sharedTimes, thumbnail, blurhash } = this.props;
const { expanded, url, title, lang, publisher, author, publishedAt, sharedTimes, thumbnail, blurhash } = this.props;
const { thumbnailLoaded } = this.state;
return (
<a className='story' href={url} target='blank' rel='noopener'>
<a className={classNames('story', { expanded })} href={url} target='blank' rel='noopener'>
<div className='story__details'>
<div className='story__details__publisher'>{publisher ? publisher : <Skeleton width={50} />}</div>
<div className='story__details__title'>{title ? title : <Skeleton />}</div>
<div className='story__details__shared'>{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
<div className='story__details__publisher'>{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}</div>
<div className='story__details__title' lang={lang}>{title ? title : <Skeleton />}</div>
<div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
</div>
<div className='story__thumbnail'>

@ -55,12 +55,16 @@ class Links extends PureComponent {
<div className='explore__links'>
{banner}
{isLoading ? (<LoadingIndicator />) : links.map(link => (
{isLoading ? (<LoadingIndicator />) : links.map((link, i) => (
<Story
key={link.get('id')}
expanded={i === 0}
lang={link.get('language')}
url={link.get('url')}
title={link.get('title')}
publisher={link.get('provider_name')}
publishedAt={link.get('published_at')}
author={link.get('author_name')}
sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
thumbnail={link.get('image')}
blurhash={link.get('blurhash')}

@ -86,7 +86,6 @@ class Favourites extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
showBackButton
/>
<StatusList

@ -65,7 +65,7 @@ class Lists extends ImmutablePureComponent {
return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
<ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' multiColumn={multiColumn} showBackButton />
<ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' multiColumn={multiColumn} />
<NewListForm />

@ -12,6 +12,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { useBlurhash } from 'mastodon/initial_state';
const IDNA_PREFIX = 'xn--';
@ -57,14 +58,9 @@ export default class Card extends PureComponent {
static propTypes = {
card: ImmutablePropTypes.map,
onOpenMedia: PropTypes.func.isRequired,
compact: PropTypes.bool,
sensitive: PropTypes.bool,
};
static defaultProps = {
compact: false,
};
state = {
previewLoaded: false,
embedded: false,
@ -148,7 +144,7 @@ export default class Card extends PureComponent {
}
render () {
const { card, compact } = this.props;
const { card } = this.props;
const { embedded, revealed } = this.state;
if (card === null) {
@ -156,29 +152,27 @@ export default class Card extends PureComponent {
}
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
const horizontal = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded;
const interactive = card.get('type') !== 'link';
const className = classnames('status-card', { horizontal, compact, interactive });
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const language = card.get('language') || '';
const description = (
<div className='status-card__content' lang={language}>
{title}
{!(horizontal || compact) && <p className='status-card__description' title={card.get('description')}>{card.get('description')}</p>}
<span className='status-card__host'>{provider}</span>
<div className='status-card__content'>
<span className='status-card__host'>
<span lang={language}>{provider}</span>
{card.get('published_at') && <> · <RelativeTimestamp timestamp={card.get('published_at')} /></>}
</span>
<strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong>
{card.get('author_name').length > 0 && <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span>}
</div>
);
const thumbnailStyle = {
visibility: revealed? null : 'hidden',
visibility: revealed ? null : 'hidden',
aspectRatio: `${card.get('width')} / ${card.get('height')}`
};
if (horizontal) {
thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`;
}
let embed;
let embed = '';
let canvas = (
<Blurhash
className={classnames('status-card__image-preview', {
@ -188,12 +182,18 @@ export default class Card extends PureComponent {
dummy={!useBlurhash}
/>
);
let thumbnail = <img src={card.get('image')} alt='' style={thumbnailStyle} onLoad={this.handleImageLoad} className='status-card__image-image' />;
let spoilerButton = (
<button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
<span className='spoiler-button__overlay__label'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
<span className='spoiler-button__overlay__label'>
<FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />
<span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
</span>
</button>
);
spoilerButton = (
<div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}>
{spoilerButton}
@ -219,19 +219,20 @@ export default class Card extends PureComponent {
<div className='status-card__actions'>
<div>
<button type='button' onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>}
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>
</div>
</div>
)}
{!revealed && spoilerButton}
</div>
);
}
return (
<div className={className} ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
<div className='status-card' ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
{embed}
{!compact && description}
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'>{description}</a>
</div>
);
} else if (card.get('image')) {
@ -243,14 +244,14 @@ export default class Card extends PureComponent {
);
} else {
embed = (
<div className='status-card__image'>
<div className='status-card__image' style={{ aspectRatio: '1.9 / 1' }}>
<Icon id='file-text' />
</div>
);
}
return (
<a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
<a href={card.get('url')} className='status-card' target='_blank' rel='noopener noreferrer' ref={this.setRef}>
{embed}
{description}
</a>

@ -113,8 +113,30 @@ class DetailedStatus extends ImmutablePureComponent {
onTranslate(status);
};
_properStatus () {
const { status } = this.props;
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
return status.get('reblog');
} else {
return status;
}
}
getAttachmentAspectRatio () {
const attachments = this._properStatus().get('media_attachments');
if (attachments.getIn([0, 'type']) === 'video') {
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
} else if (attachments.getIn([0, 'type']) === 'audio') {
return '16 / 9';
} else {
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
}
}
render () {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const status = this._properStatus();
const outerStyle = { boxSizing: 'border-box' };
const { intl, compact, pictureInPicture } = this.props;
@ -136,7 +158,7 @@ class DetailedStatus extends ImmutablePureComponent {
const language = status.getIn(['translation', 'language']) || status.get('language');
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder />;
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);
@ -167,13 +189,13 @@ class DetailedStatus extends ImmutablePureComponent {
<Video
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={description}
lang={language}
width={300}
height={150}
inline
onOpenVideo={this.handleOpenVideo}
sensitive={status.get('sensitive')}
visible={this.props.showMedia}

@ -172,6 +172,7 @@ class MediaModal extends ImmutablePureComponent {
width={image.get('width')}
height={image.get('height')}
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
aspectRatio={`${image.getIn(['meta', 'original', 'width'])} / ${image.getIn(['meta', 'original', 'height'])}`}
currentTime={currentTime || 0}
autoPlay={autoPlay || false}
volume={volume || 1}

@ -49,6 +49,7 @@ class VideoModal extends ImmutablePureComponent {
<Video
preview={media.get('preview_url')}
frameRate={media.getIn(['meta', 'original', 'frame_rate'])}
aspectRatio={`${media.getIn(['meta', 'original', 'width'])} / ${media.getIn(['meta', 'original', 'height'])}`}
blurhash={media.get('blurhash')}
src={media.get('url')}
currentTime={options.startTime}

@ -105,6 +105,7 @@ class Video extends PureComponent {
static propTypes = {
preview: PropTypes.string,
frameRate: PropTypes.string,
aspectRatio: PropTypes.string,
src: PropTypes.string.isRequired,
alt: PropTypes.string,
lang: PropTypes.string,
@ -113,7 +114,6 @@ class Video extends PureComponent {
onOpenVideo: PropTypes.func,
onCloseVideo: PropTypes.func,
detailed: PropTypes.bool,
inline: PropTypes.bool,
editable: PropTypes.bool,
alwaysVisible: PropTypes.bool,
visible: PropTypes.bool,
@ -500,14 +500,9 @@ class Video extends PureComponent {
}
render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { preview, src, aspectRatio, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {};
if (inline) {
playerStyle.aspectRatio = '16 / 9';
}
let preload;
@ -527,95 +522,101 @@ class Video extends PureComponent {
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
}
// The outer wrapper is necessary to avoid reflowing the layout when going into full screen
return (
<div
role='menuitem'
className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable })}
style={playerStyle}
ref={this.setPlayerRef}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClickRoot}
onKeyDown={this.handleKeyDown}
tabIndex={0}
>
<Blurhash
hash={blurhash}
className={classNames('media-gallery__preview', {
'media-gallery__preview--hidden': revealed,
})}
dummy={!useBlurhash}
/>
{(revealed || editable) && <video
ref={this.setVideoRef}
src={src}
poster={preview}
preload={preload}
role='button'
<div style={{ aspectRatio }}>
<div
role='menuitem'
className={classNames('video-player', { inactive: !revealed, detailed, fullscreen, editable })}
style={{ aspectRatio }}
ref={this.setPlayerRef}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClickRoot}
onKeyDown={this.handleKeyDown}
tabIndex={0}
aria-label={alt}
title={alt}
lang={lang}
volume={volume}
onClick={this.togglePlay}
onKeyDown={this.handleVideoKeyDown}
onPlay={this.handlePlay}
onPause={this.handlePause}
onLoadedData={this.handleLoadedData}
onProgress={this.handleProgress}
onVolumeChange={this.handleVolumeChange}
style={{ ...playerStyle, width: '100%' }}
/>}
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
<span className='spoiler-button__overlay__label'>{warning}</span>
</button>
</div>
<div className={classNames('video-player__controls', { active: paused || hovered })}>
<div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
<div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
<div className='video-player__seek__progress' style={{ width: `${progress}%` }} />
<span
className={classNames('video-player__seek__handle', { active: dragging })}
tabIndex={0}
style={{ left: `${progress}%` }}
onKeyDown={this.handleVideoKeyDown}
/>
>
<Blurhash
hash={blurhash}
className={classNames('media-gallery__preview', {
'media-gallery__preview--hidden': revealed,
})}
dummy={!useBlurhash}
/>
{(revealed || editable) && <video
ref={this.setVideoRef}
src={src}
poster={preview}
preload={preload}
role='button'
tabIndex={0}
aria-label={alt}
title={alt}
lang={lang}
volume={volume}
onClick={this.togglePlay}
onKeyDown={this.handleVideoKeyDown}
onPlay={this.handlePlay}
onPause={this.handlePause}
onLoadedData={this.handleLoadedData}
onProgress={this.handleProgress}
onVolumeChange={this.handleVolumeChange}
style={{ width: '100%' }}
/>}
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
<span className='spoiler-button__overlay__label'>
{warning}
<span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
</span>
</button>
</div>
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={autoFocus}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
<div className='video-player__volume__current' style={{ width: `${volume * 100}%` }} />
<div className={classNames('video-player__controls', { active: paused || hovered })}>
<div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
<div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
<div className='video-player__seek__progress' style={{ width: `${progress}%` }} />
<span
className={classNames('video-player__seek__handle', { active: dragging })}
tabIndex={0}
style={{ left: `${progress}%` }}
onKeyDown={this.handleVideoKeyDown}
/>
</div>
<span
className={classNames('video-player__volume__handle')}
tabIndex={0}
style={{ left: `${volume * 100}%` }}
/>
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={autoFocus}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
<div className='video-player__volume__current' style={{ width: `${volume * 100}%` }} />
<span
className={classNames('video-player__volume__handle')}
tabIndex={0}
style={{ left: `${volume * 100}%` }}
/>
</div>
{(detailed || fullscreen) && (
<span className='video-player__time'>
<span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
<span className='video-player__time-sep'>/</span>
<span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
</span>
)}
</div>
{(detailed || fullscreen) && (
<span className='video-player__time'>
<span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
<span className='video-player__time-sep'>/</span>
<span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
</span>
)}
</div>
<div className='video-player__buttons right'>
{(!onCloseVideo && !editable && !fullscreen && !this.props.alwaysVisible) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
{(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} className='player-button' onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
{onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} className='player-button' onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
<button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} className='player-button' onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
<div className='video-player__buttons right'>
{(!onCloseVideo && !editable && !fullscreen && !this.props.alwaysVisible) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
{(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} className='player-button' onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
{onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} className='player-button' onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
<button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} className='player-button' onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
</div>
</div>
</div>
</div>

@ -51,7 +51,6 @@
* @property {boolean} activity_api_enabled
* @property {string} admin
* @property {boolean=} boost_modal
* @property {boolean} crop_images
* @property {boolean=} delete_modal
* @property {boolean=} disable_swiping
* @property {string=} disabled_account_id
@ -112,7 +111,6 @@ const getMeta = (prop) => initialState?.meta && initialState.meta[prop];
export const activityApiEnabled = getMeta('activity_api_enabled');
export const autoPlayGif = getMeta('auto_play_gif');
export const boostModal = getMeta('boost_modal');
export const cropImages = getMeta('crop_images');
export const deleteModal = getMeta('delete_modal');
export const disableSwiping = getMeta('disable_swiping');
export const disabledAccountId = getMeta('disabled_account_id');

@ -363,6 +363,7 @@
"lightbox.previous": "Previous",
"limited_account_hint.action": "Show profile anyway",
"limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
"link_preview.author": "By {name}",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",

@ -24,13 +24,16 @@ html {
.column > .scrollable,
.getting-started,
.column-inline-form,
.error-column,
.regeneration-indicator {
background: $white;
border: 1px solid lighten($ui-base-color, 8%);
border-top: 0;
}
.error-column {
border: 1px solid lighten($ui-base-color, 8%);
}
.column > .scrollable.about {
border-top: 1px solid lighten($ui-base-color, 8%);
}
@ -77,6 +80,10 @@ html {
background: $white;
}
.column-header {
border-bottom: 0;
}
.column-header__button.active {
color: $ui-highlight-color;
@ -414,7 +421,7 @@ html {
.column-header__collapsible-inner {
background: darken($ui-base-color, 4%);
border: 1px solid lighten($ui-base-color, 8%);
border-top: 0;
border-bottom: 0;
}
.dashboard__quick-access,

@ -5,7 +5,7 @@ $white: #ffffff;
$classic-base-color: #282c37;
$classic-primary-color: #9baec8;
$classic-secondary-color: #d9e1e8;
$classic-highlight-color: #6364ff;
$classic-highlight-color: #858afa;
$blurple-600: #563acc; // Iris
$blurple-500: #6364ff; // Brand purple

@ -175,6 +175,10 @@ a {
button {
font-family: inherit;
cursor: pointer;
&:focus:not(:focus-visible) {
outline: none;
}
}
.app-holder {

@ -252,13 +252,14 @@
&.overlayed {
box-sizing: content-box;
background: rgba($base-overlay-background, 0.6);
color: rgba($primary-text-color, 0.7);
background: rgba($black, 0.65);
backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
color: rgba($white, 0.7);
border-radius: 4px;
padding: 2px;
&:hover {
background: rgba($base-overlay-background, 0.9);
background: rgba($black, 0.9);
}
}
@ -1352,6 +1353,10 @@ body > [data-popper-placement] {
}
}
.scrollable > div:first-child .detailed-status {
border-top: 0;
}
.detailed-status__meta {
margin-top: 16px;
color: $dark-text-color;
@ -3504,12 +3509,10 @@ button.icon-button.active i.fa-retweet {
}
.status-card {
display: block;
position: relative;
display: flex;
font-size: 14px;
border: 1px solid lighten($ui-base-color, 8%);
border-radius: 4px;
color: $dark-text-color;
color: $darker-text-color;
margin-top: 14px;
text-decoration: none;
overflow: hidden;
@ -3563,8 +3566,29 @@ button.icon-button.active i.fa-retweet {
a.status-card {
cursor: pointer;
&:hover {
background: lighten($ui-base-color, 8%);
&:hover,
&:focus,
&:active {
.status-card__title,
.status-card__host,
.status-card__author {
color: $highlight-text-color;
}
}
}
.status-card a {
color: inherit;
text-decoration: none;
&:hover,
&:focus,
&:active {
.status-card__title,
.status-card__host,
.status-card__author {
color: $highlight-text-color;
}
}
}
@ -3590,42 +3614,42 @@ a.status-card {
.status-card__title {
display: block;
font-weight: 500;
margin-bottom: 5px;
color: $darker-text-color;
font-weight: 700;
font-size: 19px;
line-height: 24px;
color: $primary-text-color;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-decoration: none;
}
.status-card__content {
flex: 1 1 auto;
overflow: hidden;
padding: 14px 14px 14px 8px;
padding: 15px 0;
padding-bottom: 0;
}
.status-card__description {
color: $darker-text-color;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
.status-card__host {
display: block;
font-size: 14px;
margin-bottom: 8px;
}
.status-card__host {
.status-card__author {
display: block;
margin-top: 5px;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 8px;
font-size: 14px;
color: $primary-text-color;
strong {
font-weight: 500;
}
}
.status-card__image {
flex: 0 0 100px;
width: 100%;
background: lighten($ui-base-color, 8%);
position: relative;
border-radius: 8px;
& > .fa {
font-size: 21px;
@ -3637,50 +3661,8 @@ a.status-card {
}
}
.status-card.horizontal {
display: block;
.status-card__image {
width: 100%;
}
.status-card__image-image,
.status-card__image-preview {
border-radius: 4px 4px 0 0;
}
.status-card__title {
white-space: inherit;
}
}
.status-card.compact {
border-color: lighten($ui-base-color, 4%);
&.interactive {
border: 0;
}
.status-card__content {
padding: 8px;
padding-top: 10px;
}
.status-card__title {
white-space: nowrap;
}
.status-card__image {
flex: 0 0 60px;
}
}
a.status-card.compact:hover {
background-color: lighten($ui-base-color, 4%);
}
.status-card__image-image {
border-radius: 4px 0 0 4px;
border-radius: 8px;
display: block;
margin: 0;
width: 100%;
@ -3691,7 +3673,7 @@ a.status-card.compact:hover {
}
.status-card__image-preview {
border-radius: 4px 0 0 4px;
border-radius: 8px;
display: block;
margin: 0;
width: 100%;
@ -4204,6 +4186,7 @@ a.status-card.compact:hover {
margin: 0;
border: 0;
border-radius: 4px;
color: $white;
&__label {
display: flex;
@ -4211,7 +4194,6 @@ a.status-card.compact:hover {
justify-content: center;
gap: 8px;
flex-direction: column;
color: $primary-text-color;
font-weight: 500;
font-size: 14px;
}
@ -5033,7 +5015,6 @@ a.status-card.compact:hover {
position: absolute;
top: 16px;
inset-inline-end: 10px;
z-index: 2;
display: inline-block;
opacity: 0;
transition: all 100ms linear;
@ -5172,9 +5153,9 @@ a.status-card.compact:hover {
display: flex;
}
.video-modal__container {
.video-modal .video-player {
max-height: 80vh;
max-width: 100vw;
max-height: 100vh;
}
.audio-modal__container {
@ -6192,7 +6173,7 @@ a.status-card.compact:hover {
box-sizing: border-box;
margin-top: 8px;
overflow: hidden;
border-radius: 4px;
border-radius: 8px;
position: relative;
width: 100%;
min-height: 64px;
@ -6207,7 +6188,7 @@ a.status-card.compact:hover {
box-sizing: border-box;
display: block;
position: relative;
border-radius: 4px;
border-radius: 8px;
overflow: hidden;
&--tall {
@ -6293,7 +6274,7 @@ a.status-card.compact:hover {
box-sizing: border-box;
position: relative;
background: darken($ui-base-color, 8%);
border-radius: 4px;
border-radius: 8px;
padding-bottom: 44px;
width: 100%;
@ -6360,7 +6341,7 @@ a.status-card.compact:hover {
position: relative;
background: $base-shadow-color;
max-width: 100%;
border-radius: 4px;
border-radius: 8px;
box-sizing: border-box;
color: $white;
display: flex;
@ -6377,8 +6358,6 @@ a.status-card.compact:hover {
video {
display: block;
max-width: 100vw;
max-height: 80vh;
z-index: 1;
}
@ -6386,22 +6365,15 @@ a.status-card.compact:hover {
width: 100% !important;
height: 100% !important;
margin: 0;
aspect-ratio: auto !important;
video {
max-width: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
outline: 0;
}
}
&.inline {
video {
object-fit: contain;
}
}
&__controls {
position: absolute;
direction: ltr;
@ -8163,6 +8135,7 @@ noscript {
.search__input {
border: 1px solid lighten($ui-base-color, 8%);
padding: 10px;
padding-inline-end: 28px;
}
.search__popout {
@ -8191,8 +8164,9 @@ noscript {
align-items: center;
color: $primary-text-color;
text-decoration: none;
padding: 15px 0;
padding: 15px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
gap: 15px;
&:last-child {
border-bottom: 0;
@ -8201,33 +8175,40 @@ noscript {
&:hover,
&:active,
&:focus {
background-color: lighten($ui-base-color, 4%);
color: $highlight-text-color;
.story__details__publisher,
.story__details__shared {
color: $highlight-text-color;
}
}
&__details {
padding: 0 15px;
flex: 1 1 auto;
&__publisher {
color: $darker-text-color;
margin-bottom: 4px;
margin-bottom: 8px;
}
&__title {
font-size: 19px;
line-height: 24px;
font-weight: 500;
margin-bottom: 4px;
margin-bottom: 8px;
}
&__shared {
color: $darker-text-color;
}
strong {
font-weight: 500;
}
}
&__thumbnail {
flex: 0 0 auto;
margin: 0 15px;
position: relative;
width: 120px;
height: 120px;
@ -8238,7 +8219,7 @@ noscript {
}
img {
border-radius: 4px;
border-radius: 8px;
display: block;
margin: 0;
width: 100%;
@ -8247,7 +8228,7 @@ noscript {
}
&__preview {
border-radius: 4px;
border-radius: 8px;
display: block;
margin: 0;
width: 100%;
@ -8263,6 +8244,23 @@ noscript {
}
}
}
&.expanded {
flex-direction: column;
.story__thumbnail {
order: 1;
width: 100%;
height: auto;
aspect-ratio: 1.91 / 1;
}
.story__details {
order: 2;
width: 100%;
flex: 0 0 auto;
}
}
}
.server-banner {

@ -121,11 +121,6 @@
border-radius: 4px;
}
&.active {
border-color: $valid-value-color;
background: $valid-value-color;
}
&:active,
&:focus,
&:hover {
@ -133,6 +128,11 @@
border-width: 4px;
}
&.active {
background-color: $valid-value-color;
border-color: $valid-value-color;
}
&::-moz-focus-inner {
outline: 0 !important;
border: 0;
@ -216,6 +216,14 @@
padding: 10px;
}
.poll__input {
&:active,
&:focus,
&:hover {
border-color: $ui-button-focus-background-color;
}
}
.poll__footer {
border-top: 1px solid darken($simple-background-color, 8%);
padding: 10px;
@ -241,6 +249,14 @@
color: $action-button-color;
border-color: $action-button-color;
margin-inline-end: 5px;
&:hover,
&:focus,
&.active {
border-color: $action-button-color;
background-color: $action-button-color;
color: $ui-button-color;
}
}
li {

@ -124,6 +124,7 @@ class LinkDetailsExtractor
author_url: author_url || '',
embed_url: embed_url || '',
language: language,
published_at: published_at.presence,
}
end
@ -159,6 +160,10 @@ class LinkDetailsExtractor
html_entities.decode(structured_data&.description || opengraph_tag('og:description') || meta_tag('description'))
end
def published_at
structured_data&.date_published || opengraph_tag('article:published_time')
end
def image
valid_url_or_nil(opengraph_tag('og:image'))
end

@ -103,10 +103,6 @@ module HasUserSettings
settings['web.trends']
end
def setting_crop_images
settings['web.crop_images']
end
def setting_disable_swiping
settings['web.disable_swiping']
end

@ -30,6 +30,7 @@
# max_score_at :datetime
# trendable :boolean
# link_type :integer
# published_at :datetime
#
class PreviewCard < ApplicationRecord

@ -58,7 +58,8 @@ class Report < ApplicationRecord
before_validation :set_uri, only: :create
after_create_commit :trigger_webhooks
after_create_commit :trigger_create_webhooks
after_update_commit :trigger_update_webhooks
def object_type
:flag
@ -155,7 +156,11 @@ class Report < ApplicationRecord
errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size
end
def trigger_webhooks
def trigger_create_webhooks
TriggerWebhookWorker.perform_async('report.created', 'Report', id)
end
def trigger_update_webhooks
TriggerWebhookWorker.perform_async('report.updated', 'Report', id)
end
end

@ -20,7 +20,6 @@ class UserSettings
setting :hide_followers_count, default: false
namespace :web do
setting :crop_images, default: true
setting :advanced_layout, default: false
setting :trends, default: true
setting :use_blurhash, default: true

@ -20,6 +20,7 @@ class Webhook < ApplicationRecord
account.created
account.updated
report.created
report.updated
status.created
status.updated
).freeze
@ -59,7 +60,7 @@ class Webhook < ApplicationRecord
case event
when 'account.approved', 'account.created', 'account.updated'
:manage_users
when 'report.created'
when 'report.created', 'report.updated'
:manage_reports
when 'status.created', 'status.updated'
:view_devops

@ -65,13 +65,11 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:default_content_type] = object.current_account.user.setting_default_content_type
store[:system_emoji_font] = object.current_account.user.setting_system_emoji_font
store[:show_trends] = Setting.trends && object.current_account.user.setting_trends
store[:crop_images] = object.current_account.user.setting_crop_images
else
store[:auto_play_gif] = Setting.auto_play_gif
store[:display_media] = Setting.display_media
store[:reduce_motion] = Setting.reduce_motion
store[:use_blurhash] = Setting.use_blurhash
store[:crop_images] = Setting.crop_images
end
store[:disabled_account_id] = object.disabled_account.id.to_s if object.disabled_account

@ -6,7 +6,7 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer
attributes :url, :title, :description, :language, :type,
:author_name, :author_url, :provider_name,
:provider_url, :html, :width, :height,
:image, :embed_url, :blurhash
:image, :embed_url, :blurhash, :published_at
def image
object.image? ? full_asset_url(object.image.url(:original)) : nil

@ -37,11 +37,6 @@
= ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui')
= ff.input :'web.use_system_emoji_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_emoji_font'), glitch_only: true
%h4= t 'appearance.toot_layout'
.fields-group
= ff.input :'web.crop_images', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_crop_images')
%h4= t 'appearance.discovery'
.fields-group

@ -923,7 +923,6 @@ an:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Totz pueden contribuyir.
sensitive_content: Conteniu sensible
toot_layout: Disenyo d'as publicacions
application_mailer:
notification_preferences: Cambiar preferencias de correu electronico
salutation: "%{name}:"

@ -982,7 +982,6 @@ ar:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: يمكن للجميع المساهمة.
sensitive_content: المحتوى الحساس
toot_layout: شكل المنشور
application_mailer:
notification_preferences: تعديل تفضيلات البريد الإلكتروني
salutation: "%{name}،"

@ -439,7 +439,6 @@ ast:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: tol mundu pue collaborar.
sensitive_content: Conteníu sensible
toot_layout: Distribución de los artículos
application_mailer:
notification_preferences: Camudar les preferencies de los mensaxes de corréu electrónicu
applications:

@ -1009,7 +1009,6 @@ be:
guide_link: https://be.crowdin.com/project/mastodon/be
guide_link_text: Кожны можа зрабіць унёсак.
sensitive_content: Далікатны змест
toot_layout: Макет допісу
application_mailer:
notification_preferences: Змяніць налады эл. пошты
salutation: "%{name},"

@ -973,7 +973,6 @@ bg:
guide_link: https://ru.crowdin.com/project/mastodon
guide_link_text: Всеки може да участва.
sensitive_content: Деликатно съдържание
toot_layout: Оформление на публикацията
application_mailer:
notification_preferences: Промяна на предпочитанията за имейл
salutation: "%{name},"

@ -973,7 +973,6 @@ ca:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Tothom hi pot contribuir.
sensitive_content: Contingut sensible
toot_layout: Disseny dels tuts
application_mailer:
notification_preferences: Canvia les preferències de correu
salutation: "%{name},"

@ -571,7 +571,6 @@ ckb:
body: ماستۆدۆن لەلایەن خۆبەخشەوە وەردەگێڕێت.
guide_link_text: هەموو کەسێک دەتوانێت بەشداری بکات.
sensitive_content: ناوەڕۆکی هەستیار
toot_layout: لۆی توت
application_mailer:
notification_preferences: گۆڕینی پەسەندکراوەکانی ئیمەیڵ
salutation: "%{name},"

@ -537,7 +537,6 @@ co:
guide_link: https://fr.crowdin.com/project/mastodon
guide_link_text: Tuttu u mondu pò participà.
sensitive_content: Cuntinutu sensibile
toot_layout: Urganizazione
application_mailer:
notification_preferences: Cambià e priferenze e-mail
salutation: "%{name},"

@ -997,7 +997,6 @@ cs:
guide_link: https://cs.crowdin.com/project/mastodon
guide_link_text: Zapojit se může každý.
sensitive_content: Citlivý obsah
toot_layout: Rozložení příspěvků
application_mailer:
notification_preferences: Změnit předvolby e-mailů
salutation: "%{name},"

@ -1045,7 +1045,6 @@ cy:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Gall pawb gyfrannu.
sensitive_content: Cynnwys sensitif
toot_layout: Cynllun postiad
application_mailer:
notification_preferences: Newid gosodiadau e-bost
salutation: "%{name},"

@ -973,7 +973,6 @@ da:
guide_link: https://da.crowdin.com/project/mastodon
guide_link_text: Alle kan bidrage.
sensitive_content: Sensitivt indhold
toot_layout: Indlægslayout
application_mailer:
notification_preferences: Skift e-mailpræferencer
salutation: "%{name}"

@ -973,7 +973,6 @@ de:
guide_link: https://de.crowdin.com/project/mastodon/de
guide_link_text: Alle können mitmachen und etwas dazu beitragen.
sensitive_content: Inhaltswarnung
toot_layout: Timeline-Layout
application_mailer:
notification_preferences: E-Mail-Einstellungen ändern
salutation: "%{name},"

@ -961,7 +961,6 @@ el:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Μπορεί να συνεισφέρει ο οποιοσδήποτε.
sensitive_content: Ευαίσθητο περιεχόμενο
toot_layout: Διαρρύθμιση αναρτήσεων
application_mailer:
notification_preferences: Αλλαγή προτιμήσεων email
salutation: "%{name},"

@ -973,7 +973,6 @@ en-GB:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Everyone can contribute.
sensitive_content: Sensitive content
toot_layout: Post layout
application_mailer:
notification_preferences: Change e-mail preferences
salutation: "%{name},"

@ -973,7 +973,6 @@ en:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Everyone can contribute.
sensitive_content: Sensitive content
toot_layout: Post layout
application_mailer:
notification_preferences: Change e-mail preferences
salutation: "%{name},"

@ -973,7 +973,6 @@ eo:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Ĉiu povas kontribui.
sensitive_content: Tikla enhavo
toot_layout: Mesaĝo aranĝo
application_mailer:
notification_preferences: Ŝanĝi retmesaĝajn preferojn
salutation: "%{name},"

@ -973,7 +973,6 @@ es-AR:
guide_link: https://es.crowdin.com/project/mastodon
guide_link_text: Todos pueden contribuir.
sensitive_content: Contenido sensible
toot_layout: Diseño del mensaje
application_mailer:
notification_preferences: Cambiar configuración de correo electrónico
salutation: "%{name}:"

@ -973,7 +973,6 @@ es-MX:
guide_link: https://es.crowdin.com/project/mastodon
guide_link_text: Todos pueden contribuir.
sensitive_content: Contenido sensible
toot_layout: Diseño de los toots
application_mailer:
notification_preferences: Cambiar preferencias de correo electrónico
salutation: "%{name}:"

@ -973,7 +973,6 @@ es:
guide_link: https://es.crowdin.com/project/mastodon
guide_link_text: Todos pueden contribuir.
sensitive_content: Contenido sensible
toot_layout: Diseño de las publicaciones
application_mailer:
notification_preferences: Cambiar preferencias de correo electrónico
salutation: "%{name}:"

@ -973,7 +973,6 @@ et:
guide_link: https://crowdin.com/project/mastodon/et
guide_link_text: Panustada võib igaüks!
sensitive_content: Tundlik sisu
toot_layout: Postituse väljanägemine
application_mailer:
notification_preferences: Muuda e-kirjade eelistusi
salutation: "%{name}!"

@ -972,7 +972,6 @@ eu:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Edonork lagundu dezake.
sensitive_content: Eduki hunkigarria
toot_layout: Bidalketen diseinua
application_mailer:
notification_preferences: Aldatu e-mail hobespenak
salutation: "%{name},"

@ -821,7 +821,6 @@ fa:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: همه میتوانند کمک کنند.
sensitive_content: محتوای حساس
toot_layout: آرایش فرسته
application_mailer:
notification_preferences: تغییر ترجیحات ایمیل
salutation: "%{name}،"

@ -973,7 +973,6 @@ fi:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Kaikki voivat osallistua.
sensitive_content: Arkaluonteinen sisältö
toot_layout: Viestin asettelu
application_mailer:
notification_preferences: Muuta sähköpostiasetuksia
salutation: "%{name},"

@ -973,7 +973,6 @@ fo:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Øll kunnu geva íkast.
sensitive_content: Viðkvæmt innihald
toot_layout: Uppseting av postum
application_mailer:
notification_preferences: Broyt teldupostastillingar
salutation: "%{name}"

@ -973,7 +973,6 @@ fr-QC:
guide_link: https://fr.crowdin.com/project/mastodon
guide_link_text: Tout le monde peut y contribuer.
sensitive_content: Contenu sensible
toot_layout: Agencement des messages
application_mailer:
notification_preferences: Modifier les préférences de courriel
salutation: "%{name},"

@ -973,7 +973,6 @@ fr:
guide_link: https://fr.crowdin.com/project/mastodon
guide_link_text: Tout le monde peut y contribuer.
sensitive_content: Contenu sensible
toot_layout: Agencement des messages
application_mailer:
notification_preferences: Modifier les préférences de courriel
salutation: "%{name},"

@ -973,7 +973,6 @@ fy:
guide_link: https://crowdin.com/project/mastodon/fy
guide_link_text: Elkenien kin bydrage.
sensitive_content: Gefoelige ynhâld
toot_layout: Lay-out fan berjochten
application_mailer:
notification_preferences: E-mailynstellingen wizigje
salutation: "%{name},"

@ -1009,7 +1009,6 @@ gd:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: "’S urrainn do neach sam bith cuideachadh."
sensitive_content: Susbaint fhrionasach
toot_layout: Co-dhealbhachd nam postaichean
application_mailer:
notification_preferences: Atharraich roghainnean a’ phuist-d
salutation: "%{name},"

@ -973,7 +973,6 @@ gl:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Todas podemos contribuír.
sensitive_content: Contido sensible
toot_layout: Disposición da publicación
application_mailer:
notification_preferences: Cambiar os axustes de email
salutation: "%{name},"

@ -1009,7 +1009,6 @@ he:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: כולם יכולים לתרום.
sensitive_content: תוכן רגיש
toot_layout: פריסת הודעה
application_mailer:
notification_preferences: שינוי העדפות דוא"ל
salutation: "%{name},"

@ -973,7 +973,6 @@ hu:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Bárki közreműködhet.
sensitive_content: Kényes tartalom
toot_layout: Bejegyzések elrendezése
application_mailer:
notification_preferences: E-mail beállítások módosítása
salutation: "%{name}!"

@ -901,7 +901,6 @@ id:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Siapa saja bisa berkontribusi.
sensitive_content: Konten sensitif
toot_layout: Tata letak kiriman
application_mailer:
notification_preferences: Ubah pilihan email
salutation: "%{name},"

@ -880,7 +880,6 @@ io:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Omnu povas kontributar.
sensitive_content: Sentoza kontenajo
toot_layout: Postostrukturo
application_mailer:
notification_preferences: Chanjez retpostopreferaji
salutation: "%{name},"

@ -975,7 +975,6 @@ is:
guide_link: https://crowdin.com/project/mastodon/is
guide_link_text: Allir geta tekið þátt.
sensitive_content: Viðkvæmt efni
toot_layout: Framsetning færslu
application_mailer:
notification_preferences: Breyta kjörstillingum tölvupósts
salutation: "%{name},"

@ -975,7 +975,6 @@ it:
guide_link: https://it.crowdin.com/project/mastodon
guide_link_text: Tutti possono contribuire.
sensitive_content: Contenuto sensibile
toot_layout: Layout dei toot
application_mailer:
notification_preferences: Cambia preferenze email
salutation: "%{name},"

@ -955,7 +955,6 @@ ja:
guide_link: https://ja.crowdin.com/project/mastodon
guide_link_text: 誰でも参加することができます。
sensitive_content: 閲覧注意コンテンツ
toot_layout: 投稿のレイアウト
application_mailer:
notification_preferences: メール設定の変更
salutation: "%{name}さん"

@ -320,7 +320,6 @@ kk:
confirmation_dialogs: Пікірталас диалогтары
discovery: Пікірталас
sensitive_content: Нәзік контент
toot_layout: Жазба формасы
application_mailer:
notification_preferences: Change e-mail prеferences
settings: 'Change e-mail preferеnces: %{link}'

@ -957,7 +957,6 @@ ko:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: 누구나 기여할 수 있습니다.
sensitive_content: 민감한 내용
toot_layout: 게시물 레이아웃
application_mailer:
notification_preferences: 메일 설정 변경
salutation: "%{name} 님,"

@ -920,7 +920,6 @@ ku:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Herkes dikare beşdar bibe.
sensitive_content: Naveroka hestiyarî
toot_layout: Xêzkirina şandîya
application_mailer:
notification_preferences: Sazkariyên e-nameyê biguherîne
salutation: "%{name},"

@ -979,7 +979,6 @@ lv:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Ikviens var piedalīties.
sensitive_content: Sensitīvs saturs
toot_layout: Ziņas izskats
application_mailer:
notification_preferences: Mainīt e-pasta uztādījumus
salutation: "%{name},"

@ -955,7 +955,6 @@ my:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: ဝငကတယ
sensitive_content: သတရသ အကအရ
toot_layout: အပအဆင
application_mailer:
notification_preferences: သတကက
salutation: "%{name}"

@ -973,7 +973,6 @@ nl:
guide_link: https://crowdin.com/project/mastodon/nl
guide_link_text: Iedereen kan bijdragen.
sensitive_content: Gevoelige inhoud
toot_layout: Lay-out van berichten
application_mailer:
notification_preferences: E-mailvoorkeuren wijzigen
salutation: "%{name},"

@ -965,7 +965,6 @@ nn:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Alle kan bidra.
sensitive_content: Ømtolig innhald
toot_layout: Tutoppsett
application_mailer:
notification_preferences: Endr e-post-innstillingane
salutation: Hei %{name},

@ -914,7 +914,6 @@
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Alle kan bidra.
sensitive_content: Følsomt innhold
toot_layout: Innleggsoppsett
application_mailer:
notification_preferences: Endre E-postinnstillingene
salutation: "%{name},"

@ -459,7 +459,6 @@ oc:
body: Mastodon es traduch per de benevòls.
guide_link_text: Tot lo monde pòt contribuïr.
sensitive_content: Contengut sensible
toot_layout: Disposicion del tut
application_mailer:
notification_preferences: Cambiar las preferéncias de corrièl
salutation: "%{name},"

@ -1009,7 +1009,6 @@ pl:
guide_link: https://pl.crowdin.com/project/mastodon
guide_link_text: Każdy może wnieść swój wkład.
sensitive_content: Wrażliwa zawartość
toot_layout: Wygląd wpisów
application_mailer:
notification_preferences: Zmień ustawienia e-maili
salutation: "%{name},"

@ -973,7 +973,6 @@ pt-BR:
guide_link: https://br.crowdin.com/project/mastodon
guide_link_text: Todos podem contribuir.
sensitive_content: Conteúdo sensível
toot_layout: Formato da publicação
application_mailer:
notification_preferences: Alterar preferências de e-mail
salutation: "%{name},"

@ -973,7 +973,6 @@ pt-PT:
guide_link: https://pt.crowdin.com/project/mastodon/
guide_link_text: Todos podem contribuir.
sensitive_content: Conteúdo problemático
toot_layout: Disposição da publicação
application_mailer:
notification_preferences: Alterar preferências de e-mail
salutation: "%{name},"

@ -408,7 +408,6 @@ ro:
localization:
guide_link_text: Toată lumea poate contribui.
sensitive_content: Conținut sensibil
toot_layout: Aspect postare
application_mailer:
notification_preferences: Modifică preferințe e-mail
settings: 'Modifică preferințe e-mail: %{link}'

@ -1009,7 +1009,6 @@ ru:
guide_link: https://ru.crowdin.com/project/mastodon
guide_link_text: Каждый может внести свой вклад.
sensitive_content: Содержимое деликатного характера
toot_layout: Структура постов
application_mailer:
notification_preferences: Настроить уведомления можно здесь
salutation: "%{name},"

@ -491,7 +491,6 @@ sc:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Chie si siat podet contribuire.
sensitive_content: Cuntenutu sensìbile
toot_layout: Dispositzione de is tuts
application_mailer:
notification_preferences: Muda is preferèntzias de posta
salutation: "%{name},"

@ -913,7 +913,6 @@ sco:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Awbody kin contribute.
sensitive_content: Sensitive content
toot_layout: Post leyoot
application_mailer:
notification_preferences: Chynge email preferences
salutation: "%{name},"

@ -757,7 +757,6 @@ si:
guide_link: https://crowdin.com/project/mastodon
guide_link_text: ම කටම දයක වය හය.
sensitive_content: අනතරගතය
toot_layout: ලස
application_mailer:
notification_preferences: ඊම මනප වනස කරන
salutation: "%{name},"

@ -192,7 +192,6 @@ an:
setting_always_send_emails: Ninviar siempre notificacions per correu
setting_auto_play_gif: Reproducir automaticament los GIFs animaus
setting_boost_modal: Amostrar finestra de confirmación antes de retutar
setting_crop_images: Retallar a 16x9 las imachens d'as publicacions no expandidas
setting_default_language: Idioma de publicación
setting_default_privacy: Privacidat de publicacions
setting_default_sensitive: Marcar siempre imachens como sensibles

@ -200,7 +200,6 @@ ar:
setting_always_send_emails: ارسل إشعارات البريد الإلكتروني دائماً
setting_auto_play_gif: تشغيل تلقائي لِوَسائط جيف المتحركة
setting_boost_modal: إظهار مربع حوار التأكيد قبل إعادة مشاركة أي منشور
setting_crop_images: قص الصور في المنشورات غير الموسعة إلى 16x9
setting_default_language: لغة النشر
setting_default_privacy: خصوصية المنشور
setting_default_sensitive: اعتبر الوسائط دائما كمحتوى حساس

@ -111,7 +111,6 @@ ast:
setting_always_send_emails: Unviar siempres los avisos per corréu electrónicu
setting_auto_play_gif: Reproducir automáticamente los GIFs
setting_boost_modal: Amosar el diálogu de confirmación enantes de compartir un artículu
setting_crop_images: Recortar les imáxenes de los artículos ensin espander a la proporción 16:9
setting_default_language: Llingua de los artículos
setting_default_privacy: Privacidá de los artículos
setting_default_sensitive: Marcar siempres tol conteníu como sensible

@ -200,7 +200,6 @@ be:
setting_always_send_emails: Заўжды дасылаць для апавяшчэнні эл. пошты
setting_auto_play_gif: Аўтапрайграванне анімаваных GIF
setting_boost_modal: Паказваць акно пацвярджэння перад пашырэннем
setting_crop_images: У неразгорнутых допісах абразаць відарысы да 16:9
setting_default_language: Мова допісаў
setting_default_privacy: Прыватнасць допісаў
setting_default_sensitive: Заўсёды пазначаць кантэнт як далікатны

@ -200,7 +200,6 @@ bg:
setting_always_send_emails: Все да се пращат известия по имейла
setting_auto_play_gif: Самопускащи се анимирани гифчета
setting_boost_modal: Показване на прозорец за потвърждение преди подсилване
setting_crop_images: Изрязване на образи в неразгънати публикации до 16x9
setting_default_language: Език на публикуване
setting_default_privacy: Поверителност на публикуване
setting_default_sensitive: Все да се бележи мултимедията като деликатна

@ -200,7 +200,6 @@ ca:
setting_always_send_emails: Envia'm sempre notificacions per correu electrònic
setting_auto_play_gif: Reprodueix automàticament els GIF animats
setting_boost_modal: Mostra la finestra de confirmació abans d'impulsar
setting_crop_images: Retalla les imatges en tuts no ampliats a 16x9
setting_default_language: Llengua dels tuts
setting_default_privacy: Privacitat dels tuts
setting_default_sensitive: Marcar sempre el contingut gràfic com a sensible

@ -136,7 +136,6 @@ ckb:
setting_aggregate_reblogs: گرووپی توتەکان یەکبخە
setting_auto_play_gif: خۆکاربەخشکردنی GIFــەکان
setting_boost_modal: پیشاندانی دیالۆگی دووپاتکردنەوە پێش دوبارە توتاندن
setting_crop_images: لە تووتی نەکراوە،وینەکان لە ئەندازی ۱٦×۹ ببڕە
setting_default_language: زمانی نووسراوەکانتان
setting_default_privacy: چوارچێوەی تایبەتێتی ئێوە
setting_default_sensitive: هەمیشە نیشانکردنی میدیا وەک هەستیار

@ -137,7 +137,6 @@ co:
setting_aggregate_reblogs: Gruppà e spartere indè e linee
setting_auto_play_gif: Lettura autumatica di i GIF animati
setting_boost_modal: Mustrà una cunfirmazione per sparte un statutu
setting_crop_images: Riquatrà i ritratti in 16x9 indè i statuti micca selezziunati
setting_default_language: Lingua di pubblicazione
setting_default_privacy: Cunfidenzialità di i statuti
setting_default_sensitive: Sempre cunsiderà media cum’è sensibili

@ -195,7 +195,6 @@ cs:
setting_always_send_emails: Vždy posílat e-mailová oznámení
setting_auto_play_gif: Automaticky přehrávat animace GIF
setting_boost_modal: Před boostnutím zobrazovat potvrzovací okno
setting_crop_images: Ořezávat obrázky v nerozbalených příspěvcích na 16x9
setting_default_language: Jazyk příspěvků
setting_default_privacy: Soukromí příspěvků
setting_default_sensitive: Vždy označovat média jako citlivá

@ -200,7 +200,6 @@ cy:
setting_always_send_emails: Anfonwch hysbysiadau e-bost bob amser
setting_auto_play_gif: Chwarae GIFs wedi'u hanimeiddio yn awtomatig
setting_boost_modal: Dangos deialog cadarnhau cyn rhoi hwb
setting_crop_images: Tocio delweddau o fewn postiadau nad ydynt wedi'u hehangu i 16x9
setting_default_language: Iaith postio
setting_default_privacy: Preifatrwydd cyhoeddi
setting_default_sensitive: Marcio cyfryngau fel eu bod yn sensitif bob tro

@ -200,7 +200,6 @@ da:
setting_always_send_emails: Send altid en e-mailnotifikationer
setting_auto_play_gif: Autoafspil animerede GIF'er
setting_boost_modal: Vis bekræftelsesdialog inden boosting
setting_crop_images: Beskær billeder i ikke-ekspanderede indlæg til 16x9
setting_default_language: Sprog for indlæg
setting_default_privacy: Fortrolighed for indlæg
setting_default_sensitive: Markér altid medier som sensitive

@ -200,7 +200,6 @@ de:
setting_always_send_emails: Benachrichtigungen immer senden
setting_auto_play_gif: Animierte GIFs automatisch abspielen
setting_boost_modal: Bestätigungsdialog beim Teilen eines Beitrags anzeigen
setting_crop_images: Bilder in nicht ausgeklappten Beiträgen auf 16:9 zuschneiden
setting_default_language: Beitragssprache
setting_default_privacy: Beitragssichtbarkeit
setting_default_sensitive: Eigene Medien immer mit einer Inhaltswarnung versehen

@ -195,7 +195,6 @@ el:
setting_always_send_emails: Πάντα να αποστέλλονται ειδοποίησεις μέσω email
setting_auto_play_gif: Αυτόματη αναπαραγωγή των GIF
setting_boost_modal: Επιβεβαίωση πριν την προώθηση
setting_crop_images: Περιορισμός των εικόνων σε μη-ανεπτυγμένα τουτ σε αναλογία 16x9
setting_default_language: Γλώσσα δημοσιεύσεων
setting_default_privacy: Ιδιωτικότητα δημοσιεύσεων
setting_default_sensitive: Σημείωση όλων των πολυμέσων ως ευαίσθητου περιεχομένου

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save