mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-13 15:58:50 +00:00
Compare commits
123 Commits
wide-tootb
...
update-mas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc8532359b | ||
|
|
e0298d66f8 | ||
|
|
73bf0ea7d1 | ||
|
|
276432790a | ||
|
|
254b74c71f | ||
|
|
870d71b78b | ||
|
|
781105293c | ||
|
|
0cb329f63a | ||
|
|
0129f5eada | ||
|
|
656f5b6f87 | ||
|
|
22da775a85 | ||
|
|
dd28b94cf0 | ||
|
|
d556be2968 | ||
|
|
4f337c020a | ||
|
|
02f7f3619a | ||
|
|
a2612d0d38 | ||
|
|
31814ddda0 | ||
|
|
42f2045c21 | ||
|
|
5f0268ab84 | ||
|
|
20fee786b1 | ||
|
|
74777599cf | ||
|
|
1ba3725473 | ||
|
|
e40fe4092d | ||
|
|
d9485e6497 | ||
|
|
d5c8ebe205 | ||
|
|
d03b48cea0 | ||
|
|
9226257a1b | ||
|
|
641f90e73a | ||
|
|
f5a3283976 | ||
|
|
516eeeb43d | ||
|
|
664c9aa708 | ||
|
|
119d477c8b | ||
|
|
8410d33b49 | ||
|
|
4f01e6e8d5 | ||
|
|
a76b024228 | ||
|
|
3db80f75a6 | ||
|
|
af8f06413e | ||
|
|
1a60445a5f | ||
|
|
4c84513e04 | ||
|
|
4b68e82a19 | ||
|
|
19826774f0 | ||
|
|
fdb0848e08 | ||
|
|
ad86c86fa8 | ||
|
|
670e6a33f8 | ||
|
|
cd04e3df58 | ||
|
|
4a64181461 | ||
|
|
2e03a10059 | ||
|
|
4fa2f7e82d | ||
|
|
b4b657eb1d | ||
|
|
693c66dfde | ||
|
|
a4851100fd | ||
|
|
9f609bc94e | ||
|
|
603cf02b70 | ||
|
|
4745d6eeca | ||
|
|
9093e2de7a | ||
|
|
d589dd7cd0 | ||
|
|
a7be86e875 | ||
|
|
b15dd05514 | ||
|
|
21bafc6555 | ||
|
|
f5e2469485 | ||
|
|
8392ddbf87 | ||
|
|
9423553e5c | ||
|
|
049381b284 | ||
|
|
90770f6d59 | ||
|
|
c756651278 | ||
|
|
eb907a5bab | ||
|
|
39c9cdf7fe | ||
|
|
86cf4468af | ||
|
|
42e8c8eb0e | ||
|
|
09d81defcd | ||
|
|
3810d98cd8 | ||
|
|
26b2a6a71e | ||
|
|
edf9a5e4fc | ||
|
|
c710069c12 | ||
|
|
990d6dd565 | ||
|
|
402da46ff6 | ||
|
|
e7099d8d9e | ||
|
|
637ea3bb5b | ||
|
|
363d0d3a44 | ||
|
|
6e54719474 | ||
|
|
f3003417c5 | ||
|
|
33ea042dec | ||
|
|
8b22a63ab0 | ||
|
|
05686cc99d | ||
|
|
484208ce12 | ||
|
|
3bc8924940 | ||
|
|
a02de9e012 | ||
|
|
2d395324e1 | ||
|
|
e6c9756fa9 | ||
|
|
5050719fac | ||
|
|
989553c69a | ||
|
|
0e0c6b1b4b | ||
|
|
554c2fd8af | ||
|
|
a2b600428c | ||
|
|
df1a9c5ab5 | ||
|
|
4421f6598f | ||
|
|
64a5e9a1de | ||
|
|
7364b26e4b | ||
|
|
313ba202ef | ||
|
|
7c44ad6355 | ||
|
|
37ff061d9b | ||
|
|
8b9ee5f16b | ||
|
|
4b397adb5b | ||
|
|
8980aa804f | ||
|
|
34118169ac | ||
|
|
4fd7aebd5e | ||
|
|
65154869df | ||
|
|
bc89995f65 | ||
|
|
7e9d93472c | ||
|
|
dbb1fce94d | ||
|
|
7cc71748ce | ||
|
|
aec70b44fc | ||
|
|
6f490b4bfe | ||
|
|
03975dbde4 | ||
|
|
f72936b4e6 | ||
|
|
3c530d95f6 | ||
|
|
1e7b3bf625 | ||
|
|
bf0ee1a25c | ||
|
|
fa0be3f834 | ||
|
|
981e20b03a | ||
|
|
d5b767c374 | ||
|
|
93b54b8d4b | ||
|
|
e7ab9bf8b4 |
@@ -5,12 +5,14 @@ env:
|
||||
browser: true
|
||||
node: true
|
||||
es6: true
|
||||
jest: true
|
||||
|
||||
parser: babel-eslint
|
||||
|
||||
plugins:
|
||||
- react
|
||||
- jsx-a11y
|
||||
- import
|
||||
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
@@ -21,8 +23,19 @@ parserOptions:
|
||||
modules: true
|
||||
spread: true
|
||||
|
||||
rules:
|
||||
settings:
|
||||
import/extensions:
|
||||
- .js
|
||||
import/ignore:
|
||||
- node_modules
|
||||
- \\.(css|scss|json)$
|
||||
import/resolver:
|
||||
node:
|
||||
moduleDirectory:
|
||||
- node_modules
|
||||
- app/javascript
|
||||
|
||||
rules:
|
||||
brace-style: warn
|
||||
comma-dangle:
|
||||
- error
|
||||
@@ -125,3 +138,17 @@ rules:
|
||||
jsx-a11y/role-supports-aria-props: off
|
||||
jsx-a11y/scope: warn
|
||||
jsx-a11y/tabindex-no-positive: warn
|
||||
|
||||
import/extensions:
|
||||
- error
|
||||
- always
|
||||
- js: never
|
||||
import/newline-after-import: error
|
||||
import/no-extraneous-dependencies:
|
||||
- error
|
||||
- devDependencies:
|
||||
- "config/webpack/**"
|
||||
- "app/javascript/mastodon/test_setup.js"
|
||||
- "app/javascript/**/__tests__/**"
|
||||
import/no-unresolved: error
|
||||
import/no-webpack-loader-syntax: error
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "app/javascript/themes/mastodon-go"]
|
||||
path = app/javascript/themes/mastodon-go
|
||||
url = https://github.com/marrus-sh/mastodon-go
|
||||
@@ -53,5 +53,5 @@ before_script:
|
||||
|
||||
script:
|
||||
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
|
||||
- npm test
|
||||
- bundle exec i18n-tasks unused
|
||||
- yarn test
|
||||
- bundle exec i18n-tasks check-normalized && bundle exec i18n-tasks unused
|
||||
|
||||
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@@ -83,7 +83,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.name = "mastodon"
|
||||
vb.customize ["modifyvm", :id, "--memory", "2048"]
|
||||
vb.customize ["modifyvm", :id, "--memory", "4096"]
|
||||
|
||||
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
|
||||
# https://github.com/mitchellh/vagrant/issues/1172
|
||||
|
||||
@@ -22,6 +22,14 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @custom_emoji.update(resource_params)
|
||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
|
||||
else
|
||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg')
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@custom_emoji.destroy
|
||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
|
||||
@@ -36,7 +44,7 @@ module Admin
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
|
||||
end
|
||||
|
||||
redirect_to admin_custom_emojis_path(params[:page])
|
||||
redirect_to admin_custom_emojis_path(page: params[:page])
|
||||
end
|
||||
|
||||
def enable
|
||||
@@ -56,7 +64,7 @@ module Admin
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_emoji).permit(:shortcode, :image)
|
||||
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
|
||||
end
|
||||
|
||||
def filtered_custom_emojis
|
||||
|
||||
60
app/controllers/api/v1/timelines/direct_controller.rb
Normal file
60
app/controllers/api/v1/timelines/direct_controller.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::DirectController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }, only: [:show]
|
||||
before_action :require_user!, only: [:show]
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_statuses
|
||||
cached_direct_statuses
|
||||
end
|
||||
|
||||
def cached_direct_statuses
|
||||
cache_collection direct_statuses, Status
|
||||
end
|
||||
|
||||
def direct_statuses
|
||||
direct_timeline_statuses.paginate_by_max_id(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def direct_timeline_statuses
|
||||
Status.as_direct_timeline(current_account)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
||||
64
app/controllers/settings/keyword_mutes_controller.rb
Normal file
64
app/controllers/settings/keyword_mutes_controller.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Settings::KeywordMutesController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :load_keyword_mute, only: [:edit, :update, :destroy]
|
||||
|
||||
def index
|
||||
@keyword_mutes = paginated_keyword_mutes_for_account
|
||||
end
|
||||
|
||||
def new
|
||||
@keyword_mute = keyword_mutes_for_account.build
|
||||
end
|
||||
|
||||
def create
|
||||
@keyword_mute = keyword_mutes_for_account.create(keyword_mute_params)
|
||||
|
||||
if @keyword_mute.persisted?
|
||||
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @keyword_mute.update(keyword_mute_params)
|
||||
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@keyword_mute.destroy!
|
||||
|
||||
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
end
|
||||
|
||||
def destroy_all
|
||||
keyword_mutes_for_account.delete_all
|
||||
|
||||
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def keyword_mutes_for_account
|
||||
Glitch::KeywordMute.where(account: current_account)
|
||||
end
|
||||
|
||||
def load_keyword_mute
|
||||
@keyword_mute = keyword_mutes_for_account.find(params[:id])
|
||||
end
|
||||
|
||||
def keyword_mute_params
|
||||
params.require(:keyword_mute).permit(:keyword, :whole_word)
|
||||
end
|
||||
|
||||
def paginated_keyword_mutes_for_account
|
||||
keyword_mutes_for_account.order(:keyword).page params[:page]
|
||||
end
|
||||
end
|
||||
@@ -39,6 +39,7 @@ class Settings::PreferencesController < ApplicationController
|
||||
:setting_boost_modal,
|
||||
:setting_delete_modal,
|
||||
:setting_auto_play_gif,
|
||||
:setting_reduce_motion,
|
||||
:setting_system_font_ui,
|
||||
:setting_noindex,
|
||||
:setting_theme,
|
||||
|
||||
@@ -9,6 +9,10 @@ module JsonLdHelper
|
||||
value.is_a?(Array) ? value.first : value
|
||||
end
|
||||
|
||||
def as_array(value)
|
||||
value.is_a?(Array) ? value : [value]
|
||||
end
|
||||
|
||||
def value_or_id(value)
|
||||
value.is_a?(String) || value.nil? ? value : value['id']
|
||||
end
|
||||
|
||||
2
app/helpers/settings/keyword_mutes_helper.rb
Normal file
2
app/helpers/settings/keyword_mutes_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Settings::KeywordMutesHelper
|
||||
end
|
||||
@@ -27,6 +27,7 @@ module SettingsHelper
|
||||
pt: 'Português',
|
||||
'pt-BR': 'Português do Brasil',
|
||||
ru: 'Русский',
|
||||
sv: 'Svenska',
|
||||
th: 'ภาษาไทย',
|
||||
tr: 'Türkçe',
|
||||
uk: 'Українська',
|
||||
|
||||
@@ -48,7 +48,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
// Mastodon imports //
|
||||
import emojify from 'mastodon/features/emoji/emoji';
|
||||
import emojify from '../../../mastodon/features/emoji/emoji';
|
||||
import IconButton from '../../../mastodon/components/icon_button';
|
||||
import Avatar from '../../../mastodon/components/avatar';
|
||||
|
||||
|
||||
@@ -47,11 +47,9 @@ import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
// Mastodon imports //
|
||||
import IconButton from '../../../../mastodon/components/icon_button';
|
||||
|
||||
// Our imports //
|
||||
import ComposeAdvancedOptionsToggle from './toggle';
|
||||
import ComposeDropdown from '../dropdown/index';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
@@ -77,11 +75,6 @@ const messages = defineMessages({
|
||||
{ id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
|
||||
});
|
||||
|
||||
const iconStyle = {
|
||||
height : null,
|
||||
lineHeight : '27px',
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Implementation:
|
||||
@@ -100,67 +93,6 @@ export default class ComposeAdvancedOptions extends React.PureComponent {
|
||||
intl : PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
### `onToggleDropdown()`
|
||||
|
||||
This function toggles the opening and closing of the advanced options
|
||||
dropdown.
|
||||
|
||||
*/
|
||||
|
||||
onToggleDropdown = () => {
|
||||
this.setState({ open: !this.state.open });
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
### `onGlobalClick(e)`
|
||||
|
||||
This function closes the advanced options dropdown if you click
|
||||
anywhere else on the screen.
|
||||
|
||||
*/
|
||||
|
||||
onGlobalClick = (e) => {
|
||||
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
|
||||
this.setState({ open: false });
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
### `componentDidMount()`, `componentWillUnmount()`
|
||||
|
||||
This function closes the advanced options dropdown if you click
|
||||
anywhere else on the screen.
|
||||
|
||||
*/
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('click', this.onGlobalClick);
|
||||
window.addEventListener('touchstart', this.onGlobalClick);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('click', this.onGlobalClick);
|
||||
window.removeEventListener('touchstart', this.onGlobalClick);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
### `setRef(c)`
|
||||
|
||||
`setRef()` stores a reference to the dropdown's `<div> in `this.node`.
|
||||
|
||||
*/
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -171,7 +103,6 @@ anywhere else on the screen.
|
||||
*/
|
||||
|
||||
render () {
|
||||
const { open } = this.state;
|
||||
const { intl, values } = this.props;
|
||||
|
||||
/*
|
||||
@@ -218,23 +149,14 @@ toggle as its `key` so that React can keep track of it.
|
||||
Finally, we can render our component.
|
||||
|
||||
*/
|
||||
|
||||
return (
|
||||
<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${anyEnabled ? 'active' : ''} `}>
|
||||
<div className='advanced-options-dropdown__value'>
|
||||
<IconButton
|
||||
className='advanced-options-dropdown__value'
|
||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
||||
icon='ellipsis-h' active={open || anyEnabled}
|
||||
size={18}
|
||||
style={iconStyle}
|
||||
onClick={this.onToggleDropdown}
|
||||
/>
|
||||
</div>
|
||||
<div className='advanced-options-dropdown__dropdown'>
|
||||
{optionElems}
|
||||
</div>
|
||||
</div>
|
||||
<ComposeDropdown
|
||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
||||
icon='home'
|
||||
highlight={anyEnabled}
|
||||
>
|
||||
{optionElems}
|
||||
</ComposeDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
133
app/javascript/glitch/components/compose/attach_options/index.js
Normal file
133
app/javascript/glitch/components/compose/attach_options/index.js
Normal file
@@ -0,0 +1,133 @@
|
||||
// Package imports //
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
// Our imports //
|
||||
import ComposeDropdown from '../dropdown/index';
|
||||
import { uploadCompose } from '../../../../mastodon/actions/compose';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { openModal } from '../../../../mastodon/actions/modal';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
const messages = defineMessages({
|
||||
upload :
|
||||
{ id: 'compose.attach.upload', defaultMessage: 'Upload a file' },
|
||||
doodle :
|
||||
{ id: 'compose.attach.doodle', defaultMessage: 'Draw something' },
|
||||
attach :
|
||||
{ id: 'compose.attach', defaultMessage: 'Attach...' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
// This horrible expression is copied from vanilla upload_button_container
|
||||
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
|
||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onSelectFile (files) {
|
||||
dispatch(uploadCompose(files));
|
||||
},
|
||||
onOpenDoodle () {
|
||||
dispatch(openModal('DOODLE', { noEsc: true }));
|
||||
},
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
export default class ComposeAttachOptions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl : PropTypes.object.isRequired,
|
||||
resetFileKey: PropTypes.number,
|
||||
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
onSelectFile: PropTypes.func.isRequired,
|
||||
onOpenDoodle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleItemClick = bt => {
|
||||
if (bt === 'upload') {
|
||||
this.fileElement.click();
|
||||
}
|
||||
|
||||
if (bt === 'doodle') {
|
||||
this.props.onOpenDoodle();
|
||||
}
|
||||
|
||||
this.dropdown.setState({ open: false });
|
||||
};
|
||||
|
||||
handleFileChange = (e) => {
|
||||
if (e.target.files.length > 0) {
|
||||
this.props.onSelectFile(e.target.files);
|
||||
}
|
||||
}
|
||||
|
||||
setFileRef = (c) => {
|
||||
this.fileElement = c;
|
||||
}
|
||||
|
||||
setDropdownRef = (c) => {
|
||||
this.dropdown = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, resetFileKey, disabled, acceptContentTypes } = this.props;
|
||||
|
||||
const options = [
|
||||
{ icon: 'cloud-upload', text: messages.upload, name: 'upload' },
|
||||
{ icon: 'paint-brush', text: messages.doodle, name: 'doodle' },
|
||||
];
|
||||
|
||||
const optionElems = options.map((item) => {
|
||||
const hdl = () => this.handleItemClick(item.name);
|
||||
return (
|
||||
<div
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
key={item.name}
|
||||
onClick={hdl}
|
||||
className='privacy-dropdown__option'
|
||||
>
|
||||
<div className='privacy-dropdown__option__icon'>
|
||||
<i className={`fa fa-fw fa-${item.icon}`} />
|
||||
</div>
|
||||
|
||||
<div className='privacy-dropdown__option__content'>
|
||||
<strong>{intl.formatMessage(item.text)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ComposeDropdown
|
||||
title={intl.formatMessage(messages.attach)}
|
||||
icon='paperclip'
|
||||
disabled={disabled}
|
||||
ref={this.setDropdownRef}
|
||||
>
|
||||
{optionElems}
|
||||
</ComposeDropdown>
|
||||
<input
|
||||
key={resetFileKey}
|
||||
ref={this.setFileRef}
|
||||
type='file'
|
||||
multiple={false}
|
||||
accept={acceptContentTypes.toArray().join(',')}
|
||||
onChange={this.handleFileChange}
|
||||
disabled={disabled}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
77
app/javascript/glitch/components/compose/dropdown/index.js
Normal file
77
app/javascript/glitch/components/compose/dropdown/index.js
Normal file
@@ -0,0 +1,77 @@
|
||||
// Package imports //
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// Mastodon imports //
|
||||
import IconButton from '../../../../mastodon/components/icon_button';
|
||||
|
||||
const iconStyle = {
|
||||
height : null,
|
||||
lineHeight : '27px',
|
||||
};
|
||||
|
||||
export default class ComposeDropdown extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string,
|
||||
highlight: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
children: PropTypes.arrayOf(PropTypes.node).isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
onGlobalClick = (e) => {
|
||||
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
|
||||
this.setState({ open: false });
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('click', this.onGlobalClick);
|
||||
window.addEventListener('touchstart', this.onGlobalClick);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('click', this.onGlobalClick);
|
||||
window.removeEventListener('touchstart', this.onGlobalClick);
|
||||
}
|
||||
|
||||
onToggleDropdown = () => {
|
||||
if (this.props.disabled) return;
|
||||
this.setState({ open: !this.state.open });
|
||||
};
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
};
|
||||
|
||||
render () {
|
||||
const { open } = this.state;
|
||||
let { highlight, title, icon, disabled } = this.props;
|
||||
|
||||
if (!icon) icon = 'ellipsis-h';
|
||||
|
||||
return (
|
||||
<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${highlight ? 'active' : ''} `}>
|
||||
<div className='advanced-options-dropdown__value'>
|
||||
<IconButton
|
||||
className={'inverted'}
|
||||
title={title}
|
||||
icon={icon} active={open || highlight}
|
||||
size={18}
|
||||
style={iconStyle}
|
||||
disabled={disabled}
|
||||
onClick={this.onToggleDropdown}
|
||||
/>
|
||||
</div>
|
||||
<div className='advanced-options-dropdown__dropdown'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
// Mastodon imports //
|
||||
import { closeModal } from 'mastodon/actions/modal';
|
||||
import { closeModal } from '../../../mastodon/actions/modal';
|
||||
|
||||
// Our imports //
|
||||
import { changeLocalSetting } from 'glitch/actions/local_settings';
|
||||
import { changeLocalSetting } from '../../../glitch/actions/local_settings';
|
||||
import LocalSettings from '.';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
||||
@@ -8,7 +8,7 @@ import LocalSettingsPage from './page';
|
||||
import LocalSettingsNavigation from './navigation';
|
||||
|
||||
// Stylesheet imports
|
||||
import './style';
|
||||
import './style.scss';
|
||||
|
||||
export default class LocalSettings extends React.PureComponent {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { injectIntl, defineMessages } from 'react-intl';
|
||||
import LocalSettingsNavigationItem from './item';
|
||||
|
||||
// Stylesheet imports
|
||||
import './style';
|
||||
import './style.scss';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
// Stylesheet imports
|
||||
import './style';
|
||||
import './style.scss';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'styles/variables';
|
||||
@import 'styles/mastodon/variables';
|
||||
|
||||
.glitch.local-settings__navigation__item {
|
||||
display: block;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'styles/variables';
|
||||
@import 'styles/mastodon/variables';
|
||||
|
||||
.glitch.local-settings__navigation {
|
||||
background: $primary-text-color;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import LocalSettingsPageItem from './item';
|
||||
|
||||
// Stylesheet imports
|
||||
import './style';
|
||||
import './style.scss';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
@@ -124,6 +124,16 @@ export default class LocalSettingsPage extends React.PureComponent {
|
||||
>
|
||||
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
item={['collapsed', 'auto', 'reblogs']}
|
||||
id='mastodon-settings--collapsed-auto-reblogs'
|
||||
onChange={onChange}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||
>
|
||||
<FormattedMessage id='settings.auto_collapse_reblogs' defaultMessage='Boosts' />
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
item={['collapsed', 'auto', 'replies']}
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
// Stylesheet imports
|
||||
import './style';
|
||||
import './style.scss';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'styles/variables';
|
||||
@import 'styles/mastodon/variables';
|
||||
|
||||
.glitch.local-settings__page__item {
|
||||
select {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'styles/variables';
|
||||
@import 'styles/mastodon/variables';
|
||||
|
||||
.glitch.local-settings__page {
|
||||
display: block;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'styles/variables';
|
||||
@import 'styles/mastodon/variables';
|
||||
|
||||
.glitch.local-settings {
|
||||
position: relative;
|
||||
|
||||
@@ -102,6 +102,16 @@ const makeMapStateToProps = () => {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
let status = getStatus(state, ownProps.id);
|
||||
|
||||
if(status === null) {
|
||||
console.error(`ERROR! NULL STATUS! ${ownProps.id}`);
|
||||
// work-around: find first good status
|
||||
for (let k of state.get('statuses').keys()) {
|
||||
status = getStatus(state, k);
|
||||
if (status !== null) break;
|
||||
}
|
||||
}
|
||||
|
||||
let reblogStatus = status.get('reblog', null);
|
||||
let account = undefined;
|
||||
let prepend = undefined;
|
||||
|
||||
@@ -287,6 +287,7 @@ properly and our intersection observer is good to go.
|
||||
muted,
|
||||
id,
|
||||
intersectionObserverWrapper,
|
||||
prepend,
|
||||
} = this.props;
|
||||
const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
|
||||
|
||||
@@ -299,6 +300,9 @@ properly and our intersection observer is good to go.
|
||||
node.clientHeight > (
|
||||
status.get('media_attachments').size && !muted ? 650 : 400
|
||||
)
|
||||
) || (
|
||||
autoCollapseSettings.get('reblogs') &&
|
||||
prepend === 'reblogged_by'
|
||||
) || (
|
||||
autoCollapseSettings.get('replies') &&
|
||||
status.get('in_reply_to_id', null) !== null
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"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",
|
||||
|
||||
@@ -59,6 +59,7 @@ const initialState = ImmutableMap({
|
||||
all : false,
|
||||
notifications : true,
|
||||
lengthy : true,
|
||||
reblogs : false,
|
||||
replies : false,
|
||||
media : false,
|
||||
}),
|
||||
|
||||
@@ -69,6 +69,10 @@ functions are:
|
||||
easier to read and to maintain. I leave it to the future readers of
|
||||
this code to determine the extent of my successes in this endeavor.
|
||||
|
||||
UPDATE 19 Oct 2017: We no longer allow character escapes inside our
|
||||
double-quoted strings for ease of processing. We now internally use
|
||||
the name "ƔAML" in our code to clarify that this is Not Quite YAML.
|
||||
|
||||
Sending love + warmth eternal,
|
||||
- kibigo [@kibi@glitch.social]
|
||||
|
||||
@@ -96,10 +100,7 @@ const ALLOWED_CHAR = unirex( // `c-printable` in the YAML 1.2 spec.
|
||||
compat_mode ? '[\t\n\r\x20-\x7e\x85\xa0-\ufffd]' : '[\t\n\r\x20-\x7e\x85\xa0-\ud7ff\ue000-\ufffd\u{10000}-\u{10FFFF}]'
|
||||
);
|
||||
const WHITE_SPACE = /[ \t]/;
|
||||
const INDENTATION = / */; // Indentation must be only spaces.
|
||||
const LINE_BREAK = /\r?\n|\r|<br\s*\/?>/;
|
||||
const ESCAPE_CHAR = /[0abt\tnvfre "\/\\N_LP]/;
|
||||
const HEXADECIMAL_CHARS = /[0-9a-fA-F]/;
|
||||
const INDICATOR = /[-?:,[\]{}&#*!|>'"%@`]/;
|
||||
const FLOW_CHAR = /[,[\]{}]/;
|
||||
|
||||
@@ -121,7 +122,7 @@ const NEW_LINE = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK)
|
||||
);
|
||||
const SOME_NEW_LINES = unirex(
|
||||
'(?:' + rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK) + ')+'
|
||||
'(?:' + rexstr(NEW_LINE) + ')+'
|
||||
);
|
||||
const POSSIBLE_STARTS = unirex(
|
||||
rexstr(DOCUMENT_START) + rexstr(/<p[^<>]*>/) + '?'
|
||||
@@ -131,22 +132,13 @@ const POSSIBLE_ENDS = unirex(
|
||||
rexstr(DOCUMENT_END) + '|' +
|
||||
rexstr(/<\/p>/)
|
||||
);
|
||||
const CHARACTER_ESCAPE = unirex(
|
||||
rexstr(/\\/) +
|
||||
'(?:' +
|
||||
rexstr(ESCAPE_CHAR) + '|' +
|
||||
rexstr(/x/) + rexstr(HEXADECIMAL_CHARS) + '{2}' + '|' +
|
||||
rexstr(/u/) + rexstr(HEXADECIMAL_CHARS) + '{4}' + '|' +
|
||||
rexstr(/U/) + rexstr(HEXADECIMAL_CHARS) + '{8}' +
|
||||
')'
|
||||
const QUOTE_CHAR = unirex(
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')[^"]'
|
||||
);
|
||||
const ESCAPED_CHAR = unirex(
|
||||
rexstr(/(?!["\\])/) + rexstr(NOT_LINE_BREAK) + '|' +
|
||||
rexstr(CHARACTER_ESCAPE)
|
||||
);
|
||||
const ANY_ESCAPED_CHARS = unirex(
|
||||
rexstr(ESCAPED_CHAR) + '*'
|
||||
const ANY_QUOTE_CHAR = unirex(
|
||||
rexstr(QUOTE_CHAR) + '*'
|
||||
);
|
||||
|
||||
const ESCAPED_APOS = unirex(
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' + rexstr(/[^']|''/)
|
||||
);
|
||||
@@ -190,120 +182,76 @@ const LATER_VALUE_CHAR = unirex(
|
||||
|
||||
/* YAML CONSTRUCTS */
|
||||
|
||||
const YAML_START = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + rexstr(/---/)
|
||||
const ƔAML_START = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + '---'
|
||||
);
|
||||
const YAML_END = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + rexstr(/(?:---|\.\.\.)/)
|
||||
const ƔAML_END = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + '(?:---|\.\.\.)'
|
||||
);
|
||||
const YAML_LOOKAHEAD = unirex(
|
||||
const ƔAML_LOOKAHEAD = unirex(
|
||||
'(?=' +
|
||||
rexstr(YAML_START) +
|
||||
rexstr(ƔAML_START) +
|
||||
rexstr(ANY_ALLOWED_CHARS) + rexstr(NEW_LINE) +
|
||||
rexstr(YAML_END) + rexstr(POSSIBLE_ENDS) +
|
||||
rexstr(ƔAML_END) + rexstr(POSSIBLE_ENDS) +
|
||||
')'
|
||||
);
|
||||
const YAML_DOUBLE_QUOTE = unirex(
|
||||
rexstr(/"/) + rexstr(ANY_ESCAPED_CHARS) + rexstr(/"/)
|
||||
const ƔAML_DOUBLE_QUOTE = unirex(
|
||||
'"' + rexstr(ANY_QUOTE_CHAR) + '"'
|
||||
);
|
||||
const YAML_SINGLE_QUOTE = unirex(
|
||||
rexstr(/'/) + rexstr(ANY_ESCAPED_APOS) + rexstr(/'/)
|
||||
const ƔAML_SINGLE_QUOTE = unirex(
|
||||
'\'' + rexstr(ANY_ESCAPED_APOS) + '\''
|
||||
);
|
||||
const YAML_SIMPLE_KEY = unirex(
|
||||
const ƔAML_SIMPLE_KEY = unirex(
|
||||
rexstr(FIRST_KEY_CHAR) + rexstr(LATER_KEY_CHAR) + '*'
|
||||
);
|
||||
const YAML_SIMPLE_VALUE = unirex(
|
||||
const ƔAML_SIMPLE_VALUE = unirex(
|
||||
rexstr(FIRST_VALUE_CHAR) + rexstr(LATER_VALUE_CHAR) + '*'
|
||||
);
|
||||
const YAML_KEY = unirex(
|
||||
rexstr(YAML_DOUBLE_QUOTE) + '|' +
|
||||
rexstr(YAML_SINGLE_QUOTE) + '|' +
|
||||
rexstr(YAML_SIMPLE_KEY)
|
||||
const ƔAML_KEY = unirex(
|
||||
rexstr(ƔAML_DOUBLE_QUOTE) + '|' +
|
||||
rexstr(ƔAML_SINGLE_QUOTE) + '|' +
|
||||
rexstr(ƔAML_SIMPLE_KEY)
|
||||
);
|
||||
const YAML_VALUE = unirex(
|
||||
rexstr(YAML_DOUBLE_QUOTE) + '|' +
|
||||
rexstr(YAML_SINGLE_QUOTE) + '|' +
|
||||
rexstr(YAML_SIMPLE_VALUE)
|
||||
const ƔAML_VALUE = unirex(
|
||||
rexstr(ƔAML_DOUBLE_QUOTE) + '|' +
|
||||
rexstr(ƔAML_SINGLE_QUOTE) + '|' +
|
||||
rexstr(ƔAML_SIMPLE_VALUE)
|
||||
);
|
||||
const YAML_SEPARATOR = unirex(
|
||||
const ƔAML_SEPARATOR = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) +
|
||||
':' + rexstr(WHITE_SPACE) +
|
||||
rexstr(ANY_WHITE_SPACE)
|
||||
);
|
||||
const YAML_LINE = unirex(
|
||||
'(' + rexstr(YAML_KEY) + ')' +
|
||||
rexstr(YAML_SEPARATOR) +
|
||||
'(' + rexstr(YAML_VALUE) + ')'
|
||||
const ƔAML_LINE = unirex(
|
||||
'(' + rexstr(ƔAML_KEY) + ')' +
|
||||
rexstr(ƔAML_SEPARATOR) +
|
||||
'(' + rexstr(ƔAML_VALUE) + ')'
|
||||
);
|
||||
|
||||
/* FRONTMATTER REGEX */
|
||||
|
||||
const YAML_FRONTMATTER = unirex(
|
||||
const ƔAML_FRONTMATTER = unirex(
|
||||
rexstr(POSSIBLE_STARTS) +
|
||||
rexstr(YAML_LOOKAHEAD) +
|
||||
rexstr(YAML_START) + rexstr(SOME_NEW_LINES) +
|
||||
rexstr(ƔAML_LOOKAHEAD) +
|
||||
rexstr(ƔAML_START) + rexstr(SOME_NEW_LINES) +
|
||||
'(?:' +
|
||||
'(' + rexstr(INDENTATION) + ')' +
|
||||
rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) +
|
||||
'(?:' +
|
||||
'\\1' + rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) +
|
||||
'){0,4}' +
|
||||
')?' +
|
||||
rexstr(YAML_END) + rexstr(POSSIBLE_ENDS)
|
||||
rexstr(ANY_WHITE_SPACE) + rexstr(ƔAML_LINE) + rexstr(SOME_NEW_LINES) +
|
||||
'){0,5}' +
|
||||
rexstr(ƔAML_END) + rexstr(POSSIBLE_ENDS)
|
||||
);
|
||||
|
||||
/* SEARCHES */
|
||||
|
||||
const FIND_YAML_LINES = unirex(
|
||||
rexstr(NEW_LINE) + rexstr(INDENTATION) + rexstr(YAML_LINE)
|
||||
const FIND_ƔAML_LINE = unirex(
|
||||
rexstr(NEW_LINE) + rexstr(ANY_WHITE_SPACE) + rexstr(ƔAML_LINE)
|
||||
);
|
||||
|
||||
/* STRING PROCESSING */
|
||||
|
||||
function processString(str) {
|
||||
function processString (str) {
|
||||
switch (str.charAt(0)) {
|
||||
case '"':
|
||||
return str
|
||||
.substring(1, str.length - 1)
|
||||
.replace(/\\0/g, '\x00')
|
||||
.replace(/\\a/g, '\x07')
|
||||
.replace(/\\b/g, '\x08')
|
||||
.replace(/\\t/g, '\x09')
|
||||
.replace(/\\\x09/g, '\x09')
|
||||
.replace(/\\n/g, '\x0a')
|
||||
.replace(/\\v/g, '\x0b')
|
||||
.replace(/\\f/g, '\x0c')
|
||||
.replace(/\\r/g, '\x0d')
|
||||
.replace(/\\e/g, '\x1b')
|
||||
.replace(/\\ /g, '\x20')
|
||||
.replace(/\\"/g, '\x22')
|
||||
.replace(/\\\//g, '\x2f')
|
||||
.replace(/\\\\/g, '\x5c')
|
||||
.replace(/\\N/g, '\x85')
|
||||
.replace(/\\_/g, '\xa0')
|
||||
.replace(/\\L/g, '\u2028')
|
||||
.replace(/\\P/g, '\u2029')
|
||||
.replace(
|
||||
new RegExp(
|
||||
unirex(
|
||||
rexstr(/\\x/) + '(' + rexstr(HEXADECIMAL_CHARS) + '{2})'
|
||||
), 'gu'
|
||||
), (_, n) => String.fromCodePoint('0x' + n)
|
||||
)
|
||||
.replace(
|
||||
new RegExp(
|
||||
unirex(
|
||||
rexstr(/\\u/) + '(' + rexstr(HEXADECIMAL_CHARS) + '{4})'
|
||||
), 'gu'
|
||||
), (_, n) => String.fromCodePoint('0x' + n)
|
||||
)
|
||||
.replace(
|
||||
new RegExp(
|
||||
unirex(
|
||||
rexstr(/\\U/) + '(' + rexstr(HEXADECIMAL_CHARS) + '{8})'
|
||||
), 'gu'
|
||||
), (_, n) => String.fromCodePoint('0x' + n)
|
||||
);
|
||||
return str.substring(1, str.length - 1);
|
||||
case '\'':
|
||||
return str
|
||||
.substring(1, str.length - 1)
|
||||
@@ -321,15 +269,18 @@ export function processBio(content) {
|
||||
text: content,
|
||||
metadata: [],
|
||||
};
|
||||
let yaml = content.match(YAML_FRONTMATTER);
|
||||
if (!yaml) return result;
|
||||
else yaml = yaml[0];
|
||||
let start = content.search(YAML_START);
|
||||
let end = start + yaml.length - yaml.search(YAML_START);
|
||||
result.text = content.substr(0, start) + content.substr(end);
|
||||
let ɣaml = content.match(ƔAML_FRONTMATTER);
|
||||
if (!ɣaml) {
|
||||
return result;
|
||||
} else {
|
||||
ɣaml = ɣaml[0];
|
||||
}
|
||||
const start = content.search(ƔAML_START);
|
||||
const end = start + ɣaml.length - ɣaml.search(ƔAML_START);
|
||||
result.text = content.substr(end);
|
||||
let metadata = null;
|
||||
let query = new RegExp(FIND_YAML_LINES, 'g');
|
||||
while ((metadata = query.exec(yaml))) {
|
||||
let query = new RegExp(rexstr(FIND_ƔAML_LINE), 'g'); // Some browsers don't allow flags unless both args are strings
|
||||
while ((metadata = query.exec(ɣaml))) {
|
||||
result.metadata.push([
|
||||
processString(metadata[1]),
|
||||
processString(metadata[2]),
|
||||
@@ -352,63 +303,23 @@ export function createBio(note, data) {
|
||||
let val = '' + data[i][1];
|
||||
|
||||
// Key processing
|
||||
if (key === (key.match(YAML_SIMPLE_KEY) || [])[0]) /* do nothing */;
|
||||
else if (key.indexOf('\'') === -1 && key === (key.match(ANY_ESCAPED_APOS) || [])[0]) key = '\'' + key + '\'';
|
||||
if (key === (key.match(ƔAML_SIMPLE_KEY) || [])[0]) /* do nothing */;
|
||||
else if (key === (key.match(ANY_QUOTE_CHAR) || [])[0]) key = '"' + key + '"';
|
||||
else {
|
||||
key = key
|
||||
.replace(/\x00/g, '\\0')
|
||||
.replace(/\x07/g, '\\a')
|
||||
.replace(/\x08/g, '\\b')
|
||||
.replace(/\x0a/g, '\\n')
|
||||
.replace(/\x0b/g, '\\v')
|
||||
.replace(/\x0c/g, '\\f')
|
||||
.replace(/\x0d/g, '\\r')
|
||||
.replace(/\x1b/g, '\\e')
|
||||
.replace(/\x22/g, '\\"')
|
||||
.replace(/\x5c/g, '\\\\');
|
||||
let badchars = key.match(
|
||||
new RegExp(rexstr(NOT_ALLOWED_CHAR), 'gu')
|
||||
) || [];
|
||||
for (let j = 0; j < badchars.length; j++) {
|
||||
key = key.replace(
|
||||
badchars[i],
|
||||
'\\u' + badchars[i].codePointAt(0).toLocaleString('en', {
|
||||
useGrouping: false,
|
||||
minimumIntegerDigits: 4,
|
||||
})
|
||||
);
|
||||
}
|
||||
key = '"' + key + '"';
|
||||
.replace(/'/g, '\'\'')
|
||||
.replace(new RegExp(rexstr(NOT_ALLOWED_CHAR), compat_mode ? 'g' : 'gu'), '<EFBFBD>');
|
||||
key = '\'' + key + '\'';
|
||||
}
|
||||
|
||||
// Value processing
|
||||
if (val === (val.match(YAML_SIMPLE_VALUE) || [])[0]) /* do nothing */;
|
||||
else if (val.indexOf('\'') === -1 && val === (val.match(ANY_ESCAPED_APOS) || [])[0]) val = '\'' + val + '\'';
|
||||
if (val === (val.match(ƔAML_SIMPLE_VALUE) || [])[0]) /* do nothing */;
|
||||
else if (val === (val.match(ANY_QUOTE_CHAR) || [])[0]) val = '"' + val + '"';
|
||||
else {
|
||||
val = val
|
||||
.replace(/\x00/g, '\\0')
|
||||
.replace(/\x07/g, '\\a')
|
||||
.replace(/\x08/g, '\\b')
|
||||
.replace(/\x0a/g, '\\n')
|
||||
.replace(/\x0b/g, '\\v')
|
||||
.replace(/\x0c/g, '\\f')
|
||||
.replace(/\x0d/g, '\\r')
|
||||
.replace(/\x1b/g, '\\e')
|
||||
.replace(/\x22/g, '\\"')
|
||||
.replace(/\x5c/g, '\\\\');
|
||||
let badchars = val.match(
|
||||
new RegExp(rexstr(NOT_ALLOWED_CHAR), 'gu')
|
||||
) || [];
|
||||
for (let j = 0; j < badchars.length; j++) {
|
||||
val = val.replace(
|
||||
badchars[i],
|
||||
'\\u' + badchars[i].codePointAt(0).toLocaleString('en', {
|
||||
useGrouping: false,
|
||||
minimumIntegerDigits: 4,
|
||||
})
|
||||
);
|
||||
}
|
||||
val = '"' + val + '"';
|
||||
key = key
|
||||
.replace(/'/g, '\'\'')
|
||||
.replace(new RegExp(rexstr(NOT_ALLOWED_CHAR), compat_mode ? 'g' : 'gu'), '<EFBFBD>');
|
||||
key = '\'' + key + '\'';
|
||||
}
|
||||
|
||||
frontmatter += key + ': ' + val + '\n';
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
refreshHomeTimeline,
|
||||
refreshCommunityTimeline,
|
||||
refreshPublicTimeline,
|
||||
refreshDirectTimeline,
|
||||
} from './timelines';
|
||||
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
@@ -133,6 +134,8 @@ export function submitCompose() {
|
||||
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
|
||||
insertOrRefresh('community', refreshCommunityTimeline);
|
||||
insertOrRefresh('public', refreshPublicTimeline);
|
||||
} else if (response.data.visibility === 'direct') {
|
||||
insertOrRefresh('direct', refreshDirectTimeline);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
dispatch(submitComposeFail(error));
|
||||
|
||||
@@ -92,3 +92,4 @@ export const connectCommunityStream = () => connectTimelineStream('community', '
|
||||
export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
|
||||
export const connectPublicStream = () => connectTimelineStream('public', 'public');
|
||||
export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
|
||||
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
||||
|
||||
@@ -115,6 +115,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
|
||||
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
|
||||
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
|
||||
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||
export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct');
|
||||
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||
@@ -155,6 +156,7 @@ export function expandTimeline(timelineId, path, params = {}) {
|
||||
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
|
||||
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
|
||||
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||
export const expandDirectTimeline = () => expandTimeline('direct', '/api/v1/timelines/direct');
|
||||
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/en.js';
|
||||
import 'intl/locale-data/jsonp/en';
|
||||
import 'es6-symbol/implement';
|
||||
import includes from 'array-includes';
|
||||
import assign from 'object-assign';
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
|
||||
<div
|
||||
className="account__avatar"
|
||||
data-avatar-of="@alice"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(/animated/alice.gif)",
|
||||
"backgroundSize": "100px 100px",
|
||||
"height": "100px",
|
||||
"width": "100px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<Avatar /> Still renders a still avatar 1`] = `
|
||||
<div
|
||||
className="account__avatar"
|
||||
data-avatar-of="@alice"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(/static/alice.jpg)",
|
||||
"backgroundSize": "100px 100px",
|
||||
"height": "100px",
|
||||
"width": "100px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -0,0 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
||||
<div
|
||||
className="account__avatar-overlay"
|
||||
>
|
||||
<div
|
||||
className="account__avatar-overlay-base"
|
||||
data-avatar-of="@alice"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(/static/alice.jpg)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="account__avatar-overlay-overlay"
|
||||
data-avatar-of="@eve@blackhat.lair"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(/static/eve.jpg)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,130 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Button /> adds class "button-secondary" if props.secondary given 1`] = `
|
||||
<button
|
||||
className="button button-secondary"
|
||||
disabled={undefined}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<Button /> renders a button element 1`] = `
|
||||
<button
|
||||
className="button"
|
||||
disabled={undefined}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<Button /> renders a disabled attribute if props.disabled given 1`] = `
|
||||
<button
|
||||
className="button"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<Button /> renders class="button--block" if props.block given 1`] = `
|
||||
<button
|
||||
className="button button--block"
|
||||
disabled={undefined}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<Button /> renders the children 1`] = `
|
||||
<button
|
||||
className="button"
|
||||
disabled={undefined}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<p>
|
||||
children
|
||||
</p>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`<Button /> renders the given text 1`] = `
|
||||
<button
|
||||
className="button"
|
||||
disabled={undefined}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
>
|
||||
foo
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`<Button /> renders the props.text instead of children 1`] = `
|
||||
<button
|
||||
className="button"
|
||||
disabled={undefined}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
>
|
||||
foo
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`<Button /> renders title if props.title is given 1`] = `
|
||||
<button
|
||||
className="button"
|
||||
disabled={undefined}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "36px",
|
||||
"lineHeight": "36px",
|
||||
"padding": "0 16px",
|
||||
}
|
||||
}
|
||||
title="foo"
|
||||
/>
|
||||
`;
|
||||
@@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DisplayName /> renders display name + account name 1`] = `
|
||||
<span
|
||||
className="display-name"
|
||||
>
|
||||
<strong
|
||||
className="display-name__html"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>Foo</p>",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<span
|
||||
className="display-name__account"
|
||||
>
|
||||
@
|
||||
bar@baz
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
36
app/javascript/mastodon/components/__tests__/avatar-test.js
Normal file
36
app/javascript/mastodon/components/__tests__/avatar-test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { fromJS } from 'immutable';
|
||||
import Avatar from '../avatar';
|
||||
|
||||
describe('<Avatar />', () => {
|
||||
const account = fromJS({
|
||||
username: 'alice',
|
||||
acct: 'alice',
|
||||
display_name: 'Alice',
|
||||
avatar: '/animated/alice.gif',
|
||||
avatar_static: '/static/alice.jpg',
|
||||
});
|
||||
|
||||
const size = 100;
|
||||
|
||||
describe('Autoplay', () => {
|
||||
it('renders a animated avatar', () => {
|
||||
const component = renderer.create(<Avatar account={account} animate size={size} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Still', () => {
|
||||
it('renders a still avatar', () => {
|
||||
const component = renderer.create(<Avatar account={account} size={size} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO add autoplay test if possible
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { fromJS } from 'immutable';
|
||||
import AvatarOverlay from '../avatar_overlay';
|
||||
|
||||
describe('<AvatarOverlay', () => {
|
||||
const account = fromJS({
|
||||
username: 'alice',
|
||||
acct: 'alice',
|
||||
display_name: 'Alice',
|
||||
avatar: '/animated/alice.gif',
|
||||
avatar_static: '/static/alice.jpg',
|
||||
});
|
||||
|
||||
const friend = fromJS({
|
||||
username: 'eve',
|
||||
acct: 'eve@blackhat.lair',
|
||||
display_name: 'Evelyn',
|
||||
avatar: '/animated/eve.gif',
|
||||
avatar_static: '/static/eve.jpg',
|
||||
});
|
||||
|
||||
it('renders a overlay avatar', () => {
|
||||
const component = renderer.create(<AvatarOverlay account={account} friend={friend} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
82
app/javascript/mastodon/components/__tests__/button-test.js
Normal file
82
app/javascript/mastodon/components/__tests__/button-test.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import Button from '../button';
|
||||
|
||||
describe('<Button />', () => {
|
||||
it('renders a button element', () => {
|
||||
const component = renderer.create(<Button />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the given text', () => {
|
||||
const text = 'foo';
|
||||
const component = renderer.create(<Button text={text} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles click events using the given handler', () => {
|
||||
const handler = jest.fn();
|
||||
const button = shallow(<Button onClick={handler} />);
|
||||
button.find('button').simulate('click');
|
||||
|
||||
expect(handler.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not handle click events if props.disabled given', () => {
|
||||
const handler = jest.fn();
|
||||
const button = shallow(<Button onClick={handler} disabled />);
|
||||
button.find('button').simulate('click');
|
||||
|
||||
expect(handler.mock.calls.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('renders a disabled attribute if props.disabled given', () => {
|
||||
const component = renderer.create(<Button disabled />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the children', () => {
|
||||
const children = <p>children</p>;
|
||||
const component = renderer.create(<Button>{children}</Button>);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the props.text instead of children', () => {
|
||||
const text = 'foo';
|
||||
const children = <p>children</p>;
|
||||
const component = renderer.create(<Button text={text}>{children}</Button>);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders class="button--block" if props.block given', () => {
|
||||
const component = renderer.create(<Button block />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds class "button-secondary" if props.secondary given', () => {
|
||||
const component = renderer.create(<Button secondary />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders title if props.title is given', () => {
|
||||
const component = renderer.create(<Button title='foo' />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { fromJS } from 'immutable';
|
||||
import DisplayName from '../display_name';
|
||||
|
||||
describe('<DisplayName />', () => {
|
||||
it('renders display name + account name', () => {
|
||||
const account = fromJS({
|
||||
username: 'bar',
|
||||
acct: 'bar@baz',
|
||||
display_name_html: '<p>Foo</p>',
|
||||
});
|
||||
const component = renderer.create(<DisplayName account={account} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,7 @@ export default class Button extends React.PureComponent {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -35,26 +36,26 @@ export default class Button extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const style = {
|
||||
padding: `0 ${this.props.size / 2.25}px`,
|
||||
height: `${this.props.size}px`,
|
||||
lineHeight: `${this.props.size}px`,
|
||||
...this.props.style,
|
||||
let attrs = {
|
||||
className: classNames('button', this.props.className, {
|
||||
'button-secondary': this.props.secondary,
|
||||
'button--block': this.props.block,
|
||||
}),
|
||||
disabled: this.props.disabled,
|
||||
onClick: this.handleClick,
|
||||
ref: this.setRef,
|
||||
style: {
|
||||
padding: `0 ${this.props.size / 2.25}px`,
|
||||
height: `${this.props.size}px`,
|
||||
lineHeight: `${this.props.size}px`,
|
||||
...this.props.style,
|
||||
},
|
||||
};
|
||||
|
||||
const className = classNames('button', this.props.className, {
|
||||
'button-secondary': this.props.secondary,
|
||||
'button--block': this.props.block,
|
||||
});
|
||||
if (this.props.title) attrs.title = this.props.title;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
disabled={this.props.disabled}
|
||||
onClick={this.handleClick}
|
||||
ref={this.setRef}
|
||||
style={style}
|
||||
>
|
||||
<button {...attrs}>
|
||||
{this.props.text || this.props.children}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../features/ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
@@ -175,7 +175,9 @@ export default class ColumnHeader extends React.PureComponent {
|
||||
<div className={wrapperClassName}>
|
||||
<h1 tabIndex={focusable ? 0 : null} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
|
||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||
{title}
|
||||
<span className='column-header__title'>
|
||||
{title}
|
||||
</span>
|
||||
<div className='column-header__buttons'>
|
||||
{backButton}
|
||||
{ notifCleaning ? (
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import IconButton from './icon_button';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../features/ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../features/ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class IconButton extends React.PureComponent {
|
||||
|
||||
@@ -56,27 +57,26 @@ export default class IconButton extends React.PureComponent {
|
||||
style.textAlign = 'left';
|
||||
}
|
||||
|
||||
const classes = ['icon-button'];
|
||||
const {
|
||||
active,
|
||||
animate,
|
||||
className,
|
||||
disabled,
|
||||
expanded,
|
||||
icon,
|
||||
inverted,
|
||||
overlay,
|
||||
pressed,
|
||||
tabIndex,
|
||||
title,
|
||||
} = this.props;
|
||||
|
||||
if (this.props.active) {
|
||||
classes.push('active');
|
||||
}
|
||||
|
||||
if (this.props.disabled) {
|
||||
classes.push('disabled');
|
||||
}
|
||||
|
||||
if (this.props.inverted) {
|
||||
classes.push('inverted');
|
||||
}
|
||||
|
||||
if (this.props.overlay) {
|
||||
classes.push('overlayed');
|
||||
}
|
||||
|
||||
if (this.props.className) {
|
||||
classes.push(this.props.className);
|
||||
}
|
||||
const classes = classNames(className, 'icon-button', {
|
||||
active,
|
||||
disabled,
|
||||
inverted,
|
||||
overlayed: overlay,
|
||||
});
|
||||
|
||||
const flipDeg = this.props.flip ? -180 : -360;
|
||||
const rotateDeg = this.props.active ? flipDeg : 0;
|
||||
@@ -90,23 +90,23 @@ export default class IconButton extends React.PureComponent {
|
||||
damping: 7,
|
||||
};
|
||||
const motionStyle = {
|
||||
rotate: this.props.animate ? spring(rotateDeg, springOpts) : 0,
|
||||
rotate: animate ? spring(rotateDeg, springOpts) : 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={motionDefaultStyle} style={motionStyle}>
|
||||
{({ rotate }) =>
|
||||
<button
|
||||
aria-label={this.props.title}
|
||||
aria-pressed={this.props.pressed}
|
||||
aria-expanded={this.props.expanded}
|
||||
title={this.props.title}
|
||||
className={classes.join(' ')}
|
||||
aria-label={title}
|
||||
aria-pressed={pressed}
|
||||
aria-expanded={expanded}
|
||||
title={title}
|
||||
className={classes}
|
||||
onClick={this.handleClick}
|
||||
style={style}
|
||||
tabIndex={this.props.tabIndex}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
|
||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
||||
{this.props.label}
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const messages = defineMessages({
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
@@ -182,7 +183,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||
{shareButton}
|
||||
|
||||
<div className='status__action-bar-dropdown'>
|
||||
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
|
||||
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel={intl.formatMessage(messages.more)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -125,6 +125,7 @@ export default class StatusContent extends React.PureComponent {
|
||||
const directionStyle = { direction: 'ltr' };
|
||||
const classNames = classnames('status__content', {
|
||||
'status__content--with-action': this.props.onClick && this.context.router,
|
||||
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
||||
});
|
||||
|
||||
if (isRtl(status.get('search_index'))) {
|
||||
@@ -156,7 +157,7 @@ export default class StatusContent extends React.PureComponent {
|
||||
|
||||
{mentionsPlaceholder}
|
||||
|
||||
<div tabIndex={!hidden && 0} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.onClick) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { hydrateStore } from '../actions/store';
|
||||
import { connectUserStream } from '../actions/streaming';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from '../locales';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
@@ -5,8 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||
import UploadButtonContainer from '../containers/upload_button_container';
|
||||
import DoodleButtonContainer from '../containers/doodle_button_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Collapsable from '../../../components/collapsable';
|
||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||
@@ -20,6 +18,7 @@ import { isMobile } from '../../../is_mobile';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { length } from 'stringz';
|
||||
import { countableText } from '../util/counter';
|
||||
import ComposeAttachOptions from '../../../../glitch/components/compose/attach_options/index';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
||||
@@ -58,7 +57,6 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
onPickEmoji: PropTypes.func.isRequired,
|
||||
showSearch: PropTypes.bool,
|
||||
settings : ImmutablePropTypes.map.isRequired,
|
||||
filesAttached : PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -156,17 +154,18 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, onPaste, showSearch, filesAttached } = this.props;
|
||||
const { intl, onPaste, showSearch } = this.props;
|
||||
const disabled = this.props.is_submitting;
|
||||
const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : '';
|
||||
const text = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
|
||||
|
||||
const secondaryVisibility = this.props.settings.get('side_arm');
|
||||
const isWideView = this.props.settings.get('stretch');
|
||||
let showSideArm = secondaryVisibility !== 'none';
|
||||
|
||||
let publishText = '';
|
||||
let publishText2 = '';
|
||||
let title = '';
|
||||
let title2 = '';
|
||||
|
||||
const privacyIcons = {
|
||||
none: '',
|
||||
@@ -176,30 +175,30 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
direct: 'envelope',
|
||||
};
|
||||
|
||||
title = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${this.props.privacy}.short` })}`;
|
||||
|
||||
if (showSideArm) {
|
||||
// Enhanced behavior with dual toot buttons
|
||||
publishText = (
|
||||
<span>
|
||||
{
|
||||
<i
|
||||
className={`fa fa-${privacyIcons[this.props.privacy]}`}
|
||||
style={{
|
||||
paddingRight: (filesAttached || !isWideView) ? '0' : '5px',
|
||||
}}
|
||||
style={{ paddingRight: '5px' }}
|
||||
/>
|
||||
}{
|
||||
(filesAttached || !isWideView) ? '' :
|
||||
intl.formatMessage(messages.publish)
|
||||
}
|
||||
}{intl.formatMessage(messages.publish)}
|
||||
</span>
|
||||
);
|
||||
|
||||
title2 = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`;
|
||||
publishText2 = (
|
||||
<i
|
||||
className={`fa fa-${privacyIcons[secondaryVisibility]}`}
|
||||
aria-label={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`}
|
||||
aria-label={title2}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// Original vanilla behavior - no icon if public or unlisted
|
||||
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
|
||||
} else {
|
||||
@@ -248,12 +247,12 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
</div>
|
||||
|
||||
<div className='compose-form__buttons'>
|
||||
<UploadButtonContainer />
|
||||
<DoodleButtonContainer />
|
||||
<PrivacyDropdownContainer />
|
||||
<ComposeAdvancedOptionsContainer />
|
||||
<ComposeAttachOptions />
|
||||
<SensitiveButtonContainer />
|
||||
<div className='compose-form__buttons-separator' />
|
||||
<PrivacyDropdownContainer />
|
||||
<SpoilerButtonContainer />
|
||||
<ComposeAdvancedOptionsContainer />
|
||||
</div>
|
||||
|
||||
<div className='compose-form__publish'>
|
||||
@@ -264,6 +263,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
<Button
|
||||
className='compose-form__publish__side-arm'
|
||||
text={publishText2}
|
||||
title={title2}
|
||||
onClick={this.handleSubmit2}
|
||||
disabled={submitDisabled}
|
||||
/> : ''
|
||||
@@ -271,9 +271,9 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
<Button
|
||||
className='compose-form__publish__primary'
|
||||
text={publishText}
|
||||
title={title}
|
||||
onClick={this.handleSubmit}
|
||||
disabled={submitDisabled}
|
||||
block
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
const messages = defineMessages({
|
||||
doodle: { id: 'doodle_button.label', defaultMessage: 'Add a drawing' },
|
||||
});
|
||||
|
||||
const iconStyle = {
|
||||
height: null,
|
||||
lineHeight: '27px',
|
||||
};
|
||||
|
||||
@injectIntl
|
||||
export default class UploadButton extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
onOpenCanvas: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onOpenCanvas();
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
const { intl, disabled } = this.props;
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload-button'>
|
||||
<IconButton icon='pencil' title={intl.formatMessage(messages.doodle)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
@@ -68,7 +68,7 @@ export default class Upload extends ImmutablePureComponent {
|
||||
<div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ scale }) => (
|
||||
<div className='compose-form__upload-thumbnail' style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})` }}>
|
||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})` }}>
|
||||
<IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.handleUndoClick} />
|
||||
|
||||
<div className={classNames('compose-form__upload-description', { active })}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
export default class Warning extends React.PureComponent {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import AutosuggestStatus from '../components/autosuggest_status';
|
||||
import { makeGetStatus } from '../../../selectors';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
status: getStatus(state, id),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default connect(makeMapStateToProps)(AutosuggestStatus);
|
||||
@@ -1,15 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import DoodleButton from '../components/doodle_button';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onOpenCanvas () {
|
||||
dispatch(openModal('DOODLE', { noEsc: true }));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DoodleButton);
|
||||
@@ -46,7 +46,7 @@ const getFrequentlyUsedEmojis = createSelector([
|
||||
|
||||
const getCustomEmojis = createSelector([
|
||||
state => state.get('custom_emojis'),
|
||||
], emojis => emojis.sort((a, b) => {
|
||||
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
|
||||
const aShort = a.get('shortcode').toLowerCase();
|
||||
const bShort = b.get('shortcode').toLowerCase();
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import { changeComposeSensitivity } from '../../../actions/compose';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
@@ -47,7 +47,7 @@ class SensitiveButton extends React.PureComponent {
|
||||
'compose-form__sensitive-button--visible': visible,
|
||||
});
|
||||
return (
|
||||
<div className={className} style={{ transform: `translateZ(0) scale(${scale})` }}>
|
||||
<div className={className} style={{ transform: `scale(${scale})` }}>
|
||||
<IconButton
|
||||
className='compose-form__sensitive-button__icon'
|
||||
title={intl.formatMessage(messages.title)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { changeLocalSetting } from '../../../glitch/actions/local_settings';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import SearchContainer from './containers/search_container';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import SearchResultsContainer from './containers/search_results_container';
|
||||
import { changeComposing } from '../../actions/compose';
|
||||
@@ -105,7 +105,7 @@ export default class Compose extends React.PureComponent {
|
||||
<SearchContainer />
|
||||
|
||||
<div className='drawer__pager'>
|
||||
<div className='drawer__inner' onFocus={this.onFocus}>
|
||||
<div className='drawer__inner scrollable optionally-scrollable' onFocus={this.onFocus}>
|
||||
<NavigationContainer onClose={this.onBlur} />
|
||||
<ComposeFormContainer />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ColumnSettings from '../../community_timeline/components/column_settings';
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
settings: state.getIn(['settings', 'direct']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['direct', ...key], checked));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
|
||||
107
app/javascript/mastodon/features/direct_timeline/index.js
Normal file
107
app/javascript/mastodon/features/direct_timeline/index.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from '../ui/containers/status_list_container';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import {
|
||||
refreshDirectTimeline,
|
||||
expandDirectTimeline,
|
||||
} from '../../actions/timelines';
|
||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import { connectDirectStream } from '../../actions/streaming';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.direct', defaultMessage: 'Direct messages' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0,
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
@injectIntl
|
||||
export default class DirectTimeline extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
|
||||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
dispatch(addColumn('DIRECT', {}));
|
||||
}
|
||||
}
|
||||
|
||||
handleMove = (dir) => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
dispatch(moveColumn(columnId, dir));
|
||||
}
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(refreshDirectTimeline());
|
||||
this.disconnect = dispatch(connectDirectStream());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
handleLoadMore = () => {
|
||||
this.props.dispatch(expandDirectTimeline());
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<ColumnHeader
|
||||
icon='envelope'
|
||||
active={hasUnread}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onPin={this.handlePin}
|
||||
onMove={this.handleMove}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusListContainer
|
||||
trackScroll={!pinned}
|
||||
scrollKey={`direct_timeline-${columnId}`}
|
||||
timelineId='direct'
|
||||
loadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import emojify from '../emoji';
|
||||
|
||||
describe('emoji', () => {
|
||||
describe('.emojify', () => {
|
||||
it('ignores unknown shortcodes', () => {
|
||||
expect(emojify(':foobarbazfake:')).toEqual(':foobarbazfake:');
|
||||
});
|
||||
|
||||
it('ignores shortcodes inside of tags', () => {
|
||||
expect(emojify('<p data-foo=":smile:"></p>')).toEqual('<p data-foo=":smile:"></p>');
|
||||
});
|
||||
|
||||
it('works with unclosed tags', () => {
|
||||
expect(emojify('hello>')).toEqual('hello>');
|
||||
expect(emojify('<hello')).toEqual('<hello');
|
||||
});
|
||||
|
||||
it('works with unclosed shortcodes', () => {
|
||||
expect(emojify('smile:')).toEqual('smile:');
|
||||
expect(emojify(':smile')).toEqual(':smile');
|
||||
});
|
||||
|
||||
it('does unicode', () => {
|
||||
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="👩👩👦👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg" />');
|
||||
expect(emojify('👨👩👧👧')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="👨👩👧👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg" />');
|
||||
expect(emojify('👩👩👦')).toEqual('<img draggable="false" class="emojione" alt="👩👩👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg" />');
|
||||
expect(emojify('\u2757')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
|
||||
});
|
||||
|
||||
it('does multiple unicode', () => {
|
||||
expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />');
|
||||
expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />');
|
||||
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
|
||||
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
|
||||
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> bar');
|
||||
});
|
||||
|
||||
it('ignores unicode inside of tags', () => {
|
||||
expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).toEqual('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>');
|
||||
});
|
||||
|
||||
it('does multiple emoji properly (issue 5188)', () => {
|
||||
expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
|
||||
expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
|
||||
});
|
||||
|
||||
it('does an emoji that has no shortcode', () => {
|
||||
expect(emojify('🕉️')).toEqual('<img draggable="false" class="emojione" alt="🕉️" title="" src="/emoji/1f549.svg" />');
|
||||
});
|
||||
|
||||
it('does an emoji whose filename is irregular', () => {
|
||||
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import { pick } from 'lodash';
|
||||
import { emojiIndex } from 'emoji-mart';
|
||||
import { search } from '../emoji_mart_search_light';
|
||||
|
||||
const trimEmojis = emoji => pick(emoji, ['id', 'unified', 'native', 'custom']);
|
||||
|
||||
describe('emoji_index', () => {
|
||||
it('should give same result for emoji_index_light and emoji-mart', () => {
|
||||
const expected = [
|
||||
{
|
||||
id: 'pineapple',
|
||||
unified: '1f34d',
|
||||
native: '🍍',
|
||||
},
|
||||
];
|
||||
expect(search('pineapple').map(trimEmojis)).toEqual(expected);
|
||||
expect(emojiIndex.search('pineapple').map(trimEmojis)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('orders search results correctly', () => {
|
||||
const expected = [
|
||||
{
|
||||
id: 'apple',
|
||||
unified: '1f34e',
|
||||
native: '🍎',
|
||||
},
|
||||
{
|
||||
id: 'pineapple',
|
||||
unified: '1f34d',
|
||||
native: '🍍',
|
||||
},
|
||||
{
|
||||
id: 'green_apple',
|
||||
unified: '1f34f',
|
||||
native: '🍏',
|
||||
},
|
||||
{
|
||||
id: 'iphone',
|
||||
unified: '1f4f1',
|
||||
native: '📱',
|
||||
},
|
||||
];
|
||||
expect(search('apple').map(trimEmojis)).toEqual(expected);
|
||||
expect(emojiIndex.search('apple').map(trimEmojis)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles custom emoji', () => {
|
||||
const custom = [
|
||||
{
|
||||
id: 'mastodon',
|
||||
name: 'mastodon',
|
||||
short_names: ['mastodon'],
|
||||
text: '',
|
||||
emoticons: [],
|
||||
keywords: ['mastodon'],
|
||||
imageUrl: 'http://example.com',
|
||||
custom: true,
|
||||
},
|
||||
];
|
||||
search('', { custom });
|
||||
emojiIndex.search('', { custom });
|
||||
const expected = [
|
||||
{
|
||||
id: 'mastodon',
|
||||
custom: true,
|
||||
},
|
||||
];
|
||||
expect(search('masto').map(trimEmojis)).toEqual(expected);
|
||||
expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should filter only emojis we care about, exclude pineapple', () => {
|
||||
const emojisToShowFilter = unified => unified !== '1F34D';
|
||||
expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id))
|
||||
.not.toContain('pineapple');
|
||||
expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id))
|
||||
.not.toContain('pineapple');
|
||||
});
|
||||
|
||||
it('can include/exclude categories', () => {
|
||||
expect(search('flag', { include: ['people'] })).toEqual([]);
|
||||
expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]);
|
||||
});
|
||||
|
||||
it('does an emoji whose unified name is irregular', () => {
|
||||
const expected = [
|
||||
{
|
||||
'id': 'water_polo',
|
||||
'unified': '1f93d',
|
||||
'native': '🤽',
|
||||
},
|
||||
{
|
||||
'id': 'man-playing-water-polo',
|
||||
'unified': '1f93d-200d-2642-fe0f',
|
||||
'native': '🤽♂️',
|
||||
},
|
||||
{
|
||||
'id': 'woman-playing-water-polo',
|
||||
'unified': '1f93d-200d-2640-fe0f',
|
||||
'native': '🤽♀️',
|
||||
},
|
||||
];
|
||||
expect(search('polo').map(trimEmojis)).toEqual(expected);
|
||||
expect(emojiIndex.search('polo').map(trimEmojis)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('can search for thinking_face', () => {
|
||||
const expected = [
|
||||
{
|
||||
id: 'thinking_face',
|
||||
unified: '1f914',
|
||||
native: '🤔',
|
||||
},
|
||||
];
|
||||
expect(search('thinking_fac').map(trimEmojis)).toEqual(expected);
|
||||
expect(emojiIndex.search('thinking_fac').map(trimEmojis)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('can search for woman-facepalming', () => {
|
||||
const expected = [
|
||||
{
|
||||
id: 'woman-facepalming',
|
||||
unified: '1f926-200d-2640-fe0f',
|
||||
native: '🤦♀️',
|
||||
},
|
||||
];
|
||||
expect(search('woman-facep').map(trimEmojis)).toEqual(expected);
|
||||
expect(emojiIndex.search('woman-facep').map(trimEmojis)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,8 @@ const { unicodeToFilename } = require('./unicode_to_filename');
|
||||
const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
|
||||
const emojiMap = require('./emoji_map.json');
|
||||
const { emojiIndex } = require('emoji-mart');
|
||||
const emojiMartData = require('emoji-mart/dist/data').default;
|
||||
const { default: emojiMartData } = require('emoji-mart/dist/data');
|
||||
|
||||
const excluded = ['®', '©', '™'];
|
||||
const skins = ['🏻', '🏼', '🏽', '🏾', '🏿'];
|
||||
const shortcodeMap = {};
|
||||
|
||||
@@ -17,6 +17,7 @@ const messages = defineMessages({
|
||||
navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' },
|
||||
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
|
||||
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
||||
direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
@@ -78,18 +79,22 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
navItems = navItems.concat([
|
||||
<ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
||||
<ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
|
||||
]);
|
||||
|
||||
if (me.get('locked')) {
|
||||
navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
|
||||
if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) {
|
||||
navItems.push(<ColumnLink key='4' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />);
|
||||
}
|
||||
|
||||
navItems = navItems.concat([
|
||||
<ColumnLink key='7' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
|
||||
<ColumnLink key='8' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
|
||||
<ColumnLink key='5' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
||||
<ColumnLink key='6' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
|
||||
]);
|
||||
|
||||
if (me.get('locked')) {
|
||||
navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
|
||||
}
|
||||
|
||||
navItems = navItems.concat([
|
||||
<ColumnLink key='8' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
|
||||
<ColumnLink key='9' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -48,6 +48,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
let media = '';
|
||||
let mediaIcon = null;
|
||||
let applicationLink = '';
|
||||
let reblogLink = '';
|
||||
let reblogIcon = 'retweet';
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||
@@ -85,6 +87,23 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
|
||||
}
|
||||
|
||||
if (status.get('visibility') === 'direct') {
|
||||
reblogIcon = 'envelope';
|
||||
} else if (status.get('visibility') === 'private') {
|
||||
reblogIcon = 'lock';
|
||||
}
|
||||
|
||||
if (status.get('visibility') === 'private') {
|
||||
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
||||
} else {
|
||||
reblogLink = (<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<i className={`fa fa-${reblogIcon}`} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<FormattedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
</Link>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='detailed-status'>
|
||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
||||
@@ -101,12 +120,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
<div className='detailed-status__meta'>
|
||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||
</a>{applicationLink} · <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<i className='fa fa-retweet' />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<FormattedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
</Link> · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||
</a>{applicationLink} · {reblogLink} · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||
<i className='fa fa-star' />
|
||||
<span className='detailed-status__favorites'>
|
||||
<FormattedNumber value={status.get('favourites_count')} />
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { expect } from 'chai';
|
||||
import { mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import React from 'react';
|
||||
import Column from '../../../../../../app/javascript/mastodon/features/ui/components/column';
|
||||
import ColumnHeader from '../../../../../../app/javascript/mastodon/features/ui/components/column_header';
|
||||
import { mount } from 'enzyme';
|
||||
import Column from '../column';
|
||||
import ColumnHeader from '../column_header';
|
||||
|
||||
describe('<Column />', () => {
|
||||
describe('<ColumnHeader /> click handler', () => {
|
||||
const originalRaf = global.requestAnimationFrame;
|
||||
|
||||
beforeEach(() => {
|
||||
global.requestAnimationFrame = sinon.spy();
|
||||
global.requestAnimationFrame = jest.fn();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.requestAnimationFrame = originalRaf;
|
||||
});
|
||||
|
||||
it('runs the scroll animation if the column contains scrollable content', () => {
|
||||
@@ -18,13 +22,13 @@ describe('<Column />', () => {
|
||||
</Column>
|
||||
);
|
||||
wrapper.find(ColumnHeader).simulate('click');
|
||||
expect(global.requestAnimationFrame.called).to.equal(true);
|
||||
expect(global.requestAnimationFrame.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not try to scroll if there is no scrollable content', () => {
|
||||
const wrapper = mount(<Column heading='notifications' />);
|
||||
wrapper.find(ColumnHeader).simulate('click');
|
||||
expect(global.requestAnimationFrame.called).to.equal(false);
|
||||
expect(global.requestAnimationFrame.mock.calls.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import BundleContainer from '../containers/bundle_container';
|
||||
import ColumnLoading from './column_loading';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
import BundleColumnError from './bundle_column_error';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses } from '../../ui/util/async-components';
|
||||
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
import { scrollRight } from '../../../scroll';
|
||||
@@ -23,6 +23,7 @@ const componentMap = {
|
||||
'PUBLIC': PublicTimeline,
|
||||
'COMMUNITY': CommunityTimeline,
|
||||
'HASHTAG': HashtagTimeline,
|
||||
'DIRECT': DirectTimeline,
|
||||
'FAVOURITES': FavouritedStatuses,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -40,7 +40,7 @@ export default class UploadArea extends React.PureComponent {
|
||||
{({ backgroundOpacity, backgroundScale }) =>
|
||||
<div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
|
||||
<div className='upload-area__drop'>
|
||||
<div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} />
|
||||
<div className='upload-area__background' style={{ transform: `scale(${backgroundScale})` }} />
|
||||
<div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Following,
|
||||
Reblogs,
|
||||
Favourites,
|
||||
DirectTimeline,
|
||||
HashtagTimeline,
|
||||
Notifications,
|
||||
FollowRequests,
|
||||
@@ -71,6 +72,7 @@ const keyMap = {
|
||||
goToNotifications: 'g n',
|
||||
goToLocal: 'g l',
|
||||
goToFederated: 'g t',
|
||||
goToDirect: 'g d',
|
||||
goToStart: 'g s',
|
||||
goToFavourites: 'g f',
|
||||
goToPinned: 'g p',
|
||||
@@ -302,6 +304,10 @@ export default class UI extends React.Component {
|
||||
this.context.router.history.push('/timelines/public');
|
||||
}
|
||||
|
||||
handleHotkeyGoToDirect = () => {
|
||||
this.context.router.history.push('/timelines/direct');
|
||||
}
|
||||
|
||||
handleHotkeyGoToStart = () => {
|
||||
this.context.router.history.push('/getting-started');
|
||||
}
|
||||
@@ -357,6 +363,7 @@ export default class UI extends React.Component {
|
||||
goToNotifications: this.handleHotkeyGoToNotifications,
|
||||
goToLocal: this.handleHotkeyGoToLocal,
|
||||
goToFederated: this.handleHotkeyGoToFederated,
|
||||
goToDirect: this.handleHotkeyGoToDirect,
|
||||
goToStart: this.handleHotkeyGoToStart,
|
||||
goToFavourites: this.handleHotkeyGoToFavourites,
|
||||
goToPinned: this.handleHotkeyGoToPinned,
|
||||
@@ -377,6 +384,7 @@ export default class UI extends React.Component {
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||
|
||||
@@ -26,6 +26,10 @@ export function HashtagTimeline () {
|
||||
return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline');
|
||||
}
|
||||
|
||||
export function DirectTimeline() {
|
||||
return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline');
|
||||
}
|
||||
|
||||
export function Status () {
|
||||
return import(/* webpackChunkName: "features/status" */'../../status');
|
||||
}
|
||||
|
||||
56
app/javascript/mastodon/features/ui/util/optional_motion.js
Normal file
56
app/javascript/mastodon/features/ui/util/optional_motion.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// Like react-motion's Motion, but checks to see if the user prefers
|
||||
// reduced motion and uses a cross-fade in those cases.
|
||||
|
||||
import React from 'react';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const stylesToKeep = ['opacity', 'backgroundOpacity'];
|
||||
|
||||
let reduceMotion;
|
||||
|
||||
const extractValue = (value) => {
|
||||
// This is either an object with a "val" property or it's a number
|
||||
return (typeof value === 'object' && value && 'val' in value) ? value.val : value;
|
||||
};
|
||||
|
||||
class OptionalMotion extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
defaultStyle: PropTypes.object,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.func,
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { style, defaultStyle, children } = this.props;
|
||||
|
||||
if (typeof reduceMotion !== 'boolean') {
|
||||
// This never changes without a page reload, so we can just grab it
|
||||
// once from the body classes as opposed to using Redux's connect(),
|
||||
// which would unnecessarily update every state change
|
||||
reduceMotion = document.body.classList.contains('reduce-motion');
|
||||
}
|
||||
if (reduceMotion) {
|
||||
Object.keys(style).forEach(key => {
|
||||
if (stylesToKeep.includes(key)) {
|
||||
return;
|
||||
}
|
||||
// If it's setting an x or height or scale or some other value, we need
|
||||
// to preserve the end-state value without actually animating it
|
||||
style[key] = defaultStyle[key] = extractValue(style[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Motion style={style} defaultStyle={defaultStyle}>
|
||||
{children}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default OptionalMotion;
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "الخط الزمني المحلي فارغ. اكتب شيئا ما للعامة كبداية.",
|
||||
"empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
|
||||
"empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.",
|
||||
"empty_column.home.inactivity": "صفحتك فارغة. لقد كنت غائبا لفترة طويلة عن الشبكة. سوف تملأ تلقائيا في وقت قريب.",
|
||||
"empty_column.home.public_timeline": "الخيط العام",
|
||||
"empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
|
||||
"empty_column.public": "لا يوجد شيء هنا ! قم بتحرير شيء ما بشكل عام، أو اتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "للعامة",
|
||||
"privacy.unlisted.long": "لا تقم بإدراجه على الخيوط العامة",
|
||||
"privacy.unlisted.short": "غير مدرج",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "إلغاء",
|
||||
"report.placeholder": "تعليقات إضافية",
|
||||
"report.submit": "إرسال",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "حمّل المزيد",
|
||||
"status.media_hidden": "الصورة مستترة",
|
||||
"status.mention": "أذكُر @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "كتم المحادثة",
|
||||
"status.open": "وسع هذه المشاركة",
|
||||
"status.pin": "تدبيس على الملف الشخصي",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"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.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"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",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Do not show in public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Отказ",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Споменаване",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per fer rodar la pilota!",
|
||||
"empty_column.hashtag": "Encara no hi ha res amb aquesta etiqueta.",
|
||||
"empty_column.home": "Encara no segueixes ningú. Visita {public} o fes cerca per començar i conèixer altres usuaris.",
|
||||
"empty_column.home.inactivity": "La línia Inici és buida. si has estat inactiu durant un temps es regenerarà aviat.",
|
||||
"empty_column.home.public_timeline": "la línia de temps pública",
|
||||
"empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.",
|
||||
"empty_column.public": "No hi ha res aquí! Escriu alguna cosa públicament o segueix manualment usuaris d'altres instàncies per omplir-ho",
|
||||
@@ -160,11 +159,11 @@
|
||||
"privacy.public.short": "Públic",
|
||||
"privacy.unlisted.long": "No publicar en línies de temps públiques",
|
||||
"privacy.unlisted.short": "No llistat",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"relative_time.days": "fa {number} jorns",
|
||||
"relative_time.hours": "fa {number} hores",
|
||||
"relative_time.just_now": "ara",
|
||||
"relative_time.minutes": "fa {number} minutes",
|
||||
"relative_time.seconds": "fa {number} segondes",
|
||||
"reply_indicator.cancel": "Cancel·lar",
|
||||
"report.placeholder": "Comentaris addicionals",
|
||||
"report.submit": "Enviar",
|
||||
@@ -184,6 +183,7 @@
|
||||
"status.load_more": "Carrega més",
|
||||
"status.media_hidden": "Multimèdia amagat",
|
||||
"status.mention": "Esmentar @{name}",
|
||||
"status.more": "Més",
|
||||
"status.mute_conversation": "Silenciar conversació",
|
||||
"status.open": "Ampliar aquest estat",
|
||||
"status.pin": "Fixat en el perfil",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe einen öffentlichen Beitrag, um den Ball ins Rollen zu bringen!",
|
||||
"empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
|
||||
"empty_column.home": "Deine Startseite ist leer! Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.",
|
||||
"empty_column.home.inactivity": "Deine Zeitleiste ist leer. Falls du eine längere Zeit inaktiv warst, wird sie für dich so schnell wie möglich neu erstellt.",
|
||||
"empty_column.home.public_timeline": "die öffentliche Zeitleiste",
|
||||
"empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.",
|
||||
"empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um die Zeitleiste aufzufüllen",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Öffentlich",
|
||||
"privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen",
|
||||
"privacy.unlisted.short": "Nicht gelistet",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Abbrechen",
|
||||
"report.placeholder": "Zusätzliche Kommentare",
|
||||
"report.submit": "Absenden",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Weitere laden",
|
||||
"status.media_hidden": "Medien versteckt",
|
||||
"status.mention": "@{name} erwähnen",
|
||||
"status.more": "Mehr",
|
||||
"status.mute_conversation": "Thread stummschalten",
|
||||
"status.open": "Diesen Beitrag öffnen",
|
||||
"status.pin": "Im Profil anheften",
|
||||
|
||||
@@ -183,6 +183,10 @@
|
||||
"defaultMessage": "Share",
|
||||
"id": "status.share"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "More",
|
||||
"id": "status.more"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Reply to thread",
|
||||
"id": "status.replyAll"
|
||||
@@ -751,6 +755,19 @@
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/compose/index.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Direct messages",
|
||||
"id": "column.direct"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||
"id": "empty_column.direct"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/direct_timeline/index.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
@@ -812,6 +829,10 @@
|
||||
"defaultMessage": "Local timeline",
|
||||
"id": "navigation_bar.community_timeline"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Direct messages",
|
||||
"id": "navigation_bar.direct"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Preferences",
|
||||
"id": "navigation_bar.preferences"
|
||||
@@ -907,10 +928,6 @@
|
||||
"defaultMessage": "Home",
|
||||
"id": "column.home"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"id": "empty_column.home.inactivity"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
|
||||
"id": "empty_column.home"
|
||||
@@ -1408,4 +1425,4 @@
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/video/index.json"
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -28,6 +28,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",
|
||||
@@ -80,9 +81,9 @@
|
||||
"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.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"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",
|
||||
@@ -107,6 +108,7 @@
|
||||
"missing_indicator.label": "Not found",
|
||||
"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",
|
||||
@@ -160,6 +162,11 @@
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Do not post to public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Cancel",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
@@ -179,6 +186,7 @@
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Mention @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -1,216 +1,221 @@
|
||||
{
|
||||
"account.block": "Bloki @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.block_domain": "Kaŝi ĉion el {domain}",
|
||||
"account.disclaimer_full": "La ĉi-subaj informoj povas ne plene reflekti la profilon de la uzanto.",
|
||||
"account.edit_profile": "Redakti la profilon",
|
||||
"account.follow": "Sekvi",
|
||||
"account.followers": "Sekvantoj",
|
||||
"account.follows": "Sekvatoj",
|
||||
"account.follows_you": "Sekvas vin",
|
||||
"account.media": "Media",
|
||||
"account.media": "Sonbildaĵoj",
|
||||
"account.mention": "Mencii @{name}",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute": "Silentigi @{name}",
|
||||
"account.posts": "Mesaĝoj",
|
||||
"account.report": "Report @{name}",
|
||||
"account.report": "Signali @{name}",
|
||||
"account.requested": "Atendas aprobon",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.share": "Diskonigi la profilon de @{name}",
|
||||
"account.unblock": "Malbloki @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Malsekvi",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"boost_modal.combo": "You can press {combo} to skip this next time",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"bundle_column_error.retry": "Try again",
|
||||
"bundle_column_error.title": "Network error",
|
||||
"bundle_modal_error.close": "Close",
|
||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||
"bundle_modal_error.retry": "Try again",
|
||||
"column.blocks": "Blocked users",
|
||||
"account.unblock_domain": "Malkaŝi {domain}",
|
||||
"account.unfollow": "Ne plus sekvi",
|
||||
"account.unmute": "Malsilentigi @{name}",
|
||||
"account.view_full_profile": "Vidi plenan profilon",
|
||||
"boost_modal.combo": "La proksiman fojon, premu {combo} por pasigi",
|
||||
"bundle_column_error.body": "Io malfunkciis ŝargante tiun ĉi komponanton.",
|
||||
"bundle_column_error.retry": "Bonvolu reprovi",
|
||||
"bundle_column_error.title": "Reta eraro",
|
||||
"bundle_modal_error.close": "Fermi",
|
||||
"bundle_modal_error.message": "Io malfunkciis ŝargante tiun ĉi komponanton.",
|
||||
"bundle_modal_error.retry": "Bonvolu reprovi",
|
||||
"column.blocks": "Blokitaj uzantoj",
|
||||
"column.community": "Loka tempolinio",
|
||||
"column.favourites": "Favourites",
|
||||
"column.follow_requests": "Follow requests",
|
||||
"column.favourites": "Favoritoj",
|
||||
"column.follow_requests": "Abonpetoj",
|
||||
"column.home": "Hejmo",
|
||||
"column.mutes": "Muted users",
|
||||
"column.mutes": "Silentigitaj uzantoj",
|
||||
"column.notifications": "Sciigoj",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.pins": "Alpinglitaj pepoj",
|
||||
"column.public": "Fratara tempolinio",
|
||||
"column_back_button.label": "Reveni",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
|
||||
"compose_form.lock_disclaimer.lock": "locked",
|
||||
"column_header.hide_settings": "Kaŝi agordojn",
|
||||
"column_header.moveLeft_settings": "Movi kolumnon maldekstren",
|
||||
"column_header.moveRight_settings": "Movi kolumnon dekstren",
|
||||
"column_header.pin": "Alpingli",
|
||||
"column_header.show_settings": "Malkaŝi agordojn",
|
||||
"column_header.unpin": "Depingli",
|
||||
"column_subheading.navigation": "Navigado",
|
||||
"column_subheading.settings": "Agordoj",
|
||||
"compose_form.lock_disclaimer": "Via konta ne estas ŝlosita. Iu ajn povas sekvi vin por vidi viajn privatajn pepojn.",
|
||||
"compose_form.lock_disclaimer.lock": "ŝlosita",
|
||||
"compose_form.placeholder": "Pri kio vi pensas?",
|
||||
"compose_form.publish": "Hup",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive": "Marki ke la enhavo estas tikla",
|
||||
"compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
|
||||
"compose_form.spoiler_placeholder": "Content warning",
|
||||
"confirmation_modal.cancel": "Cancel",
|
||||
"confirmations.block.confirm": "Block",
|
||||
"confirmations.block.message": "Are you sure you want to block {name}?",
|
||||
"confirmations.delete.confirm": "Delete",
|
||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"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": "Mute",
|
||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||
"embed.preview": "Here is what it will look like:",
|
||||
"emoji_button.activity": "Activity",
|
||||
"emoji_button.custom": "Custom",
|
||||
"emoji_button.flags": "Flags",
|
||||
"emoji_button.food": "Food & Drink",
|
||||
"emoji_button.label": "Insert emoji",
|
||||
"emoji_button.nature": "Nature",
|
||||
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Objects",
|
||||
"emoji_button.people": "People",
|
||||
"emoji_button.recent": "Frequently used",
|
||||
"emoji_button.search": "Search...",
|
||||
"emoji_button.search_results": "Search results",
|
||||
"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.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.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"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",
|
||||
"follow_request.reject": "Reject",
|
||||
"getting_started.appsshort": "Apps",
|
||||
"getting_started.faq": "FAQ",
|
||||
"compose_form.spoiler_placeholder": "Skribu tie vian averton",
|
||||
"confirmation_modal.cancel": "Malfari",
|
||||
"confirmations.block.confirm": "Bloki",
|
||||
"confirmations.block.message": "Ĉu vi konfirmas la blokadon de {name}?",
|
||||
"confirmations.delete.confirm": "Malaperigi",
|
||||
"confirmations.delete.message": "Ĉu vi konfirmas la malaperigon de tiun pepon?",
|
||||
"confirmations.domain_block.confirm": "Kaŝi la tutan reton",
|
||||
"confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas bloki {domain} tute? Plej ofte, kelkaj celitaj blokadoj aŭ silentigoj estas sufiĉaj kaj preferindaj.",
|
||||
"confirmations.mute.confirm": "Silentigi",
|
||||
"confirmations.mute.message": "Ĉu vi konfirmas la silentigon de {name}?",
|
||||
"confirmations.unfollow.confirm": "Ne plu sekvi",
|
||||
"confirmations.unfollow.message": "Ĉu vi volas ĉesi sekvi {name}?",
|
||||
"embed.instructions": "Enmetu tiun statkonigon ĉe vian retejon kopiante la ĉi-suban kodon.",
|
||||
"embed.preview": "Ĝi aperos tiel:",
|
||||
"emoji_button.activity": "Aktivecoj",
|
||||
"emoji_button.custom": "Personaj",
|
||||
"emoji_button.flags": "Flagoj",
|
||||
"emoji_button.food": "Manĝi kaj trinki",
|
||||
"emoji_button.label": "Enmeti mieneton",
|
||||
"emoji_button.nature": "Naturo",
|
||||
"emoji_button.not_found": "Neniuj mienetoj!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Objektoj",
|
||||
"emoji_button.people": "Homoj",
|
||||
"emoji_button.recent": "Ofte uzataj",
|
||||
"emoji_button.search": "Serĉo…",
|
||||
"emoji_button.search_results": "Rezultatoj de serĉo",
|
||||
"emoji_button.symbols": "Simboloj",
|
||||
"emoji_button.travel": "Vojaĝoj & lokoj",
|
||||
"empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
|
||||
"empty_column.hashtag": "Ĝise, neniu enhavo estas asociita kun tiu kradvorto.",
|
||||
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
|
||||
"empty_column.home.public_timeline": "la publika tempolinio",
|
||||
"empty_column.notifications": "Vi dume ne havas sciigojn. Interagi kun aliajn uzantojn por komenci la konversacion.",
|
||||
"empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj instancoj por plenigi la publikan tempolinion.",
|
||||
"follow_request.authorize": "Akcepti",
|
||||
"follow_request.reject": "Rifuzi",
|
||||
"getting_started.appsshort": "Aplikaĵoj",
|
||||
"getting_started.faq": "Oftaj demandoj",
|
||||
"getting_started.heading": "Por komenci",
|
||||
"getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en github je {github}.",
|
||||
"getting_started.userguide": "User Guide",
|
||||
"home.column_settings.advanced": "Advanced",
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.filter_regex": "Filter out by regular expressions",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"getting_started.open_source_notice": "Mastodono estas malfermkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
|
||||
"getting_started.userguide": "Gvidilo de uzo",
|
||||
"home.column_settings.advanced": "Precizaj agordoj",
|
||||
"home.column_settings.basic": "Bazaj agordoj",
|
||||
"home.column_settings.filter_regex": "Forfiltri per regulesprimo",
|
||||
"home.column_settings.show_reblogs": "Montri diskonigojn",
|
||||
"home.column_settings.show_replies": "Montri respondojn",
|
||||
"home.settings": "Agordoj de la kolumno",
|
||||
"lightbox.close": "Fermi",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Ŝarĝanta...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
"navigation_bar.blocks": "Blocked users",
|
||||
"lightbox.next": "Malantaŭa",
|
||||
"lightbox.previous": "Antaŭa",
|
||||
"loading_indicator.label": "Ŝarganta…",
|
||||
"media_gallery.toggle_visible": "Baskuli videblecon",
|
||||
"missing_indicator.label": "Ne trovita",
|
||||
"navigation_bar.blocks": "Blokitaj uzantoj",
|
||||
"navigation_bar.community_timeline": "Loka tempolinio",
|
||||
"navigation_bar.edit_profile": "Redakti la profilon",
|
||||
"navigation_bar.favourites": "Favourites",
|
||||
"navigation_bar.follow_requests": "Follow requests",
|
||||
"navigation_bar.info": "Extended information",
|
||||
"navigation_bar.favourites": "Favoritaj",
|
||||
"navigation_bar.follow_requests": "Abonpetoj",
|
||||
"navigation_bar.info": "Plia informo",
|
||||
"navigation_bar.logout": "Elsaluti",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.mutes": "Silentigitaj uzantoj",
|
||||
"navigation_bar.pins": "Alpinglitaj pepoj",
|
||||
"navigation_bar.preferences": "Preferoj",
|
||||
"navigation_bar.public_timeline": "Fratara tempolinio",
|
||||
"notification.favourite": "{name} favoris vian mesaĝon",
|
||||
"notification.follow": "{name} sekvis vin",
|
||||
"notification.mention": "{name} menciis vin",
|
||||
"notification.reblog": "{name} diskonigis vian mesaĝon",
|
||||
"notifications.clear": "Clear notifications",
|
||||
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
|
||||
"notifications.clear": "Forviŝi la sciigojn",
|
||||
"notifications.clear_confirmation": "Ĉu vi certe volas malaperigi ĉiujn viajn sciigojn?",
|
||||
"notifications.column_settings.alert": "Retumilaj atentigoj",
|
||||
"notifications.column_settings.favourite": "Favoroj:",
|
||||
"notifications.column_settings.favourite": "Favoritoj:",
|
||||
"notifications.column_settings.follow": "Novaj sekvantoj:",
|
||||
"notifications.column_settings.mention": "Mencioj:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.push_meta": "This device",
|
||||
"notifications.column_settings.push": "Puŝsciigoj",
|
||||
"notifications.column_settings.push_meta": "Tiu ĉi aparato",
|
||||
"notifications.column_settings.reblog": "Diskonigoj:",
|
||||
"notifications.column_settings.show": "Montri en kolono",
|
||||
"notifications.column_settings.sound": "Play sound",
|
||||
"onboarding.done": "Done",
|
||||
"onboarding.next": "Next",
|
||||
"onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
|
||||
"onboarding.page_four.home": "The home timeline shows posts from people you follow.",
|
||||
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
|
||||
"onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||
"onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
|
||||
"onboarding.page_one.welcome": "Welcome to Mastodon!",
|
||||
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
|
||||
"onboarding.page_six.almost_done": "Almost done...",
|
||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
||||
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
|
||||
"onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||
"onboarding.page_six.guidelines": "community guidelines",
|
||||
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
|
||||
"onboarding.page_six.various_app": "mobile apps",
|
||||
"onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
|
||||
"onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
|
||||
"onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
|
||||
"onboarding.skip": "Skip",
|
||||
"privacy.change": "Adjust status privacy",
|
||||
"privacy.direct.long": "Post to mentioned users only",
|
||||
"privacy.direct.short": "Direct",
|
||||
"privacy.private.long": "Post to followers only",
|
||||
"privacy.private.short": "Followers-only",
|
||||
"privacy.public.long": "Post to public timelines",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Do not show in public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"reply_indicator.cancel": "Rezigni",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
"report.target": "Reporting",
|
||||
"notifications.column_settings.sound": "Eligi sonon",
|
||||
"onboarding.done": "Farita",
|
||||
"onboarding.next": "Malantaŭa",
|
||||
"onboarding.page_five.public_timelines": "La loka tempolinio enhavas mesaĝojn de ĉiuj ĉe {domain}. La federacia tempolinio enhavas ĉiujn mesaĝojn de uzantoj, kiujn iu ĉe {domain} sekvas. Ambaŭ tre utilas por trovi novajn kunparolantojn.",
|
||||
"onboarding.page_four.home": "La hejma tempolinio enhavas la mesaĝojn de ĉiuj uzantoj, kiuj vi sekvas.",
|
||||
"onboarding.page_four.notifications": "La sciiga kolumno informas vin kiam iu interagas kun vi.",
|
||||
"onboarding.page_one.federation": "Mastodono estas reto de nedependaj serviloj, unuiĝintaj por krei pligrandan socian retejon. Ni nomas tiujn servilojn instancoj.",
|
||||
"onboarding.page_one.handle": "Vi estas ĉe {domain}, unu el la multaj instancoj de Mastodono. Via kompleta uznomo do estas {handle}",
|
||||
"onboarding.page_one.welcome": "Bonvenon al Mastodono!",
|
||||
"onboarding.page_six.admin": "Via instancestro estas {admin}.",
|
||||
"onboarding.page_six.almost_done": "Estas preskaŭ finita…",
|
||||
"onboarding.page_six.appetoot": "Bonan a‘pepi’ton!",
|
||||
"onboarding.page_six.apps_available": "{apps} estas elŝuteblaj por iOS, Androido kaj alioj. Kaj nun… bonan a‘pepi’ton!",
|
||||
"onboarding.page_six.github": "Mastodono estas libera, senpaga kaj malfermkoda programaro. Vi povas signali cimojn, proponi funkciojn aŭ kontribui al gîa kreskado ĉe {github}.",
|
||||
"onboarding.page_six.guidelines": "komunreguloj",
|
||||
"onboarding.page_six.read_guidelines": "Ni petas vin: ne forgesu legi la {guidelines}n de {domain}!",
|
||||
"onboarding.page_six.various_app": "telefon-aplikaĵoj",
|
||||
"onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian avataron, priskribon kaj vian nomon. Vi tie trovos ankoraŭ aliajn agordojn.",
|
||||
"onboarding.page_three.search": "Uzu la serĉokampo por trovi uzantojn kaj esplori kradvortojn tiel ke {illustration} kaj {introductions}. Por trovi iun, kiu ne estas ĉe ĉi tiu instanco, uzu ĝian kompletan uznomon.",
|
||||
"onboarding.page_two.compose": "Skribu pepojn en la verkkolumno. Vi povas aldoni bildojn, ŝanĝi la agordojn de privateco kaj aldoni tiklavertojn (« content warning ») dank' al la piktogramoj malsupre.",
|
||||
"onboarding.skip": "Pasigi",
|
||||
"privacy.change": "Alĝustigi la privateco de la mesaĝo",
|
||||
"privacy.direct.long": "Vidigi nur al la menciitaj personoj",
|
||||
"privacy.direct.short": "Rekta",
|
||||
"privacy.private.long": "Vidigi nur al viaj sekvantoj",
|
||||
"privacy.private.short": "Nursekvanta",
|
||||
"privacy.public.long": "Vidigi en publikaj tempolinioj",
|
||||
"privacy.public.short": "Publika",
|
||||
"privacy.unlisted.long": "Ne vidigi en publikaj tempolinioj",
|
||||
"privacy.unlisted.short": "Nelistigita",
|
||||
"relative_time.days": "{number}t",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "nun",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Malfari",
|
||||
"report.placeholder": "Pliaj komentoj",
|
||||
"report.submit": "Sendi",
|
||||
"report.target": "Signalaĵo",
|
||||
"search.placeholder": "Serĉi",
|
||||
"search_popout.search_format": "Advanced search format",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
"search_popout.tips.user": "user",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"status.cannot_reblog": "This post cannot be boosted",
|
||||
"search_popout.search_format": "Detala serĉo",
|
||||
"search_popout.tips.hashtag": "kradvorto",
|
||||
"search_popout.tips.status": "statkonigo",
|
||||
"search_popout.tips.text": "Simpla teksto eligas la kongruajn afiŝnomojn, uznomojn kaj kradvortojn.",
|
||||
"search_popout.tips.user": "uzanto",
|
||||
"search_results.total": "{count, number} {count, plural, one {rezultato} other {rezultatoj}}",
|
||||
"standalone.public_title": "Rigardeti…",
|
||||
"status.cannot_reblog": "Tiun publikaĵon oni ne povas diskonigi",
|
||||
"status.delete": "Forigi",
|
||||
"status.embed": "Embed",
|
||||
"status.embed": "Enmeti",
|
||||
"status.favourite": "Favori",
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.load_more": "Ŝargi plie",
|
||||
"status.media_hidden": "Sonbildaĵo kaŝita",
|
||||
"status.mention": "Mencii @{name}",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.more": "Pli",
|
||||
"status.mute_conversation": "Silentigi konversacion",
|
||||
"status.open": "Disfaldi statkonigon",
|
||||
"status.pin": "Pingli al la profilo",
|
||||
"status.reblog": "Diskonigi",
|
||||
"status.reblogged_by": "{name} diskonigita",
|
||||
"status.reblogged_by": "{name} diskonigis",
|
||||
"status.reply": "Respondi",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.report": "Report @{name}",
|
||||
"status.replyAll": "Respondi al la fadeno",
|
||||
"status.report": "Signali @{name}",
|
||||
"status.sensitive_toggle": "Alklaki por vidi",
|
||||
"status.sensitive_warning": "Tikla enhavo",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.share": "Diskonigi",
|
||||
"status.show_less": "Refaldi",
|
||||
"status.show_more": "Disfaldi",
|
||||
"status.unmute_conversation": "Malsilentigi konversacion",
|
||||
"status.unpin": "Depingli de profilo",
|
||||
"tabs_bar.compose": "Ekskribi",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.federated_timeline": "Federacia tempolinio",
|
||||
"tabs_bar.home": "Hejmo",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.local_timeline": "Loka tempolinio",
|
||||
"tabs_bar.notifications": "Sciigoj",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Aldoni enhavaĵon",
|
||||
"upload_form.description": "Describe for the visually impaired",
|
||||
"upload_area.title": "Algliti por alŝuti",
|
||||
"upload_button.label": "Aldoni sonbildaĵon",
|
||||
"upload_form.description": "Priskribi por la misvidantaj",
|
||||
"upload_form.undo": "Malfari",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound"
|
||||
"upload_progress.label": "Alŝutanta…",
|
||||
"video.close": "Fermi videon",
|
||||
"video.exit_fullscreen": "Eliri el plenekrano",
|
||||
"video.expand": "Vastigi videon",
|
||||
"video.fullscreen": "Igi plenekrane",
|
||||
"video.hide": "Kaŝi videon",
|
||||
"video.mute": "Silentigi",
|
||||
"video.pause": "Paŭzi",
|
||||
"video.play": "Legi",
|
||||
"video.unmute": "Malsilentigi"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
|
||||
"empty_column.hashtag": "No hay nada en este hashtag aún.",
|
||||
"empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
|
||||
"empty_column.home.inactivity": "Tus notificaciones están vacías. Si has estado inactivo por un tiempo, se regenerará para ti pronto.",
|
||||
"empty_column.home.public_timeline": "la línea de tiempo pública",
|
||||
"empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
|
||||
"empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Público",
|
||||
"privacy.unlisted.long": "No mostrar en la historia federada",
|
||||
"privacy.unlisted.short": "Sin federar",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "ahora",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Cancelar",
|
||||
"report.placeholder": "Comentarios adicionales",
|
||||
"report.submit": "Publicar",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Cargar más",
|
||||
"status.media_hidden": "Contenido multimedia oculto",
|
||||
"status.mention": "Mencionar",
|
||||
"status.more": "Más",
|
||||
"status.mute_conversation": "Silenciar conversación",
|
||||
"status.open": "Expandir estado",
|
||||
"status.pin": "Fijar",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "فهرست نوشتههای محلی خالی است. چیزی بنویسید تا چرخش بچرخد!",
|
||||
"empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.",
|
||||
"empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
|
||||
"empty_column.home.inactivity": "فهرست پیگیریهای شما خالی است. اگر مدتی است که غیرفعال بودید، این فهرست به زودی برایتان پر میشود.",
|
||||
"empty_column.home.public_timeline": "فهرست نوشتههای همهجا",
|
||||
"empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشتههای دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
|
||||
"empty_column.public": "اینجا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا اینجا پر شود",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "عمومی",
|
||||
"privacy.unlisted.long": "عمومی، ولی فهرست نکن",
|
||||
"privacy.unlisted.short": "فهرستنشده",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "لغو",
|
||||
"report.placeholder": "توضیح اضافه",
|
||||
"report.submit": "بفرست",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "بیشتر نشان بده",
|
||||
"status.media_hidden": "تصویر پنهان شده",
|
||||
"status.mention": "نامبردن از @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "بیصداکردن گفتگو",
|
||||
"status.open": "این نوشته را باز کن",
|
||||
"status.pin": "نوشتهٔ ثابت نمایه",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"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.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"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",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Do not show in public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Peruuta",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Mainitse @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"confirmations.mute.message": "Confirmez-vous le masquage de {name} ?",
|
||||
"confirmations.unfollow.confirm": "Ne plus suivre",
|
||||
"confirmations.unfollow.message": "Voulez-vous arrêter de suivre {name} ?",
|
||||
"embed.instructions": "Intégrez ce statut à votre site en copiant ce code ci-dessous.",
|
||||
"embed.instructions": "Intégrez ce statut à votre site en copiant le code ci-dessous.",
|
||||
"embed.preview": "Il apparaîtra comme cela : ",
|
||||
"emoji_button.activity": "Activités",
|
||||
"emoji_button.custom": "Personnalisés",
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !",
|
||||
"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.inactivity": "Votre accueil est vide. Si vous ne vous êtes pas connecté⋅e depuis un moment, il se remplira automatiquement très bientôt.",
|
||||
"empty_column.home.public_timeline": "le fil public",
|
||||
"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.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Ne pas afficher dans les fils publics",
|
||||
"privacy.unlisted.short": "Non-listé",
|
||||
"relative_time.days": "{number} j",
|
||||
"relative_time.hours": "{number} h",
|
||||
"relative_time.just_now": "à l’instant",
|
||||
"relative_time.minutes": "{number} min",
|
||||
"relative_time.seconds": "{number} s",
|
||||
"reply_indicator.cancel": "Annuler",
|
||||
"report.placeholder": "Commentaires additionnels",
|
||||
"report.submit": "Envoyer",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Charger plus",
|
||||
"status.media_hidden": "Média caché",
|
||||
"status.mention": "Mentionner",
|
||||
"status.more": "Plus",
|
||||
"status.mute_conversation": "Masquer la conversation",
|
||||
"status.open": "Déplier ce statut",
|
||||
"status.pin": "Épingler sur le profil",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!",
|
||||
"empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.",
|
||||
"empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.",
|
||||
"empty_column.home.inactivity": "ציר זמן הבית שלך ריק. אם לא הייתה פעילות מצידך לאחרונה, הוא יתמלא מחדש בקרוב.",
|
||||
"empty_column.home.public_timeline": "ציר זמן בין-קהילתי",
|
||||
"empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!",
|
||||
"empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "פומבי",
|
||||
"privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים",
|
||||
"privacy.unlisted.short": "לא לפיד הכללי",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "ביטול",
|
||||
"report.placeholder": "הערות נוספות",
|
||||
"report.submit": "שליחה",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "עוד",
|
||||
"status.media_hidden": "מדיה מוסתרת",
|
||||
"status.mention": "פניה אל @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "השתקת שיחה",
|
||||
"status.open": "הרחבת הודעה",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!",
|
||||
"empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
|
||||
"empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.",
|
||||
"empty_column.home.inactivity": "Tvoj home feed je prazan. Ako si neko vrijeme bio neaktivan, uskoro ćese regenerirati.",
|
||||
"empty_column.home.public_timeline": "javni timeline",
|
||||
"empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
|
||||
"empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Javno",
|
||||
"privacy.unlisted.long": "Ne prikazuj u javnim timelineovima",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Otkaži",
|
||||
"report.placeholder": "Dodatni komentari",
|
||||
"report.submit": "Pošalji",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Učitaj više",
|
||||
"status.media_hidden": "Sakriven media sadržaj",
|
||||
"status.mention": "Spomeni @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Utišaj razgovor",
|
||||
"status.open": "Proširi ovaj status",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"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.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"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",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Do not show in public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Mégsem",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Említés",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!",
|
||||
"empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.",
|
||||
"empty_column.home": "Anda sedang tidak mengikuti siapapun. Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
|
||||
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "linimasa publik",
|
||||
"empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
|
||||
"empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisinya secara manual",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Publik",
|
||||
"privacy.unlisted.long": "Tidak ditampilkan di linimasa publik",
|
||||
"privacy.unlisted.short": "Tak Terdaftar",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Batal",
|
||||
"report.placeholder": "Komentar tambahan",
|
||||
"report.submit": "Kirim",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Tampilkan semua",
|
||||
"status.media_hidden": "Media disembunyikan",
|
||||
"status.mention": "Balasan @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Tampilkan status ini",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
|
||||
"empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
|
||||
"empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.",
|
||||
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "la publika tempolineo",
|
||||
"empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
|
||||
"empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Publike",
|
||||
"privacy.unlisted.long": "Ne montrar en publika tempolinei",
|
||||
"privacy.unlisted.short": "Ne enlistigota",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Nihiligar",
|
||||
"report.placeholder": "Plusa komenti",
|
||||
"report.submit": "Sendar",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Kargar pluse",
|
||||
"status.media_hidden": "Kontenajo celita",
|
||||
"status.mention": "Mencionar @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Detaligar ca mesajo",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
|
||||
"empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
|
||||
"empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
|
||||
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "la timeline pubblica",
|
||||
"empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
|
||||
"empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Pubblico",
|
||||
"privacy.unlisted.long": "Non mostrare sulla timeline pubblica",
|
||||
"privacy.unlisted.short": "Non elencato",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Annulla",
|
||||
"report.placeholder": "Commenti aggiuntivi",
|
||||
"report.submit": "Invia",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Mostra di più",
|
||||
"status.media_hidden": "Allegato nascosto",
|
||||
"status.mention": "Nomina @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Espandi questo post",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
|
||||
"empty_column.hashtag": "このハッシュタグはまだ使われていません。",
|
||||
"empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
|
||||
"empty_column.home.inactivity": "あなたのホームフィードにはなにもありません。あなたがしばらくの間アクティブではなかった場合はすぐ元通りになります。",
|
||||
"empty_column.home.public_timeline": "連合タイムライン",
|
||||
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
|
||||
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
|
||||
@@ -184,6 +183,7 @@
|
||||
"status.load_more": "もっと見る",
|
||||
"status.media_hidden": "非表示のメディア",
|
||||
"status.mention": "返信",
|
||||
"status.more": "もっと見る",
|
||||
"status.mute_conversation": "会話をミュート",
|
||||
"status.open": "詳細を表示",
|
||||
"status.pin": "プロフィールに固定表示",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"account.unblock_domain": "{domain} 숨김 해제",
|
||||
"account.unfollow": "팔로우 해제",
|
||||
"account.unmute": "뮤트 해제",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"account.view_full_profile": "전체 프로필 보기",
|
||||
"boost_modal.combo": "다음부터 {combo}를 누르면 이 과정을 건너뛸 수 있습니다.",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"bundle_column_error.retry": "Try again",
|
||||
@@ -33,7 +33,7 @@
|
||||
"column.home": "홈",
|
||||
"column.mutes": "뮤트 중인 사용자",
|
||||
"column.notifications": "알림",
|
||||
"column.pins": "고정된 Toot",
|
||||
"column.pins": "고정된 툿",
|
||||
"column.public": "연합 타임라인",
|
||||
"column_back_button.label": "돌아가기",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
@@ -47,7 +47,7 @@
|
||||
"compose_form.lock_disclaimer": "이 계정은 {locked}로 설정 되어 있지 않습니다. 누구나 이 계정을 팔로우 할 수 있으며, 팔로워 공개의 포스팅을 볼 수 있습니다.",
|
||||
"compose_form.lock_disclaimer.lock": "비공개",
|
||||
"compose_form.placeholder": "지금 무엇을 하고 있나요?",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.publish": "툿",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive": "이 미디어를 민감한 미디어로 취급",
|
||||
"compose_form.spoiler": "텍스트 숨기기",
|
||||
@@ -63,8 +63,8 @@
|
||||
"confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||
"embed.preview": "Here is what it will look like:",
|
||||
"embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 퍼가세요.",
|
||||
"embed.preview": "다음과 같이 표시됩니다:",
|
||||
"emoji_button.activity": "활동",
|
||||
"emoji_button.custom": "Custom",
|
||||
"emoji_button.flags": "국기",
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!",
|
||||
"empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
|
||||
"empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.",
|
||||
"empty_column.home.inactivity": "홈 피드에 아무 것도 없습니다. 한동안 활동하지 않은 경우 곧 원래대로 돌아올 것입니다.",
|
||||
"empty_column.home.public_timeline": "연합 타임라인",
|
||||
"empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요!",
|
||||
"empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스 유저를 팔로우 해서 가득 채워보세요!",
|
||||
@@ -113,7 +112,7 @@
|
||||
"navigation_bar.info": "이 인스턴스에 대해서",
|
||||
"navigation_bar.logout": "로그아웃",
|
||||
"navigation_bar.mutes": "뮤트 중인 사용자",
|
||||
"navigation_bar.pins": "고정된 Toot",
|
||||
"navigation_bar.pins": "고정된 툿",
|
||||
"navigation_bar.preferences": "사용자 설정",
|
||||
"navigation_bar.public_timeline": "연합 타임라인",
|
||||
"notification.favourite": "{name}님이 즐겨찾기 했습니다",
|
||||
@@ -159,29 +158,35 @@
|
||||
"privacy.public.long": "공개 타임라인에 표시",
|
||||
"privacy.public.short": "공개",
|
||||
"privacy.unlisted.long": "공개 타임라인에 표시하지 않음",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"privacy.unlisted.short": "타임라인에 비표시",
|
||||
"relative_time.days": "{number}일 전",
|
||||
"relative_time.hours": "{number}시간 전",
|
||||
"relative_time.just_now": "방금",
|
||||
"relative_time.minutes": "{number}분 전",
|
||||
"relative_time.seconds": "{number}초 전",
|
||||
"reply_indicator.cancel": "취소",
|
||||
"report.placeholder": "코멘트",
|
||||
"report.submit": "신고하기",
|
||||
"report.target": "문제가 된 사용자",
|
||||
"search.placeholder": "검색",
|
||||
"search_popout.search_format": "Advanced search format",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
"search_popout.tips.user": "user",
|
||||
"search_popout.search_format": "고급 검색 방법",
|
||||
"search_popout.tips.hashtag": "해시태그",
|
||||
"search_popout.tips.status": "툿",
|
||||
"search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
|
||||
"search_popout.tips.user": "유저",
|
||||
"search_results.total": "{count, number}건의 결과",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다",
|
||||
"status.delete": "삭제",
|
||||
"status.embed": "Embed",
|
||||
"status.embed": "공유하기",
|
||||
"status.favourite": "즐겨찾기",
|
||||
"status.load_more": "더 보기",
|
||||
"status.media_hidden": "미디어 숨겨짐",
|
||||
"status.mention": "답장",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "이 대화를 뮤트",
|
||||
"status.open": "상세 정보 표시",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pin": "고정",
|
||||
"status.reblog": "부스트",
|
||||
"status.reblogged_by": "{name}님이 부스트 했습니다",
|
||||
"status.reply": "답장",
|
||||
@@ -193,7 +198,7 @@
|
||||
"status.show_less": "숨기기",
|
||||
"status.show_more": "더 보기",
|
||||
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.unpin": "고정 해제",
|
||||
"tabs_bar.compose": "포스트",
|
||||
"tabs_bar.federated_timeline": "연합",
|
||||
"tabs_bar.home": "홈",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de bal aan het rollen te krijgen!",
|
||||
"empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
|
||||
"empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.",
|
||||
"empty_column.home.inactivity": "Deze tijdlijn is leeg. Wanneer je een tijdje inactief bent geweest wordt deze snel opnieuw aangemaakt.",
|
||||
"empty_column.home.public_timeline": "de globale tijdlijn",
|
||||
"empty_column.notifications": "Je hebt nog geen meldingen. Heb interactie met andere mensen om het gesprek aan te gaan.",
|
||||
"empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere Mastodon-servers om het te vullen.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Openbaar",
|
||||
"privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
|
||||
"privacy.unlisted.short": "Minder openbaar",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Annuleren",
|
||||
"report.placeholder": "Extra opmerkingen",
|
||||
"report.submit": "Verzenden",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Meer laden",
|
||||
"status.media_hidden": "Media verborgen",
|
||||
"status.mention": "Vermeld @{name}",
|
||||
"status.more": "Meer",
|
||||
"status.mute_conversation": "Negeer conversatie",
|
||||
"status.open": "Toot volledig tonen",
|
||||
"status.pin": "Aan profielpagina vastmaken",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
|
||||
"empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
|
||||
"empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
|
||||
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.public_timeline": "en offentlig tidslinje",
|
||||
"empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
|
||||
"empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Offentlig",
|
||||
"privacy.unlisted.long": "Ikke vis i offentlige tidslinjer",
|
||||
"privacy.unlisted.short": "Uoppført",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Avbryt",
|
||||
"report.placeholder": "Tilleggskommentarer",
|
||||
"report.submit": "Send inn",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Last mer",
|
||||
"status.media_hidden": "Media skjult",
|
||||
"status.mention": "Nevn @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Demp samtale",
|
||||
"status.open": "Utvid denne statusen",
|
||||
"status.pin": "Pin on profile",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !",
|
||||
"empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
|
||||
"empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
|
||||
"empty_column.home.inactivity": "Vòstra pagina d’acuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.",
|
||||
"empty_column.home.public_timeline": "lo flux public",
|
||||
"empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
|
||||
"empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Mostrar pas dins los fluxes publics",
|
||||
"privacy.unlisted.short": "Pas-listat",
|
||||
"relative_time.days": "fa {number}j",
|
||||
"relative_time.hours": "fa {number}h",
|
||||
"relative_time.just_now": "ara",
|
||||
"relative_time.minutes": "fa {number} minutas",
|
||||
"relative_time.seconds": "fa {number} segondas",
|
||||
"reply_indicator.cancel": "Anullar",
|
||||
"report.placeholder": "Comentaris addicionals",
|
||||
"report.submit": "Mandar",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Cargar mai",
|
||||
"status.media_hidden": "Mèdia rescondut",
|
||||
"status.mention": "Mencionar",
|
||||
"status.more": "Mai",
|
||||
"status.mute_conversation": "Rescondre la conversacion",
|
||||
"status.open": "Desplegar aqueste estatut",
|
||||
"status.pin": "Penjar al perfil",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
|
||||
"empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!",
|
||||
"empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.",
|
||||
"empty_column.home.inactivity": "Strumień jest pusty. Jeżeli nie było Cię tu ostatnio, zostanie on wypełniony wkrótce.",
|
||||
"empty_column.home.public_timeline": "publiczna oś czasu",
|
||||
"empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
|
||||
"empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Publiczny",
|
||||
"privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu",
|
||||
"privacy.unlisted.short": "Niewidoczny",
|
||||
"relative_time.days": "{number} dni",
|
||||
"relative_time.hours": "{number} godz.",
|
||||
"relative_time.just_now": "teraz",
|
||||
"relative_time.minutes": "{number} min.",
|
||||
"relative_time.seconds": "{number} s.",
|
||||
"reply_indicator.cancel": "Anuluj",
|
||||
"report.placeholder": "Dodatkowe komentarze",
|
||||
"report.submit": "Wyślij",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Załaduj więcej",
|
||||
"status.media_hidden": "Zawartość multimedialna ukryta",
|
||||
"status.mention": "Wspomnij o @{name}",
|
||||
"status.more": "Więcej",
|
||||
"status.mute_conversation": "Wycisz konwersację",
|
||||
"status.open": "Rozszerz ten wpis",
|
||||
"status.pin": "Przypnij do profilu",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user