Merge commit '4b65740722
' into kb_development
This commit is contained in:
commit
83065ff389
398 changed files with 3376 additions and 2449 deletions
|
@ -17,13 +17,16 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
|||
|
||||
if fav
|
||||
@status = fav.status
|
||||
count = [@status.favourites_count - 1, 0].max
|
||||
UnfavouriteWorker.perform_async(current_account.id, @status.id)
|
||||
else
|
||||
@status = Status.find(params[:status_id])
|
||||
count = @status.favourites_count
|
||||
authorize @status, :show?
|
||||
end
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false })
|
||||
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }, attributes_map: { @status.id => { favourites_count: count } })
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: relationships
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
|
|
@ -24,15 +24,18 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
|||
|
||||
if @status
|
||||
authorize @status, :unreblog?
|
||||
@reblog = @status.reblog
|
||||
count = [@reblog.reblogs_count - 1, 0].max
|
||||
@status.discard
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
@reblog = @status.reblog
|
||||
else
|
||||
@reblog = Status.find(params[:status_id])
|
||||
count = @reblog.reblogs_count
|
||||
authorize @reblog, :show?
|
||||
end
|
||||
|
||||
render json: @reblog, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false })
|
||||
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }, attributes_map: { @reblog.id => { reblogs_count: count } })
|
||||
render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
|
|
@ -19,6 +19,7 @@ class Api::V1::TagsController < Api::BaseController
|
|||
|
||||
def unfollow
|
||||
TagFollow.find_by(account: current_account, tag: @tag)&.destroy!
|
||||
TagUnmergeWorker.perform_async(@tag.id, current_account.id)
|
||||
render json: @tag, serializer: REST::TagSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -108,7 +108,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)
|
||||
|
||||
|
|
|
@ -249,6 +249,6 @@ module ApplicationHelper
|
|||
private
|
||||
|
||||
def storage_host_var
|
||||
ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil)
|
||||
ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil) || ENV.fetch('AZURE_ALIAS_HOST', nil)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
module DatabaseHelper
|
||||
def with_read_replica(&block)
|
||||
ApplicationRecord.connected_to(role: :read, prevent_writes: true, &block)
|
||||
ApplicationRecord.connected_to(role: :reading, prevent_writes: true, &block)
|
||||
end
|
||||
|
||||
def with_primary(&block)
|
||||
ApplicationRecord.connected_to(role: :primary, &block)
|
||||
ApplicationRecord.connected_to(role: :writing, &block)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -65,33 +65,6 @@ module StatusesHelper
|
|||
embedded_view? ? '_blank' : nil
|
||||
end
|
||||
|
||||
def style_classes(status, is_predecessor, is_successor, include_threads)
|
||||
classes = ['entry']
|
||||
classes << 'entry-predecessor' if is_predecessor
|
||||
classes << 'entry-reblog' if status.reblog?
|
||||
classes << 'entry-successor' if is_successor
|
||||
classes << 'entry-center' if include_threads
|
||||
classes.join(' ')
|
||||
end
|
||||
|
||||
def microformats_classes(status, is_direct_parent, is_direct_child)
|
||||
classes = []
|
||||
classes << 'p-in-reply-to' if is_direct_parent
|
||||
classes << 'p-repost-of' if status.reblog? && is_direct_parent
|
||||
classes << 'p-comment' if is_direct_child
|
||||
classes.join(' ')
|
||||
end
|
||||
|
||||
def microformats_h_class(status, is_predecessor, is_successor, include_threads)
|
||||
if is_predecessor || status.reblog? || is_successor
|
||||
'h-cite'
|
||||
elsif include_threads
|
||||
''
|
||||
else
|
||||
'h-entry'
|
||||
end
|
||||
end
|
||||
|
||||
def fa_visibility_icon(status)
|
||||
case status.visibility
|
||||
when 'public'
|
||||
|
|
|
@ -23,9 +23,7 @@ export default class ColumnBackButton extends PureComponent {
|
|||
|
||||
if (onClick) {
|
||||
onClick();
|
||||
// Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201
|
||||
// When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location
|
||||
} else if (router.route.location.key) {
|
||||
} else if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
} else {
|
||||
router.history.push('/');
|
||||
|
|
|
@ -63,10 +63,12 @@ class ColumnHeader extends PureComponent {
|
|||
};
|
||||
|
||||
handleBackClick = () => {
|
||||
if (window.history && window.history.state) {
|
||||
this.context.router.history.goBack();
|
||||
const { router } = this.context;
|
||||
|
||||
if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
} else {
|
||||
this.context.router.history.push('/');
|
||||
router.history.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -83,6 +85,7 @@ class ColumnHeader extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { router } = this.context;
|
||||
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
|
||||
const { collapsed, animating } = this.state;
|
||||
|
||||
|
@ -126,7 +129,7 @@ class ColumnHeader extends PureComponent {
|
|||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||
}
|
||||
|
||||
if (!pinned && (multiColumn || showBackButton)) {
|
||||
if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) {
|
||||
backButton = (
|
||||
<button onClick={this.handleBackClick} className='column-header__back-button'>
|
||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||
|
|
|
@ -297,7 +297,7 @@ export default class Dropdown extends PureComponent {
|
|||
onKeyPress: this.handleKeyPress,
|
||||
}) : (
|
||||
<IconButton
|
||||
icon={icon}
|
||||
icon={!open ? icon : 'close'}
|
||||
title={title}
|
||||
active={open}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -114,7 +114,7 @@ export default class IntersectionObserverArticle extends Component {
|
|||
aria-setsize={listLength}
|
||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||
data-id={id}
|
||||
tabIndex={0}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{children && cloneElement(children, { hidden: true })}
|
||||
</article>
|
||||
|
@ -122,7 +122,7 @@ export default class IntersectionObserverArticle extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex={0}>
|
||||
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex={-1}>
|
||||
{children && cloneElement(children, { hidden: false })}
|
||||
</article>
|
||||
);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { debounce } from 'lodash';
|
|||
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
|
||||
import { autoPlayGif, cropImages, displayMedia, displayMediaExpand, useBlurhash } from '../initial_state';
|
||||
import { autoPlayGif, displayMedia, displayMediaExpand, useBlurhash } from '../initial_state';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
|
||||
|
@ -225,7 +225,6 @@ class MediaGallery extends PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
sensitive: PropTypes.bool,
|
||||
standalone: PropTypes.bool,
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
lang: PropTypes.string,
|
||||
size: PropTypes.object,
|
||||
|
@ -239,10 +238,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,
|
||||
|
@ -311,7 +306,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;
|
||||
|
||||
|
@ -319,10 +314,10 @@ 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 maxSize = displayMediaExpand ? 16 : 4;
|
||||
|
@ -330,7 +325,7 @@ class MediaGallery extends PureComponent {
|
|||
const size = media.take(maxSize).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(maxSize).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>
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
import type { PropsWithChildren } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { History } from 'history';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { Router as OriginalRouter } from 'react-router';
|
||||
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
|
||||
const browserHistory = createBrowserHistory();
|
||||
const originalPush = browserHistory.push.bind(browserHistory);
|
||||
interface MastodonLocationState {
|
||||
fromMastodon?: boolean;
|
||||
mastodonModalKey?: string;
|
||||
}
|
||||
|
||||
const browserHistory = createBrowserHistory<
|
||||
MastodonLocationState | undefined
|
||||
>();
|
||||
const originalPush = browserHistory.push.bind(browserHistory);
|
||||
const originalReplace = browserHistory.replace.bind(browserHistory);
|
||||
|
||||
browserHistory.push = (path: string, state?: MastodonLocationState) => {
|
||||
state = state ?? {};
|
||||
state.fromMastodon = true;
|
||||
|
||||
browserHistory.push = (path: string, state: History.LocationState) => {
|
||||
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
|
||||
originalPush(`/deck${path}`, state);
|
||||
} else {
|
||||
|
@ -18,6 +28,19 @@ browserHistory.push = (path: string, state: History.LocationState) => {
|
|||
}
|
||||
};
|
||||
|
||||
browserHistory.replace = (path: string, state?: MastodonLocationState) => {
|
||||
if (browserHistory.location.state?.fromMastodon) {
|
||||
state = state ?? {};
|
||||
state.fromMastodon = true;
|
||||
}
|
||||
|
||||
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
|
||||
originalReplace(`/deck${path}`, state);
|
||||
} else {
|
||||
originalReplace(path, state);
|
||||
}
|
||||
};
|
||||
|
||||
export const Router: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return <OriginalRouter history={browserHistory}>{children}</OriginalRouter>;
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
@ -198,17 +197,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');
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
renderLoadingVideoPlayer () {
|
||||
return <div className='video-player' style={{ height: '110px' }} />;
|
||||
}
|
||||
renderLoadingMediaGallery = () => {
|
||||
return (
|
||||
<div className='media-gallery' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
|
||||
);
|
||||
};
|
||||
|
||||
renderLoadingAudioPlayer () {
|
||||
return <div className='audio-player' style={{ height: '110px' }} />;
|
||||
}
|
||||
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();
|
||||
|
@ -445,18 +462,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');
|
||||
|
||||
|
@ -494,11 +504,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}
|
||||
|
@ -527,7 +537,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}
|
||||
|
|
|
@ -34,7 +34,7 @@ const messages = defineMessages({
|
|||
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
|
||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
emojiReaction: { id: 'status.emoji_reaction', defaultMessage: 'Stamp' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
||||
|
|
|
@ -57,7 +57,7 @@ const messages = defineMessages({
|
|||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' },
|
||||
|
|
|
@ -48,7 +48,7 @@ const messages = defineMessages({
|
|||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,7 +13,7 @@ const messages = defineMessages({
|
|||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
reaction_deck: { id: 'navigation_bar.reaction_deck', defaultMessage: 'Reaction deck' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
emoji_reactions: { id: 'navigation_bar.emoji_reactions', defaultMessage: 'Stamps' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
|
||||
|
|
|
@ -8,7 +8,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
|
||||
import ActionBar from './action_bar';
|
||||
|
||||
|
@ -21,23 +20,27 @@ export default class NavigationBar extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const username = this.props.account.get('acct')
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Link to={`/@${this.props.account.get('acct')}`}>
|
||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
|
||||
<Link to={`/@${username}`}>
|
||||
<span style={{ display: 'none' }}>{username}</span>
|
||||
<Avatar account={this.props.account} size={46} />
|
||||
</Link>
|
||||
|
||||
<div className='navigation-bar__profile'>
|
||||
<Link to={`/@${this.props.account.get('acct')}`}>
|
||||
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
|
||||
</Link>
|
||||
<span>
|
||||
<Link to={`/@${username}`}>
|
||||
<strong className='navigation-bar__profile-account'>@{username}</strong>
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
||||
<span>
|
||||
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='navigation-bar__actions'>
|
||||
<IconButton className='close' title='' icon='close' onClick={this.props.onClose} />
|
||||
<ActionBar account={this.props.account} onLogout={this.props.onLogout} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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')}
|
||||
|
|
|
@ -47,7 +47,7 @@ class Statuses extends PureComponent {
|
|||
return (
|
||||
<>
|
||||
<DismissableBanner id='explore/statuses'>
|
||||
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favourites are ranked higher.' />
|
||||
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' />
|
||||
</DismissableBanner>
|
||||
|
||||
<StatusList
|
||||
|
|
|
@ -18,7 +18,7 @@ import Column from 'mastodon/features/ui/components/column';
|
|||
import { getStatusList } from 'mastodon/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
|
||||
heading: { id: 'column.favourites', defaultMessage: 'Favorites' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -74,7 +74,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite posts yet. When you favourite one, it will show up here." />;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favorite posts yet. When you favorite one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
||||
|
@ -86,7 +86,6 @@ class Favourites extends ImmutablePureComponent {
|
|||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
<StatusList
|
||||
|
|
|
@ -61,7 +61,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this post yet. When someone does, they will show up here.' />;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favorited this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn}>
|
||||
|
|
|
@ -32,7 +32,7 @@ const messages = defineMessages({
|
|||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
|
|
|
@ -116,8 +116,8 @@ class InteractionModal extends PureComponent {
|
|||
break;
|
||||
case 'favourite':
|
||||
icon = <Icon id='star' />;
|
||||
title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favourite {name}'s post" values={{ name }} />;
|
||||
actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.' />;
|
||||
title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favorite {name}'s post" values={{ name }} />;
|
||||
actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.' />;
|
||||
break;
|
||||
case 'emoji_reaction':
|
||||
icon = <Icon id='smile-o' />;
|
||||
|
@ -163,7 +163,7 @@ class InteractionModal extends PureComponent {
|
|||
|
||||
<div className='interaction-modal__choices__choice'>
|
||||
<h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3>
|
||||
<p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.' /></p>
|
||||
<p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Copy and paste this URL into the search field of your favorite Mastodon app or the web interface of your Mastodon server.' /></p>
|
||||
<Copypaste value={url} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -54,7 +54,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
</tr>
|
||||
<tr>
|
||||
<td><kbd>f</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to favourite' /></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to favorite' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>b</kbd></td>
|
||||
|
@ -138,7 +138,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>f</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open favourites list' /></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open favorites list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>e</kbd></td>
|
||||
|
|
|
@ -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 />
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ export default class ColumnSettings extends PureComponent {
|
|||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-favourite'>
|
||||
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
|
||||
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favorites:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Icon } from 'mastodon/components/icon';
|
|||
|
||||
const tooltips = defineMessages({
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
|
||||
emojiReactions: { id: 'notifications.filter.emoji_reactions', defaultMessage: 'Stamps' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
status_references: { id: 'notifications.filter.status_references', defaultMessage: 'Status references' },
|
||||
|
|
|
@ -21,7 +21,7 @@ import FollowRequestContainer from '../containers/follow_request_container';
|
|||
import Report from './report';
|
||||
|
||||
const messages = defineMessages({
|
||||
favourite: { id: 'notification.favourite', defaultMessage: '{name} favourited your status' },
|
||||
favourite: { id: 'notification.favourite', defaultMessage: '{name} favorited your status' },
|
||||
emojiReaction: { id: 'notification.emoji_reaction', defaultMessage: '{name} reacted your status with emoji' },
|
||||
follow: { id: 'notification.follow', defaultMessage: '{name} followed you' },
|
||||
ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' },
|
||||
|
@ -201,7 +201,7 @@ class Notification extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
||||
<FormattedMessage id='notification.favourite' defaultMessage='{name} favorited your status' values={{ name: link }} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ const messages = defineMessages({
|
|||
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
|
||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
|
|
|
@ -26,7 +26,7 @@ const messages = defineMessages({
|
|||
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
|
||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -122,8 +122,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;
|
||||
|
||||
|
@ -147,7 +169,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]);
|
||||
|
@ -178,13 +200,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}
|
||||
|
|
|
@ -38,7 +38,7 @@ const messages = defineMessages({
|
|||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
});
|
||||
|
|
|
@ -75,7 +75,7 @@ const messages = defineMessages({
|
|||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
||||
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
|
||||
statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}' },
|
||||
|
@ -208,9 +208,9 @@ class Status extends ImmutablePureComponent {
|
|||
dispatch: PropTypes.func.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
isLoading: PropTypes.bool,
|
||||
ancestorsIds: ImmutablePropTypes.list,
|
||||
descendantsIds: ImmutablePropTypes.list,
|
||||
referenceIds: ImmutablePropTypes.list,
|
||||
ancestorsIds: ImmutablePropTypes.list.isRequired,
|
||||
descendantsIds: ImmutablePropTypes.list.isRequired,
|
||||
referenceIds: ImmutablePropTypes.list.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
askReplyConfirmation: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
@ -237,14 +237,9 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||
this._scrolledIntoView = false;
|
||||
this.props.dispatch(fetchStatus(nextProps.params.statusId));
|
||||
}
|
||||
|
||||
if (nextProps.params.statusId && nextProps.ancestorsIds.size > this.props.ancestorsIds.size) {
|
||||
this._scrolledIntoView = false;
|
||||
}
|
||||
|
||||
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
|
||||
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
|
||||
}
|
||||
|
@ -625,20 +620,23 @@ class Status extends ImmutablePureComponent {
|
|||
this.node = c;
|
||||
};
|
||||
|
||||
componentDidUpdate () {
|
||||
if (this._scrolledIntoView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status, ancestorsIds } = this.props;
|
||||
|
||||
if (status && ancestorsIds && ancestorsIds.size > 0) {
|
||||
const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
|
||||
componentDidUpdate (prevProps) {
|
||||
const { status, ancestorsIds, multiColumn } = this.props;
|
||||
|
||||
if (status && (ancestorsIds.size > prevProps.ancestorsIds.size || prevProps.status?.get('id') !== status.get('id'))) {
|
||||
window.requestAnimationFrame(() => {
|
||||
element.scrollIntoView(true);
|
||||
this.node?.querySelector('.detailed-status__wrapper')?.scrollIntoView(true);
|
||||
|
||||
// In the single-column interface, `scrollIntoView` will put the post behind the header,
|
||||
// so compensate for that.
|
||||
if (!multiColumn) {
|
||||
const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom;
|
||||
if (offset) {
|
||||
const scrollingElement = document.scrollingElement || document.body;
|
||||
scrollingElement.scrollBy(0, -offset);
|
||||
}
|
||||
}
|
||||
});
|
||||
this._scrolledIntoView = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -24,7 +24,7 @@ const messages = defineMessages({
|
|||
local: { id: 'column.local', defaultMessage: 'Local' },
|
||||
firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
||||
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
|
|
|
@ -35,7 +35,7 @@ const SignInBanner = () => {
|
|||
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Login to follow profiles or hashtags, favorite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
</div>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -190,6 +190,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
|
||||
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
||||
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}
|
||||
{!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null}
|
||||
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
|
@ -493,10 +494,12 @@ class UI extends PureComponent {
|
|||
};
|
||||
|
||||
handleHotkeyBack = () => {
|
||||
if (window.history && window.history.state) {
|
||||
this.context.router.history.goBack();
|
||||
const { router } = this.context;
|
||||
|
||||
if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
} else {
|
||||
this.context.router.history.push('/');
|
||||
router.history.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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%' }}
|
||||
/>}
|
||||
>
|
||||
<Blurhash
|
||||
hash={blurhash}
|
||||
className={classNames('media-gallery__preview', {
|
||||
'media-gallery__preview--hidden': revealed,
|
||||
})}
|
||||
dummy={!useBlurhash}
|
||||
/>
|
||||
|
||||
<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>
|
||||
{(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('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 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__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}%` }} />
|
||||
|
||||
<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>
|
||||
)}
|
||||
<span
|
||||
className={classNames('video-player__seek__handle', { active: dragging })}
|
||||
tabIndex={0}
|
||||
style={{ left: `${progress}%` }}
|
||||
onKeyDown={this.handleVideoKeyDown}
|
||||
/>
|
||||
</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-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>
|
||||
|
||||
<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
|
||||
|
@ -114,7 +113,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');
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Схаваць апавяшчэнні ад гэтага карыстальніка?",
|
||||
"mute_modal.indefinite": "Бестэрмінова",
|
||||
"navigation_bar.about": "Пра нас",
|
||||
"navigation_bar.advanced_interface": "Ireki web interfaze aurreratuan",
|
||||
"navigation_bar.blocks": "Заблакаваныя карыстальнікі",
|
||||
"navigation_bar.bookmarks": "Закладкі",
|
||||
"navigation_bar.community_timeline": "Лакальная стужка",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Скривате ли известията от потребителя?",
|
||||
"mute_modal.indefinite": "Неопределено",
|
||||
"navigation_bar.about": "Относно",
|
||||
"navigation_bar.advanced_interface": "Отваряне в разширен уебинтерфейс",
|
||||
"navigation_bar.blocks": "Блокирани потребители",
|
||||
"navigation_bar.bookmarks": "Отметки",
|
||||
"navigation_bar.community_timeline": "Локална часова ос",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"account.badges.group": "দল",
|
||||
"account.block": "@{name} কে ব্লক করো",
|
||||
"account.block_domain": "{domain} থেকে সব লুকাও",
|
||||
"account.block_short": "অবরোধ",
|
||||
"account.blocked": "অবরুদ্ধ",
|
||||
"account.browse_more_on_origin_server": "মূল প্রোফাইলটিতে আরও ব্রাউজ করুন",
|
||||
"account.cancel_follow_request": "অনুসরণ অনুরোধ প্রত্যাহার করুন",
|
||||
|
@ -48,7 +49,10 @@
|
|||
"account.mention": "@{name} কে উল্লেখ করুন",
|
||||
"account.moved_to": "{name} নির্দেশ করেছে যে তাদের নতুন অ্যাকাউন্ট এখন হলো:",
|
||||
"account.mute": "@{name} কে নিঃশব্দ করুন",
|
||||
"account.mute_notifications_short": "বিজ্ঞপ্তি নিংশব্দ",
|
||||
"account.mute_short": "নিঃশব্দ",
|
||||
"account.muted": "নিঃশব্দ",
|
||||
"account.no_bio": "কোনো বর্ণনা দেওয়া হয়নি।",
|
||||
"account.open_original_page": "মূল পৃষ্ঠা খুলুন",
|
||||
"account.posts": "টুট",
|
||||
"account.posts_with_replies": "টুট এবং মতামত",
|
||||
|
@ -64,6 +68,7 @@
|
|||
"account.unendorse": "আপনার নিজের পাতায় এটা দেখবেন না",
|
||||
"account.unfollow": "অনুসরণ করো না",
|
||||
"account.unmute": "@{name} র কার্যকলাপ আবার দেখুন",
|
||||
"account.unmute_notifications_short": "বিজ্ঞপ্তি শব্দ চালু করো",
|
||||
"account.unmute_short": "আনমিউট করুন",
|
||||
"account_note.placeholder": "নোট যোগ করতে ক্লিক করুন",
|
||||
"admin.dashboard.daily_retention": "সাইন আপের পর দিনে ব্যবহারকারীর ধরে রাখার হার",
|
||||
|
@ -353,6 +358,7 @@
|
|||
"onboarding.steps.setup_profile.title": "Customize your profile",
|
||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
||||
"onboarding.steps.share_profile.title": "Share your profile",
|
||||
"onboarding.tips.accounts_from_other_servers": "<strong>তুমি কি জানতে?</strong> যেহেতু মাস্টোডন বিকেন্দ্রীভূত, কিছু অ্যাকাউন্ট তোমার নিজের ছাড়া অন্য কোনো সার্ভারে থাকতে পারে। অথচ তুমি তাদের সাথে কোনো সমস্যা ছাড়াই কথা বলতে পারছো! তাদের সার্ভার তাদের ব্যবহারকারী নামের দ্বিতীয় অর্ধাংশ!",
|
||||
"poll.closed": "বন্ধ",
|
||||
"poll.refresh": "বদলেছে কিনা দেখতে",
|
||||
"poll.total_people": "{count, plural, one {# ব্যক্তি} other {# ব্যক্তি}}",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Cuddio hysbysiadau gan y defnyddiwr hwn?",
|
||||
"mute_modal.indefinite": "Parhaus",
|
||||
"navigation_bar.about": "Ynghylch",
|
||||
"navigation_bar.advanced_interface": "Abrir coa interface web avanzada",
|
||||
"navigation_bar.blocks": "Defnyddwyr wedi eu blocio",
|
||||
"navigation_bar.bookmarks": "Llyfrnodau",
|
||||
"navigation_bar.community_timeline": "Ffrwd leol",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"account.badges.group": "Gruppe",
|
||||
"account.block": "Blokér @{name}",
|
||||
"account.block_domain": "Blokér domænet {domain}",
|
||||
"account.block_short": "Bloker",
|
||||
"account.blocked": "Blokeret",
|
||||
"account.browse_more_on_origin_server": "Se mere på den oprindelige profil",
|
||||
"account.cancel_follow_request": "Annullér anmodning om at følge",
|
||||
|
@ -48,7 +49,10 @@
|
|||
"account.mention": "Nævn @{name}",
|
||||
"account.moved_to": "{name} har angivet, at vedkommendes nye konto nu er:",
|
||||
"account.mute": "Skjul @{name}",
|
||||
"account.mute_notifications_short": "Slå lyden fra for notifikationer",
|
||||
"account.mute_short": "Skjul (mute)",
|
||||
"account.muted": "Skjult (muted)",
|
||||
"account.no_bio": "Ingen beskrivelse til rådighed.",
|
||||
"account.open_original_page": "Åbn oprindelig side",
|
||||
"account.posts": "Indlæg",
|
||||
"account.posts_with_replies": "Indlæg og svar",
|
||||
|
@ -64,6 +68,7 @@
|
|||
"account.unendorse": "Fjern visning på din profil",
|
||||
"account.unfollow": "Følg ikke længere",
|
||||
"account.unmute": "Vis @{name} igen (unmute)",
|
||||
"account.unmute_notifications_short": "Slå lyden fra for notifikationer",
|
||||
"account.unmute_short": "Vis igen (unmute)",
|
||||
"account_note.placeholder": "Klik for at tilføje notat",
|
||||
"admin.dashboard.daily_retention": "Brugerfastholdelsesrate per dag efter tilmelding",
|
||||
|
@ -71,6 +76,10 @@
|
|||
"admin.dashboard.retention.average": "Gennemsnitlig",
|
||||
"admin.dashboard.retention.cohort": "Tilmeldingsmåned",
|
||||
"admin.dashboard.retention.cohort_size": "Nye brugere",
|
||||
"admin.impact_report.instance_accounts": "Konti profiler, som dette ville slette",
|
||||
"admin.impact_report.instance_followers": "Følgere vores brugere ville miste",
|
||||
"admin.impact_report.instance_follows": "Følgere deres brugere ville miste",
|
||||
"admin.impact_report.title": "Resumé af virkninger",
|
||||
"alert.rate_limited.message": "Forsøg igen efter {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Hastighedsbegrænset",
|
||||
"alert.unexpected.message": "En uventet fejl opstod.",
|
||||
|
@ -266,7 +275,7 @@
|
|||
"firehose.remote": "Andre servere",
|
||||
"follow_request.authorize": "Godkend",
|
||||
"follow_request.reject": "Afvis",
|
||||
"follow_requests.unlocked_explanation": "Selvom din konto ikke er låst, antog {domain}-personalet, at du måske vil gennemgå dine anmodninger manuelt.",
|
||||
"follow_requests.unlocked_explanation": "Selvom din konto ikke er låst, synes {domain}-personalet, du måske bør gennemgå disse anmodninger manuelt.",
|
||||
"followed_tags": "Hashtag, som følges",
|
||||
"footer.about": "Om",
|
||||
"footer.directory": "Profiloversigt",
|
||||
|
@ -289,9 +298,13 @@
|
|||
"hashtag.column_settings.tag_toggle": "Inkludér ekstra tags for denne kolonne",
|
||||
"hashtag.follow": "Følg hashtag",
|
||||
"hashtag.unfollow": "Stop med at følge hashtag",
|
||||
"home.actions.go_to_explore": "Se, hvad som trender",
|
||||
"home.actions.go_to_suggestions": "Find nogle personer at følge",
|
||||
"home.column_settings.basic": "Grundlæggende",
|
||||
"home.column_settings.show_reblogs": "Vis boosts",
|
||||
"home.column_settings.show_replies": "Vis svar",
|
||||
"home.explore_prompt.body": "Dit hjem feed vil have en blanding af indlæg fra de hashtags du har valgt at følge, de personer, du har valgt at følge, og de indlæg, de booste. Det ser temmelig stille lige nu, så hvordan vi:",
|
||||
"home.explore_prompt.title": "Dette er din hjemmebase i Mastodon.",
|
||||
"home.hide_announcements": "Skjul bekendtgørelser",
|
||||
"home.show_announcements": "Vis bekendtgørelser",
|
||||
"interaction_modal.description.favourite": "Med en konto på Mastodon kan dette indlæg gøres til favorit for at lade forfatteren vide, at det værdsættes, samt gemme det til senere.",
|
||||
|
@ -355,6 +368,7 @@
|
|||
"lists.delete": "Slet liste",
|
||||
"lists.edit": "Redigér liste",
|
||||
"lists.edit.submit": "Skift titel",
|
||||
"lists.exclusive": "Skjul disse indlæg hjemmefra",
|
||||
"lists.new.create": "Tilføj liste",
|
||||
"lists.new.title_placeholder": "Ny listetitel",
|
||||
"lists.replies_policy.followed": "Enhver bruger, der følges",
|
||||
|
@ -371,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Skjul notifikationer fra denne bruger?",
|
||||
"mute_modal.indefinite": "Tidsubegrænset",
|
||||
"navigation_bar.about": "Om",
|
||||
"navigation_bar.advanced_interface": "Åbn i avanceret webgrænseflade",
|
||||
"navigation_bar.blocks": "Blokerede brugere",
|
||||
"navigation_bar.bookmarks": "Bogmærker",
|
||||
"navigation_bar.community_timeline": "Lokal tidslinje",
|
||||
|
@ -528,6 +543,8 @@
|
|||
"report.placeholder": "Yderligere kommentarer",
|
||||
"report.reasons.dislike": "Jeg bryder mig ikke om det",
|
||||
"report.reasons.dislike_description": "Det er ikke noget, man ønsker at se",
|
||||
"report.reasons.legal": "Det er ulovligt",
|
||||
"report.reasons.legal_description": "Du mener, at det er i strid med lovgivningen i dit eller serverens land",
|
||||
"report.reasons.other": "Det er noget andet",
|
||||
"report.reasons.other_description": "Problemet passer ikke ind i andre kategorier",
|
||||
"report.reasons.spam": "Det er spam",
|
||||
|
@ -547,6 +564,7 @@
|
|||
"report.unfollow": "Følg ikke længere @{name}",
|
||||
"report.unfollow_explanation": "Du følger denne konto. For ikke længere at se vedkommendes indlæg i dit hjemmefeed, kan du stoppe med at følge dem.",
|
||||
"report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} poster}} vedhæftet",
|
||||
"report_notification.categories.legal": "Juridisk",
|
||||
"report_notification.categories.other": "Andre",
|
||||
"report_notification.categories.spam": "Spam",
|
||||
"report_notification.categories.violation": "Regelovertrædelse",
|
||||
|
|
|
@ -135,6 +135,8 @@
|
|||
"community.column_settings.remote_only": "Remote only",
|
||||
"compose.language.change": "Change language",
|
||||
"compose.language.search": "Search languages...",
|
||||
"compose.published.body": "Příspěvek zveřejněn.",
|
||||
"compose.published.open": "Open",
|
||||
"compose_form.direct_message_warning_learn_more": "Learn more",
|
||||
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any sensitive information over Mastodon.",
|
||||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is not public. Only public posts can be searched by hashtag.",
|
||||
|
@ -383,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
||||
"mute_modal.indefinite": "Indefinite",
|
||||
"navigation_bar.about": "About",
|
||||
"navigation_bar.advanced_interface": "Ireki web interfaze aurreratuan",
|
||||
"navigation_bar.blocks": "Blocked users",
|
||||
"navigation_bar.bookmarks": "Bookmarks",
|
||||
"navigation_bar.community_timeline": "Local timeline",
|
||||
|
@ -470,7 +473,7 @@
|
|||
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
|
||||
"onboarding.start.skip": "Want to skip right ahead?",
|
||||
"onboarding.start.title": "You've made it!",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.body": "Following interesting people is what Mastodon is all about.",
|
||||
"onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
|
||||
"onboarding.steps.publish_status.body": "Say hello to the World.",
|
||||
"onboarding.steps.publish_status.title": "Make your first post",
|
||||
|
@ -616,6 +619,8 @@
|
|||
"status.history.created": "{name} created {date}",
|
||||
"status.history.edited": "{name} edited {date}",
|
||||
"status.load_more": "Load more",
|
||||
"status.media.open": "Klikka fyri at lata upp",
|
||||
"status.media.show": "Klik om te toanen",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Mention @{name}",
|
||||
"status.more": "More",
|
||||
|
@ -646,6 +651,7 @@
|
|||
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}",
|
||||
"status.translate": "Translate",
|
||||
"status.translated_from_with": "Translated from {lang} using {provider}",
|
||||
"status.uncached_media_warning": "A vista previa non está dispoñíble",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
"column.direct": "Private mentions",
|
||||
"column.directory": "Browse profiles",
|
||||
"column.domain_blocks": "Blocked domains",
|
||||
"column.favourites": "Favourites",
|
||||
"column.favourites": "Favorites",
|
||||
"column.firehose": "Live feeds",
|
||||
"column.follow_requests": "Follow requests",
|
||||
"column.home": "Home",
|
||||
|
@ -189,7 +189,7 @@
|
|||
"confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
|
||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||
"confirmations.redraft.confirm": "Delete & redraft",
|
||||
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||
"confirmations.reply.confirm": "Reply",
|
||||
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
|
@ -210,7 +210,7 @@
|
|||
"dismissable_banner.community_timeline": "These are the most recent public posts from people whose accounts are hosted by {domain}.",
|
||||
"dismissable_banner.dismiss": "Dismiss",
|
||||
"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_statuses": "These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites 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 the social web that people on {domain} follow.",
|
||||
"embed.instructions": "Embed this post on your website by copying the code below.",
|
||||
|
@ -239,8 +239,8 @@
|
|||
"empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.",
|
||||
"empty_column.domain_blocks": "There are no blocked domains yet.",
|
||||
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
|
||||
"empty_column.favourited_statuses": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
|
||||
"empty_column.favourites": "No one has favourited this post yet. When someone does, they will show up here.",
|
||||
"empty_column.favourited_statuses": "You don't have any favorite posts yet. When you favorite one, it will show up here.",
|
||||
"empty_column.favourites": "No one has favorited this post yet. When someone does, they will show up here.",
|
||||
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
|
||||
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
|
@ -315,15 +315,15 @@
|
|||
"home.explore_prompt.title": "This is your home base within Mastodon.",
|
||||
"home.hide_announcements": "Hide announcements",
|
||||
"home.show_announcements": "Show announcements",
|
||||
"interaction_modal.description.favourite": "With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.",
|
||||
"interaction_modal.description.favourite": "With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.",
|
||||
"interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
|
||||
"interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
|
||||
"interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
|
||||
"interaction_modal.on_another_server": "On a different server",
|
||||
"interaction_modal.on_this_server": "On this server",
|
||||
"interaction_modal.other_server_instructions": "Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.",
|
||||
"interaction_modal.other_server_instructions": "Copy and paste this URL into the search field of your favorite Mastodon app or the web interface of your Mastodon server.",
|
||||
"interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
|
||||
"interaction_modal.title.favourite": "Favourite {name}'s post",
|
||||
"interaction_modal.title.favourite": "Favorite {name}'s post",
|
||||
"interaction_modal.title.follow": "Follow {name}",
|
||||
"interaction_modal.title.reblog": "Boost {name}'s post",
|
||||
"interaction_modal.title.reply": "Reply to {name}'s post",
|
||||
|
@ -339,8 +339,8 @@
|
|||
"keyboard_shortcuts.direct": "to open private mentions column",
|
||||
"keyboard_shortcuts.down": "Move down in the list",
|
||||
"keyboard_shortcuts.enter": "Open post",
|
||||
"keyboard_shortcuts.favourite": "Favourite post",
|
||||
"keyboard_shortcuts.favourites": "Open favourites list",
|
||||
"keyboard_shortcuts.favourite": "Favorite post",
|
||||
"keyboard_shortcuts.favourites": "Open favorites list",
|
||||
"keyboard_shortcuts.federated": "Open federated timeline",
|
||||
"keyboard_shortcuts.heading": "Keyboard shortcuts",
|
||||
"keyboard_shortcuts.home": "Open home timeline",
|
||||
|
@ -371,6 +371,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.antennas": "Related antennas",
|
||||
|
@ -405,7 +406,7 @@
|
|||
"navigation_bar.domain_blocks": "Blocked domains",
|
||||
"navigation_bar.edit_profile": "Edit profile",
|
||||
"navigation_bar.explore": "Explore",
|
||||
"navigation_bar.favourites": "Favourites",
|
||||
"navigation_bar.favourites": "Favorites",
|
||||
"navigation_bar.filters": "Muted words",
|
||||
"navigation_bar.follow_requests": "Follow requests",
|
||||
"navigation_bar.followed_tags": "Followed hashtags",
|
||||
|
@ -424,7 +425,7 @@
|
|||
"notification.admin.report": "{name} reported {target}",
|
||||
"notification.admin.sign_up": "{name} signed up",
|
||||
"notification.emoji_reaction": "{name} reacted your post with emoji",
|
||||
"notification.favourite": "{name} favourited your post",
|
||||
"notification.favourite": "{name} favorited your post",
|
||||
"notification.follow": "{name} followed you",
|
||||
"notification.follow_request": "{name} has requested to follow you",
|
||||
"notification.mention": "{name} mentioned you",
|
||||
|
@ -439,7 +440,7 @@
|
|||
"notifications.column_settings.admin.report": "New reports:",
|
||||
"notifications.column_settings.admin.sign_up": "New sign-ups:",
|
||||
"notifications.column_settings.alert": "Desktop notifications",
|
||||
"notifications.column_settings.favourite": "Favourites:",
|
||||
"notifications.column_settings.favourite": "Favorites:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Show filter bar",
|
||||
|
@ -457,7 +458,7 @@
|
|||
"notifications.column_settings.update": "Edits:",
|
||||
"notifications.filter.all": "All",
|
||||
"notifications.filter.boosts": "Boosts",
|
||||
"notifications.filter.favourites": "Favourites",
|
||||
"notifications.filter.favourites": "Favorites",
|
||||
"notifications.filter.follows": "Follows",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
"notifications.filter.polls": "Poll results",
|
||||
|
@ -623,7 +624,7 @@
|
|||
"server_banner.server_stats": "Server stats:",
|
||||
"sign_in_banner.create_account": "Create account",
|
||||
"sign_in_banner.sign_in": "Login",
|
||||
"sign_in_banner.text": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
||||
"sign_in_banner.text": "Login to follow profiles or hashtags, favorite, share and reply to posts. You can also interact from your account on a different server.",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_domain": "Open moderation interface for {domain}",
|
||||
"status.admin_status": "Open this post in the moderation interface",
|
||||
|
@ -643,7 +644,7 @@
|
|||
"status.emoji_reaction": "Stamp",
|
||||
"status.emoji_reaction.pick": "Pick stamp",
|
||||
"status.expiration.add": "Set status expired time",
|
||||
"status.favourite": "Favourite",
|
||||
"status.favourite": "Favorite",
|
||||
"status.filter": "Filter this post",
|
||||
"status.filtered": "Filtered",
|
||||
"status.hide": "Hide post",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "¿Ocultar notificaciones de este usuario?",
|
||||
"mute_modal.indefinite": "Indefinida",
|
||||
"navigation_bar.about": "Acerca de",
|
||||
"navigation_bar.advanced_interface": "Abrir en la interfaz web avanzada",
|
||||
"navigation_bar.blocks": "Usuarios bloqueados",
|
||||
"navigation_bar.bookmarks": "Marcadores",
|
||||
"navigation_bar.community_timeline": "Cronología local",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Kas peita teated sellelt kasutajalt?",
|
||||
"mute_modal.indefinite": "Lõpmatu",
|
||||
"navigation_bar.about": "Teave",
|
||||
"navigation_bar.advanced_interface": "Ireki web interfaze aurreratuan",
|
||||
"navigation_bar.blocks": "Blokeeritud kasutajad",
|
||||
"navigation_bar.bookmarks": "Järjehoidjad",
|
||||
"navigation_bar.community_timeline": "Kohalik ajajoon",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.block": "مسدود کردن @{name}",
|
||||
"account.block_domain": "مسدود کردن دامنهٔ {domain}",
|
||||
"account.block_short": "انسداد",
|
||||
"account.blocked": "مسدود",
|
||||
"account.blocked": "مسدود شده",
|
||||
"account.browse_more_on_origin_server": "مرور بیشتر روی نمایهٔ اصلی",
|
||||
"account.cancel_follow_request": "رد کردن درخواست پیگیری",
|
||||
"account.direct": "خصوصی از @{name} نام ببرید",
|
||||
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "نهفتن آگاهیها از این کاربر؟",
|
||||
"mute_modal.indefinite": "نامعلوم",
|
||||
"navigation_bar.about": "درباره",
|
||||
"navigation_bar.advanced_interface": "بازکردن در رابط کاربری وب پیشرفته",
|
||||
"navigation_bar.blocks": "کاربران مسدود شده",
|
||||
"navigation_bar.bookmarks": "نشانکها",
|
||||
"navigation_bar.community_timeline": "خط زمانی محلّی",
|
||||
|
|
|
@ -204,6 +204,7 @@
|
|||
"dismissable_banner.explore_links": "Näistä uutisista puhuvat ihmiset juuri nyt tällä ja muilla hajautetun verkon palvelimilla.",
|
||||
"dismissable_banner.explore_statuses": "Nämä viestit juuri nyt tältä ja muilta hajautetun verkon palvelimilta ovat saamassa vetoa tältä palvelimelta.",
|
||||
"dismissable_banner.explore_tags": "Nämä aihetunnisteet saavat juuri nyt vetovoimaa tällä ja muilla hajautetun verkon palvelimilla olevien ihmisten keskuudessa.",
|
||||
"dismissable_banner.public_timeline": "Nämä ovat viimeisimmät julkiset viestit sosiaalisen verkon ihmisiltä, joita {domain} käyttäjät seuraa.",
|
||||
"embed.instructions": "Upota julkaisu verkkosivullesi kopioimalla alla oleva koodi.",
|
||||
"embed.preview": "Se tulee näyttämään tältä:",
|
||||
"emoji_button.activity": "Aktiviteetit",
|
||||
|
@ -270,6 +271,8 @@
|
|||
"filter_modal.select_filter.title": "Suodata tämä viesti",
|
||||
"filter_modal.title.status": "Suodata viesti",
|
||||
"firehose.all": "Kaikki",
|
||||
"firehose.local": "Tämä palvelin",
|
||||
"firehose.remote": "Muut palvelimet",
|
||||
"follow_request.authorize": "Valtuuta",
|
||||
"follow_request.reject": "Hylkää",
|
||||
"follow_requests.unlocked_explanation": "Vaikkei tiliäsi ole lukittu, on palvelun {domain} ylläpito arvioinut, että saatat olla halukas tarkistamaan nämä seurauspyynnöt erikseen.",
|
||||
|
@ -382,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Piilota tältä käyttäjältä tulevat ilmoitukset?",
|
||||
"mute_modal.indefinite": "Ikuisesti",
|
||||
"navigation_bar.about": "Tietoja",
|
||||
"navigation_bar.advanced_interface": "Avaa edistyneessä käyttöliittymässä",
|
||||
"navigation_bar.blocks": "Estetyt käyttäjät",
|
||||
"navigation_bar.bookmarks": "Kirjanmerkit",
|
||||
"navigation_bar.community_timeline": "Paikallinen aikajana",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Fjal fráboðanir frá hesum brúkaranum?",
|
||||
"mute_modal.indefinite": "Óásett tíðarskeið",
|
||||
"navigation_bar.about": "Um",
|
||||
"navigation_bar.advanced_interface": "Lat upp í framkomnum vevmarkamóti",
|
||||
"navigation_bar.blocks": "Bannaðir brúkarar",
|
||||
"navigation_bar.bookmarks": "Goymd",
|
||||
"navigation_bar.community_timeline": "Lokal tíðarlinja",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Masquer les notifications de ce compte?",
|
||||
"mute_modal.indefinite": "Indéfinie",
|
||||
"navigation_bar.about": "À propos",
|
||||
"navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée",
|
||||
"navigation_bar.blocks": "Comptes bloqués",
|
||||
"navigation_bar.bookmarks": "Signets",
|
||||
"navigation_bar.community_timeline": "Fil local",
|
||||
|
|
|
@ -135,6 +135,8 @@
|
|||
"community.column_settings.remote_only": "Feadhainn chèin a-mhàin",
|
||||
"compose.language.change": "Atharraich an cànan",
|
||||
"compose.language.search": "Lorg cànan…",
|
||||
"compose.published.body": "Postimi u botua.",
|
||||
"compose.published.open": "Fosgail",
|
||||
"compose_form.direct_message_warning_learn_more": "Barrachd fiosrachaidh",
|
||||
"compose_form.encryption_warning": "Chan eil crioptachadh ceann gu ceann air postaichean Mhastodon. Na co-roinn fiosrachadh dìomhair idir le Mastodon.",
|
||||
"compose_form.hashtag_warning": "Cha nochd am post seo fon taga hais o nach eil e poblach. Cha ghabh ach postaichean poblach a lorg a-rèir an tagaichean hais.",
|
||||
|
@ -383,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "A bheil thu airson na brathan fhalach on chleachdaiche seo?",
|
||||
"mute_modal.indefinite": "Gun chrìoch",
|
||||
"navigation_bar.about": "Mu dhèidhinn",
|
||||
"navigation_bar.advanced_interface": "Ireki web interfaze aurreratuan",
|
||||
"navigation_bar.blocks": "Cleachdaichean bacte",
|
||||
"navigation_bar.bookmarks": "Comharran-lìn",
|
||||
"navigation_bar.community_timeline": "Loidhne-ama ionadail",
|
||||
|
@ -616,6 +619,8 @@
|
|||
"status.history.created": "Chruthaich {name} {date} e",
|
||||
"status.history.edited": "Dheasaich {name} {date} e",
|
||||
"status.load_more": "Luchdaich barrachd dheth",
|
||||
"status.media.open": "Klikoni për hapje",
|
||||
"status.media.show": "Klikoni për shfaqje",
|
||||
"status.media_hidden": "Meadhan falaichte",
|
||||
"status.mention": "Thoir iomradh air @{name}",
|
||||
"status.more": "Barrachd",
|
||||
|
@ -646,6 +651,7 @@
|
|||
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
|
||||
"status.translate": "Eadar-theangaich",
|
||||
"status.translated_from_with": "Air eadar-theangachadh o {lang} le {provider}",
|
||||
"status.uncached_media_warning": "S’ka paraparje",
|
||||
"status.unmute_conversation": "Dì-mhùch an còmhradh",
|
||||
"status.unpin": "Dì-phrìnich on phròifil",
|
||||
"subscribed_languages.lead": "Cha nochd ach na postaichean sna cànanan a thagh thu air loidhnichean-ama na dachaigh ’s nan liostaichean às dèidh an atharrachaidh seo. Na tagh gin ma tha thu airson na postaichean uile fhaighinn ge b’ e dè an cànan.",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Agochar notificacións desta persoa?",
|
||||
"mute_modal.indefinite": "Indefinida",
|
||||
"navigation_bar.about": "Acerca de",
|
||||
"navigation_bar.advanced_interface": "Abrir coa interface web avanzada",
|
||||
"navigation_bar.blocks": "Usuarias bloqueadas",
|
||||
"navigation_bar.bookmarks": "Marcadores",
|
||||
"navigation_bar.community_timeline": "Cronoloxía local",
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"boost_modal.combo": "次からは{combo}を押せばスキップできます",
|
||||
"bundle_column_error.copy_stacktrace": "エラーレポートをコピー",
|
||||
"bundle_column_error.error.body": "要求されたページをレンダリングできませんでした。コードのバグ、またはブラウザの互換性の問題が原因である可能性があります。",
|
||||
"bundle_column_error.error.title": "あらら……",
|
||||
"bundle_column_error.error.title": "おっと!",
|
||||
"bundle_column_error.network.body": "このページを読み込もうとしたときにエラーが発生しました。インターネット接続またはこのサーバーの一時的な問題が発生した可能性があります。",
|
||||
"bundle_column_error.network.title": "ネットワークエラー",
|
||||
"bundle_column_error.retry": "再試行",
|
||||
|
@ -210,9 +210,9 @@
|
|||
"disabled_account_banner.text": "あなたのアカウント『{disabledAccount}』は現在無効になっています。",
|
||||
"dismissable_banner.community_timeline": "これらは{domain}がホストしている人たちの最新の公開投稿です。",
|
||||
"dismissable_banner.dismiss": "閉じる",
|
||||
"dismissable_banner.explore_links": "これらのニュース記事は現在分散型ネットワークの他のサーバーの人たちに話されています。",
|
||||
"dismissable_banner.explore_statuses": "分散型ネットワーク内の他のサーバーのこれらの投稿は現在このサーバー上で注目されています。",
|
||||
"dismissable_banner.explore_tags": "これらのハッシュタグは現在分散型ネットワークの他のサーバーの人たちに話されています。",
|
||||
"dismissable_banner.explore_links": "ネットワーク上で話題になっているニュースです。たくさんのユーザーにシェアされた記事ほど上位に表示されます。",
|
||||
"dismissable_banner.explore_statuses": "ネットワーク上で注目を集めている投稿です。ブーストやお気に入り登録の多い投稿が上位に表示されます。",
|
||||
"dismissable_banner.explore_tags": "ネットワーク上でトレンドになっているハッシュタグです。たくさんのユーザーに使われたタグほど上位に表示されます。",
|
||||
"dismissable_banner.public_timeline": "{domain} のユーザーがリモートフォローしているアカウントからの公開投稿のタイムラインです。",
|
||||
"embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
|
||||
"embed.preview": "表示例:",
|
||||
|
@ -246,7 +246,7 @@
|
|||
"empty_column.follow_requests": "まだフォローリクエストを受けていません。フォローリクエストを受けるとここに表示されます。",
|
||||
"empty_column.followed_tags": "まだハッシュタグをフォローしていません。フォローするとここに表示されます。",
|
||||
"empty_column.hashtag": "このハッシュタグはまだ使われていません。",
|
||||
"empty_column.home": "ホームタイムラインはまだ空っぽです。誰かフォローして埋めてみましょう。 {suggestions}",
|
||||
"empty_column.home": "ホームタイムラインはまだ空っぽです。だれかをフォローして埋めてみましょう。",
|
||||
"empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しい投稿をするとここに表示されます。",
|
||||
"empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。",
|
||||
"empty_column.mutes": "まだ誰もミュートしていません。",
|
||||
|
@ -485,18 +485,18 @@
|
|||
"onboarding.actions.go_to_local_timeline": "ローカルの投稿を見る",
|
||||
"onboarding.compose.template": "#Mastodon はじめました",
|
||||
"onboarding.follows.empty": "おすすめに表示できるアカウントはまだありません。検索や「見つける」を活用して、ほかのアカウントを探してみましょう。",
|
||||
"onboarding.follows.lead": "自分の手でタイムラインを作ってみましょう。フォローを増やせば、タイムラインはより賑やかでおもしろいものになります。最初のフォローの参考になりそうなアカウントをいくつか表示しています。気になったものがあれば、ここからフォローしてみましょう。フォローはいつでも解除して大丈夫です。",
|
||||
"onboarding.follows.title": "おすすめのアカウント",
|
||||
"onboarding.follows.lead": "ホームタイムラインは Mastodon の軸足となる場所です。たくさんのユーザーをフォローすることで、ホームタイムラインはよりにぎやかでおもしろいものになります。手はじめに、おすすめのアカウントから何人かフォローしてみましょう:",
|
||||
"onboarding.follows.title": "ホームタイムラインを埋める",
|
||||
"onboarding.share.lead": "新しい Mastodon アカウントをみんなに紹介しましょう。",
|
||||
"onboarding.share.message": "「{username}」で #Mastodon はじめました! {url}",
|
||||
"onboarding.share.next_steps": "次のステップに進む:",
|
||||
"onboarding.share.title": "プロフィールをシェアする",
|
||||
"onboarding.start.lead": "Mastodon アカウントの準備ができました。次のステップに進みましょう:",
|
||||
"onboarding.start.lead": "Mastodon へようこそ。Mastodon は非中央集権型SNSのひとつで、ユーザーそれぞれの考えかたを尊重するプラットフォームです。ユーザーはどんな「好き」も自由に追いかけることができます。次のステップに進んで、新天地でのつながりをみつけましょう:",
|
||||
"onboarding.start.skip": "下のどれかをクリックしてチュートリアルを終了",
|
||||
"onboarding.start.title": "はじめに",
|
||||
"onboarding.steps.follow_people.body": "タイムラインを充実させましょう。",
|
||||
"onboarding.steps.follow_people.title": "最初の{count, plural, other {#人}}をフォローする",
|
||||
"onboarding.steps.publish_status.body": "試しに何か書いてみましょう。",
|
||||
"onboarding.steps.follow_people.body": "ユーザーをフォローしてみましょう。これが Mastodon を楽しむ基本です。",
|
||||
"onboarding.steps.follow_people.title": "ホームタイムラインを埋める",
|
||||
"onboarding.steps.publish_status.body": "試しになにか書いてみましょう。写真、ビデオ、アンケートなど、なんでも大丈夫です {emoji}",
|
||||
"onboarding.steps.publish_status.title": "はじめての投稿",
|
||||
"onboarding.steps.setup_profile.body": "ほかのユーザーが親しみやすいように、プロフィールを整えましょう。",
|
||||
"onboarding.steps.setup_profile.title": "プロフィールを完成させる",
|
||||
|
|
|
@ -373,7 +373,7 @@
|
|||
"lists.new.title_placeholder": "새 리스트의 이름",
|
||||
"lists.replies_policy.followed": "팔로우 한 사용자 누구나",
|
||||
"lists.replies_policy.list": "리스트의 구성원",
|
||||
"lists.replies_policy.none": "선택 안함",
|
||||
"lists.replies_policy.none": "모두 제외",
|
||||
"lists.replies_policy.title": "답글 표시:",
|
||||
"lists.search": "팔로우 중인 사람들 중에서 찾기",
|
||||
"lists.subheading": "리스트",
|
||||
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "이 사용자로부터의 알림을 숨기시겠습니까?",
|
||||
"mute_modal.indefinite": "무기한",
|
||||
"navigation_bar.about": "정보",
|
||||
"navigation_bar.advanced_interface": "고급 웹 인터페이스에서 열기",
|
||||
"navigation_bar.blocks": "차단한 사용자",
|
||||
"navigation_bar.bookmarks": "북마크",
|
||||
"navigation_bar.community_timeline": "로컬 타임라인",
|
||||
|
@ -392,7 +393,7 @@
|
|||
"navigation_bar.direct": "개인적인 멘션",
|
||||
"navigation_bar.discover": "발견하기",
|
||||
"navigation_bar.domain_blocks": "차단한 도메인",
|
||||
"navigation_bar.edit_profile": "프로필 편집",
|
||||
"navigation_bar.edit_profile": "프로필 수정",
|
||||
"navigation_bar.explore": "둘러보기",
|
||||
"navigation_bar.favourites": "좋아요",
|
||||
"navigation_bar.filters": "뮤트한 단어",
|
||||
|
@ -608,7 +609,7 @@
|
|||
"status.direct": "@{name} 님에게 개인적으로 멘션",
|
||||
"status.direct_indicator": "개인적인 멘션",
|
||||
"status.edit": "수정",
|
||||
"status.edited": "{date}에 편집됨",
|
||||
"status.edited": "{date}에 수정함",
|
||||
"status.edited_x_times": "{count}번 수정됨",
|
||||
"status.embed": "공유하기",
|
||||
"status.favourite": "좋아요",
|
||||
|
@ -682,7 +683,7 @@
|
|||
"upload_form.audio_description": "청각 장애인을 위한 설명",
|
||||
"upload_form.description": "시각장애인을 위한 설명",
|
||||
"upload_form.description_missing": "설명이 추가되지 않음",
|
||||
"upload_form.edit": "편집",
|
||||
"upload_form.edit": "수정",
|
||||
"upload_form.thumbnail": "썸네일 변경",
|
||||
"upload_form.undo": "삭제",
|
||||
"upload_form.video_description": "청각, 시각 장애인을 위한 설명",
|
||||
|
@ -692,7 +693,7 @@
|
|||
"upload_modal.choose_image": "이미지 선택",
|
||||
"upload_modal.description_placeholder": "다람쥐 헌 쳇바퀴 타고파",
|
||||
"upload_modal.detect_text": "이미지에서 텍스트 추출",
|
||||
"upload_modal.edit_media": "미디어 편집",
|
||||
"upload_modal.edit_media": "미디어 수정",
|
||||
"upload_modal.hint": "미리보기를 클릭하거나 드래그 해서 포컬 포인트를 맞추세요. 이 점은 썸네일에 항상 보여질 부분을 나타냅니다.",
|
||||
"upload_modal.preparing_ocr": "OCR 준비 중…",
|
||||
"upload_modal.preview_label": "미리보기 ({ratio})",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "ဤအကောင့်မှသတိပေးချက်များကိုပိတ်မလား?",
|
||||
"mute_modal.indefinite": "ရေတွက်လို့မရပါ",
|
||||
"navigation_bar.about": "အကြောင်း",
|
||||
"navigation_bar.advanced_interface": "အဆင့်မြင့်ဝဘ်ပုံစံ ဖွင့်ပါ",
|
||||
"navigation_bar.blocks": "ဘလော့ထားသောအကောင့်များ",
|
||||
"navigation_bar.bookmarks": "မှတ်ထားသည်များ",
|
||||
"navigation_bar.community_timeline": "ဒေသစံတော်ချိန်",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Skjul varsel frå denne brukaren?",
|
||||
"mute_modal.indefinite": "På ubestemt tid",
|
||||
"navigation_bar.about": "Om",
|
||||
"navigation_bar.advanced_interface": "Åpne i det avanserte nettgrensesnittet",
|
||||
"navigation_bar.blocks": "Blokkerte brukarar",
|
||||
"navigation_bar.bookmarks": "Bokmerke",
|
||||
"navigation_bar.community_timeline": "Lokal tidsline",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Skjul varslinger fra denne brukeren?",
|
||||
"mute_modal.indefinite": "På ubestemt tid",
|
||||
"navigation_bar.about": "Om",
|
||||
"navigation_bar.advanced_interface": "Åpne i det avanserte nettgrensesnittet",
|
||||
"navigation_bar.blocks": "Blokkerte brukere",
|
||||
"navigation_bar.bookmarks": "Bokmerker",
|
||||
"navigation_bar.community_timeline": "Lokal tidslinje",
|
||||
|
|
|
@ -76,6 +76,9 @@
|
|||
"admin.dashboard.retention.average": "Średnia",
|
||||
"admin.dashboard.retention.cohort": "Miesiąc rejestracji",
|
||||
"admin.dashboard.retention.cohort_size": "Nowi użytkownicy",
|
||||
"admin.impact_report.instance_accounts": "Profile kont, które usuną",
|
||||
"admin.impact_report.instance_followers": "Obserwujący stracili nasi użytkownicy",
|
||||
"admin.impact_report.instance_follows": "Obserwujący ich użytkownicy stracą",
|
||||
"admin.impact_report.title": "Podsumowanie wpływu",
|
||||
"alert.rate_limited.message": "Spróbuj ponownie po {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Ograniczony czasowo",
|
||||
|
@ -301,6 +304,7 @@
|
|||
"home.column_settings.show_reblogs": "Pokazuj podbicia",
|
||||
"home.column_settings.show_replies": "Pokazuj odpowiedzi",
|
||||
"home.explore_prompt.body": "Twój kanał główny będzie zawierał kombinację postów z tagów, które wybrano do obserwacji, osoby, które wybrano obserwować i wpisy, które one podbijają. Obecnie jest tu całkiem cicho, więc co myślisz o:",
|
||||
"home.explore_prompt.title": "To twoja baza domowa w Mastodon.",
|
||||
"home.hide_announcements": "Ukryj ogłoszenia",
|
||||
"home.show_announcements": "Pokaż ogłoszenia",
|
||||
"interaction_modal.description.favourite": "Mając konto na Mastodonie, możesz dodawać wpisy do ulubionych by dać znać jego autorowi, że podoba Ci się ten wpis i zachować go na później.",
|
||||
|
@ -381,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
|
||||
"mute_modal.indefinite": "Nieokreślony",
|
||||
"navigation_bar.about": "O serwerze",
|
||||
"navigation_bar.advanced_interface": "Otwórz w zaawansowanym interfejsie użytkownika",
|
||||
"navigation_bar.blocks": "Zablokowani użytkownicy",
|
||||
"navigation_bar.bookmarks": "Zakładki",
|
||||
"navigation_bar.community_timeline": "Lokalna oś czasu",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"account.badges.group": "Grupo",
|
||||
"account.block": "Bloquear @{name}",
|
||||
"account.block_domain": "Esconder tudo do domínio {domain}",
|
||||
"account.block_short": "Bloquear",
|
||||
"account.blocked": "Bloqueado(a)",
|
||||
"account.browse_more_on_origin_server": "Encontrar mais no perfil original",
|
||||
"account.cancel_follow_request": "Retirar pedido para seguir",
|
||||
|
@ -48,7 +49,10 @@
|
|||
"account.mention": "Mencionar @{name}",
|
||||
"account.moved_to": "{name} indicou que a sua nova conta é agora:",
|
||||
"account.mute": "Silenciar @{name}",
|
||||
"account.mute_notifications_short": "Silenciar notificações",
|
||||
"account.mute_short": "Silenciar",
|
||||
"account.muted": "Silenciada",
|
||||
"account.no_bio": "Nenhuma descrição fornecida.",
|
||||
"account.open_original_page": "Abrir a página original",
|
||||
"account.posts": "Publicações",
|
||||
"account.posts_with_replies": "Publicações e respostas",
|
||||
|
@ -64,6 +68,7 @@
|
|||
"account.unendorse": "Não destacar no perfil",
|
||||
"account.unfollow": "Deixar de seguir",
|
||||
"account.unmute": "Deixar de silenciar @{name}",
|
||||
"account.unmute_notifications_short": "Parar de silenciar notificações",
|
||||
"account.unmute_short": "Deixar de silenciar",
|
||||
"account_note.placeholder": "Clique para adicionar nota",
|
||||
"admin.dashboard.daily_retention": "Taxa de retenção de utilizadores por dia após a inscrição",
|
||||
|
@ -71,6 +76,10 @@
|
|||
"admin.dashboard.retention.average": "Média",
|
||||
"admin.dashboard.retention.cohort": "Mês de inscrição",
|
||||
"admin.dashboard.retention.cohort_size": "Novos utilizadores",
|
||||
"admin.impact_report.instance_accounts": "Perfis de contas que isto eliminaria",
|
||||
"admin.impact_report.instance_followers": "Seguidores que os nossos utilizadores perderiam",
|
||||
"admin.impact_report.instance_follows": "Seguidores que os utilizadores deles perderiam",
|
||||
"admin.impact_report.title": "Resumo do impacto",
|
||||
"alert.rate_limited.message": "Volte a tentar depois das {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Limite de tentativas",
|
||||
"alert.unexpected.message": "Ocorreu um erro inesperado.",
|
||||
|
@ -105,6 +114,7 @@
|
|||
"column.directory": "Explorar perfis",
|
||||
"column.domain_blocks": "Domínios bloqueados",
|
||||
"column.favourites": "Preferidos",
|
||||
"column.firehose": "Cronologias",
|
||||
"column.follow_requests": "Seguidores pendentes",
|
||||
"column.home": "Início",
|
||||
"column.lists": "Listas",
|
||||
|
@ -125,6 +135,8 @@
|
|||
"community.column_settings.remote_only": "Apenas remoto",
|
||||
"compose.language.change": "Alterar língua",
|
||||
"compose.language.search": "Pesquisar línguas...",
|
||||
"compose.published.body": "Publicado.",
|
||||
"compose.published.open": "Abrir",
|
||||
"compose_form.direct_message_warning_learn_more": "Conhecer mais",
|
||||
"compose_form.encryption_warning": "As publicações no Mastodon não são criptografadas de ponta a ponta. Não partilhe nenhuma informação sensível através do Mastodon.",
|
||||
"compose_form.hashtag_warning": "Esta publicação não será listada em qualquer etiqueta, pois não é pública. Apenas as publicações públicas podem ser pesquisadas por etiquetas.",
|
||||
|
@ -192,6 +204,7 @@
|
|||
"dismissable_banner.explore_links": "Essas histórias de notícias estão, no momento, a ser faladas por pessoas neste e noutros servidores da rede descentralizada.",
|
||||
"dismissable_banner.explore_statuses": "Estas publicações, deste e de outros servidores na rede descentralizada, estão, neste momento, a ganhar atenção neste servidor.",
|
||||
"dismissable_banner.explore_tags": "Estas #etiquetas estão presentemente a ganhar atenção entre as pessoas neste e noutros servidores da rede descentralizada.",
|
||||
"dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas na rede social que as pessoas em {domain} seguem.",
|
||||
"embed.instructions": "Incorpore esta publicação no seu site copiando o código abaixo.",
|
||||
"embed.preview": "Podes ver aqui como irá ficar:",
|
||||
"emoji_button.activity": "Actividade",
|
||||
|
@ -257,6 +270,9 @@
|
|||
"filter_modal.select_filter.subtitle": "Utilize uma categoria existente ou crie uma nova",
|
||||
"filter_modal.select_filter.title": "Filtrar esta publicação",
|
||||
"filter_modal.title.status": "Filtrar uma publicação",
|
||||
"firehose.all": "Todas",
|
||||
"firehose.local": "Este servidor",
|
||||
"firehose.remote": "Outros servidores",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Rejeitar",
|
||||
"follow_requests.unlocked_explanation": "Apesar de a sua não ser privada, a administração de {domain} pensa que poderá querer rever manualmente os pedidos de seguimento dessas contas.",
|
||||
|
@ -282,9 +298,13 @@
|
|||
"hashtag.column_settings.tag_toggle": "Incluir etiquetas adicionais para esta coluna",
|
||||
"hashtag.follow": "Seguir #etiqueta",
|
||||
"hashtag.unfollow": "Deixar de seguir #etiqueta",
|
||||
"home.actions.go_to_explore": "Veja as tendências atuais",
|
||||
"home.actions.go_to_suggestions": "Encontrar pessoas para seguir",
|
||||
"home.column_settings.basic": "Básico",
|
||||
"home.column_settings.show_reblogs": "Mostrar impulsos",
|
||||
"home.column_settings.show_replies": "Mostrar respostas",
|
||||
"home.explore_prompt.body": "A sua página inicial terá uma mistura de publicações com as hashtags que escolheu seguir, das pessoas que escolheu seguir e as publicações que elas partilham. Parece bastante quieto por agora, então que tal:",
|
||||
"home.explore_prompt.title": "Esta é a sua base principal dentro do Mastodon.",
|
||||
"home.hide_announcements": "Ocultar comunicações",
|
||||
"home.show_announcements": "Exibir comunicações",
|
||||
"interaction_modal.description.favourite": "Com uma conta no Mastodon, pode adicionar esta publicação aos marcadores para que o autor saiba que gostou e guardá-la para mais tarde.",
|
||||
|
@ -348,6 +368,7 @@
|
|||
"lists.delete": "Eliminar lista",
|
||||
"lists.edit": "Editar lista",
|
||||
"lists.edit.submit": "Mudar o título",
|
||||
"lists.exclusive": "Ocultar essas publicações da página inicial",
|
||||
"lists.new.create": "Adicionar lista",
|
||||
"lists.new.title_placeholder": "Título da nova lista",
|
||||
"lists.replies_policy.followed": "Qualquer utilizador seguido",
|
||||
|
@ -364,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Esconder notificações deste utilizador?",
|
||||
"mute_modal.indefinite": "Indefinidamente",
|
||||
"navigation_bar.about": "Sobre",
|
||||
"navigation_bar.advanced_interface": "Abrir na interface web avançada",
|
||||
"navigation_bar.blocks": "Utilizadores bloqueados",
|
||||
"navigation_bar.bookmarks": "Marcadores",
|
||||
"navigation_bar.community_timeline": "Cronologia local",
|
||||
|
@ -468,6 +490,7 @@
|
|||
"picture_in_picture.restore": "Colocá-lo de volta",
|
||||
"poll.closed": "Fechado",
|
||||
"poll.refresh": "Recarregar",
|
||||
"poll.reveal": "Ver resultados",
|
||||
"poll.total_people": "{count, plural, one {# pessoa} other {# pessoas}}",
|
||||
"poll.total_votes": "{count, plural, one {# voto} other {# votos}}",
|
||||
"poll.vote": "Votar",
|
||||
|
@ -520,6 +543,8 @@
|
|||
"report.placeholder": "Comentários adicionais",
|
||||
"report.reasons.dislike": "Não gosto disto",
|
||||
"report.reasons.dislike_description": "Não é algo que deseje ver",
|
||||
"report.reasons.legal": "É ilegal",
|
||||
"report.reasons.legal_description": "Acredita que isto viola a lei do seu país ou do país do seu servidor",
|
||||
"report.reasons.other": "É outra coisa",
|
||||
"report.reasons.other_description": "O problema não se encaixa nas outras categorias",
|
||||
"report.reasons.spam": "É spam",
|
||||
|
@ -539,6 +564,7 @@
|
|||
"report.unfollow": "Deixar de seguir @{name}",
|
||||
"report.unfollow_explanation": "Está a seguir esta conta. Para não ver mais as publicações desta conta na sua página inicial, deixe de segui-la.",
|
||||
"report_notification.attached_statuses": "{count, plural,one {{count} publicação} other {{count} publicações}} em anexo",
|
||||
"report_notification.categories.legal": "Legal",
|
||||
"report_notification.categories.other": "Outro",
|
||||
"report_notification.categories.spam": "Spam",
|
||||
"report_notification.categories.violation": "Violação de regra",
|
||||
|
@ -593,6 +619,8 @@
|
|||
"status.history.created": "{name} criado em {date}",
|
||||
"status.history.edited": "{name} editado em {date}",
|
||||
"status.load_more": "Carregar mais",
|
||||
"status.media.open": "Clique para abrir",
|
||||
"status.media.show": "Clique para ver",
|
||||
"status.media_hidden": "Media escondida",
|
||||
"status.mention": "Mencionar @{name}",
|
||||
"status.more": "Mais",
|
||||
|
@ -623,6 +651,7 @@
|
|||
"status.title.with_attachments": "{user} publicou {attachmentCount, plural,one {um anexo} other {{attachmentCount} anexos}}",
|
||||
"status.translate": "Traduzir",
|
||||
"status.translated_from_with": "Traduzido do {lang} usando {provider}",
|
||||
"status.uncached_media_warning": "Pré-visualização não disponível",
|
||||
"status.unmute_conversation": "Deixar de silenciar esta conversa",
|
||||
"status.unpin": "Desafixar do perfil",
|
||||
"subscribed_languages.lead": "Após a alteração, apenas as publicações nas línguas seleccionadas aparecerão na sua página inicial e listas. Não selecione nenhuma para receber publicações de todas as línguas.",
|
||||
|
|
|
@ -78,6 +78,8 @@
|
|||
"admin.dashboard.retention.cohort_size": "Новые пользователи",
|
||||
"admin.impact_report.instance_accounts": "Профили учетных записей, которые будут удалены",
|
||||
"admin.impact_report.instance_followers": "Последователи, которых потеряют наши пользователи",
|
||||
"admin.impact_report.instance_follows": "Последователи, которых потеряют наши пользователи",
|
||||
"admin.impact_report.title": "Резюме воздействия",
|
||||
"alert.rate_limited.message": "Пожалуйста, повторите после {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Ограничение количества запросов",
|
||||
"alert.unexpected.message": "Произошла непредвиденная ошибка.",
|
||||
|
@ -112,6 +114,7 @@
|
|||
"column.directory": "Просмотр профилей",
|
||||
"column.domain_blocks": "Заблокированные домены",
|
||||
"column.favourites": "Избранное",
|
||||
"column.firehose": "Живая лента",
|
||||
"column.follow_requests": "Запросы на подписку",
|
||||
"column.home": "Главная",
|
||||
"column.lists": "Списки",
|
||||
|
@ -132,6 +135,8 @@
|
|||
"community.column_settings.remote_only": "Только удалённые",
|
||||
"compose.language.change": "Изменить язык",
|
||||
"compose.language.search": "Поиск языков...",
|
||||
"compose.published.body": "Запись опубликована.",
|
||||
"compose.published.open": "Открыть",
|
||||
"compose_form.direct_message_warning_learn_more": "Подробнее",
|
||||
"compose_form.encryption_warning": "Посты в Mastodon не защищены сквозным шифрованием. Не делитесь конфиденциальной информацией через Mastodon.",
|
||||
"compose_form.hashtag_warning": "Этот пост не будет виден ни под одним из хэштегов, так как он не публичный. Только публичные посты можно найти по хэштегу.",
|
||||
|
@ -199,6 +204,7 @@
|
|||
"dismissable_banner.explore_links": "Об этих новостях прямо сейчас говорят люди на этом и других серверах децентрализованной сети.",
|
||||
"dismissable_banner.explore_statuses": "Эти сообщения с этого и других серверов в децентрализованной сети сейчас набирают популярность на этом сервере.",
|
||||
"dismissable_banner.explore_tags": "Эти хэштеги привлекают людей на этом и других серверах децентрализованной сети прямо сейчас.",
|
||||
"dismissable_banner.public_timeline": "Это самые последние публичные сообщения от людей в социальной сети, за которыми подписались пользователи {domain}.",
|
||||
"embed.instructions": "Встройте этот пост на свой сайт, скопировав следующий код:",
|
||||
"embed.preview": "Так это будет выглядеть:",
|
||||
"emoji_button.activity": "Занятия",
|
||||
|
@ -264,6 +270,9 @@
|
|||
"filter_modal.select_filter.subtitle": "Используйте существующую категорию или создайте новую",
|
||||
"filter_modal.select_filter.title": "Фильтровать этот пост",
|
||||
"filter_modal.title.status": "Фильтровать пост",
|
||||
"firehose.all": "Все",
|
||||
"firehose.local": "Текущий сервер",
|
||||
"firehose.remote": "Другие серверы",
|
||||
"follow_request.authorize": "Авторизовать",
|
||||
"follow_request.reject": "Отказать",
|
||||
"follow_requests.unlocked_explanation": "Хотя ваша учетная запись не закрыта, команда {domain} подумала, что вы захотите просмотреть запросы от этих учетных записей вручную.",
|
||||
|
@ -289,9 +298,13 @@
|
|||
"hashtag.column_settings.tag_toggle": "Включить дополнительные теги для этой колонки",
|
||||
"hashtag.follow": "Подписаться на новые посты",
|
||||
"hashtag.unfollow": "Отписаться",
|
||||
"home.actions.go_to_explore": "Посмотреть, что актуально",
|
||||
"home.actions.go_to_suggestions": "Подпишитесь на людей",
|
||||
"home.column_settings.basic": "Основные",
|
||||
"home.column_settings.show_reblogs": "Показывать продвижения",
|
||||
"home.column_settings.show_replies": "Показывать ответы",
|
||||
"home.explore_prompt.body": "В вашем доме появятся сообщения из хэштегов, на которые вы хотите подписаться, люди, которых вы выбрали подписаться, и сообщения, которые они увеличили. Сейчас выглядит спокойно, так что:",
|
||||
"home.explore_prompt.title": "Это ваша домашняя база в Мастодоне.",
|
||||
"home.hide_announcements": "Скрыть объявления",
|
||||
"home.show_announcements": "Показать объявления",
|
||||
"interaction_modal.description.favourite": "С учётной записью Mastodon, вы можете добавить этот пост в избранное, чтобы сохранить его на будущее и дать автору знать, что пост вам понравился.",
|
||||
|
@ -355,6 +368,7 @@
|
|||
"lists.delete": "Удалить список",
|
||||
"lists.edit": "Изменить список",
|
||||
"lists.edit.submit": "Изменить название",
|
||||
"lists.exclusive": "Скрыть эти сообщения из дома",
|
||||
"lists.new.create": "Создать список",
|
||||
"lists.new.title_placeholder": "Название для нового списка",
|
||||
"lists.replies_policy.followed": "Любой подписанный пользователь",
|
||||
|
@ -371,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Скрыть уведомления от этого пользователя?",
|
||||
"mute_modal.indefinite": "Не определена",
|
||||
"navigation_bar.about": "О проекте",
|
||||
"navigation_bar.advanced_interface": "Включить многоколоночный интерфейс",
|
||||
"navigation_bar.blocks": "Заблокированные пользователи",
|
||||
"navigation_bar.bookmarks": "Закладки",
|
||||
"navigation_bar.community_timeline": "Локальная лента",
|
||||
|
@ -475,6 +490,7 @@
|
|||
"picture_in_picture.restore": "Вернуть обратно",
|
||||
"poll.closed": "Завершён",
|
||||
"poll.refresh": "Обновить",
|
||||
"poll.reveal": "Результаты",
|
||||
"poll.total_people": "{count, plural, one {# человек} few {# человека} many {# человек} other {# человек}}",
|
||||
"poll.total_votes": "{count, plural, one {# голос} few {# голоса} many {# голосов} other {# голосов}}",
|
||||
"poll.vote": "Голосовать",
|
||||
|
@ -527,6 +543,8 @@
|
|||
"report.placeholder": "Дополнительные комментарии",
|
||||
"report.reasons.dislike": "Мне не нравится",
|
||||
"report.reasons.dislike_description": "Не хотел(а) бы видеть такой контент",
|
||||
"report.reasons.legal": "Это незаконно",
|
||||
"report.reasons.legal_description": "Вы считаете, что оно нарушает закон вашей страны или сервера",
|
||||
"report.reasons.other": "Другое",
|
||||
"report.reasons.other_description": "Проблема не попадает ни под одну из категорий",
|
||||
"report.reasons.spam": "Это спам",
|
||||
|
@ -546,6 +564,7 @@
|
|||
"report.unfollow": "Отписаться от @{name}",
|
||||
"report.unfollow_explanation": "Вы подписаны на этого пользователя. Чтобы не видеть его/её посты в своей домашней ленте, отпишитесь от него/неё.",
|
||||
"report_notification.attached_statuses": "{count, plural, one {{count} сообщение} few {{count} сообщения} many {{count} сообщений} other {{count} сообщений}} вложено",
|
||||
"report_notification.categories.legal": "Правовая информация",
|
||||
"report_notification.categories.other": "Прочее",
|
||||
"report_notification.categories.spam": "Спам",
|
||||
"report_notification.categories.violation": "Нарушение правил",
|
||||
|
@ -600,6 +619,8 @@
|
|||
"status.history.created": "{name} создал {date}",
|
||||
"status.history.edited": "{name} отредактировал(а) {date}",
|
||||
"status.load_more": "Загрузить остальное",
|
||||
"status.media.open": "Нажмите, чтобы открыть.",
|
||||
"status.media.show": "Нажмите для просмотра",
|
||||
"status.media_hidden": "Файл скрыт",
|
||||
"status.mention": "Упомянуть @{name}",
|
||||
"status.more": "Ещё",
|
||||
|
@ -630,6 +651,7 @@
|
|||
"status.title.with_attachments": "{user} размещено {attachmentCount, plural, one {вложение} other {{attachmentCount} вложений}}",
|
||||
"status.translate": "Перевод",
|
||||
"status.translated_from_with": "Переведено с {lang}, используя {provider}",
|
||||
"status.uncached_media_warning": "Прослушивание недоступно",
|
||||
"status.unmute_conversation": "Не игнорировать обсуждение",
|
||||
"status.unpin": "Открепить от профиля",
|
||||
"subscribed_languages.lead": "Посты только на выбранных языках будут отображаться на вашей домашней странице и в списке лент после изменения. Выберите «Нет», чтобы получать посты на всех языках.",
|
||||
|
|
|
@ -75,6 +75,10 @@
|
|||
"admin.dashboard.retention.average": "Povprečje",
|
||||
"admin.dashboard.retention.cohort": "Mesec prijave",
|
||||
"admin.dashboard.retention.cohort_size": "Novi uporabniki",
|
||||
"admin.impact_report.instance_accounts": "Profili računov, ki bi jih s tem izbrisali",
|
||||
"admin.impact_report.instance_followers": "Sledilci, ki bi jih izgubili naši uporabniki",
|
||||
"admin.impact_report.instance_follows": "Sledilci, ki bi jih izgubili njihovi uporabniki",
|
||||
"admin.impact_report.title": "Povzetek učinka",
|
||||
"alert.rate_limited.message": "Poskusite znova čez {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Hitrost omejena",
|
||||
"alert.unexpected.message": "Zgodila se je nepričakovana napaka.",
|
||||
|
@ -376,6 +380,7 @@
|
|||
"mute_modal.hide_notifications": "Ali želite skriti obvestila tega uporabnika?",
|
||||
"mute_modal.indefinite": "Nedoločeno",
|
||||
"navigation_bar.about": "O Mastodonu",
|
||||
"navigation_bar.advanced_interface": "Odpri v naprednem spletnem vmesniku",
|
||||
"navigation_bar.blocks": "Blokirani uporabniki",
|
||||
"navigation_bar.bookmarks": "Zaznamki",
|
||||
"navigation_bar.community_timeline": "Krajevna časovnica",
|
||||
|
@ -480,6 +485,7 @@
|
|||
"picture_in_picture.restore": "Postavi nazaj",
|
||||
"poll.closed": "Zaprto",
|
||||
"poll.refresh": "Osveži",
|
||||
"poll.reveal": "Poglej rezultate",
|
||||
"poll.total_people": "{count, plural, one {# oseba} two {# osebi} few {# osebe} other {# oseb}}",
|
||||
"poll.total_votes": "{count, plural, one {# glas} two {# glasova} few {# glasovi} other {# glasov}}",
|
||||
"poll.vote": "Glasuj",
|
||||
|
@ -532,6 +538,7 @@
|
|||
"report.placeholder": "Dodatni komentarji",
|
||||
"report.reasons.dislike": "Ni mi všeč",
|
||||
"report.reasons.dislike_description": "To ni tisto, kar želim videti",
|
||||
"report.reasons.legal": "To ni legalno",
|
||||
"report.reasons.other": "Gre za nekaj drugega",
|
||||
"report.reasons.other_description": "Težava ne sodi v druge kategorije",
|
||||
"report.reasons.spam": "To je neželena vsebina",
|
||||
|
@ -551,6 +558,7 @@
|
|||
"report.unfollow": "Ne sledi več @{name}",
|
||||
"report.unfollow_explanation": "Temu računu sledite. Da ne boste več videli njegovih objav v svojem domačem viru, mu prenehajte slediti.",
|
||||
"report_notification.attached_statuses": "{count, plural, one {{count} objava pripeta} two {{count} objavi pripeti} few {{count} objave pripete} other {{count} objav pripetih}}",
|
||||
"report_notification.categories.legal": "Legalno",
|
||||
"report_notification.categories.other": "Drugo",
|
||||
"report_notification.categories.spam": "Neželeno",
|
||||
"report_notification.categories.violation": "Kršitev pravila",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Të kalohen të fshehura njoftimet prej këtij përdoruesi?",
|
||||
"mute_modal.indefinite": "E pacaktuar",
|
||||
"navigation_bar.about": "Mbi",
|
||||
"navigation_bar.advanced_interface": "Ireki web interfaze aurreratuan",
|
||||
"navigation_bar.blocks": "Përdorues të bllokuar",
|
||||
"navigation_bar.bookmarks": "Faqerojtës",
|
||||
"navigation_bar.community_timeline": "Rrjedhë kohore vendore",
|
||||
|
|
|
@ -209,7 +209,7 @@
|
|||
"emoji_button.food": "Mat & dryck",
|
||||
"emoji_button.label": "Lägg till emoji",
|
||||
"emoji_button.nature": "Natur",
|
||||
"emoji_button.not_found": "Inga emojos!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.not_found": "Inga matchande emojis hittades",
|
||||
"emoji_button.objects": "Objekt",
|
||||
"emoji_button.people": "Personer",
|
||||
"emoji_button.recent": "Ofta använda",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Bu kullanıcıdan bildirimler gizlensin mı?",
|
||||
"mute_modal.indefinite": "Belirsiz",
|
||||
"navigation_bar.about": "Hakkında",
|
||||
"navigation_bar.advanced_interface": "Gelişmiş web arayüzünde aç",
|
||||
"navigation_bar.blocks": "Engellenen kullanıcılar",
|
||||
"navigation_bar.bookmarks": "Yer İşaretleri",
|
||||
"navigation_bar.community_timeline": "Yerel Zaman Tüneli",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "Ẩn thông báo từ người này?",
|
||||
"mute_modal.indefinite": "Vĩnh viễn",
|
||||
"navigation_bar.about": "Giới thiệu",
|
||||
"navigation_bar.advanced_interface": "Dùng bố cục nhiều cột",
|
||||
"navigation_bar.blocks": "Người đã chặn",
|
||||
"navigation_bar.bookmarks": "Đã lưu",
|
||||
"navigation_bar.community_timeline": "Cộng đồng",
|
||||
|
@ -648,7 +649,7 @@
|
|||
"status.show_more_all": "Hiển thị tất cả",
|
||||
"status.show_original": "Bản gốc",
|
||||
"status.title.with_attachments": "{user} đã đăng {attachmentCount, plural, other {{attachmentCount} đính kèm}}",
|
||||
"status.translate": "Dịch",
|
||||
"status.translate": "Dịch Tút",
|
||||
"status.translated_from_with": "Dịch từ {lang} bằng {provider}",
|
||||
"status.uncached_media_warning": "Xem trước không sẵn có",
|
||||
"status.unmute_conversation": "Quan tâm",
|
||||
|
|
|
@ -385,6 +385,7 @@
|
|||
"mute_modal.hide_notifications": "是否隱藏來自這位使用者的通知?",
|
||||
"mute_modal.indefinite": "無期限",
|
||||
"navigation_bar.about": "關於",
|
||||
"navigation_bar.advanced_interface": "以進階網頁介面開啟",
|
||||
"navigation_bar.blocks": "已封鎖的使用者",
|
||||
"navigation_bar.bookmarks": "書籤",
|
||||
"navigation_bar.community_timeline": "本站時間軸",
|
||||
|
|
|
@ -5,11 +5,16 @@ import { normalizeStatusTranslation } from '../actions/importer/normalizer';
|
|||
import {
|
||||
REBLOG_REQUEST,
|
||||
REBLOG_FAIL,
|
||||
UNREBLOG_REQUEST,
|
||||
UNREBLOG_FAIL,
|
||||
FAVOURITE_REQUEST,
|
||||
FAVOURITE_FAIL,
|
||||
UNFAVOURITE_SUCCESS,
|
||||
UNFAVOURITE_REQUEST,
|
||||
UNFAVOURITE_FAIL,
|
||||
BOOKMARK_REQUEST,
|
||||
BOOKMARK_FAIL,
|
||||
UNBOOKMARK_REQUEST,
|
||||
UNBOOKMARK_FAIL,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
STATUS_MUTE_SUCCESS,
|
||||
|
@ -96,18 +101,28 @@ export default function statuses(state = initialState, action) {
|
|||
return importStatuses(state, action.statuses);
|
||||
case FAVOURITE_REQUEST:
|
||||
return state.setIn([action.status.get('id'), 'favourited'], true);
|
||||
case UNFAVOURITE_SUCCESS:
|
||||
return state.updateIn([action.status.get('id'), 'favourites_count'], x => Math.max(0, x - 1));
|
||||
case FAVOURITE_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false);
|
||||
case UNFAVOURITE_REQUEST:
|
||||
return state.setIn([action.status.get('id'), 'favourited'], false);
|
||||
case UNFAVOURITE_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], true);
|
||||
case BOOKMARK_REQUEST:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
|
||||
case BOOKMARK_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
|
||||
case UNBOOKMARK_REQUEST:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
|
||||
case UNBOOKMARK_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
|
||||
case REBLOG_REQUEST:
|
||||
return state.setIn([action.status.get('id'), 'reblogged'], true);
|
||||
case REBLOG_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
|
||||
case UNREBLOG_REQUEST:
|
||||
return state.setIn([action.status.get('id'), 'reblogged'], false);
|
||||
case UNREBLOG_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], true);
|
||||
case STATUS_MUTE_SUCCESS:
|
||||
return state.setIn([action.id, 'muted'], true);
|
||||
case STATUS_UNMUTE_SUCCESS:
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -423,7 +430,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
|
||||
|
|
|
@ -161,11 +161,22 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
a {
|
||||
&:focus {
|
||||
border-radius: 4px;
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
background-color: $ui-button-focus-background-color;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
|
||||
&--destructive {
|
||||
&:active,
|
||||
&:focus,
|
||||
|
@ -98,16 +102,6 @@
|
|||
transition: none;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
&.button-secondary {
|
||||
color: $ui-button-secondary-color;
|
||||
background: transparent;
|
||||
|
@ -197,8 +191,6 @@
|
|||
border-radius: 4px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: all 100ms ease-in;
|
||||
transition-property: background-color, color;
|
||||
text-decoration: none;
|
||||
|
||||
a {
|
||||
|
@ -211,12 +203,10 @@
|
|||
&:focus {
|
||||
color: lighten($action-button-color, 7%);
|
||||
background-color: rgba($action-button-color, 0.15);
|
||||
transition: all 200ms ease-out;
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: rgba($action-button-color, 0.3);
|
||||
&:focus-visible {
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
@ -225,20 +215,6 @@
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
&.inverted {
|
||||
color: $lighter-text-color;
|
||||
|
||||
|
@ -249,8 +225,8 @@
|
|||
background-color: rgba($lighter-text-color, 0.15);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: rgba($lighter-text-color, 0.3);
|
||||
&:focus-visible {
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
@ -261,6 +237,13 @@
|
|||
&.active {
|
||||
color: $highlight-text-color;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $highlight-text-color;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: lighten($highlight-text-color, 13%);
|
||||
}
|
||||
|
@ -269,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,21 +289,16 @@
|
|||
font-size: 11px;
|
||||
padding: 0 3px;
|
||||
line-height: 27px;
|
||||
outline: 0;
|
||||
transition: all 100ms ease-in;
|
||||
transition-property: background-color, color;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: darken($lighter-text-color, 7%);
|
||||
background-color: rgba($lighter-text-color, 0.15);
|
||||
transition: all 200ms ease-out;
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: rgba($lighter-text-color, 0.3);
|
||||
&:focus-visible {
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
@ -330,16 +309,13 @@
|
|||
|
||||
&.active {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $highlight-text-color;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -735,7 +711,6 @@ body > [data-popper-placement] {
|
|||
flex: 0 0 auto;
|
||||
|
||||
.compose-form__publish-button-wrapper {
|
||||
overflow: hidden;
|
||||
padding-top: 15px;
|
||||
}
|
||||
}
|
||||
|
@ -1433,6 +1408,10 @@ body > [data-popper-placement] {
|
|||
}
|
||||
}
|
||||
|
||||
.scrollable > div:first-child .detailed-status {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.detailed-status__meta {
|
||||
margin-top: 16px;
|
||||
color: $dark-text-color;
|
||||
|
@ -1478,6 +1457,7 @@ body > [data-popper-placement] {
|
|||
.detailed-status__link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detailed-status__favorites,
|
||||
|
@ -1983,13 +1963,6 @@ a.account__display-name {
|
|||
.navigation-bar__actions {
|
||||
position: relative;
|
||||
|
||||
.icon-button.close {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
transform: scale(0, 1) translate(-100%, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.compose__action-bar .icon-button {
|
||||
pointer-events: auto;
|
||||
transform: scale(1, 1) translate(0, 0);
|
||||
|
@ -1999,19 +1972,21 @@ a.account__display-name {
|
|||
}
|
||||
|
||||
.navigation-bar__profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
line-height: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.navigation-bar__profile-account {
|
||||
display: block;
|
||||
display: inline;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.navigation-bar__profile-edit {
|
||||
display: inline;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -2068,7 +2043,7 @@ a.account__display-name {
|
|||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
|
||||
&:focus {
|
||||
&:focus-visible {
|
||||
outline: 1px dotted;
|
||||
}
|
||||
}
|
||||
|
@ -3589,12 +3564,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;
|
||||
|
@ -3648,8 +3621,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3675,42 +3669,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;
|
||||
}
|
||||
|
||||
.status-card__description {
|
||||
color: $darker-text-color;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
padding: 15px 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.status-card__host {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.status-card__author {
|
||||
display: block;
|
||||
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;
|
||||
|
@ -3722,50 +3716,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%;
|
||||
|
@ -3776,7 +3728,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%;
|
||||
|
@ -3931,7 +3883,6 @@ a.status-card.compact:hover {
|
|||
position: relative;
|
||||
z-index: 2;
|
||||
outline: 0;
|
||||
overflow: hidden;
|
||||
|
||||
& > button {
|
||||
margin: 0;
|
||||
|
@ -3946,6 +3897,10 @@ a.status-card.compact:hover {
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
|
||||
&:focus-visible {
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
}
|
||||
|
||||
& > .column-header__back-button {
|
||||
|
@ -3986,10 +3941,18 @@ a.status-card.compact:hover {
|
|||
font-size: 16px;
|
||||
padding: 0 15px;
|
||||
|
||||
&:last-child {
|
||||
border-start-end-radius: 4px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: lighten($darker-text-color, 4%);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $primary-text-color;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
|
@ -4278,6 +4241,7 @@ a.status-card.compact:hover {
|
|||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
color: $white;
|
||||
|
||||
&__label {
|
||||
display: flex;
|
||||
|
@ -4285,7 +4249,6 @@ a.status-card.compact:hover {
|
|||
justify-content: center;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
color: $primary-text-color;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -4456,7 +4419,7 @@ a.status-card.compact:hover {
|
|||
}
|
||||
|
||||
.relationship-tag {
|
||||
color: $primary-text-color;
|
||||
color: $white;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
background-color: rgba($black, 0.45);
|
||||
|
@ -4647,7 +4610,7 @@ a.status-card.compact:hover {
|
|||
.emoji-picker-dropdown__menu {
|
||||
background: $simple-background-color;
|
||||
position: relative;
|
||||
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
|
||||
box-shadow: var(--dropdown-shadow);
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
z-index: 2;
|
||||
|
@ -4806,11 +4769,6 @@ a.status-card.compact:hover {
|
|||
outline: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
img {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.8;
|
||||
|
@ -4826,6 +4784,13 @@ a.status-card.compact:hover {
|
|||
img {
|
||||
opacity: 1;
|
||||
filter: none;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
img {
|
||||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4838,7 +4803,7 @@ a.status-card.compact:hover {
|
|||
.privacy-dropdown__dropdown,
|
||||
.expiration-dropdown__dropdown {
|
||||
background: $simple-background-color;
|
||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||
box-shadow: var(--dropdown-shadow);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
|
@ -4920,19 +4885,6 @@ a.status-card.compact:hover {
|
|||
.expiration-dropdown__value {
|
||||
background: $simple-background-color;
|
||||
border-radius: 4px 4px 0 0;
|
||||
box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1);
|
||||
|
||||
.icon-button {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $ui-highlight-color;
|
||||
|
||||
.icon-button {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.top .privacy-dropdown__value,
|
||||
|
@ -4943,7 +4895,7 @@ a.status-card.compact:hover {
|
|||
.privacy-dropdown__dropdown,
|
||||
.expiration-dropdown__dropdown {
|
||||
display: block;
|
||||
box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1);
|
||||
box-shadow: var(--dropdown-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4962,7 +4914,7 @@ a.status-card.compact:hover {
|
|||
.language-dropdown {
|
||||
&__dropdown {
|
||||
background: $simple-background-color;
|
||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||
box-shadow: var(--dropdown-shadow);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
|
@ -5150,7 +5102,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;
|
||||
|
@ -5289,9 +5240,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 {
|
||||
|
@ -6310,7 +6261,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;
|
||||
|
@ -6341,7 +6292,7 @@ a.status-card.compact:hover {
|
|||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
&--tall {
|
||||
|
@ -6427,7 +6378,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%;
|
||||
|
||||
|
@ -6494,7 +6445,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;
|
||||
|
@ -6511,8 +6462,6 @@ a.status-card.compact:hover {
|
|||
|
||||
video {
|
||||
display: block;
|
||||
max-width: 100vw;
|
||||
max-height: 80vh;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
@ -6520,22 +6469,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;
|
||||
|
@ -8336,6 +8278,7 @@ noscript {
|
|||
.search__input {
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
padding: 10px;
|
||||
padding-inline-end: 28px;
|
||||
}
|
||||
|
||||
.search__popout {
|
||||
|
@ -8364,8 +8307,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;
|
||||
|
@ -8374,33 +8318,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;
|
||||
|
@ -8411,7 +8362,7 @@ noscript {
|
|||
}
|
||||
|
||||
img {
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
@ -8420,7 +8371,7 @@ noscript {
|
|||
}
|
||||
|
||||
&__preview {
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
@ -8436,6 +8387,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 {
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
&__input {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
border: 1px solid $ui-primary-color;
|
||||
border: 1px solid $ui-button-background-color;
|
||||
box-sizing: border-box;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
@ -121,15 +121,10 @@
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: $valid-value-color;
|
||||
background: $valid-value-color;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: lighten($valid-value-color, 15%);
|
||||
border-color: $ui-button-focus-background-color;
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
|
@ -241,6 +236,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 {
|
||||
|
|
|
@ -5,6 +5,7 @@ $red-600: #b7253d !default; // Deep Carmine
|
|||
$red-500: #df405a !default; // Cerise
|
||||
$blurple-600: #563acc; // Iris
|
||||
$blurple-500: #6364ff; // Brand purple
|
||||
$blurple-400: #7477fd; // Medium slate blue
|
||||
$blurple-300: #858afa; // Faded Blue
|
||||
$grey-600: #4e4c5a; // Trout
|
||||
$grey-100: #dadaf3; // Topaz
|
||||
|
@ -61,6 +62,9 @@ $ui-button-tertiary-focus-color: $white !default;
|
|||
$ui-button-destructive-background-color: $red-500 !default;
|
||||
$ui-button-destructive-focus-background-color: $red-600 !default;
|
||||
|
||||
$ui-button-icon-focus-outline: solid 2px $blurple-400 !default;
|
||||
$ui-button-icon-hover-background-color: rgba(140, 141, 255, 40%) !default;
|
||||
|
||||
// Variables for texts
|
||||
$primary-text-color: $white !default;
|
||||
$darker-text-color: $ui-primary-color !default;
|
||||
|
|
|
@ -4,6 +4,8 @@ module ApplicationExtension
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application
|
||||
|
||||
validates :name, length: { maximum: 60 }
|
||||
validates :website, url: true, length: { maximum: 2_000 }, if: :website?
|
||||
validates :redirect_uri, length: { maximum: 2_000 }
|
||||
|
|
|
@ -180,6 +180,26 @@ class FeedManager
|
|||
end
|
||||
end
|
||||
|
||||
# Remove a tag's statuses from a home feed
|
||||
# @param [Tag] from_tag
|
||||
# @param [Account] into_account
|
||||
# @return [void]
|
||||
def unmerge_tag_from_home(from_tag, into_account)
|
||||
timeline_key = key(:home, into_account.id)
|
||||
timeline_status_ids = redis.zrange(timeline_key, 0, -1)
|
||||
|
||||
# This is a bit tricky because we need posts tagged with this hashtag that are not
|
||||
# also tagged with another followed hashtag or from a followed user
|
||||
scope = from_tag.statuses
|
||||
.where(id: timeline_status_ids)
|
||||
.where.not(account: into_account.following)
|
||||
.tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id))
|
||||
|
||||
scope.select('id, reblog_of_id').reorder(nil).find_each do |status|
|
||||
remove_from_feed(:home, into_account.id, status, aggregate_reblogs: into_account.user&.aggregates_reblogs?)
|
||||
end
|
||||
end
|
||||
|
||||
# Clear all statuses from or mentioning target_account from a home feed
|
||||
# @param [Account] account
|
||||
# @param [Account] target_account
|
||||
|
|
|
@ -124,6 +124,7 @@ class LinkDetailsExtractor
|
|||
author_url: author_url || '',
|
||||
embed_url: embed_url || '',
|
||||
language: language,
|
||||
created_at: published_at.presence || Time.now.utc,
|
||||
}
|
||||
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
|
||||
|
|
|
@ -4,14 +4,22 @@ require 'ipaddr'
|
|||
require 'socket'
|
||||
require 'resolv'
|
||||
|
||||
# Monkey-patch the HTTP.rb timeout class to avoid using a timeout block
|
||||
# Use our own timeout class to avoid using HTTP.rb's 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
|
||||
class PerOperationWithDeadline < HTTP::Timeout::PerOperation
|
||||
READ_DEADLINE = 30
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
@read_deadline = options.fetch(:read_deadline, READ_DEADLINE)
|
||||
end
|
||||
|
||||
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
|
||||
|
@ -24,7 +32,7 @@ class HTTP::Timeout::PerOperation
|
|||
|
||||
# Read data from the socket
|
||||
def readpartial(size, buffer = nil)
|
||||
@deadline ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) + @read_timeout
|
||||
@deadline ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) + @read_deadline
|
||||
|
||||
timeout = false
|
||||
loop do
|
||||
|
@ -33,7 +41,8 @@ class HTTP::Timeout::PerOperation
|
|||
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
|
||||
raise HTTP::TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout
|
||||
raise HTTP::TimeoutError, "Read timed out after a total of #{@read_deadline} seconds" if remaining_time <= 0
|
||||
return result if result != :wait_readable
|
||||
|
||||
# marking the socket for timeout. Why is this not being raised immediately?
|
||||
|
@ -46,7 +55,7 @@ class HTTP::Timeout::PerOperation
|
|||
# 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)
|
||||
timeout = true unless @socket.to_io.wait_readable([remaining_time, @read_timeout].min)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -57,7 +66,7 @@ class Request
|
|||
# We enforce a 5s timeout on DNS resolving, 5s timeout on socket opening
|
||||
# and 5s timeout on the TLS handshake, meaning the worst case should take
|
||||
# about 15s in total
|
||||
TIMEOUT = { connect: 5, read: 10, write: 10 }.freeze
|
||||
TIMEOUT = { connect_timeout: 5, read_timeout: 10, write_timeout: 10, read_deadline: 30 }.freeze
|
||||
|
||||
include RoutingHelper
|
||||
|
||||
|
@ -69,6 +78,7 @@ class Request
|
|||
@http_client = options.delete(:http_client)
|
||||
@allow_local = options.delete(:allow_local)
|
||||
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
|
||||
@options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT)
|
||||
@options = @options.merge(proxy_url) if use_proxy?
|
||||
@headers = {}
|
||||
|
||||
|
@ -129,7 +139,7 @@ class Request
|
|||
end
|
||||
|
||||
def http_client
|
||||
HTTP.use(:auto_inflate).timeout(TIMEOUT.dup).follow(max_hops: 3)
|
||||
HTTP.use(:auto_inflate).follow(max_hops: 3)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -274,11 +284,11 @@ class Request
|
|||
end
|
||||
|
||||
until socks.empty?
|
||||
_, available_socks, = IO.select(nil, socks, nil, Request::TIMEOUT[:connect])
|
||||
_, available_socks, = IO.select(nil, socks, nil, Request::TIMEOUT[:connect_timeout])
|
||||
|
||||
if available_socks.nil?
|
||||
socks.each(&:close)
|
||||
raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect]} seconds"
|
||||
raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect_timeout]} seconds"
|
||||
end
|
||||
|
||||
available_socks.each do |sock|
|
||||
|
|
10
app/lib/vacuum/applications_vacuum.rb
Normal file
10
app/lib/vacuum/applications_vacuum.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Vacuum::ApplicationsVacuum
|
||||
def perform
|
||||
Doorkeeper::Application.where(owner_id: nil)
|
||||
.where.missing(:created_users, :access_tokens, :access_grants)
|
||||
.where(created_at: ...1.day.ago)
|
||||
.in_batches.delete_all
|
||||
end
|
||||
end
|
|
@ -94,12 +94,19 @@ class Account < ApplicationRecord
|
|||
# Remote user validations, also applies to internal actors
|
||||
validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (!local? || actor_type == 'Application') && will_save_change_to_username? }
|
||||
|
||||
# Remote user validations
|
||||
validates :uri, presence: true, unless: :local?, on: :create
|
||||
|
||||
# Local user validations
|
||||
validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
|
||||
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
|
||||
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
|
||||
validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? }
|
||||
validates :fields, length: { maximum: 6 }, if: -> { local? && will_save_change_to_fields? }
|
||||
validates :uri, absence: true, if: :local?, on: :create
|
||||
validates :inbox_url, absence: true, if: :local?, on: :create
|
||||
validates :shared_inbox_url, absence: true, if: :local?, on: :create
|
||||
validates :followers_url, absence: true, if: :local?, on: :create
|
||||
|
||||
scope :remote, -> { where.not(domain: nil) }
|
||||
scope :local, -> { where(domain: nil) }
|
||||
|
|
|
@ -5,6 +5,8 @@ class ApplicationRecord < ActiveRecord::Base
|
|||
|
||||
include Remotable
|
||||
|
||||
connects_to database: { writing: :primary, reading: ENV['DB_REPLICA_NAME'] || ENV['READ_DATABASE_URL'] ? :read : :primary }
|
||||
|
||||
class << self
|
||||
def update_index(_type_name, *_args, &_block)
|
||||
super if Chewy.enabled?
|
||||
|
|
|
@ -151,10 +151,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,7 +32,6 @@ class UserSettings
|
|||
setting :emoji_reaction_streaming_notify_impl2, 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
|
||||
|
|
|
@ -4,7 +4,7 @@ class StatusRelationshipsPresenter
|
|||
PINNABLE_VISIBILITIES = %w(public public_unlisted unlisted login private).freeze
|
||||
|
||||
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map,
|
||||
:bookmarks_map, :filters_map, :emoji_reactions_map
|
||||
:bookmarks_map, :filters_map, :emoji_reactions_map, :attributes_map
|
||||
|
||||
def initialize(statuses, current_account_id = nil, **options)
|
||||
@current_account_id = current_account_id
|
||||
|
@ -23,13 +23,14 @@ class StatusRelationshipsPresenter
|
|||
conversation_ids = statuses.filter_map(&:conversation_id).uniq
|
||||
pinnable_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.account_id == current_account_id && PINNABLE_VISIBILITIES.include?(s.visibility) }
|
||||
|
||||
@filters_map = build_filters_map(statuses, current_account_id).merge(options[:filters_map] || {})
|
||||
@reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {})
|
||||
@favourites_map = Status.favourites_map(status_ids, current_account_id).merge(options[:favourites_map] || {})
|
||||
@bookmarks_map = Status.bookmarks_map(status_ids, current_account_id).merge(options[:bookmarks_map] || {})
|
||||
@mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {})
|
||||
@pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {})
|
||||
@filters_map = build_filters_map(statuses, current_account_id).merge(options[:filters_map] || {})
|
||||
@reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {})
|
||||
@favourites_map = Status.favourites_map(status_ids, current_account_id).merge(options[:favourites_map] || {})
|
||||
@bookmarks_map = Status.bookmarks_map(status_ids, current_account_id).merge(options[:bookmarks_map] || {})
|
||||
@mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {})
|
||||
@pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {})
|
||||
@emoji_reactions_map = Status.emoji_reactions_map(status_ids, current_account_id).merge(options[:emoji_reactions_map] || {})
|
||||
@attributes_map = options[:attributes_map] || {}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
end
|
||||
|
||||
def name
|
||||
object.suspended? ? '' : object.display_name
|
||||
object.suspended? ? object.username : (object.display_name.presence || object.username)
|
||||
end
|
||||
|
||||
def summary
|
||||
|
|
|
@ -51,13 +51,11 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:use_blurhash] = object.current_account.user.setting_use_blurhash
|
||||
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
|
||||
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
|
||||
|
@ -15,4 +15,8 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer
|
|||
def html
|
||||
Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED)
|
||||
end
|
||||
|
||||
def published_at
|
||||
object.created_at
|
||||
end
|
||||
end
|
||||
|
|
|
@ -101,9 +101,17 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
status_reference_ids.size
|
||||
end
|
||||
|
||||
def reblogs_count
|
||||
relationships&.attributes_map&.dig(object.id, :reblogs_count) || object.reblogs_count
|
||||
end
|
||||
|
||||
def favourites_count
|
||||
relationships&.attributes_map&.dig(object.id, :favourites_count) || object.favourites_count
|
||||
end
|
||||
|
||||
def favourited
|
||||
if instance_options && instance_options[:relationships]
|
||||
instance_options[:relationships].favourites_map[object.id] || false
|
||||
if relationships
|
||||
relationships.favourites_map[object.id] || false
|
||||
else
|
||||
current_user.account.favourited?(object)
|
||||
end
|
||||
|
@ -125,40 +133,40 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def reblogged
|
||||
if instance_options && instance_options[:relationships]
|
||||
instance_options[:relationships].reblogs_map[object.id] || false
|
||||
if relationships
|
||||
relationships.reblogs_map[object.id] || false
|
||||
else
|
||||
current_user.account.reblogged?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def muted
|
||||
if instance_options && instance_options[:relationships]
|
||||
instance_options[:relationships].mutes_map[object.conversation_id] || false
|
||||
if relationships
|
||||
relationships.mutes_map[object.conversation_id] || false
|
||||
else
|
||||
current_user.account.muting_conversation?(object.conversation)
|
||||
end
|
||||
end
|
||||
|
||||
def bookmarked
|
||||
if instance_options && instance_options[:relationships]
|
||||
instance_options[:relationships].bookmarks_map[object.id] || false
|
||||
if relationships
|
||||
relationships.bookmarks_map[object.id] || false
|
||||
else
|
||||
current_user.account.bookmarked?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def pinned
|
||||
if instance_options && instance_options[:relationships]
|
||||
instance_options[:relationships].pins_map[object.id] || false
|
||||
if relationships
|
||||
relationships.pins_map[object.id] || false
|
||||
else
|
||||
current_user.account.pinned?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def filtered
|
||||
if instance_options && instance_options[:relationships]
|
||||
instance_options[:relationships].filters_map[object.id] || []
|
||||
if relationships
|
||||
relationships.filters_map[object.id] || []
|
||||
else
|
||||
current_user.account.status_matches_filters(object)
|
||||
end
|
||||
|
@ -183,6 +191,12 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
object.active_mentions.to_a.sort_by(&:id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relationships
|
||||
instance_options && instance_options[:relationships]
|
||||
end
|
||||
|
||||
class ApplicationSerializer < ActiveModel::Serializer
|
||||
attributes :name, :website
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
|
||||
set_immediate_protocol_attributes!
|
||||
|
||||
@account.save
|
||||
@account.save!
|
||||
end
|
||||
|
||||
def update_account
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue