Merge remote-tracking branch 'upstream/master'
This commit is contained in:
		@@ -23,6 +23,9 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
 | 
			
		||||
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
 | 
			
		||||
export const STATUS_UNMUTE_FAIL    = 'STATUS_UNMUTE_FAIL';
 | 
			
		||||
 | 
			
		||||
export const STATUS_SET_HEIGHT = 'STATUS_SET_HEIGHT';
 | 
			
		||||
export const STATUSES_CLEAR_HEIGHT = 'STATUSES_CLEAR_HEIGHT';
 | 
			
		||||
 | 
			
		||||
export function fetchStatusRequest(id, skipLoading) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: STATUS_FETCH_REQUEST,
 | 
			
		||||
@@ -215,3 +218,17 @@ export function unmuteStatusFail(id, error) {
 | 
			
		||||
    error,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function setStatusHeight (id, height) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: STATUS_SET_HEIGHT,
 | 
			
		||||
    id,
 | 
			
		||||
    height,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function clearStatusesHeight () {
 | 
			
		||||
  return {
 | 
			
		||||
    type: STATUSES_CLEAR_HEIGHT,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import detectPassiveEvents from 'detect-passive-events';
 | 
			
		||||
import scrollTop from '../scroll';
 | 
			
		||||
import { scrollTop } from '../scroll';
 | 
			
		||||
 | 
			
		||||
export default class Column extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import escapeTextContentForBrowser from 'escape-html';
 | 
			
		||||
import emojify from '../emoji';
 | 
			
		||||
 | 
			
		||||
export default class DisplayName extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
@@ -10,12 +8,11 @@ export default class DisplayName extends React.PureComponent {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const displayName     = this.props.account.get('display_name').length === 0 ? this.props.account.get('username') : this.props.account.get('display_name');
 | 
			
		||||
    const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
 | 
			
		||||
    const displayNameHtml = { __html: this.props.account.get('display_name_html') };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <span className='display-name'>
 | 
			
		||||
        <strong className='display-name__html' dangerouslySetInnerHTML={displayNameHTML} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span>
 | 
			
		||||
        <strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span>
 | 
			
		||||
      </span>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,6 @@ import DisplayName from './display_name';
 | 
			
		||||
import StatusContent from './status_content';
 | 
			
		||||
import StatusActionBar from './status_action_bar';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
import emojify from '../emoji';
 | 
			
		||||
import escapeTextContentForBrowser from 'escape-html';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
 | 
			
		||||
import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components';
 | 
			
		||||
@@ -39,6 +37,7 @@ export default class Status extends ImmutablePureComponent {
 | 
			
		||||
    onOpenMedia: PropTypes.func,
 | 
			
		||||
    onOpenVideo: PropTypes.func,
 | 
			
		||||
    onBlock: PropTypes.func,
 | 
			
		||||
    onHeightChange: PropTypes.func,
 | 
			
		||||
    me: PropTypes.number,
 | 
			
		||||
    boostModal: PropTypes.bool,
 | 
			
		||||
    autoPlayGif: PropTypes.bool,
 | 
			
		||||
@@ -50,7 +49,6 @@ export default class Status extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  state = {
 | 
			
		||||
    isExpanded: false,
 | 
			
		||||
    isIntersecting: true, // assume intersecting until told otherwise
 | 
			
		||||
    isHidden: false, // set to true in requestIdleCallback to trigger un-render
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -111,6 +109,10 @@ export default class Status extends ImmutablePureComponent {
 | 
			
		||||
    if (this.node && this.node.children.length !== 0) {
 | 
			
		||||
      // save the height of the fully-rendered element
 | 
			
		||||
      this.height = getRectFromEntry(entry).height;
 | 
			
		||||
 | 
			
		||||
      if (this.props.onHeightChange) {
 | 
			
		||||
        this.props.onHeightChange(this.props.status, this.height);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.setState((prevState) => {
 | 
			
		||||
@@ -182,9 +184,13 @@ export default class Status extends ImmutablePureComponent {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isIntersecting && isHidden) {
 | 
			
		||||
    const hasIntersectionObserverWrapper = !!this.props.intersectionObserverWrapper;
 | 
			
		||||
    const isHiddenForSure = isIntersecting === false && isHidden;
 | 
			
		||||
    const visibilityUnknownButHeightIsCached = isIntersecting === undefined && status.has('height');
 | 
			
		||||
 | 
			
		||||
    if (hasIntersectionObserverWrapper && (isHiddenForSure || visibilityUnknownButHeightIsCached)) {
 | 
			
		||||
      return (
 | 
			
		||||
        <article ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0' style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}>
 | 
			
		||||
        <article ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0' style={{ height: `${this.height || status.get('height')}px`, opacity: 0, overflow: 'hidden' }}>
 | 
			
		||||
          {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
 | 
			
		||||
          {status.get('content')}
 | 
			
		||||
        </article>
 | 
			
		||||
@@ -192,19 +198,13 @@ export default class Status extends ImmutablePureComponent {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
 | 
			
		||||
      let displayName = status.getIn(['account', 'display_name']);
 | 
			
		||||
 | 
			
		||||
      if (displayName.length === 0) {
 | 
			
		||||
        displayName = status.getIn(['account', 'username']);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
 | 
			
		||||
      const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <article className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0'>
 | 
			
		||||
          <div className='status__prepend'>
 | 
			
		||||
            <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
 | 
			
		||||
            <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
 | 
			
		||||
            <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={display_name_html} /></a> }} />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,7 @@
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import escapeTextContentForBrowser from 'escape-html';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import emojify from '../emoji';
 | 
			
		||||
import { isRtl } from '../rtl';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
import Permalink from './permalink';
 | 
			
		||||
@@ -122,8 +120,8 @@ export default class StatusContent extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
    const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
 | 
			
		||||
 | 
			
		||||
    const content = { __html: emojify(status.get('content')) };
 | 
			
		||||
    const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) };
 | 
			
		||||
    const content = { __html: status.get('contentHtml') };
 | 
			
		||||
    const spoilerContent = { __html: status.get('spoilerHtml') };
 | 
			
		||||
    const directionStyle = { direction: 'ltr' };
 | 
			
		||||
    const classNames = classnames('status__content', {
 | 
			
		||||
      'status__content--with-action': this.props.onClick && this.context.router,
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@ export default class StatusList extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleKeyDown = (e) => {
 | 
			
		||||
    if (['PageDown', 'PageUp', 'End', 'Home'].includes(e.key)) {
 | 
			
		||||
    if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) {
 | 
			
		||||
      const article = (() => {
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
        case 'PageDown':
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import {
 | 
			
		||||
  blockAccount,
 | 
			
		||||
  muteAccount,
 | 
			
		||||
} from '../actions/accounts';
 | 
			
		||||
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
 | 
			
		||||
import { muteStatus, unmuteStatus, deleteStatus, setStatusHeight } from '../actions/statuses';
 | 
			
		||||
import { initReport } from '../actions/reports';
 | 
			
		||||
import { openModal } from '../actions/modal';
 | 
			
		||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
			
		||||
@@ -127,6 +127,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onHeightChange (status, height) {
 | 
			
		||||
    dispatch(setStatusHeight(status.get('id'), height));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
 | 
			
		||||
 
 | 
			
		||||
@@ -3,34 +3,28 @@ import Trie from 'substring-trie';
 | 
			
		||||
 | 
			
		||||
const trie = new Trie(Object.keys(unicodeMapping));
 | 
			
		||||
 | 
			
		||||
const excluded = ['™', '©', '®'];
 | 
			
		||||
 | 
			
		||||
function emojify(str) {
 | 
			
		||||
  // This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
 | 
			
		||||
  // and replacing valid unicode strings
 | 
			
		||||
  // that _aren't_ within tags with an <img> version.
 | 
			
		||||
  // The goal is to be the same as an emojione.regUnicode replacement, but faster.
 | 
			
		||||
  let i = -1;
 | 
			
		||||
  let insideTag = false;
 | 
			
		||||
  let match;
 | 
			
		||||
  while (++i < str.length) {
 | 
			
		||||
    const char = str.charAt(i);
 | 
			
		||||
    if (insideTag && char === '>') {
 | 
			
		||||
      insideTag = false;
 | 
			
		||||
    } else if (char === '<') {
 | 
			
		||||
      insideTag = true;
 | 
			
		||||
    } else if (!insideTag && (match = trie.search(str.substring(i)))) {
 | 
			
		||||
      const unicodeStr = match;
 | 
			
		||||
      if (unicodeStr in unicodeMapping && excluded.indexOf(unicodeStr) === -1) {
 | 
			
		||||
        const [filename, shortCode] = unicodeMapping[unicodeStr];
 | 
			
		||||
        const alt      = unicodeStr;
 | 
			
		||||
        const replacement =  `<img draggable="false" class="emojione" alt="${alt}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
 | 
			
		||||
        str = str.substring(0, i) + replacement + str.substring(i + unicodeStr.length);
 | 
			
		||||
        i += (replacement.length - unicodeStr.length); // jump ahead the length we've added to the string
 | 
			
		||||
      }
 | 
			
		||||
const emojify = str => {
 | 
			
		||||
  let rtn = '';
 | 
			
		||||
  for (;;) {
 | 
			
		||||
    let match, i = 0;
 | 
			
		||||
    while (i < str.length && str[i] !== '<' && !(match = trie.search(str.slice(i)))) {
 | 
			
		||||
      i += str.codePointAt(i) < 65536 ? 1 : 2;
 | 
			
		||||
    }
 | 
			
		||||
    if (i === str.length)
 | 
			
		||||
      break;
 | 
			
		||||
    else if (str[i] === '<') {
 | 
			
		||||
      let tagend = str.indexOf('>', i + 1) + 1;
 | 
			
		||||
      if (!tagend)
 | 
			
		||||
        break;
 | 
			
		||||
      rtn += str.slice(0, tagend);
 | 
			
		||||
      str = str.slice(tagend);
 | 
			
		||||
    } else {
 | 
			
		||||
      const [filename, shortCode] = unicodeMapping[match];
 | 
			
		||||
      rtn += str.slice(0, i) + `<img draggable="false" class="emojione" alt="${match}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
 | 
			
		||||
      str = str.slice(i + match.length);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return str;
 | 
			
		||||
}
 | 
			
		||||
  return rtn + str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default emojify;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,10 @@
 | 
			
		||||
const emojione = require('emojione');
 | 
			
		||||
 | 
			
		||||
const mappedUnicode = emojione.mapUnicodeToShort();
 | 
			
		||||
const excluded = ['®', '©', '™'];
 | 
			
		||||
 | 
			
		||||
module.exports.unicodeMapping = Object.keys(emojione.jsEscapeMap)
 | 
			
		||||
  .filter(c => !excluded.includes(c))
 | 
			
		||||
  .map(unicodeStr => [unicodeStr, mappedUnicode[emojione.jsEscapeMap[unicodeStr]]])
 | 
			
		||||
  .map(([unicodeStr, shortCode]) => ({ [unicodeStr]: [emojione.emojioneList[shortCode].fname, shortCode.slice(1, shortCode.length - 1)] }))
 | 
			
		||||
  .reduce((x, y) => Object.assign(x, y), { });
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@
 | 
			
		||||
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 Motion from 'react-motion/lib/Motion';
 | 
			
		||||
@@ -95,15 +93,10 @@ export default class Header extends ImmutablePureComponent {
 | 
			
		||||
      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>;
 | 
			
		||||
    }
 | 
			
		||||
@@ -128,15 +121,15 @@ export default class Header extends ImmutablePureComponent {
 | 
			
		||||
      lockedIcon = <i className='fa fa-lock' />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const content         = { __html: emojify(account.get('note')) };
 | 
			
		||||
    const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
 | 
			
		||||
    const content         = { __html: account.get('note_emojified') };
 | 
			
		||||
    const displayNameHtml = { __html: account.get('display_name_html') };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
 | 
			
		||||
        <div>
 | 
			
		||||
          <Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
 | 
			
		||||
 | 
			
		||||
          <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
 | 
			
		||||
          <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
 | 
			
		||||
          <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
 | 
			
		||||
          <div className='account__header__content' dangerouslySetInnerHTML={content} />
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 | 
			
		||||
import Avatar from '../../../components/avatar';
 | 
			
		||||
import IconButton from '../../../components/icon_button';
 | 
			
		||||
import DisplayName from '../../../components/display_name';
 | 
			
		||||
import emojify from '../../../emoji';
 | 
			
		||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +42,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const content  = { __html: emojify(status.get('content')) };
 | 
			
		||||
    const content  = { __html: status.get('contentHtml') };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='reply-indicator'>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import Permalink from '../../../components/permalink';
 | 
			
		||||
import Avatar from '../../../components/avatar';
 | 
			
		||||
import DisplayName from '../../../components/display_name';
 | 
			
		||||
import emojify from '../../../emoji';
 | 
			
		||||
import IconButton from '../../../components/icon_button';
 | 
			
		||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
@@ -26,7 +25,7 @@ export default class AccountAuthorize extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, account, onAuthorize, onReject } = this.props;
 | 
			
		||||
    const content = { __html: emojify(account.get('note')) };
 | 
			
		||||
    const content = { __html: account.get('note_emojified') };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='account-authorize__wrapper'>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,6 @@ 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 {
 | 
			
		||||
@@ -70,9 +68,8 @@ export default class Notification extends ImmutablePureComponent {
 | 
			
		||||
  render () {
 | 
			
		||||
    const { notification } = this.props;
 | 
			
		||||
    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} />;
 | 
			
		||||
    const displayNameHtml  = { __html: account.get('display_name_html') };
 | 
			
		||||
    const link             = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHtml} />;
 | 
			
		||||
 | 
			
		||||
    switch(notification.get('type')) {
 | 
			
		||||
    case 'follow':
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import emojify from '../../../emoji';
 | 
			
		||||
import Toggle from 'react-toggle';
 | 
			
		||||
 | 
			
		||||
export default class StatusCheckBox extends React.PureComponent {
 | 
			
		||||
@@ -15,7 +14,7 @@ export default class StatusCheckBox extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { status, checked, onToggle, disabled } = this.props;
 | 
			
		||||
    const content = { __html: emojify(status.get('content')) };
 | 
			
		||||
    const content = { __html: status.get('contentHtml') };
 | 
			
		||||
 | 
			
		||||
    if (status.get('reblog')) {
 | 
			
		||||
      return null;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import React from 'react';
 | 
			
		||||
import ColumnHeader from './column_header';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { debounce } from 'lodash';
 | 
			
		||||
import scrollTop from '../../../scroll';
 | 
			
		||||
import { scrollTop } from '../../../scroll';
 | 
			
		||||
import { isMobile } from '../../../is_mobile';
 | 
			
		||||
 | 
			
		||||
export default class Column extends React.PureComponent {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,8 @@ import ColumnLoading from './column_loading';
 | 
			
		||||
import BundleColumnError from './bundle_column_error';
 | 
			
		||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
 | 
			
		||||
 | 
			
		||||
import { scrollRight } from '../../../scroll';
 | 
			
		||||
 | 
			
		||||
const componentMap = {
 | 
			
		||||
  'COMPOSE': Compose,
 | 
			
		||||
  'HOME': HomeTimeline,
 | 
			
		||||
@@ -49,9 +51,13 @@ export default class ColumnsArea extends ImmutablePureComponent {
 | 
			
		||||
    this.setState({ shouldAnimate: true });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidUpdate() {
 | 
			
		||||
  componentDidUpdate(prevProps) {
 | 
			
		||||
    this.lastIndex = getIndex(this.context.router.history.location.pathname);
 | 
			
		||||
    this.setState({ shouldAnimate: true });
 | 
			
		||||
 | 
			
		||||
    if (this.props.children !== prevProps.children && !this.props.singleColumn) {
 | 
			
		||||
      scrollRight(this.node);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleSwipe = (index) => {
 | 
			
		||||
@@ -74,6 +80,10 @@ export default class ColumnsArea extends ImmutablePureComponent {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setRef = (node) => {
 | 
			
		||||
    this.node = node;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderView = (link, index) => {
 | 
			
		||||
    const columnIndex = getIndex(this.context.router.history.location.pathname);
 | 
			
		||||
    const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] });
 | 
			
		||||
@@ -114,7 +124,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='columns-area'>
 | 
			
		||||
      <div className='columns-area' ref={this.setRef}>
 | 
			
		||||
        {columns.map(column => {
 | 
			
		||||
          const params = column.get('params', null) === null ? null : column.get('params').toJS();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import { debounce } from 'lodash';
 | 
			
		||||
import { uploadCompose } from '../../actions/compose';
 | 
			
		||||
import { refreshHomeTimeline } from '../../actions/timelines';
 | 
			
		||||
import { refreshNotifications } from '../../actions/notifications';
 | 
			
		||||
import { clearStatusesHeight } from '../../actions/statuses';
 | 
			
		||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
 | 
			
		||||
import UploadArea from './components/upload_area';
 | 
			
		||||
import ColumnsAreaContainer from './containers/columns_area_container';
 | 
			
		||||
@@ -72,6 +73,9 @@ export default class UI extends React.PureComponent {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  handleResize = debounce(() => {
 | 
			
		||||
    // The cached heights are no longer accurate, invalidate
 | 
			
		||||
    this.props.dispatch(clearStatusesHeight());
 | 
			
		||||
 | 
			
		||||
    this.setState({ width: window.innerWidth });
 | 
			
		||||
  }, 500, {
 | 
			
		||||
    trailing: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "account.block": "مسدودسازی @{name}",
 | 
			
		||||
  "account.block_domain": "پنهانسازی همه چیز از سرور {domain}",
 | 
			
		||||
  "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
 | 
			
		||||
  "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.",
 | 
			
		||||
  "account.edit_profile": "ویرایش نمایه",
 | 
			
		||||
  "account.follow": "پی بگیرید",
 | 
			
		||||
  "account.followers": "پیگیران",
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
  "account.posts": "نوشتهها",
 | 
			
		||||
  "account.report": "گزارش @{name}",
 | 
			
		||||
  "account.requested": "در انتظار پذیرش",
 | 
			
		||||
  "account.share": "Share @{name}'s profile",
 | 
			
		||||
  "account.share": "همرسانی نمایهٔ @{name}",
 | 
			
		||||
  "account.unblock": "رفع انسداد @{name}",
 | 
			
		||||
  "account.unblock_domain": "رفع پنهانسازی از {domain}",
 | 
			
		||||
  "account.unfollow": "پایان پیگیری",
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
  "account.posts": "Statuts",
 | 
			
		||||
  "account.report": "Signaler",
 | 
			
		||||
  "account.requested": "Invitation envoyée",
 | 
			
		||||
  "account.share": "Share @{name}'s profile",
 | 
			
		||||
  "account.share": "Partager le profil de @{name}",
 | 
			
		||||
  "account.unblock": "Débloquer",
 | 
			
		||||
  "account.unblock_domain": "Ne plus masquer {domain}",
 | 
			
		||||
  "account.unfollow": "Ne plus suivre",
 | 
			
		||||
@@ -35,11 +35,11 @@
 | 
			
		||||
  "column.notifications": "Notifications",
 | 
			
		||||
  "column.public": "Fil public global",
 | 
			
		||||
  "column_back_button.label": "Retour",
 | 
			
		||||
  "column_header.hide_settings": "Hide settings",
 | 
			
		||||
  "column_header.moveLeft_settings": "Move column to the left",
 | 
			
		||||
  "column_header.moveRight_settings": "Move column to the right",
 | 
			
		||||
  "column_header.hide_settings": "Masquer les paramètres",
 | 
			
		||||
  "column_header.moveLeft_settings": "Déplacer la colonne vers la gauche",
 | 
			
		||||
  "column_header.moveRight_settings": "Déplacer la colonne vers la droite",
 | 
			
		||||
  "column_header.pin": "Épingler",
 | 
			
		||||
  "column_header.show_settings": "Show settings",
 | 
			
		||||
  "column_header.show_settings": "Afficher les paramètres",
 | 
			
		||||
  "column_header.unpin": "Retirer",
 | 
			
		||||
  "column_subheading.navigation": "Navigation",
 | 
			
		||||
  "column_subheading.settings": "Paramètres",
 | 
			
		||||
@@ -94,8 +94,8 @@
 | 
			
		||||
  "home.column_settings.show_replies": "Afficher les réponses",
 | 
			
		||||
  "home.settings": "Paramètres de la colonne",
 | 
			
		||||
  "lightbox.close": "Fermer",
 | 
			
		||||
  "lightbox.next": "Next",
 | 
			
		||||
  "lightbox.previous": "Previous",
 | 
			
		||||
  "lightbox.next": "Suivant",
 | 
			
		||||
  "lightbox.previous": "Précédent",
 | 
			
		||||
  "loading_indicator.label": "Chargement…",
 | 
			
		||||
  "media_gallery.toggle_visible": "Modifier la visibilité",
 | 
			
		||||
  "missing_indicator.label": "Non trouvé",
 | 
			
		||||
@@ -175,7 +175,7 @@
 | 
			
		||||
  "status.report": "Signaler @{name}",
 | 
			
		||||
  "status.sensitive_toggle": "Cliquer pour afficher",
 | 
			
		||||
  "status.sensitive_warning": "Contenu sensible",
 | 
			
		||||
  "status.share": "Share",
 | 
			
		||||
  "status.share": "Partager",
 | 
			
		||||
  "status.show_less": "Replier",
 | 
			
		||||
  "status.show_more": "Déplier",
 | 
			
		||||
  "status.unmute_conversation": "Ne plus masquer la conversation",
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@
 | 
			
		||||
  "emoji_button.search": "Szukaj...",
 | 
			
		||||
  "emoji_button.symbols": "Symbole",
 | 
			
		||||
  "emoji_button.travel": "Podróże i miejsca",
 | 
			
		||||
  "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby odbić piłeczkę!",
 | 
			
		||||
  "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
 | 
			
		||||
  "empty_column.hashtag": "Nie ma postó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.",
 | 
			
		||||
@@ -159,7 +159,7 @@
 | 
			
		||||
  "report.target": "Zgłaszanie {target}",
 | 
			
		||||
  "search.placeholder": "Szukaj",
 | 
			
		||||
  "search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}",
 | 
			
		||||
  "standalone.public_title": "Spojrzenie wgłąb…",
 | 
			
		||||
  "standalone.public_title": "Spojrzenie w głąb…",
 | 
			
		||||
  "status.cannot_reblog": "Ten post nie może zostać podbity",
 | 
			
		||||
  "status.delete": "Usuń",
 | 
			
		||||
  "status.favourite": "Ulubione",
 | 
			
		||||
@@ -178,7 +178,7 @@
 | 
			
		||||
  "status.share": "Udostępnij",
 | 
			
		||||
  "status.show_less": "Pokaż mniej",
 | 
			
		||||
  "status.show_more": "Pokaż więcej",
 | 
			
		||||
  "status.unmute_conversation": "Cofnij wyciezenie konwersacji",
 | 
			
		||||
  "status.unmute_conversation": "Cofnij wyciszenie konwersacji",
 | 
			
		||||
  "tabs_bar.compose": "Napisz",
 | 
			
		||||
  "tabs_bar.federated_timeline": "Globalne",
 | 
			
		||||
  "tabs_bar.home": "Strona główna",
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,9 @@ import {
 | 
			
		||||
  FAVOURITED_STATUSES_EXPAND_SUCCESS,
 | 
			
		||||
} from '../actions/favourites';
 | 
			
		||||
import { STORE_HYDRATE } from '../actions/store';
 | 
			
		||||
import emojify from '../emoji';
 | 
			
		||||
import { Map as ImmutableMap, fromJS } from 'immutable';
 | 
			
		||||
import escapeTextContentForBrowser from 'escape-html';
 | 
			
		||||
 | 
			
		||||
const normalizeAccount = (state, account) => {
 | 
			
		||||
  account = { ...account };
 | 
			
		||||
@@ -53,6 +55,10 @@ const normalizeAccount = (state, account) => {
 | 
			
		||||
  delete account.following_count;
 | 
			
		||||
  delete account.statuses_count;
 | 
			
		||||
 | 
			
		||||
  const displayName = account.display_name.length === 0 ? account.username : account.display_name;
 | 
			
		||||
  account.display_name_html = emojify(escapeTextContentForBrowser(displayName));
 | 
			
		||||
  account.note_emojified = emojify(account.note);
 | 
			
		||||
 | 
			
		||||
  return state.set(account.id, fromJS(account));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,8 @@ import {
 | 
			
		||||
  CONTEXT_FETCH_SUCCESS,
 | 
			
		||||
  STATUS_MUTE_SUCCESS,
 | 
			
		||||
  STATUS_UNMUTE_SUCCESS,
 | 
			
		||||
  STATUS_SET_HEIGHT,
 | 
			
		||||
  STATUSES_CLEAR_HEIGHT,
 | 
			
		||||
} from '../actions/statuses';
 | 
			
		||||
import {
 | 
			
		||||
  TIMELINE_REFRESH_SUCCESS,
 | 
			
		||||
@@ -33,7 +35,11 @@ import {
 | 
			
		||||
  FAVOURITED_STATUSES_EXPAND_SUCCESS,
 | 
			
		||||
} from '../actions/favourites';
 | 
			
		||||
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
 | 
			
		||||
import emojify from '../emoji';
 | 
			
		||||
import { Map as ImmutableMap, fromJS } from 'immutable';
 | 
			
		||||
import escapeTextContentForBrowser from 'escape-html';
 | 
			
		||||
 | 
			
		||||
const domParser = new DOMParser();
 | 
			
		||||
 | 
			
		||||
const normalizeStatus = (state, status) => {
 | 
			
		||||
  if (!status) {
 | 
			
		||||
@@ -49,7 +55,9 @@ const normalizeStatus = (state, status) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const searchContent = [status.spoiler_text, status.content].join(' ').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
 | 
			
		||||
  normalStatus.search_index = new DOMParser().parseFromString(searchContent, 'text/html').documentElement.textContent;
 | 
			
		||||
  normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
 | 
			
		||||
  normalStatus.contentHtml = emojify(normalStatus.content);
 | 
			
		||||
  normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''));
 | 
			
		||||
 | 
			
		||||
  return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus)));
 | 
			
		||||
};
 | 
			
		||||
@@ -82,6 +90,18 @@ const filterStatuses = (state, relationship) => {
 | 
			
		||||
  return state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setHeight = (state, id, height) => {
 | 
			
		||||
  return state.update(id, ImmutableMap(), map => map.set('height', height));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const clearHeights = (state) => {
 | 
			
		||||
  state.forEach(status => {
 | 
			
		||||
    state = state.deleteIn([status.get('id'), 'height']);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initialState = ImmutableMap();
 | 
			
		||||
 | 
			
		||||
export default function statuses(state = initialState, action) {
 | 
			
		||||
@@ -120,6 +140,10 @@ export default function statuses(state = initialState, action) {
 | 
			
		||||
    return deleteStatus(state, action.id, action.references);
 | 
			
		||||
  case ACCOUNT_BLOCK_SUCCESS:
 | 
			
		||||
    return filterStatuses(state, action.relationship);
 | 
			
		||||
  case STATUS_SET_HEIGHT:
 | 
			
		||||
    return setHeight(state, action.id, action.height);
 | 
			
		||||
  case STATUSES_CLEAR_HEIGHT:
 | 
			
		||||
    return clearHeights(state);
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
const easingOutQuint = (x, t, b, c, d) => c * ((t = t / d - 1) * t * t * t * t + 1) + b;
 | 
			
		||||
 | 
			
		||||
const scrollTop = (node) => {
 | 
			
		||||
const scroll = (node, key, target) => {
 | 
			
		||||
  const startTime = Date.now();
 | 
			
		||||
  const offset    = node.scrollTop;
 | 
			
		||||
  const targetY   = -offset;
 | 
			
		||||
  const offset    = node[key];
 | 
			
		||||
  const gap       = target - offset;
 | 
			
		||||
  const duration  = 1000;
 | 
			
		||||
  let interrupt   = false;
 | 
			
		||||
 | 
			
		||||
@@ -15,7 +15,7 @@ const scrollTop = (node) => {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration);
 | 
			
		||||
    node[key] = easingOutQuint(0, elapsed, offset, gap, duration);
 | 
			
		||||
    requestAnimationFrame(step);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@@ -26,4 +26,5 @@ const scrollTop = (node) => {
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default scrollTop;
 | 
			
		||||
export const scrollRight = (node) => scroll(node, 'scrollLeft', node.scrollWidth);
 | 
			
		||||
export const scrollTop = (node) => scroll(node, 'scrollTop', 0);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
  box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  @media screen and (max-width: 700px) {
 | 
			
		||||
  @media screen and (max-width: 740px) {
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
  }
 | 
			
		||||
@@ -298,7 +298,7 @@
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
 | 
			
		||||
  @media screen and (max-width: 700px) {
 | 
			
		||||
  @media screen and (max-width: 740px) {
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ body {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media screen and (max-width: 360px) {
 | 
			
		||||
  @media screen and (max-width: 400px) {
 | 
			
		||||
    padding-bottom: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,15 @@
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
    line-height: 28px;
 | 
			
		||||
    color: $ui-primary-color;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    padding: 0 10px;
 | 
			
		||||
    overflow-wrap: break-word;
 | 
			
		||||
 | 
			
		||||
    @media screen and (max-width: 740px) {
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      padding: 20px 10px 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    a {
 | 
			
		||||
      color: inherit;
 | 
			
		||||
 
 | 
			
		||||
@@ -1835,7 +1835,6 @@
 | 
			
		||||
  overflow-y: scroll;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
  flex: 1 1 auto;
 | 
			
		||||
  backface-visibility: hidden;
 | 
			
		||||
  -webkit-overflow-scrolling: touch;
 | 
			
		||||
  @supports(display: grid) { // hack to fix Chrome <57
 | 
			
		||||
    contain: strict;
 | 
			
		||||
@@ -1853,8 +1852,9 @@
 | 
			
		||||
  flex: 0 0 auto;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  text-align: start;
 | 
			
		||||
  text-align: unset;
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  z-index: 3;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  margin-top: 40px;
 | 
			
		||||
 | 
			
		||||
  @media screen and (max-width: 700px) {
 | 
			
		||||
  @media screen and (max-width: 740px) {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
  }
 | 
			
		||||
@@ -13,8 +13,9 @@
 | 
			
		||||
  margin: 100px auto;
 | 
			
		||||
  margin-bottom: 50px;
 | 
			
		||||
 | 
			
		||||
  @media screen and (max-width: 360px) {
 | 
			
		||||
  @media screen and (max-width: 400px) {
 | 
			
		||||
    margin: 30px auto;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  h1 {
 | 
			
		||||
@@ -42,3 +43,54 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.account-header {
 | 
			
		||||
  width: 400px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  line-height: 18px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  padding: 20px 0;
 | 
			
		||||
  padding-bottom: 0;
 | 
			
		||||
  margin-bottom: -30px;
 | 
			
		||||
  margin-top: 40px;
 | 
			
		||||
 | 
			
		||||
  @media screen and (max-width: 400px) {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    padding-bottom: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .avatar {
 | 
			
		||||
    width: 40px;
 | 
			
		||||
    height: 40px;
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
 | 
			
		||||
    img {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      display: block;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .name {
 | 
			
		||||
    flex: 1 1 auto;
 | 
			
		||||
    color: $ui-secondary-color;
 | 
			
		||||
 | 
			
		||||
    .username {
 | 
			
		||||
      display: block;
 | 
			
		||||
      font-weight: 500;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .logout-link {
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 32px;
 | 
			
		||||
    line-height: 40px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -317,7 +317,7 @@ code {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flash-message {
 | 
			
		||||
  background: $ui-base-color;
 | 
			
		||||
  background: lighten($ui-base-color, 8%);
 | 
			
		||||
  color: $ui-primary-color;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  padding: 15px 10px;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user