Moved glitch files to their own location ;)
This commit is contained in:
@ -1,107 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import emojify from '../../../emoji';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { processBio } from '../util/bio_metadata';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
export default class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
me: PropTypes.number.isRequired,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, me, intl } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let displayName = account.get('display_name');
|
||||
let info = '';
|
||||
let actionBtn = '';
|
||||
let lockedIcon = '';
|
||||
|
||||
if (displayName.length === 0) {
|
||||
displayName = account.get('username');
|
||||
}
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
||||
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
|
||||
}
|
||||
|
||||
if (me !== account.get('id')) {
|
||||
if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<IconButton size={26} disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />
|
||||
</div>
|
||||
);
|
||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (account.get('locked')) {
|
||||
lockedIcon = <i className='fa fa-lock' />;
|
||||
}
|
||||
|
||||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||
const { text, metadata } = processBio(account.get('note'));
|
||||
|
||||
return (
|
||||
<div className='account__header__wrapper'>
|
||||
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
|
||||
<div>
|
||||
<a href={account.get('url')} target='_blank' rel='noopener'>
|
||||
<span className='account__header__avatar'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={90} /></span>
|
||||
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
|
||||
</a>
|
||||
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
|
||||
<div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} />
|
||||
|
||||
{info}
|
||||
{actionBtn}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{metadata.length && (
|
||||
<table className='account__metadata'>
|
||||
{(() => {
|
||||
let data = [];
|
||||
for (let i = 0; i < metadata.length; i++) {
|
||||
data.push(
|
||||
<tr key={i}>
|
||||
<th scope='row'><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][0]) }} /></th>
|
||||
<td><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][1]) }} /></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return data;
|
||||
})()}
|
||||
</table>
|
||||
) || null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,380 +0,0 @@
|
||||
/*********************************************************************\
|
||||
|
||||
To my lovely code maintainers,
|
||||
|
||||
The syntax recognized by the Mastodon frontend for its bio metadata
|
||||
feature is a subset of that provided by the YAML 1.2 specification.
|
||||
In particular, Mastodon recognizes metadata which is provided as an
|
||||
implicit YAML map, where each key-value pair takes up only a single
|
||||
line (no multi-line values are permitted). To simplify the level of
|
||||
processing required, Mastodon metadata frontmatter has been limited
|
||||
to only allow those characters in the `c-printable` set, as defined
|
||||
by the YAML 1.2 specification, instead of permitting those from the
|
||||
`nb-json` characters inside double-quoted strings like YAML proper.
|
||||
¶ It is important to note that Mastodon only borrows the *syntax*
|
||||
of YAML, not its semantics. This is to say, Mastodon won't make any
|
||||
attempt to interpret the data it receives. `true` will not become a
|
||||
boolean; `56` will not be interpreted as a number. Rather, each key
|
||||
and every value will be read as a string, and as a string they will
|
||||
remain. The order of the pairs is unchanged, and any duplicate keys
|
||||
are preserved. However, YAML escape sequences will be replaced with
|
||||
the proper interpretations according to the YAML 1.2 specification.
|
||||
¶ The implementation provided below interprets `<br>` as `\n` and
|
||||
allows for an open <p> tag at the beginning of the bio. It replaces
|
||||
the escaped character entities `'` and `"` with single or
|
||||
double quotes, respectively, prior to processing. However, no other
|
||||
escaped characters are replaced, not even those which might have an
|
||||
impact on the syntax otherwise. These minor allowances are provided
|
||||
because the Mastodon backend will insert these things automatically
|
||||
into a bio before sending it through the API, so it is important we
|
||||
account for them. Aside from this, the YAML frontmatter must be the
|
||||
very first thing in the bio, leading with three consecutive hyphen-
|
||||
minues (`---`), and ending with the same or, alternatively, instead
|
||||
with three periods (`...`). No limits have been set with respect to
|
||||
the number of characters permitted in the frontmatter, although one
|
||||
should note that only limited space is provided for them in the UI.
|
||||
¶ The regular expression used to check the existence of, and then
|
||||
process, the YAML frontmatter has been split into a number of small
|
||||
components in the code below, in the vain hope that it will be much
|
||||
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.
|
||||
|
||||
Sending love + warmth eternal,
|
||||
- kibigo [@kibi@glitch.social]
|
||||
|
||||
\*********************************************************************/
|
||||
|
||||
/* CONVENIENCE FUNCTIONS */
|
||||
|
||||
const unirex = str => new RegExp(str, 'u');
|
||||
const rexstr = exp => '(?:' + exp.source + ')';
|
||||
|
||||
/* CHARACTER CLASSES */
|
||||
|
||||
const DOCUMENT_START = /^/;
|
||||
const DOCUMENT_END = /$/;
|
||||
const ALLOWED_CHAR = // `c-printable` in the YAML 1.2 spec.
|
||||
/[\t\n\r\x20-\x7e\x85\xa0-\ud7ff\ue000-\ufffd\u{10000}-\u{10FFFF}]/u;
|
||||
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 = /[,[\]{}]/;
|
||||
|
||||
/* NEGATED CHARACTER CLASSES */
|
||||
|
||||
const NOT_WHITE_SPACE = unirex('(?!' + rexstr(WHITE_SPACE) + ')[^]');
|
||||
const NOT_LINE_BREAK = unirex('(?!' + rexstr(LINE_BREAK) + ')[^]');
|
||||
const NOT_INDICATOR = unirex('(?!' + rexstr(INDICATOR) + ')[^]');
|
||||
const NOT_FLOW_CHAR = unirex('(?!' + rexstr(FLOW_CHAR) + ')[^]');
|
||||
const NOT_ALLOWED_CHAR = unirex(
|
||||
'(?!' + rexstr(ALLOWED_CHAR) + ')[^]'
|
||||
);
|
||||
|
||||
/* BASIC CONSTRUCTS */
|
||||
|
||||
const ANY_WHITE_SPACE = unirex(rexstr(WHITE_SPACE) + '*');
|
||||
const ANY_ALLOWED_CHARS = unirex(rexstr(ALLOWED_CHAR) + '*');
|
||||
const NEW_LINE = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK)
|
||||
);
|
||||
const SOME_NEW_LINES = unirex(
|
||||
'(?:' + rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK) + ')+'
|
||||
);
|
||||
const POSSIBLE_STARTS = unirex(
|
||||
rexstr(DOCUMENT_START) + rexstr(/<p[^<>]*>/) + '?'
|
||||
);
|
||||
const POSSIBLE_ENDS = unirex(
|
||||
rexstr(SOME_NEW_LINES) + '|' +
|
||||
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 ESCAPED_CHAR = unirex(
|
||||
rexstr(/(?!["\\])/) + rexstr(NOT_LINE_BREAK) + '|' +
|
||||
rexstr(CHARACTER_ESCAPE)
|
||||
);
|
||||
const ANY_ESCAPED_CHARS = unirex(
|
||||
rexstr(ESCAPED_CHAR) + '*'
|
||||
);
|
||||
const ESCAPED_APOS = unirex(
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' + rexstr(/[^']|''/)
|
||||
);
|
||||
const ANY_ESCAPED_APOS = unirex(
|
||||
rexstr(ESCAPED_APOS) + '*'
|
||||
);
|
||||
const FIRST_KEY_CHAR = unirex(
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' +
|
||||
'(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
|
||||
rexstr(NOT_INDICATOR) + '|' +
|
||||
rexstr(/[?:-]/) +
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' +
|
||||
'(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
|
||||
'(?=' + rexstr(NOT_FLOW_CHAR) + ')'
|
||||
);
|
||||
const FIRST_VALUE_CHAR = unirex(
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' +
|
||||
'(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
|
||||
rexstr(NOT_INDICATOR) + '|' +
|
||||
rexstr(/[?:-]/) +
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' +
|
||||
'(?=' + rexstr(NOT_WHITE_SPACE) + ')'
|
||||
// Flow indicators are allowed in values.
|
||||
);
|
||||
const LATER_KEY_CHAR = unirex(
|
||||
rexstr(WHITE_SPACE) + '|' +
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' +
|
||||
'(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
|
||||
'(?=' + rexstr(NOT_FLOW_CHAR) + ')' +
|
||||
rexstr(/[^:#]#?/) + '|' +
|
||||
rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
|
||||
);
|
||||
const LATER_VALUE_CHAR = unirex(
|
||||
rexstr(WHITE_SPACE) + '|' +
|
||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' +
|
||||
'(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
|
||||
// Flow indicators are allowed in values.
|
||||
rexstr(/[^:#]#?/) + '|' +
|
||||
rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
|
||||
);
|
||||
|
||||
/* YAML CONSTRUCTS */
|
||||
|
||||
const YAML_START = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + rexstr(/---/)
|
||||
);
|
||||
const YAML_END = unirex(
|
||||
rexstr(ANY_WHITE_SPACE) + rexstr(/(?:---|\.\.\.)/)
|
||||
);
|
||||
const YAML_LOOKAHEAD = unirex(
|
||||
'(?=' +
|
||||
rexstr(YAML_START) +
|
||||
rexstr(ANY_ALLOWED_CHARS) + rexstr(NEW_LINE) +
|
||||
rexstr(YAML_END) + rexstr(POSSIBLE_ENDS) +
|
||||
')'
|
||||
);
|
||||
const YAML_DOUBLE_QUOTE = unirex(
|
||||
rexstr(/"/) + rexstr(ANY_ESCAPED_CHARS) + rexstr(/"/)
|
||||
);
|
||||
const YAML_SINGLE_QUOTE = unirex(
|
||||
rexstr(/'/) + rexstr(ANY_ESCAPED_APOS) + rexstr(/'/)
|
||||
);
|
||||
const YAML_SIMPLE_KEY = unirex(
|
||||
rexstr(FIRST_KEY_CHAR) + rexstr(LATER_KEY_CHAR) + '*'
|
||||
);
|
||||
const YAML_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 YAML_VALUE = unirex(
|
||||
rexstr(YAML_DOUBLE_QUOTE) + '|' +
|
||||
rexstr(YAML_SINGLE_QUOTE) + '|' +
|
||||
rexstr(YAML_SIMPLE_VALUE)
|
||||
);
|
||||
const YAML_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) + ')'
|
||||
);
|
||||
|
||||
/* FRONTMATTER REGEX */
|
||||
|
||||
const YAML_FRONTMATTER = unirex(
|
||||
rexstr(POSSIBLE_STARTS) +
|
||||
rexstr(YAML_LOOKAHEAD) +
|
||||
rexstr(YAML_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)
|
||||
);
|
||||
|
||||
/* SEARCHES */
|
||||
|
||||
const FIND_YAML_LINES = unirex(
|
||||
rexstr(NEW_LINE) + rexstr(INDENTATION) + rexstr(YAML_LINE)
|
||||
);
|
||||
|
||||
/* STRING PROCESSING */
|
||||
|
||||
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)
|
||||
);
|
||||
case '\'':
|
||||
return str
|
||||
.substring(1, str.length - 1)
|
||||
.replace(/''/g, '\'');
|
||||
default:
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/* BIO PROCESSING */
|
||||
|
||||
export function processBio(content) {
|
||||
content = content.replace(/"/g, '"').replace(/'/g, '\'');
|
||||
let result = {
|
||||
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 metadata = null;
|
||||
let query = new RegExp(FIND_YAML_LINES, 'g');
|
||||
while ((metadata = query.exec(yaml))) {
|
||||
result.metadata.push([
|
||||
processString(metadata[1]),
|
||||
processString(metadata[2]),
|
||||
]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* BIO CREATION */
|
||||
|
||||
export function createBio(note, data) {
|
||||
if (!note) note = '';
|
||||
let frontmatter = '';
|
||||
if ((data && data.length) || note.match(/^\s*---\s+/)) {
|
||||
if (!data) frontmatter = '---\n...\n';
|
||||
else {
|
||||
frontmatter += '---\n';
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let key = '' + data[i][0];
|
||||
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 + '\'';
|
||||
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 + '"';
|
||||
}
|
||||
|
||||
// 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 + '\'';
|
||||
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 + '"';
|
||||
}
|
||||
|
||||
frontmatter += key + ': ' + val + '\n';
|
||||
}
|
||||
frontmatter += '...\n';
|
||||
}
|
||||
}
|
||||
return frontmatter + note;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import InnerHeader from '../../account/components/header';
|
||||
import InnerHeader from '../../../../glitch/components/account/header';
|
||||
import ActionBar from '../../account/components/action_bar';
|
||||
import MissingIndicator from '../../../components/missing_indicator';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -1,109 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Toggle from 'react-toggle';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
local_only_short: { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' },
|
||||
local_only_long: { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' },
|
||||
advanced_options_icon_title: { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
|
||||
});
|
||||
|
||||
const iconStyle = {
|
||||
height: null,
|
||||
lineHeight: '27px',
|
||||
};
|
||||
|
||||
@injectIntl
|
||||
export default class AdvancedOptionsDropdown extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
values: ImmutablePropTypes.contains({
|
||||
do_not_federate: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
onToggleDropdown = () => {
|
||||
this.setState({ open: !this.state.open });
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
state = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
handleClick = (e) => {
|
||||
const option = e.currentTarget.getAttribute('data-index');
|
||||
e.preventDefault();
|
||||
this.props.onChange(option);
|
||||
}
|
||||
|
||||
toggleHandler(option) {
|
||||
return () => this.props.onChange(option);
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { open } = this.state;
|
||||
const { intl, values } = this.props;
|
||||
|
||||
const options = [
|
||||
{ icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, key: 'do_not_federate' },
|
||||
];
|
||||
|
||||
const anyEnabled = values.some((enabled) => enabled);
|
||||
const optionElems = options.map((option) => {
|
||||
const active = values.get(option.key);
|
||||
return (
|
||||
<div role='button' className='advanced-options-dropdown__option' key={option.key} >
|
||||
<div className='advanced-options-dropdown__option__toggle'>
|
||||
<Toggle checked={active} onChange={this.toggleHandler(option.key)} />
|
||||
</div>
|
||||
<div className='advanced-options-dropdown__option__content'>
|
||||
<strong>{intl.formatMessage(option.shortText)}</strong>
|
||||
{intl.formatMessage(option.longText)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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>);
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Collapsable from '../../../components/collapsable';
|
||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||
import AdvancedOptionsContainer from '../containers/advanced_options_container';
|
||||
import ComposeAdvancedOptionsContainer from '../../../../glitch/containers/compose/advanced_options';
|
||||
import SensitiveButtonContainer from '../containers/sensitive_button_container';
|
||||
import EmojiPickerDropdown from './emoji_picker_dropdown';
|
||||
import UploadFormContainer from '../containers/upload_form_container';
|
||||
@ -196,7 +196,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
<div className='compose-form__buttons'>
|
||||
<UploadButtonContainer />
|
||||
<PrivacyDropdownContainer />
|
||||
<AdvancedOptionsContainer />
|
||||
<ComposeAdvancedOptionsContainer />
|
||||
<SensitiveButtonContainer />
|
||||
<SpoilerButtonContainer />
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import StatusContainer from '../../../containers/status_container';
|
||||
import StatusContainer from '../../../../glitch/containers/status';
|
||||
import Link from 'react-router-dom/Link';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import AdvancedOptionsDropdown from '../components/advanced_options_dropdown';
|
||||
import { changeComposeAdvancedOption } from '../../../actions/compose';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
values: state.getIn(['compose', 'advanced_options']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (option) {
|
||||
dispatch(changeComposeAdvancedOption(option));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AdvancedOptionsDropdown);
|
@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { mountCompose, unmountCompose } from '../../actions/compose';
|
||||
import { openModal } from '../../actions/modal';
|
||||
import { changeLocalSetting } from '../../actions/local_settings';
|
||||
import { changeLocalSetting } from '../../../glitch/actions/local_settings';
|
||||
import Link from 'react-router-dom/Link';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import SearchContainer from './containers/search_container';
|
||||
|
@ -1,88 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import StatusContainer from '../../../containers/status_container';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Permalink from '../../../components/permalink';
|
||||
import emojify from '../../../emoji';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
export default class Notification extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
notification: ImmutablePropTypes.map.isRequired,
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
renderFollow (notification) {
|
||||
const account = notification.get('account');
|
||||
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
|
||||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||
const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
|
||||
return (
|
||||
<div className='notification notification-follow'>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<i className='fa fa-fw fa-user-plus' />
|
||||
</div>
|
||||
|
||||
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
|
||||
</div>
|
||||
|
||||
<AccountContainer id={account.get('id')} withNote={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMention (notification) {
|
||||
return (
|
||||
<StatusContainer
|
||||
id={notification.get('status')}
|
||||
withDismiss
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderFavourite (notification) {
|
||||
return (
|
||||
<StatusContainer
|
||||
id={notification.get('status')}
|
||||
account={notification.get('account')}
|
||||
prepend='favourite'
|
||||
muted
|
||||
withDismiss
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderReblog (notification) {
|
||||
return (
|
||||
<StatusContainer
|
||||
id={notification.get('status')}
|
||||
account={notification.get('account')}
|
||||
prepend='reblog'
|
||||
muted
|
||||
withDismiss
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { notification } = this.props;
|
||||
|
||||
switch(notification.get('type')) {
|
||||
case 'follow':
|
||||
return this.renderFollow(notification);
|
||||
case 'mention':
|
||||
return this.renderMention(notification);
|
||||
case 'favourite':
|
||||
return this.renderFavourite(notification);
|
||||
case 'reblog':
|
||||
return this.renderReblog(notification);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { makeGetNotification } from '../../../selectors';
|
||||
import Notification from '../components/notification';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getNotification = makeGetNotification();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
notification: getNotification(state, props.notification, props.accountId),
|
||||
settings: state.get('local_settings'),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default connect(makeMapStateToProps)(Notification);
|
@ -6,7 +6,7 @@ import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { expandNotifications, scrollTopNotifications } from '../../actions/notifications';
|
||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||
import NotificationContainer from './containers/notification_container';
|
||||
import NotificationContainer from '../../../glitch/containers/notification';
|
||||
import { ScrollContainer } from 'react-router-scroll';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
|
@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import MediaGallery from '../../../components/media_gallery';
|
||||
import VideoPlayer from '../../../components/video_player';
|
||||
import StatusContent from '../../../../glitch/components/status/content';
|
||||
import StatusGallery from '../../../../glitch/components/status/gallery';
|
||||
import StatusVideoPlayer from '../../../../glitch/components/status/video_player';
|
||||
import AttachmentList from '../../../components/attachment_list';
|
||||
import Link from 'react-router-dom/Link';
|
||||
import { FormattedDate, FormattedNumber } from 'react-intl';
|
||||
@ -48,7 +48,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
media = <AttachmentList media={status.get('media_attachments')} />;
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
media = (
|
||||
<VideoPlayer
|
||||
<StatusVideoPlayer
|
||||
sensitive={status.get('sensitive')}
|
||||
media={status.getIn(['media_attachments', 0])}
|
||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||
@ -60,7 +60,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
mediaIcon = 'video-camera';
|
||||
} else {
|
||||
media = (
|
||||
<MediaGallery
|
||||
<StatusGallery
|
||||
sensitive={status.get('sensitive')}
|
||||
media={status.get('media_attachments')}
|
||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||
|
@ -22,7 +22,7 @@ import { initReport } from '../../actions/reports';
|
||||
import { makeGetStatus } from '../../selectors';
|
||||
import { ScrollContainer } from 'react-router-scroll';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import StatusContainer from '../../containers/status_container';
|
||||
import StatusContainer from '../../../glitch/containers/status';
|
||||
import { openModal } from '../../actions/modal';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from '../../../components/button';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import StatusContent from '../../../../glitch/components/status/content';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
|
@ -6,7 +6,7 @@ import VideoModal from './video_modal';
|
||||
import BoostModal from './boost_modal';
|
||||
import ConfirmationModal from './confirmation_modal';
|
||||
import ReportModal from './report_modal';
|
||||
import SettingsModal from '../containers/settings_modal_container';
|
||||
import SettingsContainer from '../../../../glitch/containers/settings';
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
@ -17,7 +17,7 @@ const MODAL_COMPONENTS = {
|
||||
'BOOST': BoostModal,
|
||||
'CONFIRM': ConfirmationModal,
|
||||
'REPORT': ReportModal,
|
||||
'SETTINGS': SettingsModal,
|
||||
'SETTINGS': SettingsContainer,
|
||||
};
|
||||
|
||||
export default class ModalRoot extends React.PureComponent {
|
||||
|
@ -1,291 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
layout_auto: { id: 'layout.auto', defaultMessage: 'Auto' },
|
||||
layout_desktop: { id: 'layout.desktop', defaultMessage: 'Desktop' },
|
||||
layout_mobile: { id: 'layout.single', defaultMessage: 'Mobile' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
class SettingsItem extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
item: PropTypes.array.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
value: PropTypes.string.isRequired,
|
||||
message: PropTypes.object.isRequired,
|
||||
})),
|
||||
dependsOn: PropTypes.array,
|
||||
dependsOnNot: PropTypes.array,
|
||||
children: PropTypes.element.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
const { item, onChange } = this.props;
|
||||
onChange(item, e);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { settings, item, id, options, children, dependsOn, dependsOnNot, intl } = this.props;
|
||||
let enabled = true;
|
||||
|
||||
if (dependsOn) {
|
||||
for (let i = 0; i < dependsOn.length; i++) {
|
||||
enabled = enabled && settings.getIn(dependsOn[i]);
|
||||
}
|
||||
}
|
||||
if (dependsOnNot) {
|
||||
for (let i = 0; i < dependsOnNot.length; i++) {
|
||||
enabled = enabled && !settings.getIn(dependsOnNot[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (options && options.length > 0) {
|
||||
const currentValue = settings.getIn(item);
|
||||
const optionElems = options && options.length > 0 && options.map((opt) => (
|
||||
<option key={opt.value} selected={currentValue === opt.value} value={opt.value} >
|
||||
{intl.formatMessage(opt.message)}
|
||||
</option>
|
||||
));
|
||||
return (
|
||||
<label htmlFor={id}>
|
||||
<p>{children}</p>
|
||||
<p>
|
||||
<select
|
||||
id={id}
|
||||
disabled={!enabled}
|
||||
onBlur={this.handleChange}
|
||||
>
|
||||
{optionElems}
|
||||
</select>
|
||||
</p>
|
||||
</label>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<label htmlFor={id}>
|
||||
<input
|
||||
id={id}
|
||||
type='checkbox'
|
||||
checked={settings.getIn(item)}
|
||||
onChange={this.handleChange}
|
||||
disabled={!enabled}
|
||||
/>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default class SettingsModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
toggleSetting: PropTypes.func.isRequired,
|
||||
changeSetting: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
currentIndex: 0,
|
||||
};
|
||||
|
||||
General = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1><FormattedMessage id='settings.general' defaultMessage='General' /></h1>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['layout']}
|
||||
id='mastodon-settings--layout'
|
||||
options={[
|
||||
{ value: 'auto', message: messages.layout_auto },
|
||||
{ value: 'multiple', message: messages.layout_desktop },
|
||||
{ value: 'single', message: messages.layout_mobile },
|
||||
]}
|
||||
onChange={this.props.changeSetting}
|
||||
>
|
||||
<FormattedMessage id='settings.layout' defaultMessage='Layout:' />
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['stretch']}
|
||||
id='mastodon-settings--stretch'
|
||||
onChange={this.props.toggleSetting}
|
||||
>
|
||||
<FormattedMessage id='settings.wide_view' defaultMessage='Wide view (Desktop mode only)' />
|
||||
</SettingsItem>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CollapsedStatuses = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1><FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' /></h1>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'enabled']}
|
||||
id='mastodon-settings--collapsed-enabled'
|
||||
onChange={this.props.toggleSetting}
|
||||
>
|
||||
<FormattedMessage id='settings.enable_collapsed' defaultMessage='Enable collapsed toots' />
|
||||
</SettingsItem>
|
||||
<section>
|
||||
<h2><FormattedMessage id='settings.auto_collapse' defaultMessage='Automatic collapsing' /></h2>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'auto', 'all']}
|
||||
id='mastodon-settings--collapsed-auto-all'
|
||||
onChange={this.props.toggleSetting}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
>
|
||||
<FormattedMessage id='settings.auto_collapse_all' defaultMessage='Everything' />
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'auto', 'notifications']}
|
||||
id='mastodon-settings--collapsed-auto-notifications'
|
||||
onChange={this.props.toggleSetting}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||
>
|
||||
<FormattedMessage id='settings.auto_collapse_notifications' defaultMessage='Notifications' />
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'auto', 'lengthy']}
|
||||
id='mastodon-settings--collapsed-auto-lengthy'
|
||||
onChange={this.props.toggleSetting}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||
>
|
||||
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'auto', 'replies']}
|
||||
id='mastodon-settings--collapsed-auto-replies'
|
||||
onChange={this.props.toggleSetting}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||
>
|
||||
<FormattedMessage id='settings.auto_collapse_replies' defaultMessage='Replies' />
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'auto', 'media']}
|
||||
id='mastodon-settings--collapsed-auto-media'
|
||||
onChange={this.props.toggleSetting}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||
>
|
||||
<FormattedMessage id='settings.auto_collapse_media' defaultMessage='Toots with media' />
|
||||
</SettingsItem>
|
||||
</section>
|
||||
<section>
|
||||
<h2><FormattedMessage id='settings.image_backgrounds' defaultMessage='Image backgrounds' /></h2>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'backgrounds', 'user_backgrounds']}
|
||||
id='mastodon-settings--collapsed-user-backgrouns'
|
||||
onChange={this.props.toggleSetting}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
>
|
||||
<FormattedMessage id='settings.image_backgrounds_users' defaultMessage='Give collapsed toots an image background' />
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['collapsed', 'backgrounds', 'preview_images']}
|
||||
id='mastodon-settings--collapsed-preview-images'
|
||||
onChange={this.props.toggleSetting}
|
||||
dependsOn={[['collapsed', 'enabled']]}
|
||||
>
|
||||
<FormattedMessage id='settings.image_backgrounds_media' defaultMessage='Preview collapsed toot media' />
|
||||
</SettingsItem>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Media = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1><FormattedMessage id='settings.media' defaultMessage='Media' /></h1>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['media', 'letterbox']}
|
||||
id='mastodon-settings--media-letterbox'
|
||||
onChange={this.props.toggleSetting}
|
||||
>
|
||||
<FormattedMessage id='settings.media_letterbox' defaultMessage='Letterbox media' />
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
settings={this.props.settings}
|
||||
item={['media', 'fullwidth']}
|
||||
id='mastodon-settings--media-fullwidth'
|
||||
onChange={this.props.toggleSetting}
|
||||
>
|
||||
<FormattedMessage id='settings.media_fullwidth' defaultMessage='Full-width media previews' />
|
||||
</SettingsItem>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
navigateTo = (e) =>
|
||||
this.setState({ currentIndex: +e.currentTarget.getAttribute('data-mastodon-navigation_index') });
|
||||
|
||||
render () {
|
||||
|
||||
const { General, CollapsedStatuses, Media, navigateTo } = this;
|
||||
const { onClose } = this.props;
|
||||
const { currentIndex } = this.state;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal settings-modal'>
|
||||
|
||||
<nav className='settings-modal__navigation'>
|
||||
<a onClick={navigateTo} role='button' data-mastodon-navigation_index='0' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 0 ? ' active' : ''}`}>
|
||||
<FormattedMessage id='settings.general' defaultMessage='General' />
|
||||
</a>
|
||||
<a onClick={navigateTo} role='button' data-mastodon-navigation_index='1' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 1 ? ' active' : ''}`}>
|
||||
<FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' />
|
||||
</a>
|
||||
<a onClick={navigateTo} role='button' data-mastodon-navigation_index='2' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 2 ? ' active' : ''}`}>
|
||||
<FormattedMessage id='settings.media' defaultMessage='Media' />
|
||||
</a>
|
||||
<a href='/settings/preferences' className='settings-modal__navigation-item'>
|
||||
<i className='fa fa-fw fa-cog' /> <FormattedMessage id='settings.preferences' defaultMessage='User preferences' />
|
||||
</a>
|
||||
<a onClick={onClose} role='button' tabIndex='0' className='settings-modal__navigation-close'>
|
||||
<FormattedMessage id='settings.close' defaultMessage='Close' />
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
<div className='settings-modal__content'>
|
||||
{
|
||||
[
|
||||
<General />,
|
||||
<CollapsedStatuses />,
|
||||
<Media />,
|
||||
][currentIndex] || <General />
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { changeLocalSetting } from '../../../actions/local_settings';
|
||||
import { closeModal } from '../../../actions/modal';
|
||||
import SettingsModal from '../components/settings_modal';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
settings: state.get('local_settings'),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleSetting (setting, e) {
|
||||
dispatch(changeLocalSetting(setting, e.target.checked));
|
||||
},
|
||||
changeSetting (setting, e) {
|
||||
dispatch(changeLocalSetting(setting, e.target.value));
|
||||
},
|
||||
onClose () {
|
||||
dispatch(closeModal());
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SettingsModal);
|
Reference in New Issue
Block a user