Moved glitch files to their own location ;)

This commit is contained in:
kibigo!
2017-07-12 01:02:51 -07:00
parent 74eff5456c
commit 297921fce5
33 changed files with 327 additions and 275 deletions

View File

@ -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>
);
}
}

View File

@ -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 `&apos;` and `&quot;` 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(/&quot;/g, '"').replace(/&apos;/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;
}

View File

@ -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';

View File

@ -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>);
}
}

View File

@ -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>

View File

@ -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';

View File

@ -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);

View File

@ -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';

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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';

View File

@ -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'])}

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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>
);
}
}

View File

@ -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);