mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-13 07:49:29 +00:00
Compare commits
74 Commits
fix-null-s
...
tootbutton
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e0ff911b4 | ||
|
|
664c9aa708 | ||
|
|
119d477c8b | ||
|
|
4f01e6e8d5 | ||
|
|
fdb0848e08 | ||
|
|
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 | ||
|
|
7364b26e4b | ||
|
|
313ba202ef | ||
|
|
7c44ad6355 | ||
|
|
37ff061d9b | ||
|
|
3d7de06db4 | ||
|
|
26f08f0791 | ||
|
|
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -247,36 +246,35 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
<UploadFormContainer />
|
||||
</div>
|
||||
|
||||
<div className='compose-form__buttons-wrapper'>
|
||||
<div className='compose-form__buttons'>
|
||||
<UploadButtonContainer />
|
||||
<DoodleButtonContainer />
|
||||
<PrivacyDropdownContainer />
|
||||
<ComposeAdvancedOptionsContainer />
|
||||
<SensitiveButtonContainer />
|
||||
<SpoilerButtonContainer />
|
||||
</div>
|
||||
<div className='compose-form__buttons'>
|
||||
<ComposeAttachOptions />
|
||||
<SensitiveButtonContainer />
|
||||
<div className='compose-form__buttons-separator' />
|
||||
<PrivacyDropdownContainer />
|
||||
<SpoilerButtonContainer />
|
||||
<ComposeAdvancedOptionsContainer />
|
||||
</div>
|
||||
|
||||
<div className='compose-form__publish'>
|
||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
||||
<div className='compose-form__publish-button-wrapper'>
|
||||
{
|
||||
showSideArm ?
|
||||
<Button
|
||||
className='compose-form__publish__side-arm'
|
||||
text={publishText2}
|
||||
onClick={this.handleSubmit2}
|
||||
disabled={submitDisabled}
|
||||
/> : ''
|
||||
}
|
||||
<Button
|
||||
className='compose-form__publish__primary'
|
||||
text={publishText}
|
||||
onClick={this.handleSubmit}
|
||||
disabled={submitDisabled}
|
||||
block
|
||||
/>
|
||||
</div>
|
||||
<div className='compose-form__publish'>
|
||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
||||
<div className='compose-form__publish-button-wrapper'>
|
||||
{
|
||||
showSideArm ?
|
||||
<Button
|
||||
className='compose-form__publish__side-arm'
|
||||
text={publishText2}
|
||||
title={title2}
|
||||
onClick={this.handleSubmit2}
|
||||
disabled={submitDisabled}
|
||||
/> : ''
|
||||
}
|
||||
<Button
|
||||
className='compose-form__publish__primary'
|
||||
text={publishText}
|
||||
title={title}
|
||||
onClick={this.handleSubmit}
|
||||
disabled={submitDisabled}
|
||||
/>
|
||||
</div>
|
||||
</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);
|
||||
@@ -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,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 = {};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
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"
|
||||
@@ -907,10 +911,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 +1408,4 @@
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/video/index.json"
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -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 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 +183,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",
|
||||
|
||||
@@ -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": "Rezigni",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Mencii @{name}",
|
||||
"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í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",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
|
||||
"empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag",
|
||||
"empty_column.home": "Você ainda não segue usuário algo. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
|
||||
"empty_column.home.inactivity": "A sua página inicial está vazia. Se você esteve inativo por um tempo, ela irá se regenerar em alguns intantes.",
|
||||
"empty_column.home.public_timeline": "global",
|
||||
"empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar!",
|
||||
"empty_column.public": "Não há nada aqui! Escreva algo publicamente ou siga manualmente usuários de outras instâncias.",
|
||||
@@ -160,16 +159,21 @@
|
||||
"privacy.public.short": "Pública",
|
||||
"privacy.unlisted.long": "Não publicar em feeds públicos",
|
||||
"privacy.unlisted.short": "Não listada",
|
||||
"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": "Cancelar",
|
||||
"report.placeholder": "Comentários adicionais",
|
||||
"report.submit": "Enviar",
|
||||
"report.target": "Denunciar",
|
||||
"search.placeholder": "Pesquisar",
|
||||
"search_popout.search_format": "Advanced search format",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.search_format": "Formato de busca avançado",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
|
||||
"search_popout.tips.user": "user",
|
||||
"search_popout.tips.user": "usuário",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
|
||||
"standalone.public_title": "Dê uma espiada...",
|
||||
"status.cannot_reblog": "Esta postagem não pode ser compartilhada",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Carregar mais",
|
||||
"status.media_hidden": "Mídia escondida",
|
||||
"status.mention": "Mencionar @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Silenciar conversa",
|
||||
"status.open": "Expandir",
|
||||
"status.pin": "Fixar no perfil",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Ainda não existem conteúdo local para mostrar!",
|
||||
"empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
|
||||
"empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
|
||||
"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": "global",
|
||||
"empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
|
||||
"empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Público",
|
||||
"privacy.unlisted.long": "Não publicar nos feeds públicos",
|
||||
"privacy.unlisted.short": "Não listar",
|
||||
"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": "Cancelar",
|
||||
"report.placeholder": "Comentários adicionais",
|
||||
"report.submit": "Enviar",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Carregar mais",
|
||||
"status.media_hidden": "Media escondida",
|
||||
"status.mention": "Mencionar @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expandir",
|
||||
"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": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
|
||||
@@ -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",
|
||||
|
||||
217
app/javascript/mastodon/locales/sv.json
Normal file
217
app/javascript/mastodon/locales/sv.json
Normal file
@@ -0,0 +1,217 @@
|
||||
{
|
||||
"account.block": "Blockera @{name}",
|
||||
"account.block_domain": "Dölj allt från {domain}",
|
||||
"account.disclaimer_full": "Informationen nedan kan spegla användarens profil ofullständigt.",
|
||||
"account.edit_profile": "Redigera profil",
|
||||
"account.follow": "Följ",
|
||||
"account.followers": "Följare",
|
||||
"account.follows": "Följer",
|
||||
"account.follows_you": "Följer dig",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Nämna @{name}",
|
||||
"account.mute": "Tysta @{name}",
|
||||
"account.posts": "Inlägg",
|
||||
"account.report": "Rapportera @{name}",
|
||||
"account.requested": "Inväntar godkännande. Klicka för att avbryta följförfrågan",
|
||||
"account.share": "Dela @{name}'s profil",
|
||||
"account.unblock": "Avblockera @{name}",
|
||||
"account.unblock_domain": "Ta fram {domain}",
|
||||
"account.unfollow": "Sluta följa",
|
||||
"account.unmute": "Ta bort tystad @{name}",
|
||||
"account.view_full_profile": "Visa hela profilen",
|
||||
"boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång",
|
||||
"bundle_column_error.body": "Något gick fel när du laddade denna komponent.",
|
||||
"bundle_column_error.retry": "Försök igen",
|
||||
"bundle_column_error.title": "Nätverksfel",
|
||||
"bundle_modal_error.close": "Stäng",
|
||||
"bundle_modal_error.message": "Något gick fel när du laddade denna komponent.",
|
||||
"bundle_modal_error.retry": "Försök igen",
|
||||
"column.blocks": "Blockerade användare",
|
||||
"column.community": "Lokal tidslinje",
|
||||
"column.favourites": "Favoriter",
|
||||
"column.follow_requests": "Följ förfrågningar",
|
||||
"column.home": "Hem",
|
||||
"column.mutes": "Tystade användare",
|
||||
"column.notifications": "Meddelanden",
|
||||
"column.pins": "Nålade toots",
|
||||
"column.public": "Förenad tidslinje",
|
||||
"column_back_button.label": "Tillbaka",
|
||||
"column_header.hide_settings": "Dölj inställningar",
|
||||
"column_header.moveLeft_settings": "Flytta kolumnen till vänster",
|
||||
"column_header.moveRight_settings": "Flytta kolumnen till höger",
|
||||
"column_header.pin": "Fäst",
|
||||
"column_header.show_settings": "Visa inställningar",
|
||||
"column_header.unpin": "Ångra fäst",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Inställningar",
|
||||
"compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.",
|
||||
"compose_form.lock_disclaimer.lock": "låst",
|
||||
"compose_form.placeholder": "Vad funderar du på?",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive": "Markera media som känslig",
|
||||
"compose_form.spoiler": "Dölj text bakom varning",
|
||||
"compose_form.spoiler_placeholder": "Skriv din varning här",
|
||||
"confirmation_modal.cancel": "Ångra",
|
||||
"confirmations.block.confirm": "Blockera",
|
||||
"confirmations.block.message": "Är du säker att du vill blockera {name}?",
|
||||
"confirmations.delete.confirm": "Ta bort",
|
||||
"confirmations.delete.message": "Är du säker att du vill ta bort denna status?",
|
||||
"confirmations.domain_block.confirm": "Blockera hela domänen",
|
||||
"confirmations.domain_block.message": "Är du verkligen, verkligen säker på att du vill blockera hela {domain}? I de flesta fall är några riktade blockeringar eller nedtystade tillräckligt och föredras.",
|
||||
"confirmations.mute.confirm": "Tysta",
|
||||
"confirmations.mute.message": "Är du säker du vill tysta ner {name}?",
|
||||
"confirmations.unfollow.confirm": "Sluta följa",
|
||||
"confirmations.unfollow.message": "Är du säker på att du vill sluta följa {name}?",
|
||||
"embed.instructions": "Bädda in den här statusen på din webbplats genom att kopiera koden nedan.",
|
||||
"embed.preview": "Här ser du hur det kommer att se ut:",
|
||||
"emoji_button.activity": "Aktivitet",
|
||||
"emoji_button.custom": "Specialgjord",
|
||||
"emoji_button.flags": "Flaggor",
|
||||
"emoji_button.food": "Mat & Dryck",
|
||||
"emoji_button.label": "Lägg till emoji",
|
||||
"emoji_button.nature": "Natur",
|
||||
"emoji_button.not_found": "Inga emojos!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Objekt",
|
||||
"emoji_button.people": "Människor",
|
||||
"emoji_button.recent": "Ofta använda",
|
||||
"emoji_button.search": "Sök...",
|
||||
"emoji_button.search_results": "Sökresultat",
|
||||
"emoji_button.symbols": "Symboler",
|
||||
"emoji_button.travel": "Resor & Platser",
|
||||
"empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!",
|
||||
"empty_column.hashtag": "Det finns inget i denna hashtag ännu.",
|
||||
"empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.",
|
||||
"empty_column.home.inactivity": "Ditt hemmafeed är tomt. Om du har varit inaktiv ett tag kommer det att regenereras för dig snart.",
|
||||
"empty_column.home.public_timeline": "den publika tidslinjen",
|
||||
"empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.",
|
||||
"empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det",
|
||||
"follow_request.authorize": "Godkänn",
|
||||
"follow_request.reject": "Avvisa",
|
||||
"getting_started.appsshort": "Appar",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Kom igång",
|
||||
"getting_started.open_source_notice": "Mastodon är programvara med öppen källkod. Du kan bidra eller rapportera problem på GitHub på {github}.",
|
||||
"getting_started.userguide": "Användarguide",
|
||||
"home.column_settings.advanced": "Avancerad",
|
||||
"home.column_settings.basic": "Grundläggande",
|
||||
"home.column_settings.filter_regex": "Filtrera ut med regelbundna uttryck",
|
||||
"home.column_settings.show_reblogs": "Visa knuffar",
|
||||
"home.column_settings.show_replies": "Visa svar",
|
||||
"home.settings": "Kolumninställningar",
|
||||
"lightbox.close": "Stäng",
|
||||
"lightbox.next": "Nästa",
|
||||
"lightbox.previous": "Tidigare",
|
||||
"loading_indicator.label": "Laddar...",
|
||||
"media_gallery.toggle_visible": "Växla synlighet",
|
||||
"missing_indicator.label": "Hittades inte",
|
||||
"navigation_bar.blocks": "Blockerade användare",
|
||||
"navigation_bar.community_timeline": "Lokal tidslinje",
|
||||
"navigation_bar.edit_profile": "Redigera profil",
|
||||
"navigation_bar.favourites": "Favoriter",
|
||||
"navigation_bar.follow_requests": "Följförfrågningar",
|
||||
"navigation_bar.info": "Om denna instans",
|
||||
"navigation_bar.logout": "Logga ut",
|
||||
"navigation_bar.mutes": "Tystade användare",
|
||||
"navigation_bar.pins": "Nålade inlägg (toots)",
|
||||
|
||||
"navigation_bar.preferences": "Inställningar",
|
||||
"navigation_bar.public_timeline": "Förenad tidslinje",
|
||||
"notification.favourite": "{name} favoriserade din status",
|
||||
"notification.follow": "{name} följer dig",
|
||||
"notification.mention": "{name} nämnde dig",
|
||||
"notification.reblog": "{name} knuffade din status",
|
||||
"notifications.clear": "Rensa meddelanden",
|
||||
"notifications.clear_confirmation": "Är du säker på att du vill radera alla dina meddelanden permanent?",
|
||||
"notifications.column_settings.alert": "Skrivbordsmeddelanden",
|
||||
"notifications.column_settings.favourite": "Favoriter:",
|
||||
"notifications.column_settings.follow": "Nya följare:",
|
||||
"notifications.column_settings.mention": "Omnämningar:",
|
||||
"notifications.column_settings.push": "Push meddelanden",
|
||||
"notifications.column_settings.push_meta": "Denna anordning",
|
||||
"notifications.column_settings.reblog": "Knuffar:",
|
||||
"notifications.column_settings.show": "Visa i kolumnen",
|
||||
"notifications.column_settings.sound": "Spela upp ljud",
|
||||
"onboarding.done": "Klart",
|
||||
"onboarding.next": "Nästa",
|
||||
"onboarding.page_five.public_timelines": "Den lokala tidslinjen visar offentliga inlägg från alla på {domain}. Den förenade tidslinjen visar offentliga inlägg från alla personer på {domain} som följer. Dom här offentliga tidslinjerna är ett bra sätt att upptäcka nya människor.",
|
||||
"onboarding.page_four.home": "Hemmatidslinjen visar inlägg från personer du följer.",
|
||||
"onboarding.page_four.notifications": "Meddelandekolumnen visar när någon interagerar med dig.",
|
||||
"onboarding.page_one.federation": "Mastodon är ett nätverk av oberoende servrar som ansluter för att skapa ett större socialt nätverk. Vi kallar dessa servrar instanser.",
|
||||
"onboarding.page_one.handle": "Du är på {domain}, så din fulla hantering är {handle}",
|
||||
"onboarding.page_one.welcome": "Välkommen till Mastodon!",
|
||||
"onboarding.page_six.admin": "Din instansadmin är {admin}.",
|
||||
"onboarding.page_six.almost_done": "Snart klart...",
|
||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
||||
"onboarding.page_six.apps_available": "Det finns {apps} tillgängligt för iOS, Android och andra plattformar.",
|
||||
"onboarding.page_six.github": "Mastodon är fri programvara med öppen källkod. Du kan rapportera fel, efterfråga funktioner eller bidra till koden på {github}.",
|
||||
"onboarding.page_six.guidelines": "gemenskapsriktlinjer",
|
||||
"onboarding.page_six.read_guidelines": "Vänligen läs {domain}'s {guidelines}!",
|
||||
"onboarding.page_six.various_app": "mobilappar",
|
||||
"onboarding.page_three.profile": "Redigera din profil för att ändra ditt avatar, bio och visningsnamn. Där hittar du även andra inställningar.",
|
||||
"onboarding.page_three.search": "Använd sökfältet för att hitta personer och titta på hashtags, till exempel {illustration} och {introductions}. För att leta efter en person som inte befinner sig i detta fall använd deras fulla handhavande.",
|
||||
"onboarding.page_two.compose": "Skriv inlägg från skrivkolumnen. Du kan ladda upp bilder, ändra integritetsinställningar och lägga till varningar med ikonerna nedan.",
|
||||
"onboarding.skip": "Hoppa över",
|
||||
"privacy.change": "Justera status sekretess",
|
||||
"privacy.direct.long": "Skicka endast till nämnda användare",
|
||||
"privacy.direct.short": "Direkt",
|
||||
"privacy.private.long": "Skicka endast till följare",
|
||||
"privacy.private.short": "Endast följare",
|
||||
"privacy.public.long": "Skicka till publik tidslinje",
|
||||
"privacy.public.short": "Publik",
|
||||
"privacy.unlisted.long": "Skicka inte till publik tidslinje",
|
||||
"privacy.unlisted.short": "Olistad",
|
||||
"reply_indicator.cancel": "Ångra",
|
||||
"report.placeholder": "Ytterligare kommentarer",
|
||||
"report.submit": "Skicka",
|
||||
"report.target": "Rapporterar {target}",
|
||||
"search.placeholder": "Sök",
|
||||
"search_popout.search_format": "Avancerat sökformat",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Enkel text returnerar matchande visningsnamn, användarnamn och hashtags",
|
||||
"search_popout.tips.user": "användare",
|
||||
"search_results.total": "{count, number} {count, plural, ett {result} andra {results}}",
|
||||
"standalone.public_title": "En titt inuti...",
|
||||
"status.cannot_reblog": "Detta inlägg kan inte knuffas",
|
||||
"status.delete": "Ta bort",
|
||||
"status.embed": "Bädda in",
|
||||
"status.favourite": "Favorit",
|
||||
"status.load_more": "Ladda fler",
|
||||
"status.media_hidden": "Media dold",
|
||||
"status.mention": "Omnämn @{name}",
|
||||
"status.mute_conversation": "Tysta konversation",
|
||||
"status.open": "Utvidga denna status",
|
||||
"status.pin": "Fäst i profil",
|
||||
"status.reblog": "Knuff",
|
||||
"status.reblogged_by": "{name} knuffade",
|
||||
"status.reply": "Svara",
|
||||
"status.replyAll": "Svara på tråden",
|
||||
"status.report": "Rapportera @{name}",
|
||||
"status.sensitive_toggle": "Klicka för att se",
|
||||
"status.sensitive_warning": "Känsligt innehåll",
|
||||
"status.share": "Dela",
|
||||
"status.show_less": "Visa mindre",
|
||||
"status.show_more": "Visa mer",
|
||||
"status.unmute_conversation": "Öppna konversation",
|
||||
"status.unpin": "Ångra fäst i profil",
|
||||
"tabs_bar.compose": "Skriv",
|
||||
"tabs_bar.federated_timeline": "Förenad",
|
||||
"tabs_bar.home": "Hem",
|
||||
"tabs_bar.local_timeline": "Lokal",
|
||||
"tabs_bar.notifications": "Meddelanden",
|
||||
"upload_area.title": "Dra & släpp för att ladda upp",
|
||||
"upload_button.label": "Lägg till media",
|
||||
"upload_form.description": "Beskriv för synskadade",
|
||||
"upload_form.undo": "Ångra",
|
||||
"upload_progress.label": "Laddar upp...",
|
||||
"video.close": "Stäng video",
|
||||
"video.exit_fullscreen": "Stäng helskärm",
|
||||
"video.expand": "Expandera video",
|
||||
"video.fullscreen": "Helskärm",
|
||||
"video.hide": "Dölj video",
|
||||
"video.mute": "Tysta ljud",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Spela upp",
|
||||
"video.unmute": "Spela upp ljud"
|
||||
}
|
||||
@@ -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 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 +183,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",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"empty_column.community": "Yerel zaman tüneliniz boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın.",
|
||||
"empty_column.hashtag": "Henüz bu hashtag’e sahip hiçbir gönderi yok.",
|
||||
"empty_column.home": "Henüz kimseyi takip etmiyorsunuz. {public} ziyaret edebilir veya arama kısmını kullanarak diğer kullanıcılarla iletişime geçebilirsiniz.",
|
||||
"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": "herkese açık zaman tüneli",
|
||||
"empty_column.notifications": "Henüz hiçbir bildiriminiz yok. Diğer insanlarla sobhet edebilmek için etkileşime geçebilirsiniz.",
|
||||
"empty_column.public": "Burada hiçbir gönderi yok! Herkese açık bir şeyler yazın, veya diğer sunucudaki insanları takip ederek bu alanın dolmasını sağlayın",
|
||||
@@ -160,6 +159,11 @@
|
||||
"privacy.public.short": "Herkese açık",
|
||||
"privacy.unlisted.long": "Herkese açık zaman tüneline gönderme",
|
||||
"privacy.unlisted.short": "Listelenmemiş",
|
||||
"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": "İptal",
|
||||
"report.placeholder": "Ek yorumlar",
|
||||
"report.submit": "Gönder",
|
||||
@@ -179,6 +183,7 @@
|
||||
"status.load_more": "Daha fazla",
|
||||
"status.media_hidden": "Gizli görsel",
|
||||
"status.mention": "Bahset @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Bu gönderiyi genişlet",
|
||||
"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": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших інстанцій, щоб заповнити стрічку.",
|
||||
@@ -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": "Згадати",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Заглушити діалог",
|
||||
"status.open": "Розгорнути допис",
|
||||
"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": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务器实例的用户吧!你和本站、友站的交流,将决定这里出现的内容。",
|
||||
@@ -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": "本站時間軸暫時未有內容,快文章來搶頭香啊!",
|
||||
"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": "本地時間軸是空的。公開寫點什麼吧!",
|
||||
"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": "置頂到個人資訊頁",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as OfflinePluginRuntime from 'offline-plugin/runtime';
|
||||
import * as WebPushSubscription from './web_push_subscription';
|
||||
import Mastodon from 'mastodon/containers/mastodon';
|
||||
import Mastodon from './containers/mastodon';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ready from './ready';
|
||||
@@ -25,7 +24,7 @@ function main() {
|
||||
ReactDOM.render(<Mastodon {...props} />, mountNode);
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// avoid offline in dev mode because it's harder to debug
|
||||
OfflinePluginRuntime.install();
|
||||
require('offline-plugin/runtime').install();
|
||||
WebPushSubscription.register();
|
||||
}
|
||||
perf.stop('main()');
|
||||
|
||||
@@ -12,7 +12,11 @@ const createAudio = sources => {
|
||||
const play = audio => {
|
||||
if (!audio.paused) {
|
||||
audio.pause();
|
||||
audio.fastSeek(0);
|
||||
if (typeof audio.fastSeek === 'function') {
|
||||
audio.fastSeek(0);
|
||||
} else {
|
||||
audio.seek(0);
|
||||
}
|
||||
}
|
||||
|
||||
audio.play();
|
||||
|
||||
@@ -31,10 +31,10 @@ const initialTimeline = ImmutableMap({
|
||||
});
|
||||
|
||||
const normalizeTimeline = (state, timeline, statuses, next) => {
|
||||
const ids = ImmutableList(statuses.map(status => status.get('id')));
|
||||
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
|
||||
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
|
||||
const wasLoaded = state.getIn([timeline, 'loaded']);
|
||||
const hadNext = state.getIn([timeline, 'next']);
|
||||
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
|
||||
|
||||
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
|
||||
mMap.set('loaded', true);
|
||||
@@ -45,8 +45,8 @@ const normalizeTimeline = (state, timeline, statuses, next) => {
|
||||
};
|
||||
|
||||
const appendNormalizedTimeline = (state, timeline, statuses, next) => {
|
||||
const ids = ImmutableList(statuses.map(status => status.get('id')));
|
||||
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
|
||||
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
|
||||
|
||||
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
|
||||
mMap.set('isLoading', false);
|
||||
@@ -75,15 +75,9 @@ const updateTimeline = (state, timeline, status, references) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const deleteStatus = (state, id, accountId, references, reblogOf) => {
|
||||
const deleteStatus = (state, id, accountId, references) => {
|
||||
state.keySeq().forEach(timeline => {
|
||||
state = state.updateIn([timeline, 'items'], list => {
|
||||
if (reblogOf && !list.includes(reblogOf)) {
|
||||
return list.map(item => item === id ? reblogOf : item);
|
||||
} else {
|
||||
return list.filterNot(item => item === id);
|
||||
}
|
||||
});
|
||||
state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
|
||||
});
|
||||
|
||||
// Remove reblogs of deleted status
|
||||
|
||||
5
app/javascript/mastodon/test_setup.js
Normal file
5
app/javascript/mastodon/test_setup.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { configure } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
const adapter = new Adapter();
|
||||
configure({ adapter });
|
||||
@@ -2,7 +2,8 @@ import loadPolyfills from '../mastodon/load_polyfills';
|
||||
|
||||
// import default stylesheet with variables
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
import 'styles/application';
|
||||
|
||||
import '../styles/application.scss';
|
||||
|
||||
require.context('../images/', true);
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { start } from 'rails-ujs';
|
||||
import 'font-awesome/css/font-awesome.css';
|
||||
|
||||
// import common styling
|
||||
require('../styles/common.scss');
|
||||
|
||||
require.context('../images/', true);
|
||||
|
||||
start();
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
@import 'mixins';
|
||||
@import 'variables';
|
||||
@import 'mastodon/mixins';
|
||||
@import 'mastodon/variables';
|
||||
@import 'variables-glitch';
|
||||
@import 'fonts/roboto';
|
||||
@import 'fonts/roboto-mono';
|
||||
@import 'fonts/montserrat';
|
||||
|
||||
@import 'reset';
|
||||
@import 'basics';
|
||||
@import 'containers';
|
||||
@import 'lists';
|
||||
@import 'footer';
|
||||
@import 'compact_header';
|
||||
@import 'landing_strip';
|
||||
@import 'forms';
|
||||
@import 'accounts';
|
||||
@import 'stream_entries';
|
||||
@import 'components';
|
||||
@import 'emoji_picker';
|
||||
@import 'about';
|
||||
@import 'tables';
|
||||
@import 'admin';
|
||||
@import 'rtl';
|
||||
@import 'mastodon/reset';
|
||||
@import 'mastodon/basics';
|
||||
@import 'mastodon/containers';
|
||||
@import 'mastodon/lists';
|
||||
@import 'mastodon/footer';
|
||||
@import 'mastodon/compact_header';
|
||||
@import 'mastodon/landing_strip';
|
||||
@import 'mastodon/forms';
|
||||
@import 'mastodon/accounts';
|
||||
@import 'mastodon/stream_entries';
|
||||
@import 'mastodon/components';
|
||||
@import 'mastodon/emoji_picker';
|
||||
@import 'mastodon/about';
|
||||
@import 'mastodon/tables';
|
||||
@import 'mastodon/admin';
|
||||
@import 'mastodon/rtl';
|
||||
|
||||
@@ -30,7 +30,7 @@ body {
|
||||
}
|
||||
|
||||
&.app-body {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user