...
 
Commits (5)
......@@ -178,7 +178,7 @@ STREAMING_CLUSTER_NUM=1
# LDAP_BIND_DN=
# LDAP_PASSWORD=
# LDAP_UID=cn
# LDAP_SEARCH_FILTER="%{uid}=%{email}"
# LDAP_SEARCH_FILTER=%{uid}=%{email}
# PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable
......
import React from 'react';
import PropTypes from 'prop-types';
import illustration from '../../images/elephant_ui_disappointed.svg';
import { FormattedMessage } from 'react-intl';
import { version, source_url } from 'mastodon/initial_state';
export default class ErrorBoundary extends React.PureComponent {
......@@ -12,26 +13,53 @@ export default class ErrorBoundary extends React.PureComponent {
hasError: false,
stackTrace: undefined,
componentStack: undefined,
}
};
componentDidCatch(error, info) {
componentDidCatch (error, info) {
this.setState({
hasError: true,
stackTrace: error.stack,
componentStack: info && info.componentStack,
copied: false,
});
}
handleCopyStackTrace = () => {
const { stackTrace } = this.state;
const textarea = document.createElement('textarea');
textarea.textContent = stackTrace;
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
try {
textarea.select();
document.execCommand('copy');
} catch (e) {
} finally {
document.body.removeChild(textarea);
}
this.setState({ copied: true });
setTimeout(() => this.setState({ copied: false }), 700);
}
render() {
const { hasError } = this.state;
const { hasError, copied } = this.state;
if (!hasError) {
return this.props.children;
}
return (
<div>
<img src={illustration} alt='' />
<div className='error-boundary'>
<div>
<p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
<p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
</div>
</div>
);
}
......
......@@ -60,12 +60,17 @@ class Search extends React.PureComponent {
onShow: PropTypes.func.isRequired,
openInRoute: PropTypes.bool,
intl: PropTypes.object.isRequired,
singleColumn: PropTypes.bool,
};
state = {
expanded: false,
};
setRef = c => {
this.searchForm = c;
}
handleChange = (e) => {
this.props.onChange(e.target.value);
}
......@@ -95,6 +100,13 @@ class Search extends React.PureComponent {
handleFocus = () => {
this.setState({ expanded: true });
this.props.onShow();
if (this.searchForm && !this.props.singleColumn) {
const { left, right } = this.searchForm.getBoundingClientRect();
if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
this.searchForm.scrollIntoView();
}
}
}
handleBlur = () => {
......@@ -111,6 +123,7 @@ class Search extends React.PureComponent {
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
<input
ref={this.setRef}
className='search__input'
type='text'
placeholder={intl.formatMessage(messages.placeholder)}
......
......@@ -11,6 +11,7 @@ import Permalink from 'mastodon/components/permalink';
import IconButton from 'mastodon/components/icon_button';
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
import { HotKeys } from 'react-hotkeys';
import { autoPlayGif } from 'mastodon/initial_state';
const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' },
......@@ -41,6 +42,43 @@ class Conversation extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
};
_updateEmojis () {
const node = this.namesNode;
if (!node || autoPlayGif) {
return;
}
const emojis = node.querySelectorAll('.custom-emoji');
for (var i = 0; i < emojis.length; i++) {
let emoji = emojis[i];
if (emoji.classList.contains('status-emoji')) {
continue;
}
emoji.classList.add('status-emoji');
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
}
}
componentDidMount () {
this._updateEmojis();
}
componentDidUpdate () {
this._updateEmojis();
}
handleEmojiMouseEnter = ({ target }) => {
target.src = target.getAttribute('data-original');
}
handleEmojiMouseLeave = ({ target }) => {
target.src = target.getAttribute('data-static');
}
handleClick = () => {
if (!this.context.router) {
return;
......@@ -83,6 +121,10 @@ class Conversation extends ImmutablePureComponent {
this.props.onToggleHidden(this.props.lastStatus);
}
setNamesRef = (c) => {
this.namesNode = c;
}
render () {
const { accounts, lastStatus, unread, intl } = this.props;
......@@ -127,7 +169,7 @@ class Conversation extends ImmutablePureComponent {
<RelativeTimestamp timestamp={lastStatus.get('created_at')} />
</div>
<div className='conversation__content__names'>
<div className='conversation__content__names' ref={this.setNamesRef}>
<FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
</div>
</div>
......
......@@ -160,7 +160,7 @@
"getting_started.heading": "Getting started",
"getting_started.invite": "Invite people",
"getting_started.open_source_notice": "quey is open source software. You can contribute or report issues on GitLab at {github}.",
"getting_started.security": "Security",
"getting_started.security": "Account settings",
"getting_started.terms": "Terms of service",
"hashtag.column_header.tag_mode.all": "and {additional}",
"hashtag.column_header.tag_mode.any": "or {additional}",
......
......@@ -135,13 +135,18 @@ button {
.app-holder {
&,
& > div {
& > div,
& > noscript {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
outline: 0 !important;
}
& > noscript {
height: 100vh;
}
}
.layout-single-column .app-holder {
......@@ -157,3 +162,70 @@ button {
height: 100%;
}
}
.error-boundary,
.app-holder noscript {
flex-direction: column;
font-size: 16px;
font-weight: 400;
line-height: 1.7;
color: lighten($error-red, 4%);
text-align: center;
& > div {
max-width: 500px;
}
p {
margin-bottom: .85em;
&:last-child {
margin-bottom: 0;
}
}
a {
color: $highlight-text-color;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
&__footer {
color: $dark-text-color;
font-size: 13px;
a {
color: $dark-text-color;
}
}
button {
display: inline;
border: 0;
background: transparent;
color: $dark-text-color;
font: inherit;
padding: 0;
margin: 0;
line-height: inherit;
cursor: pointer;
outline: 0;
transition: color 300ms linear;
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
&.copied {
color: $valid-value-color;
transition: none;
}
}
}