Merge branch 'gs-master' into prevent-local-only-federation
Conflicts: db/schema.rbremotes/1727458204337373841/tmp_refs/heads/signup-info-prompt
commit
82b2e224a2
3
Gemfile
3
Gemfile
|
@ -14,12 +14,13 @@ gem 'pg', '~> 0.20'
|
|||
gem 'pghero', '~> 1.7'
|
||||
gem 'dotenv-rails', '~> 2.2'
|
||||
|
||||
gem 'fog-aws', '~> 1.4', require: false
|
||||
gem 'aws-sdk', '~> 2.10', require: false
|
||||
gem 'fog-core', '~> 1.45'
|
||||
gem 'fog-local', '~> 0.4', require: false
|
||||
gem 'fog-openstack', '~> 0.1', require: false
|
||||
gem 'paperclip', '~> 5.1'
|
||||
gem 'paperclip-av-transcoder', '~> 0.6'
|
||||
gem 'posix-spawn'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.5'
|
||||
|
|
21
Gemfile.lock
21
Gemfile.lock
|
@ -57,6 +57,14 @@ GEM
|
|||
encryptor (~> 3.0.0)
|
||||
av (0.9.0)
|
||||
cocaine (~> 0.5.3)
|
||||
aws-sdk (2.10.100)
|
||||
aws-sdk-resources (= 2.10.100)
|
||||
aws-sdk-core (2.10.100)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.10.100)
|
||||
aws-sdk-core (= 2.10.100)
|
||||
aws-sigv4 (1.0.2)
|
||||
bcrypt (3.1.11)
|
||||
better_errors (2.4.0)
|
||||
coderay (>= 1.0.0)
|
||||
|
@ -152,11 +160,6 @@ GEM
|
|||
i18n (~> 0.5)
|
||||
fast_blank (1.0.0)
|
||||
ffi (1.9.18)
|
||||
fog-aws (1.4.1)
|
||||
fog-core (~> 1.38)
|
||||
fog-json (~> 1.0)
|
||||
fog-xml (~> 0.1)
|
||||
ipaddress (~> 0.8)
|
||||
fog-core (1.45.0)
|
||||
builder
|
||||
excon (~> 0.58)
|
||||
|
@ -170,9 +173,6 @@ GEM
|
|||
fog-core (>= 1.40)
|
||||
fog-json (>= 1.0)
|
||||
ipaddress (>= 0.8)
|
||||
fog-xml (0.1.3)
|
||||
fog-core
|
||||
nokogiri (>= 1.5.11, < 2.0.0)
|
||||
formatador (0.2.5)
|
||||
fuubar (2.2.0)
|
||||
rspec-core (~> 3.0)
|
||||
|
@ -228,6 +228,7 @@ GEM
|
|||
idn-ruby (0.1.0)
|
||||
ipaddress (0.8.3)
|
||||
iso-639 (0.2.8)
|
||||
jmespath (1.3.1)
|
||||
json (2.1.0)
|
||||
json-ld (2.1.7)
|
||||
multi_json (~> 1.12)
|
||||
|
@ -324,6 +325,7 @@ GEM
|
|||
pghero (1.7.0)
|
||||
activerecord
|
||||
pkg-config (1.2.8)
|
||||
posix-spawn (0.3.13)
|
||||
powerpack (0.1.1)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
|
@ -544,6 +546,7 @@ DEPENDENCIES
|
|||
active_record_query_trace (~> 1.5)
|
||||
addressable (~> 2.5)
|
||||
annotate (~> 2.7)
|
||||
aws-sdk (~> 2.10)
|
||||
better_errors (~> 2.4)
|
||||
binding_of_caller (~> 0.7)
|
||||
bootsnap
|
||||
|
@ -566,7 +569,6 @@ DEPENDENCIES
|
|||
fabrication (~> 2.18)
|
||||
faker (~> 1.7)
|
||||
fast_blank (~> 1.0)
|
||||
fog-aws (~> 1.4)
|
||||
fog-core (~> 1.45)
|
||||
fog-local (~> 0.4)
|
||||
fog-openstack (~> 0.1)
|
||||
|
@ -601,6 +603,7 @@ DEPENDENCIES
|
|||
pg (~> 0.20)
|
||||
pghero (~> 1.7)
|
||||
pkg-config (~> 1.2)
|
||||
posix-spawn
|
||||
pry-rails (~> 0.3)
|
||||
puma (~> 3.10)
|
||||
pundit (~> 1.1)
|
||||
|
|
|
@ -89,7 +89,8 @@ module Admin
|
|||
:username,
|
||||
:display_name,
|
||||
:email,
|
||||
:ip
|
||||
:ip,
|
||||
:staff
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -92,7 +92,9 @@ module Admin
|
|||
def filter_params
|
||||
params.permit(
|
||||
:local,
|
||||
:remote
|
||||
:remote,
|
||||
:by_domain,
|
||||
:shortcode
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,19 +72,4 @@ class Api::BaseController < ApplicationController
|
|||
def render_empty
|
||||
render json: {}, status: 200
|
||||
end
|
||||
|
||||
def set_maps(statuses) # rubocop:disable Style/AccessorMethodName
|
||||
if current_account.nil?
|
||||
@reblogs_map = {}
|
||||
@favourites_map = {}
|
||||
@mutes_map = {}
|
||||
return
|
||||
end
|
||||
|
||||
status_ids = statuses.compact.flat_map { |s| [s.id, s.reblog_of_id] }.uniq
|
||||
conversation_ids = statuses.compact.map(&:conversation_id).compact.uniq
|
||||
@reblogs_map = Status.reblogs_map(status_ids, current_account)
|
||||
@favourites_map = Status.favourites_map(status_ids, current_account)
|
||||
@mutes_map = Status.mutes_map(conversation_ids, current_account)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::ListsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!
|
||||
before_action :set_account
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@lists = @account.lists.where(account: current_account)
|
||||
render json: @lists, each_serializer: REST::ListSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
end
|
|
@ -1,18 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::ListsController < Api::BaseController
|
||||
LISTS_LIMIT = 50
|
||||
|
||||
before_action -> { doorkeeper_authorize! :read }, only: [:index, :show]
|
||||
before_action -> { doorkeeper_authorize! :write }, except: [:index, :show]
|
||||
|
||||
before_action :require_user!
|
||||
before_action :set_list, except: [:index, :create]
|
||||
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
def index
|
||||
@lists = List.where(account: current_account).paginate_by_max_id(limit_param(LISTS_LIMIT), params[:max_id], params[:since_id])
|
||||
@lists = List.where(account: current_account).all
|
||||
render json: @lists, each_serializer: REST::ListSerializer
|
||||
end
|
||||
|
||||
|
@ -44,36 +40,4 @@ class Api::V1::ListsController < Api::BaseController
|
|||
def list_params
|
||||
params.permit(:title)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_lists_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @lists.empty?
|
||||
api_v1_lists_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@lists.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@lists.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@lists.size == limit_param(LISTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,6 +28,8 @@ class Api::Web::PushSubscriptionsController < Api::BaseController
|
|||
},
|
||||
}
|
||||
|
||||
data.deep_merge!(params[:data]) if params[:data]
|
||||
|
||||
web_subscription = ::Web::PushSubscription.create!(
|
||||
endpoint: params[:subscription][:endpoint],
|
||||
key_p256dh: params[:subscription][:keys][:p256dh],
|
||||
|
|
|
@ -62,6 +62,7 @@ class ApplicationController < ActionController::Base
|
|||
pack: pack_name,
|
||||
preload: nil,
|
||||
skin: nil,
|
||||
supported_locales: data['locales'],
|
||||
}
|
||||
if data['pack'][pack_name].is_a?(Hash)
|
||||
pack_data[:common] = nil if data['pack'][pack_name]['use_common'] == false
|
||||
|
@ -93,6 +94,7 @@ class ApplicationController < ActionController::Base
|
|||
pack: nil,
|
||||
preload: nil,
|
||||
skin: nil,
|
||||
supported_locales: data['locales'],
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -44,7 +44,8 @@ module RateLimitHeaders
|
|||
end
|
||||
|
||||
def api_throttle_data
|
||||
request.env['rack.attack.throttle_data']['api']
|
||||
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] }
|
||||
request.env['rack.attack.throttle_data'][most_limited_type]
|
||||
end
|
||||
|
||||
def request_time
|
||||
|
|
|
@ -5,6 +5,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||
|
||||
before_action :store_current_location
|
||||
before_action :authenticate_resource_owner!
|
||||
before_action :set_pack
|
||||
|
||||
include Localized
|
||||
|
||||
|
@ -13,4 +14,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||
def store_current_location
|
||||
store_location_for(:user, request.url)
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'auth'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||
|
||||
before_action :store_current_location
|
||||
before_action :authenticate_resource_owner!
|
||||
before_action :set_pack
|
||||
|
||||
include Localized
|
||||
|
||||
|
@ -13,4 +14,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||
def store_current_location
|
||||
store_location_for(:user, request.url)
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'settings'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::FlavoursController < Settings::BaseController
|
||||
|
||||
def index
|
||||
redirect_to action: 'show', flavour: current_flavour
|
||||
end
|
||||
|
||||
def show
|
||||
unless Themes.instance.flavours.include?(params[:flavour]) or params[:flavour] == current_flavour
|
||||
redirect_to action: 'show', flavour: current_flavour
|
||||
end
|
||||
|
||||
@listing = Themes.instance.flavours
|
||||
@selected = params[:flavour]
|
||||
end
|
||||
|
||||
def update
|
||||
user_settings.update(user_settings_params(params[:flavour]).to_h)
|
||||
redirect_to action: 'show', flavour: params[:flavour]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_settings
|
||||
UserSettingsDecorator.new(current_user)
|
||||
end
|
||||
|
||||
def user_settings_params(flavour)
|
||||
params.require(:user).merge({ setting_flavour: flavour }).permit(
|
||||
:setting_flavour,
|
||||
:setting_skin
|
||||
)
|
||||
end
|
||||
end
|
|
@ -28,6 +28,7 @@ class Settings::MigrationsController < ApplicationController
|
|||
end
|
||||
|
||||
def migration_account_changed?
|
||||
current_account.moved_to_account_id != @migration.account&.id
|
||||
current_account.moved_to_account_id != @migration.account&.id &&
|
||||
current_account.id != @migration.account&.id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,13 +33,12 @@ class Settings::PreferencesController < Settings::BaseController
|
|||
:setting_default_sensitive,
|
||||
:setting_unfollow_modal,
|
||||
:setting_boost_modal,
|
||||
:setting_favourite_modal,
|
||||
:setting_delete_modal,
|
||||
:setting_auto_play_gif,
|
||||
:setting_reduce_motion,
|
||||
:setting_system_font_ui,
|
||||
:setting_noindex,
|
||||
:setting_flavour,
|
||||
:setting_skin,
|
||||
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
||||
interactions: %i(must_be_follower must_be_following)
|
||||
)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::FilterHelper
|
||||
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip).freeze
|
||||
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
|
||||
INVITE_FILTER = %i(available expired).freeze
|
||||
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip staff).freeze
|
||||
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
|
||||
INVITE_FILTER = %i(available expired).freeze
|
||||
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
||||
|
||||
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER
|
||||
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS
|
||||
|
||||
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
||||
new_url = filtered_url_for(link_to_params)
|
||||
|
|
|
@ -10,6 +10,7 @@ module SettingsHelper
|
|||
eo: 'Esperanto',
|
||||
es: 'Español',
|
||||
fa: 'فارسی',
|
||||
gl: 'Galego',
|
||||
fi: 'Suomi',
|
||||
fr: 'Français',
|
||||
he: 'עברית',
|
||||
|
|
|
@ -37,7 +37,3 @@ delegate(document, '#account_header', 'change', ({ target }) => {
|
|||
|
||||
header.style.backgroundImage = `url(${url})`;
|
||||
});
|
||||
|
||||
delegate(document, '#user_setting_flavour, #user_setting_skin', 'change', ({ target }) => {
|
||||
target.form.submit();
|
||||
});
|
||||
|
|
|
@ -58,6 +58,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
'settings',
|
||||
'prepend',
|
||||
'boostModal',
|
||||
'favouriteModal',
|
||||
'muted',
|
||||
'collapse',
|
||||
'notification',
|
||||
|
@ -204,8 +205,8 @@ export default class Status extends ImmutablePureComponent {
|
|||
this.props.onReply(this.props.status, this.context.router.history);
|
||||
}
|
||||
|
||||
handleHotkeyFavourite = () => {
|
||||
this.props.onFavourite(this.props.status);
|
||||
handleHotkeyFavourite = (e) => {
|
||||
this.props.onFavourite(this.props.status, e);
|
||||
}
|
||||
|
||||
handleHotkeyBoost = e => {
|
||||
|
|
|
@ -71,8 +71,8 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
handleFavouriteClick = () => {
|
||||
this.props.onFavourite(this.props.status);
|
||||
handleFavouriteClick = (e) => {
|
||||
this.props.onFavourite(this.props.status, e);
|
||||
}
|
||||
|
||||
handleReblogClick = (e) => {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
|||
import { initReport } from 'flavours/glitch/actions/reports';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
||||
import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
|
@ -78,11 +78,19 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onFavourite (status) {
|
||||
onModalFavourite (status) {
|
||||
dispatch(favourite(status));
|
||||
},
|
||||
|
||||
onFavourite (status, e) {
|
||||
if (status.get('favourited')) {
|
||||
dispatch(unfavourite(status));
|
||||
} else {
|
||||
dispatch(favourite(status));
|
||||
if (e.shiftKey || !favouriteModal) {
|
||||
this.onModalFavourite(status);
|
||||
} else {
|
||||
dispatch(openModal('FAVOURITE', { status, onFavourite: this.onModalFavourite }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -48,8 +48,8 @@ export default class ActionBar extends React.PureComponent {
|
|||
this.props.onReblog(this.props.status, e);
|
||||
}
|
||||
|
||||
handleFavouriteClick = () => {
|
||||
this.props.onFavourite(this.props.status);
|
||||
handleFavouriteClick = (e) => {
|
||||
this.props.onFavourite(this.props.status, e);
|
||||
}
|
||||
|
||||
handleDeleteClick = () => {
|
||||
|
|
|
@ -30,7 +30,7 @@ import { openModal } from 'flavours/glitch/actions/modal';
|
|||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
||||
import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -95,11 +95,19 @@ export default class Status extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleFavouriteClick = (status) => {
|
||||
handleModalFavourite = (status) => {
|
||||
this.props.dispatch(favourite(status));
|
||||
}
|
||||
|
||||
handleFavouriteClick = (status, e) => {
|
||||
if (status.get('favourited')) {
|
||||
this.props.dispatch(unfavourite(status));
|
||||
} else {
|
||||
this.props.dispatch(favourite(status));
|
||||
if (e.shiftKey || !favouriteModal) {
|
||||
this.handleModalFavourite(status);
|
||||
} else {
|
||||
this.props.dispatch(openModal('FAVOURITE', { status, onFavourite: this.handleModalFavourite }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from 'flavours/glitch/components/button';
|
||||
import StatusContent from 'flavours/glitch/components/status_content';
|
||||
import Avatar from 'flavours/glitch/components/avatar';
|
||||
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
|
||||
import DisplayName from 'flavours/glitch/components/display_name';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
const messages = defineMessages({
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
export default class FavouriteModal extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onFavourite: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.button.focus();
|
||||
}
|
||||
|
||||
handleFavourite = () => {
|
||||
this.props.onFavourite(this.props.status);
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
}
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.button = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal favourite-modal'>
|
||||
<div className='favourite-modal__container'>
|
||||
<div className='status light'>
|
||||
<div className='favourite-modal__status-header'>
|
||||
<div className='favourite-modal__status-time'>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||
</div>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
|
||||
<div className='status__avatar'>
|
||||
<Avatar account={status.get('account')} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent status={status} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='favourite-modal__action-bar'>
|
||||
<div><FormattedMessage id='favourite_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-star' /></span> }} /></div>
|
||||
<Button text={intl.formatMessage(messages.favourite)} onClick={this.handleFavourite} ref={this.setRef} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ import ActionsModal from './actions_modal';
|
|||
import MediaModal from './media_modal';
|
||||
import VideoModal from './video_modal';
|
||||
import BoostModal from './boost_modal';
|
||||
import FavouriteModal from './favourite_modal';
|
||||
import DoodleModal from './doodle_modal';
|
||||
import ConfirmationModal from './confirmation_modal';
|
||||
import {
|
||||
|
@ -23,6 +24,7 @@ const MODAL_COMPONENTS = {
|
|||
'ONBOARDING': OnboardingModal,
|
||||
'VIDEO': () => Promise.resolve({ default: VideoModal }),
|
||||
'BOOST': () => Promise.resolve({ default: BoostModal }),
|
||||
'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }),
|
||||
'DOODLE': () => Promise.resolve({ default: DoodleModal }),
|
||||
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
|
||||
'MUTE': MuteModal,
|
||||
|
@ -92,7 +94,7 @@ export default class ModalRoot extends React.PureComponent {
|
|||
}
|
||||
|
||||
renderLoading = modalId => () => {
|
||||
return ['MEDIA', 'VIDEO', 'BOOST', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
||||
return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
||||
}
|
||||
|
||||
renderError = (props) => {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 193 KiB |
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/ar.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/bg.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/ca.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/de.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,52 @@
|
|||
import inherited from 'mastodon/locales/en.json';
|
||||
|
||||
const messages = {
|
||||
'getting_started.open_source_notice': 'Glitchsoc is free open source software forked from {Mastodon}. You can contribute or report issues on GitHub at {github}.',
|
||||
'layout.auto': 'Auto',
|
||||
'layout.current_is': 'Your current layout is:',
|
||||
'layout.desktop': 'Desktop',
|
||||
'layout.mobile': 'Mobile',
|
||||
'navigation_bar.app_settings': 'App settings',
|
||||
'getting_started.onboarding': 'Show me around',
|
||||
'onboarding.page_one.federation': '{domain} is an \'instance\' of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.',
|
||||
'onboarding.page_one.welcome': 'Welcome to {domain}!',
|
||||
'onboarding.page_six.github': '{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}, and is compatible with any Mastodon instance or app. Glitchsoc is entirely free and open-source. You can report bugs, request features, or contribute to the code on {github}.',
|
||||
'settings.auto_collapse': 'Automatic collapsing',
|
||||
'settings.auto_collapse_all': 'Everything',
|
||||
'settings.auto_collapse_lengthy': 'Lengthy toots',
|
||||
'settings.auto_collapse_media': 'Toots with media',
|
||||
'settings.auto_collapse_notifications': 'Notifications',
|
||||
'settings.auto_collapse_reblogs': 'Boosts',
|
||||
'settings.auto_collapse_replies': 'Replies',
|
||||
'settings.close': 'Close',
|
||||
'settings.collapsed_statuses': 'Collapsed toots',
|
||||
'settings.enable_collapsed': 'Enable collapsed toots',
|
||||
'settings.general': 'General',
|
||||
'settings.image_backgrounds': 'Image backgrounds',
|
||||
'settings.image_backgrounds_media': 'Preview collapsed toot media',
|
||||
'settings.image_backgrounds_users': 'Give collapsed toots an image background',
|
||||
'settings.media': 'Media',
|
||||
'settings.media_letterbox': 'Letterbox media',
|
||||
'settings.media_fullwidth': 'Full-width media previews',
|
||||
'settings.preferences': 'User preferences',
|
||||
'settings.wide_view': 'Wide view (Desktop mode only)',
|
||||
'settings.navbar_under': 'Navbar at the bottom (Mobile only)',
|
||||
'status.collapse': 'Collapse',
|
||||
'status.uncollapse': 'Uncollapse',
|
||||
|
||||
'favourite_modal.combo': 'You can press {combo} to skip this next time',
|
||||
|
||||
'home.column_settings.show_direct': 'Show DMs',
|
||||
|
||||
'notification.markForDeletion': 'Mark for deletion',
|
||||
'notifications.clear': 'Clear all my notifications',
|
||||
'notifications.marked_clear_confirmation': 'Are you sure you want to permanently clear all selected notifications?',
|
||||
'notifications.marked_clear': 'Clear selected notifications',
|
||||
|
||||
'notification_purge.btn_all': 'Select\nall',
|
||||
'notification_purge.btn_none': 'Select\nnone',
|
||||
'notification_purge.btn_invert': 'Invert\nselection',
|
||||
'notification_purge.btn_apply': 'Clear\nselected',
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/eo.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/es.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/fa.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/fi.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/fr.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/he.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/hr.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/hu.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/id.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/io.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/it.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,55 @@
|
|||
import inherited from 'mastodon/locales/ja.json';
|
||||
|
||||
const messages = {
|
||||
'getting_started.open_source_notice': 'Glitchsocは{Mastodon}によるフリーなオープンソースソフトウェアです。誰でもGitHub({github})から開発に參加したり、問題を報告したりできます。',
|
||||
'layout.auto': '自動',
|
||||
'layout.current_is': 'あなたの現在のレイアウト:',
|
||||
'layout.desktop': 'Desktop',
|
||||
'layout.mobile': 'Mobile',
|
||||
'navigation_bar.app_settings': 'アプリ設定',
|
||||
'getting_started.onboarding': '解説を表示',
|
||||
'onboarding.page_one.federation': '{domain}はMastodonのインスタンスです。Mastodonとは、独立したサーバが連携して作るソーシャルネットワークです。これらのサーバーをインスタンスと呼びます。',
|
||||
'onboarding.page_one.welcome': '{domain}へようこそ!',
|
||||
'onboarding.page_six.github': '{domain}はGlitchsocを使用しています。Glitchsocは{Mastodon}のフレンドリーな{fork}で、どんなMastodonアプリやインスタンスとも互換性があります。Glitchsocは完全に無料で、オープンソースです。{github}でバグ報告や機能要望あるいは貢獻をすることが可能です。',
|
||||
'settings.auto_collapse': '自動折りたたみ',
|
||||
'settings.auto_collapse_all': 'すべて',
|
||||
'settings.auto_collapse_lengthy': '長いトゥート',
|
||||
'settings.auto_collapse_media': 'メディア付きトゥート',
|
||||
'settings.auto_collapse_notifications': '通知',
|
||||
'settings.auto_collapse_reblogs': 'ブースト',
|
||||
'settings.auto_collapse_replies': '返信',
|
||||
'settings.close': '閉じる',
|
||||
'settings.collapsed_statuses': 'トゥート',
|
||||
'settings.enable_collapsed': 'トゥート折りたたみを有効にする',
|
||||
'settings.general': '一般',
|
||||
'settings.image_backgrounds': '画像背景',
|
||||
'settings.image_backgrounds_media': '折りたまれたメディア付きトゥートをプレビュー',
|
||||
'settings.image_backgrounds_users': '折りたまれたトゥートの背景を変更する',
|
||||
'settings.media': 'メディア',
|
||||
'settings.media_letterbox': 'メディアをレターボックス式で表示',
|
||||
'settings.media_fullwidth': '全幅メディアプレビュー',
|
||||
'settings.preferences': 'ユーザー設定',
|
||||
'settings.wide_view': 'ワイドビュー(Desktopレイアウトのみ)',
|
||||
'settings.navbar_under': 'ナビを画面下部に移動させる(Mobileレイアウトのみ)',
|
||||
'settings.compose_box_opts': 'コンポーズボックス設定',
|
||||
'settings.side_arm': 'セカンダリートゥートボタン',
|
||||
'settings.layout': 'レイアウト',
|
||||
'status.collapse': '折りたたむ',
|
||||
'status.uncollapse': '折りたたみを解除',
|
||||
|
||||
'favourite_modal.combo': '次からは {combo} を押せば、これをスキップできます。',
|
||||
|
||||
'home.column_settings.show_direct': 'DMを表示',
|
||||
|
||||
'notification.markForDeletion': '選択',
|
||||
'notifications.clear': '通知を全てクリアする',
|
||||
'notifications.marked_clear_confirmation': '削除した全ての通知を完全に削除してもよろしいですか?',
|
||||
'notifications.marked_clear': '選択した通知を削除する',
|
||||
|
||||
'notification_purge.btn_all': 'すべて\n選択',
|
||||
'notification_purge.btn_none': '選択\n解除',
|
||||
'notification_purge.btn_invert': '選択を\n反転',
|
||||
'notification_purge.btn_apply': '選択したものを\n削除',
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/ko.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/nl.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/no.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/oc.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,48 @@
|
|||
import inherited from 'mastodon/locales/pl.json';
|
||||
|
||||
const messages = {
|
||||
'getting_started.open_source_notice': 'Glitchsoc jest wolnym i otwartoźródłowym forkiem oprogramowania {Mastodon}. Możesz współtworzyć projekt lub zgłaszać błędy na GitHubie pod adresem {github}.',
|
||||
'layout.auto': 'Automatyczny',
|
||||
'layout.current_is': 'Twój obecny układ to:',
|
||||
'layout.desktop': 'Desktopowy',
|
||||
'layout.mobile': 'Mobilny',
|
||||
'navigation_bar.app_settings': 'Ustawienia aplikacji',
|
||||
'getting_started.onboarding': 'Rozejrzyj się',
|
||||
'onboarding.page_one.federation': '{domain} jest \'instancją\' Mastodona. Mastodon to sieć działających niezależnie serwerów tworzących jedną sieć społecznościową. Te serwery nazywane są instancjami.',
|
||||
'onboarding.page_one.welcome': 'Witamy na {domain}!',
|
||||
'onboarding.page_six.github': '{domain} jest oparty na Glitchsoc. Glitchsoc jest {forkiem} {Mastodon}a kompatybilnym z każdym klientem i aplikacją Mastodona. Glitchsoc jest całkowicie wolnym i otwartoźródłowym oprogramowaniem. Możesz zgłaszać błędy i sugestie funkcji oraz współtworzyć projekt na {github}.',
|
||||
'settings.auto_collapse': 'Automatyczne zwijanie',
|
||||
'settings.auto_collapse_all': 'Wszystko',
|
||||
'settings.auto_collapse_lengthy': 'Długie wpisy',
|
||||
'settings.auto_collapse_media': 'Wpisy z zawartością multimedialną',
|
||||
'settings.auto_collapse_notifications': 'Powiadomienia',
|
||||
'settings.auto_collapse_reblogs': 'Podbicia',
|
||||
'settings.auto_collapse_replies': 'Odpowiedzi',
|
||||
'settings.close': 'Zamknij',
|
||||
'settings.collapsed_statuses': 'Zwijanie wpisów',
|
||||
'settings.enable_collapsed': 'Włącz zwijanie wpisów',
|
||||
'settings.general': 'Ogólne',
|
||||
'settings.image_backgrounds': 'Obrazy w tle',
|
||||
'settings.image_backgrounds_media': 'Wyświetlaj zawartość multimedialną zwiniętych wpisów',
|
||||
'settings.image_backgrounds_users': 'Nadaj tło zwiniętym wpisom',
|
||||
'settings.media': 'Zawartość multimedialna',
|
||||
'settings.media_letterbox': 'Letterbox media',
|
||||
'settings.media_fullwidth': 'Podgląd zawartości multimedialnej o pełnej szerokości',
|
||||
'settings.preferences': 'Preferencje użyytkownika',
|
||||
'settings.wide_view': 'Szeroki widok (tylko w trybie desktopowym)',
|
||||
'settings.navbar_under': 'Pasek nawigacji na dole (tylko w trybie mobilnym)',
|
||||
'status.collapse': 'Zwiń',
|
||||
'status.uncollapse': 'Rozwiń',
|
||||
|
||||
'notification.markForDeletion': 'Oznacz do usunięcia',
|
||||
'notifications.clear': 'Wyczyść wszystkie powiadomienia',
|
||||
'notifications.marked_clear_confirmation': 'Czy na pewno chcesz bezpowrtonie usunąć wszystkie powiadomienia?',
|
||||
'notifications.marked_clear': 'Usuń zaznaczone powiadomienia',
|
||||
|
||||
'notification_purge.btn_all': 'Zaznacz\nwszystkie',
|
||||
'notification_purge.btn_none': 'Odznacz\nwszystkie',
|
||||
'notification_purge.btn_invert': 'Odwróć\nzaznaczenie',
|
||||
'notification_purge.btn_apply': 'Usuń\nzaznaczone',
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/pt-BR.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/pt.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/ru.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/sv.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/th.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/tr.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/uk.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/zh-CN.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/zh-HK.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,7 @@
|
|||
import inherited from 'mastodon/locales/zh-TW.json';
|
||||
|
||||
const messages = {
|
||||
// No translations available.
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -0,0 +1,8 @@
|
|||
en:
|
||||
flavours:
|
||||
glitch:
|
||||
description: The default flavour for GlitchSoc instances.
|
||||
name: Glitch Edition
|
||||
skins:
|
||||
glitch:
|
||||
default: Default
|
|
@ -1 +1,4 @@
|
|||
import 'flavours/glitch/styles/index.scss';
|
||||
|
||||
// This ensures that webpack compiles our images.
|
||||
require.context('../images', true);
|
||||
|
|
|
@ -246,6 +246,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.flavour-screen {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.flavour-description {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
margin: 10px 0;
|
||||
|
||||
& > p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.report-accounts {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -2286,7 +2286,6 @@
|
|||
.getting-started {
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 235px;
|
||||
background: url('~images/mastodon-getting-started.png') no-repeat 0 100%;
|
||||
flex: 1 0 auto;
|
||||
|
||||
p {
|
||||
|
@ -3913,6 +3912,7 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
|
||||
.boost-modal,
|
||||
.favourite-modal,
|
||||
.confirmation-modal,
|
||||
.report-modal,
|
||||
.actions-modal,
|
||||
|
@ -3944,7 +3944,8 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
.boost-modal__container {
|
||||
.boost-modal__container,
|
||||
.favourite-modal__container{
|
||||
overflow-x: scroll;
|
||||
padding: 10px;
|
||||
|
||||
|
@ -3955,6 +3956,7 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
|
||||
.boost-modal__action-bar,
|
||||
.favourite-modal__action-bar,
|
||||
.confirmation-modal__action-bar,
|
||||
.mute-modal__action-bar,
|
||||
.report-modal__action-bar {
|
||||
|
@ -3976,11 +3978,13 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
.boost-modal__status-header {
|
||||
.boost-modal__status-header,
|
||||
.favourite-modal__status-header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.boost-modal__status-time {
|
||||
.boost-modal__status-time,
|
||||
.favourite-modal__status-time {
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,18 @@ pack:
|
|||
settings:
|
||||
share: packs/share.js
|
||||
|
||||
# (OPTIONAL) The directory which contains localization files for
|
||||
# the flavour, relative to this directory. The contents of this
|
||||
# directory must be `.js` or `.json` files whose names correspond to
|
||||
# language tags and whose default exports are a messages object.
|
||||
locales: locales
|
||||
|
||||
# (OPTIONAL) A file to use as the preview screenshot for the flavour,
|
||||
# or an array thereof. These filenames must be unique across all
|
||||
# images (regardless of path), so it's a good idea to namespace them
|
||||
# to your theme. It's up to you to let webpack know to compile them.
|
||||
screenshot: glitch-preview.jpg
|
||||
|
||||
# (OPTIONAL) The directory which contains the pack files.
|
||||
# Defaults to the theme directory (`app/javascript/themes/[theme]`),
|
||||
# which should be sufficient for like 99% of use-cases lol.
|
||||
|
|
|
@ -15,6 +15,7 @@ export const reduceMotion = getMeta('reduce_motion');
|
|||
export const autoPlayGif = getMeta('auto_play_gif');
|
||||
export const unfollowModal = getMeta('unfollow_modal');
|
||||
export const boostModal = getMeta('boost_modal');
|
||||
export const favouriteModal = getMeta('favourite_modal');
|
||||
export const deleteModal = getMeta('delete_modal');
|
||||
export const me = getMeta('me');
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
en:
|
||||
flavours:
|
||||
vanilla:
|
||||
description: The theme used by vanilla Mastodon instances. This theme might not support all of the features of GlitchSoc.
|
||||
name: Vanilla Mastodon
|
||||
skins:
|
||||
vanilla:
|
||||
default: Default
|
|
@ -20,13 +20,23 @@ pack:
|
|||
settings:
|
||||
share: share.js
|
||||
|
||||
# (OPTIONAL) The directory which contains localization files for
|
||||
# the flavour, relative to this directory.
|
||||
locales: ../../mastodon/locales
|
||||
|
||||
# (OPTIONAL) A file to use as the preview screenshot for the flavour,
|
||||
# or an array thereof. These filenames must be unique across all
|
||||
# images (regardless of path), so it's a good idea to namespace them
|
||||
# to your theme. It's up to you to let webpack know to compile them.
|
||||
screenshot: screenshot.jpg
|
||||
|
||||
# (OPTIONAL) The directory which contains the pack files.
|
||||
# Defaults to the theme directory (`app/javascript/themes/[theme]`),
|
||||
# but in the case of the vanilla Mastodon theme the pack files are
|
||||
# Defaults to this directory (`app/javascript/flavour/[flavour]`),
|
||||
# but in the case of the vanilla Mastodon flavour the pack files are
|
||||
# somewhere else.
|
||||
pack_directory: app/javascript/packs
|
||||
|
||||
# (OPTIONAL) By default the theme will fallback to the default theme
|
||||
# (OPTIONAL) By default the theme will fallback to the default flavour
|
||||
# if a particular pack is not provided. You can specify different
|
||||
# fallbacks here, or disable fallback behaviours altogether by
|
||||
# specifying a `null` value.
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"getting_started.open_source_notice": "Glitchsoc is free open source software forked from {Mastodon}. You can contribute or report issues on GitHub at {github}.",
|
||||
"layout.auto": "Auto",
|
||||
"layout.current_is": "Your current layout is:",
|
||||
"layout.desktop": "Desktop",
|
||||
"layout.mobile": "Mobile",
|
||||
"navigation_bar.app_settings": "App settings",
|
||||
"getting_started.onboarding": "Show me around",
|
||||
"onboarding.page_one.federation": "{domain} is an 'instance' of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||
"onboarding.page_one.welcome": "Welcome to {domain}!",
|
||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}, and is compatible with any Mastodon instance or app. Glitchsoc is entirely free and open-source. You can report bugs, request features, or contribute to the code on {github}.",
|
||||
"settings.auto_collapse": "Automatic collapsing",
|
||||
"settings.auto_collapse_all": "Everything",
|
||||
"settings.auto_collapse_lengthy": "Lengthy toots",
|
||||
"settings.auto_collapse_media": "Toots with media",
|
||||
"settings.auto_collapse_notifications": "Notifications",
|
||||
"settings.auto_collapse_reblogs": "Boosts",
|
||||
"settings.auto_collapse_replies": "Replies",
|
||||
"settings.close": "Close",
|
||||
"settings.collapsed_statuses": "Collapsed toots",
|
||||
"settings.enable_collapsed": "Enable collapsed toots",
|
||||
"settings.general": "General",
|
||||
"settings.image_backgrounds": "Image backgrounds",
|
||||
"settings.image_backgrounds_media": "Preview collapsed toot media",
|
||||
"settings.image_backgrounds_users": "Give collapsed toots an image background",
|
||||
"settings.media": "Media",
|
||||
"settings.media_letterbox": "Letterbox media",
|
||||
"settings.media_fullwidth": "Full-width media previews",
|
||||
"settings.preferences": "User preferences",
|
||||
"settings.wide_view": "Wide view (Desktop mode only)",
|
||||
"settings.navbar_under": "Navbar at the bottom (Mobile only)",
|
||||
"status.collapse": "Collapse",
|
||||
"status.uncollapse": "Uncollapse",
|
||||
|
||||
"home.column_settings.show_direct": "Show DMs",
|
||||
|
||||
"notification.markForDeletion": "Mark for deletion",
|
||||
"notifications.clear": "Clear all my notifications",
|
||||
"notifications.marked_clear_confirmation": "Are you sure you want to permanently clear all selected notifications?",
|
||||
"notifications.marked_clear": "Clear selected notifications",
|
||||
|
||||
"notification_purge.btn_all": "Select\nall",
|
||||
"notification_purge.btn_none": "Select\nnone",
|
||||
"notification_purge.btn_invert": "Invert\nselection",
|
||||
"notification_purge.btn_apply": "Clear\nselected"
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"getting_started.open_source_notice": "Glitchsocは{Mastodon}によるフリーなオープンソースソフトウェアです。誰でもGitHub({github})から開発に參加したり、問題を報告したりできます。",
|
||||
"layout.auto": "自動",
|
||||
"layout.current_is": "あなたの現在のレイアウト:",
|
||||
"layout.desktop": "デスクトップ",
|
||||
"layout.mobile": "モバイル",
|
||||
"navigation_bar.app_settings": "アプリ設定",
|
||||
"getting_started.onboarding": "解説",
|
||||
"onboarding.page_one.federation": "{domain}はMastodonのインスタンスです。Mastodonとは、独立したサーバが連携して作るソーシャルネットワークです。これらのサーバーをインスタンスと呼びます。",
|
||||
"onboarding.page_one.welcome": "{domain}へようこそ!",
|
||||
"onboarding.page_six.github": "{domain}はGlitchsocを使用しています。Glitchsocは{Mastodon}のフレンドリーな{fork}で、どんなMastodonアプリやインスタンスとも互換性があります。Glitchsocは完全に無料で、オープンソースです。{github}でバグ報告や機能要望あるいは貢獻をすることが可能です。",
|
||||
"settings.auto_collapse": "自動折りたたみ",
|
||||
"settings.auto_collapse_all": "すべて",
|
||||
"settings.auto_collapse_lengthy": "長いトゥート",
|
||||
"settings.auto_collapse_media": "メディア付きトゥート",
|
||||
"settings.auto_collapse_notifications": "通知",
|
||||
"settings.auto_collapse_reblogs": "ブースト",
|
||||
"settings.auto_collapse_replies": "返信",
|
||||
"settings.close": "閉じる",
|
||||
"settings.collapsed_statuses": "トゥート",
|
||||
"settings.enable_collapsed": "トゥート折りたたみを有効にする",
|
||||
"settings.general": "一般",
|
||||
"settings.image_backgrounds": "画像背景",
|
||||
"settings.image_backgrounds_media": "折りたまれたメディア付きテゥートをプレビュー",
|
||||
"settings.image_backgrounds_users": "折りたまれたトゥートの背景を変更する",
|
||||
"settings.media": "メディア",
|
||||
"settings.media_letterbox": "メディアをレターボックス式で表示",
|
||||
"settings.media_fullwidth": "全幅メディアプリビュー",
|
||||
"settings.preferences": "ユーザー設定",
|
||||
"settings.wide_view": "ワイドビュー(デスクトップレイアウトのみ)",
|
||||
"settings.navbar_under": "ナビを画面下部に移動させる(モバイルレイアウトのみ)",
|
||||
"status.collapse": "折りたたむ",
|
||||
"status.uncollapse": "折りたたみを解除",
|
||||
|
||||
"notification.markForDeletion": "選択",
|
||||
"notifications.clear": "通知を全てクリアする",
|
||||
"notifications.marked_clear_confirmation": "削除した全ての通知を完全に削除してもよろしいですか?",
|
||||
"notifications.marked_clear": "選択した通知を削除する",
|
||||
|
||||
"notification_purge.btn_all": "すべて\n選択",
|
||||
"notification_purge.btn_none": "選択\n解除",
|
||||
"notification_purge.btn_invert": "選択を\n反転",
|
||||
"notification_purge.btn_apply": "選択したものを\n削除"
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"getting_started.open_source_notice": "Glitchsoc jest wolnym i otwartoźródłowym forkiem oprogramowania {Mastodon}. Możesz współtworzyć projekt lub zgłaszać błędy na GitHubie pod adresem {github}.",
|
||||
"layout.auto": "Automatyczny",
|
||||
"layout.current_is": "Twój obecny układ to:",
|
||||
"layout.desktop": "Desktopowy",
|
||||
"layout.mobile": "Mobilny",
|
||||
"navigation_bar.app_settings": "Ustawienia aplikacji",
|
||||
"getting_started.onboarding": "Rozejrzyj się",
|
||||
"onboarding.page_one.federation": "{domain} jest 'instancją' Mastodona. Mastodon to sieć działających niezależnie serwerów tworzących jedną sieć społecznościową. Te serwery nazywane są instancjami.",
|
||||
"onboarding.page_one.welcome": "Witamy na {domain}!",
|
||||
"onboarding.page_six.github": "{domain} jest oparty na Glitchsoc. Glitchsoc jest {forkiem} {Mastodon}a kompatybilnym z każdym klientem i aplikacją Mastodona. Glitchsoc jest całkowicie wolnym i otwartoźródłowym oprogramowaniem. Możesz zgłaszać błędy i sugestie funkcji oraz współtworzyć projekt na {github}.",
|
||||
"settings.auto_collapse": "Automatyczne zwijanie",
|
||||
"settings.auto_collapse_all": "Wszystko",
|
||||
"settings.auto_collapse_lengthy": "Długie wpisy",
|
||||
"settings.auto_collapse_media": "Wpisy z zawartością multimedialną",
|
||||
"settings.auto_collapse_notifications": "Powiadomienia",
|
||||
"settings.auto_collapse_reblogs": "Podbicia",
|
||||
"settings.auto_collapse_replies": "Odpowiedzi",
|
||||
"settings.close": "Zamknij",
|
||||
"settings.collapsed_statuses": "Zwijanie wpisów",
|
||||
"settings.enable_collapsed": "Włącz zwijanie wpisów",
|
||||
"settings.general": "Ogólne",
|
||||
"settings.image_backgrounds": "Obrazy w tle",
|
||||
"settings.image_backgrounds_media": "Wyświetlaj zawartość multimedialną zwiniętych wpisów",
|
||||
"settings.image_backgrounds_users": "Nadaj tło zwiniętym wpisom",
|
||||
"settings.media": "Zawartość multimedialna",
|
||||
"settings.media_letterbox": "Letterbox media",
|
||||
"settings.media_fullwidth": "Podgląd zawartości multimedialnej o pełnej szerokości",
|
||||
"settings.preferences": "Preferencje użyytkownika",
|
||||
"settings.wide_view": "Szeroki widok (tylko w trybie desktopowym)",
|
||||
"settings.navbar_under": "Pasek nawigacji na dole (tylko w trybie mobilnym)",
|
||||
"status.collapse": "Zwiń",
|
||||
"status.uncollapse": "Rozwiń",
|
||||
|
||||
"notification.markForDeletion": "Oznacz do usunięcia",
|
||||
"notifications.clear": "Wyczyść wszystkie powiadomienia",
|
||||
"notifications.marked_clear_confirmation": "Czy na pewno chcesz bezpowrtonie usunąć wszystkie powiadomienia?",
|
||||
"notifications.marked_clear": "Usuń zaznaczone powiadomienia",
|
||||
|
||||
"notification_purge.btn_all": "Zaznacz\nwszystkie",
|
||||
"notification_purge.btn_none": "Odznacz\nwszystkie",
|
||||
"notification_purge.btn_invert": "Odwróć\nzaznaczenie",
|
||||
"notification_purge.btn_apply": "Usuń\nzaznaczone"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 234 KiB |
|
@ -10,6 +10,10 @@ export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FA
|
|||
|
||||
export function fetchFavouritedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
if (getState().getIn(['status_lists', 'favourites', 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchFavouritedStatusesRequest());
|
||||
|
||||
api(getState).get('/api/v1/favourites').then(response => {
|
||||
|
@ -46,7 +50,7 @@ export function expandFavouritedStatuses() {
|
|||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['status_lists', 'favourites', 'next'], null);
|
||||
|
||||
if (url === null) {
|
||||
if (url === null || getState().getIn(['status_lists', 'favourites', 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import axios from 'axios';
|
||||
import { pushNotificationsSetting } from '../settings';
|
||||
|
||||
export const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT';
|
||||
export const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION';
|
||||
|
@ -42,11 +43,15 @@ export function saveSettings() {
|
|||
const state = getState().get('push_notifications');
|
||||
const subscription = state.get('subscription');
|
||||
const alerts = state.get('alerts');
|
||||
const data = { alerts };
|
||||
|
||||
axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
|
||||
data: {
|
||||
alerts,
|
||||
},
|
||||
data,
|
||||
}).then(() => {
|
||||
const me = getState().getIn(['meta', 'me']);
|
||||
if (me) {
|
||||
pushNotificationsSetting.set(me, data);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
export default class Avatar extends React.PureComponent {
|
||||
|
||||
|
@ -8,12 +9,12 @@ export default class Avatar extends React.PureComponent {
|
|||
account: ImmutablePropTypes.map.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
style: PropTypes.object,
|
||||
animate: PropTypes.bool,
|
||||
inline: PropTypes.bool,
|
||||
animate: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animate: false,
|
||||
animate: autoPlayGif,
|
||||
size: 20,
|
||||
inline: false,
|
||||
};
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
export default class AvatarOverlay extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
friend: ImmutablePropTypes.map.isRequired,
|
||||
animate: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animate: autoPlayGif,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { account, friend } = this.props;
|
||||
const { account, friend, animate } = this.props;
|
||||
|
||||
const baseStyle = {
|
||||
backgroundImage: `url(${account.get('avatar_static')})`,
|
||||
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
|
||||
};
|
||||
|
||||
const overlayStyle = {
|
||||
backgroundImage: `url(${friend.get('avatar_static')})`,
|
||||
backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -156,6 +156,8 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='compose-form'>
|
||||
<WarningContainer />
|
||||
|
||||
<Collapsable isVisible={this.props.spoiler} fullHeight={50}>
|
||||
<div className='spoiler-input'>
|
||||
<label>
|
||||
|
@ -165,8 +167,6 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
</div>
|
||||
</Collapsable>
|
||||
|
||||
<WarningContainer />
|
||||
|
||||
<ReplyIndicatorContainer />
|
||||
|
||||
<div className='compose-form__autosuggest-wrapper'>
|
||||
|
@ -199,11 +199,11 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
<SensitiveButtonContainer />
|
||||
<SpoilerButtonContainer />
|
||||
</div>
|
||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
||||
</div>
|
||||
|
||||
<div className='compose-form__publish'>
|
||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
|
||||
</div>
|
||||
<div className='compose-form__publish'>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import IconButton from '../../../components/icon_button';
|
|||
import DisplayName from '../../../components/display_name';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { isRtl } from '../../../rtl';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
|
@ -42,7 +43,10 @@ export default class ReplyIndicator extends ImmutablePureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const style = {
|
||||
direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='reply-indicator'>
|
||||
|
@ -55,7 +59,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
|
||||
<div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
render () {
|
||||
const { intl, media } = this.props;
|
||||
const active = this.state.hovered || this.state.focused;
|
||||
const description = this.state.dirtyDescription || media.get('description') || '';
|
||||
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
|||
import StatusList from '../../components/status_list';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
|
||||
|
@ -16,6 +17,7 @@ const messages = defineMessages({
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
||||
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
||||
});
|
||||
|
||||
|
@ -30,6 +32,7 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
columnId: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -59,12 +62,12 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
this.column = c;
|
||||
}
|
||||
|
||||
handleScrollToBottom = () => {
|
||||
handleScrollToBottom = debounce(() => {
|
||||
this.props.dispatch(expandFavouritedStatuses());
|
||||
}
|
||||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
const { intl, statusIds, columnId, multiColumn, hasMore } = this.props;
|
||||
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
|
@ -85,6 +88,7 @@ export default class Favourites extends ImmutablePureComponent {
|
|||
statusIds={statusIds}
|
||||
scrollKey={`favourited_statuses-${columnId}`}
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onScrollToBottom={this.handleScrollToBottom}
|
||||
/>
|
||||
</Column>
|
||||
|
|
|
@ -161,7 +161,7 @@ export default class ListTimeline extends React.PureComponent {
|
|||
scrollKey={`list_timeline-${columnId}`}
|
||||
timelineId={`list:${id}`}
|
||||
loadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />}
|
||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from '../../../actions/timelines';
|
||||
import Column from '../../../components/column';
|
||||
import ColumnHeader from '../../../components/column_header';
|
||||
import { connectHashtagStream } from '../../../actions/streaming';
|
||||
|
||||
@connect()
|
||||
export default class HashtagTimeline extends React.PureComponent {
|
||||
|
@ -29,16 +30,13 @@ export default class HashtagTimeline extends React.PureComponent {
|
|||
const { dispatch, hashtag } = this.props;
|
||||
|
||||
dispatch(refreshHashtagTimeline(hashtag));
|
||||
|
||||
this.polling = setInterval(() => {
|
||||
dispatch(refreshHashtagTimeline(hashtag));
|
||||
}, 10000);
|
||||
this.disconnect = dispatch(connectHashtagStream(hashtag));
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (typeof this.polling !== 'undefined') {
|
||||
clearInterval(this.polling);
|
||||
this.polling = null;
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
import Column from '../../../components/column';
|
||||
import ColumnHeader from '../../../components/column_header';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connectPublicStream } from '../../../actions/streaming';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
|
||||
|
@ -35,16 +36,13 @@ export default class PublicTimeline extends React.PureComponent {
|
|||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(refreshPublicTimeline());
|
||||
|
||||
this.polling = setInterval(() => {
|
||||
dispatch(refreshPublicTimeline());
|
||||
}, 3000);
|
||||
this.disconnect = dispatch(connectPublicStream());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (typeof this.polling !== 'undefined') {
|
||||
clearInterval(this.polling);
|
||||
this.polling = null;
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ export default class Card extends React.PureComponent {
|
|||
Immutable.fromJS([
|
||||
{
|
||||
type: 'image',
|
||||
url: card.get('url'),
|
||||
url: card.get('embed_url'),
|
||||
description: card.get('title'),
|
||||
meta: {
|
||||
original: {
|
||||
|
@ -59,6 +59,8 @@ export default class Card extends React.PureComponent {
|
|||
|
||||
renderLink () {
|
||||
const { card, maxDescription } = this.props;
|
||||
const { width } = this.state;
|
||||
const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width);
|
||||
|
||||
let image = '';
|
||||
let provider = card.get('provider_name');
|
||||
|
@ -75,17 +77,15 @@ export default class Card extends React.PureComponent {
|
|||
provider = decodeIDNA(getHostname(card.get('url')));
|
||||
}
|
||||
|
||||
const className = classnames('status-card', {
|
||||
'horizontal': card.get('width') > card.get('height'),
|
||||
});
|
||||
const className = classnames('status-card', { horizontal });
|
||||
|
||||
return (
|
||||
<a href={card.get('url')} className={className} target='_blank' rel='noopener'>
|
||||
<a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
|
||||
{image}
|
||||
|
||||
<div className='status-card__content'>
|
||||
<strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>
|
||||
<p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>
|
||||
{!horizontal && <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>}
|
||||
<span className='status-card__host'>{provider}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -53,7 +53,10 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
if (!this.props.singleColumn) {
|
||||
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||
}
|
||||
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
||||
|
||||
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
||||
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
||||
|
||||
this.setState({ shouldAnimate: true });
|
||||
}
|
||||
|
||||
|
@ -79,7 +82,8 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
|
||||
handleChildrenContentChange() {
|
||||
if (!this.props.singleColumn) {
|
||||
this._interruptScrollAnimation = scrollRight(this.node, this.node.scrollWidth - window.innerWidth);
|
||||
const modifier = this.isRtlLayout ? -1 : 1;
|
||||
this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
|||
src={media.get('url')}
|
||||
startTime={time}
|
||||
onCloseVideo={onClose}
|
||||
detailed
|
||||
description={media.get('description')}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,18 @@ const messages = defineMessages({
|
|||
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
||||
});
|
||||
|
||||
const formatTime = secondsNum => {
|
||||
let hours = Math.floor(secondsNum / 3600);
|
||||
let minutes = Math.floor((secondsNum - (hours * 3600)) / 60);
|
||||
let seconds = secondsNum - (hours * 3600) - (minutes * 60);
|
||||
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
if (minutes < 10) minutes = '0' + minutes;
|
||||
if (seconds < 10) seconds = '0' + seconds;
|
||||
|
||||
return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
const findElementPosition = el => {
|
||||
let box;
|
||||
|
||||
|
@ -83,11 +95,13 @@ export default class Video extends React.PureComponent {
|
|||
startTime: PropTypes.number,
|
||||
onOpenVideo: PropTypes.func,
|
||||
onCloseVideo: PropTypes.func,
|
||||
detailed: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
progress: 0,
|
||||
currentTime: 0,
|
||||
duration: 0,
|
||||
paused: true,
|
||||
dragging: false,
|
||||
fullscreen: false,
|
||||
|
@ -117,7 +131,10 @@ export default class Video extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleTimeUpdate = () => {
|
||||
this.setState({ progress: 100 * (this.video.currentTime / this.video.duration) });
|
||||
this.setState({
|
||||
currentTime: Math.floor(this.video.currentTime),
|
||||
duration: Math.floor(this.video.duration),
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseDown = e => {
|
||||
|
@ -143,8 +160,10 @@ export default class Video extends React.PureComponent {
|
|||
|
||||
handleMouseMove = throttle(e => {
|
||||
const { x } = getPointerPosition(this.seek, e);
|
||||
this.video.currentTime = this.video.duration * x;
|
||||
this.setState({ progress: x * 100 });
|
||||
const currentTime = Math.floor(this.video.duration * x);
|
||||
|
||||
this.video.currentTime = currentTime;
|
||||
this.setState({ currentTime });
|
||||
}, 60);
|
||||
|
||||
togglePlay = () => {
|
||||
|
@ -226,11 +245,12 @@ export default class Video extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt } = this.props;
|
||||
const { progress, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||
const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
|
||||
const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||
const progress = (currentTime / duration) * 100;
|
||||
|
||||
return (
|
||||
<div className={classNames('video-player', { inactive: !revealed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<video
|
||||
ref={this.setVideoRef}
|
||||
src={src}
|
||||
|
@ -267,16 +287,27 @@ export default class Video extends React.PureComponent {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons left'>
|
||||
<button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
|
||||
<button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
|
||||
{!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
|
||||
</div>
|
||||
<div className='video-player__buttons-bar'>
|
||||
<div className='video-player__buttons left'>
|
||||
<button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
|
||||
<button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
|
||||
{onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-times' /></button>}
|
||||
<button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
|
||||
{!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
|
||||
|
||||
{(detailed || fullscreen) &&
|
||||
<span>
|
||||
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
||||
<span className='video-player__time-sep'>/</span>
|
||||
<span className='video-player__time-total'>{formatTime(duration)}</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
|
||||
{onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>}
|
||||
<button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
"column.favourites": "المفضلة",
|
||||
"column.follow_requests": "طلبات المتابعة",
|
||||
"column.home": "الرئيسية",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "القوائم",
|
||||
"column.mutes": "الحسابات المكتومة",
|
||||
"column.notifications": "الإشعارات",
|
||||
"column.notifications": "الإخطارات",
|
||||
"column.pins": "التبويقات المثبتة",
|
||||
"column.public": "الخيط العام الموحد",
|
||||
"column_back_button.label": "العودة",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "حذف",
|
||||
"confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "هل تود حقا حذف هذه القائمة ؟",
|
||||
"confirmations.domain_block.confirm": "إخفاء إسم النطاق كاملا",
|
||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
||||
"confirmations.mute.confirm": "أكتم",
|
||||
|
@ -109,32 +109,32 @@
|
|||
"home.settings": "إعدادات العمود",
|
||||
"keyboard_shortcuts.back": "للعودة",
|
||||
"keyboard_shortcuts.boost": "للترقية",
|
||||
"keyboard_shortcuts.column": "to focus a status in one of the columns",
|
||||
"keyboard_shortcuts.compose": "to focus the compose textarea",
|
||||
"keyboard_shortcuts.column": "للتركيز على منشور على أحد الأعمدة",
|
||||
"keyboard_shortcuts.compose": "للتركيز على نافذة تحرير النصوص",
|
||||
"keyboard_shortcuts.description": "Description",
|
||||
"keyboard_shortcuts.down": "للإنتقال إلى أسفل القائمة",
|
||||
"keyboard_shortcuts.enter": "to open status",
|
||||
"keyboard_shortcuts.favourite": "to favourite",
|
||||
"keyboard_shortcuts.favourite": "للإضافة إلى المفضلة",
|
||||
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.legend": "to display this legend",
|
||||
"keyboard_shortcuts.hotkey": "مفتاح الإختصار",
|
||||
"keyboard_shortcuts.legend": "لعرض هذا المفتاح",
|
||||
"keyboard_shortcuts.mention": "لذِكر الناشر",
|
||||
"keyboard_shortcuts.reply": "للردّ",
|
||||
"keyboard_shortcuts.search": "to focus search",
|
||||
"keyboard_shortcuts.search": "للتركيز على البحث",
|
||||
"keyboard_shortcuts.toot": "لتحرير تبويق جديد",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة",
|
||||
"lightbox.close": "إغلاق",
|
||||
"lightbox.next": "التالي",
|
||||
"lightbox.previous": "العودة",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "أضف إلى القائمة",
|
||||
"lists.account.remove": "إحذف من القائمة",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "تعديل القائمة",
|
||||
"lists.new.create": "إنشاء قائمة",
|
||||
"lists.new.title_placeholder": "عنوان القائمة الجديدة",
|
||||
"lists.search": "إبحث في قائمة الحسابات التي تُتابِعها",
|
||||
"lists.subheading": "قوائمك",
|
||||
"loading_indicator.label": "تحميل ...",
|
||||
"media_gallery.toggle_visible": "عرض / إخفاء",
|
||||
"missing_indicator.label": "تعذر العثور عليه",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "طلبات المتابعة",
|
||||
"navigation_bar.info": "معلومات إضافية",
|
||||
"navigation_bar.keyboard_shortcuts": "إختصارات لوحة المفاتيح",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "القوائم",
|
||||
"navigation_bar.logout": "خروج",
|
||||
"navigation_bar.mutes": "الحسابات المكتومة",
|
||||
"navigation_bar.pins": "التبويقات المثبتة",
|
||||
|
@ -209,7 +209,7 @@
|
|||
"search_popout.search_format": "نمط البحث المتقدم",
|
||||
"search_popout.tips.hashtag": "وسم",
|
||||
"search_popout.tips.status": "حالة",
|
||||
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
"search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
|
||||
"search_popout.tips.user": "مستخدِم",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} و {results}}",
|
||||
"standalone.public_title": "نظرة على ...",
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "Зареждане...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"column.favourites": "Favorits",
|
||||
"column.follow_requests": "Peticions per seguir-te",
|
||||
"column.home": "Inici",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "Llistes",
|
||||
"column.mutes": "Usuaris silenciats",
|
||||
"column.notifications": "Notificacions",
|
||||
"column.pins": "Toot fixat",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "Esborrar",
|
||||
"confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?",
|
||||
"confirmations.domain_block.confirm": "Amagar tot el domini",
|
||||
"confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
|
@ -127,14 +127,14 @@
|
|||
"lightbox.close": "Tancar",
|
||||
"lightbox.next": "Següent",
|
||||
"lightbox.previous": "Anterior",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "Afegir a la llista",
|
||||
"lists.account.remove": "Treure de la llista",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "Editar llista",
|
||||
"lists.new.create": "Afegir llista",
|
||||
"lists.new.title_placeholder": "Nou títol de llista",
|
||||
"lists.search": "Cercar entre les persones que segueixes",
|
||||
"lists.subheading": "Les teves llistes",
|
||||
"loading_indicator.label": "Carregant...",
|
||||
"media_gallery.toggle_visible": "Alternar visibilitat",
|
||||
"missing_indicator.label": "No trobat",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "Sol·licituds de seguiment",
|
||||
"navigation_bar.info": "Informació addicional",
|
||||
"navigation_bar.keyboard_shortcuts": "Dreceres de teclat",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "Llistes",
|
||||
"navigation_bar.logout": "Tancar sessió",
|
||||
"navigation_bar.mutes": "Usuaris silenciats",
|
||||
"navigation_bar.pins": "Toots fixats",
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "Wird geladen …",
|
||||
"media_gallery.toggle_visible": "Sichtbarkeit umschalten",
|
||||
|
|
|
@ -1293,6 +1293,19 @@
|
|||
],
|
||||
"path": "app/javascript/mastodon/features/ui/components/boost_modal.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Favourite",
|
||||
"id": "status.favourite"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "You can press {combo} to skip this next time",
|
||||
"id": "favourite_modal.combo"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/ui/components/favourite_modal.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
|
@ -1601,4 +1614,4 @@
|
|||
],
|
||||
"path": "app/javascript/mastodon/features/video/index.json"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"bundle_modal_error.retry": "Try again",
|
||||
"column.blocks": "Blocked users",
|
||||
"column.community": "Local timeline",
|
||||
"column.direct": "Direct messages",
|
||||
"column.favourites": "Favourites",
|
||||
"column.follow_requests": "Follow requests",
|
||||
"column.home": "Home",
|
||||
|
@ -88,10 +89,11 @@
|
|||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"empty_column.list": "There is nothing in this list yet.",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
|
||||
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
|
||||
"follow_request.authorize": "Authorize",
|
||||
|
@ -133,7 +135,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
|
@ -141,6 +143,7 @@
|
|||
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
||||
"navigation_bar.blocks": "Blocked users",
|
||||
"navigation_bar.community_timeline": "Local timeline",
|
||||
"navigation_bar.direct": "Direct messages",
|
||||
"navigation_bar.edit_profile": "Edit profile",
|
||||
"navigation_bar.favourites": "Favourites",
|
||||
"navigation_bar.follow_requests": "Follow requests",
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "Ŝarganta…",
|
||||
"media_gallery.toggle_visible": "Baskuli videblecon",
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "Cargando…",
|
||||
"media_gallery.toggle_visible": "Cambiar visibilidad",
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "بارگیری...",
|
||||
"media_gallery.toggle_visible": "تغییر پیدایی",
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "Ladataan...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -63,8 +63,8 @@
|
|||
"confirmations.block.message": "Confirmez-vous le blocage de {name} ?",
|
||||
"confirmations.delete.confirm": "Supprimer",
|
||||
"confirmations.delete.message": "Confirmez-vous la suppression de ce pouet ?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.confirm": "Supprimer",
|
||||
"confirmations.delete_list.message": "Êtes-vous sûr de vouloir supprimer définitivement cette liste ?",
|
||||
"confirmations.domain_block.confirm": "Masquer le domaine entier",
|
||||
"confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables.",
|
||||
"confirmations.mute.confirm": "Masquer",
|
||||
|
@ -91,7 +91,7 @@
|
|||
"empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag",
|
||||
"empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateur⋅ice⋅s.",
|
||||
"empty_column.home.public_timeline": "le fil public",
|
||||
"empty_column.list": "Il n'y a rien dans cette liste pour l'instant.",
|
||||
"empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publierons de nouveaux statuts, ils apparaîtront ici.",
|
||||
"empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateur⋅ice⋅s pour débuter la conversation.",
|
||||
"empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s d’autres instances pour remplir le fil public.",
|
||||
"follow_request.authorize": "Accepter",
|
||||
|
@ -113,28 +113,28 @@
|
|||
"keyboard_shortcuts.compose": "pour centrer la zone de redaction",
|
||||
"keyboard_shortcuts.description": "Description",
|
||||
"keyboard_shortcuts.down": "descendre dans la liste",
|
||||
"keyboard_shortcuts.enter": "to open status",
|
||||
"keyboard_shortcuts.favourite": "to favourite",
|
||||
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.legend": "to display this legend",
|
||||
"keyboard_shortcuts.mention": "to mention author",
|
||||
"keyboard_shortcuts.reply": "to reply",
|
||||
"keyboard_shortcuts.enter": "pour ouvrir le statut",
|
||||
"keyboard_shortcuts.favourite": "vers les favoris",
|
||||
"keyboard_shortcuts.heading": "Raccourcis clavier",
|
||||
"keyboard_shortcuts.hotkey": "Raccourci",
|
||||
"keyboard_shortcuts.legend": "pour afficher cette légende",
|
||||
"keyboard_shortcuts.mention": "pour mentionner l'auteur",
|
||||
"keyboard_shortcuts.reply": "pour répondre",
|
||||
"keyboard_shortcuts.search": "to focus search",
|
||||
"keyboard_shortcuts.toot": "to start a brand new toot",
|
||||
"keyboard_shortcuts.toot": "pour démarrer un tout nouveau pouet",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "to move up in the list",
|
||||
"lightbox.close": "Fermer",
|
||||
"lightbox.next": "Suivant",
|
||||
"lightbox.previous": "Précédent",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among follows",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.account.add": "Ajouter à la liste",
|
||||
"lists.account.remove": "Supprimer de la liste",
|
||||
"lists.delete": "Effacer la liste",
|
||||
"lists.edit": "Éditer la liste",
|
||||
"lists.new.create": "Ajouter une liste",
|
||||
"lists.new.title_placeholder": "Titre de la nouvelle liste",
|
||||
"lists.search": "Rechercher parmi les gens que vous suivez",
|
||||
"lists.subheading": "Vos listes",
|
||||
"loading_indicator.label": "Chargement…",
|
||||
"media_gallery.toggle_visible": "Modifier la visibilité",
|
||||
"missing_indicator.label": "Non trouvé",
|
||||
|
@ -145,8 +145,8 @@
|
|||
"navigation_bar.favourites": "Favoris",
|
||||
"navigation_bar.follow_requests": "Demandes de suivi",
|
||||
"navigation_bar.info": "Plus d’informations",
|
||||
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.keyboard_shortcuts": "Raccourcis clavier",
|
||||
"navigation_bar.lists": "Listes",
|
||||
"navigation_bar.logout": "Déconnexion",
|
||||
"navigation_bar.mutes": "Comptes masqués",
|
||||
"navigation_bar.pins": "Pouets épinglés",
|
||||
|
@ -241,7 +241,7 @@
|
|||
"tabs_bar.home": "Accueil",
|
||||
"tabs_bar.local_timeline": "Fil public local",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.",
|
||||
"upload_area.title": "Glissez et déposez pour envoyer",
|
||||
"upload_button.label": "Joindre un média",
|
||||
"upload_form.description": "Décrire pour les malvoyants",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue