about summary refs log tree commit diff
path: root/app/javascript/mastodon/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/mastodon/components')
-rw-r--r--app/javascript/mastodon/components/account.js23
-rw-r--r--app/javascript/mastodon/components/autosuggest_textarea.js4
-rw-r--r--app/javascript/mastodon/components/avatar.js1
-rw-r--r--app/javascript/mastodon/components/avatar_overlay.js4
-rw-r--r--app/javascript/mastodon/components/column.js6
-rw-r--r--app/javascript/mastodon/components/column_back_button.js3
-rw-r--r--app/javascript/mastodon/components/column_back_button_slim.js8
-rw-r--r--app/javascript/mastodon/components/column_header.js67
-rw-r--r--app/javascript/mastodon/components/icon_button.js26
-rw-r--r--app/javascript/mastodon/components/media_gallery.js3
-rw-r--r--app/javascript/mastodon/components/status.js3
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js3
-rw-r--r--app/javascript/mastodon/components/status_content.js3
-rw-r--r--app/javascript/mastodon/components/status_list.js2
14 files changed, 137 insertions, 19 deletions
diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js
index d614a52c9..7cdb8c672 100644
--- a/app/javascript/mastodon/components/account.js
+++ b/app/javascript/mastodon/components/account.js
@@ -14,6 +14,8 @@ const messages = defineMessages({
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
   unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+  mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'You are not currently muting notifications from @{name}. Click to mute notifications' },
+  unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'You are currently muting notifications from @{name}. Click to unmute notifications' },
 });
 
 @injectIntl
@@ -41,6 +43,14 @@ export default class Account extends ImmutablePureComponent {
     this.props.onMute(this.props.account);
   }
 
+  handleMuteNotifications = () => {
+    this.props.onMuteNotifications(this.props.account, true);
+  }
+
+  handleUnmuteNotifications = () => {
+    this.props.onMuteNotifications(this.props.account, false);
+  }
+
   render () {
     const { account, me, intl, hidden } = this.props;
 
@@ -70,7 +80,18 @@ export default class Account extends ImmutablePureComponent {
       } else if (blocking) {
         buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
       } else if (muting) {
-        buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
+        let hidingNotificationsButton;
+        if (muting.get('notifications')) {
+          hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username')  })} onClick={this.handleUnmuteNotifications} />;
+        } else {
+          hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username')  })} onClick={this.handleMuteNotifications} />;
+        }
+        buttons = (
+          <div>
+            <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />
+            {hidingNotificationsButton}
+          </div>
+        );
       } else {
         buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
       }
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index 14a8d4c38..a065ac988 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -11,8 +11,8 @@ import classNames from 'classnames';
 const textAtCursorMatchesToken = (str, caretPosition) => {
   let word;
 
-  let left  = str.slice(0, caretPosition).search(/\S+$/);
-  let right = str.slice(caretPosition).search(/\s/);
+  let left  = str.slice(0, caretPosition).search(/[^\s\u200B]+$/);
+  let right = str.slice(caretPosition).search(/[\s\u200B]/);
 
   if (right < 0) {
     word = str.slice(left);
diff --git a/app/javascript/mastodon/components/avatar.js b/app/javascript/mastodon/components/avatar.js
index f7c484ee3..dd155f059 100644
--- a/app/javascript/mastodon/components/avatar.js
+++ b/app/javascript/mastodon/components/avatar.js
@@ -64,6 +64,7 @@ export default class Avatar extends React.PureComponent {
         onMouseEnter={this.handleMouseEnter}
         onMouseLeave={this.handleMouseLeave}
         style={style}
+        data-avatar-of={`@${account.get('acct')}`}
       />
     );
   }
diff --git a/app/javascript/mastodon/components/avatar_overlay.js b/app/javascript/mastodon/components/avatar_overlay.js
index f5d67b34e..2ecf9fa44 100644
--- a/app/javascript/mastodon/components/avatar_overlay.js
+++ b/app/javascript/mastodon/components/avatar_overlay.js
@@ -21,8 +21,8 @@ export default class AvatarOverlay extends React.PureComponent {
 
     return (
       <div className='account__avatar-overlay'>
-        <div className='account__avatar-overlay-base' style={baseStyle} />
-        <div className='account__avatar-overlay-overlay' style={overlayStyle} />
+        <div className='account__avatar-overlay-base' style={baseStyle} data-avatar-of={`@${account.get('acct')}`} />
+        <div className='account__avatar-overlay-overlay' style={overlayStyle} data-avatar-of={`@${friend.get('acct')}`} />
       </div>
     );
   }
diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js
index e81236d26..2e1467595 100644
--- a/app/javascript/mastodon/components/column.js
+++ b/app/javascript/mastodon/components/column.js
@@ -7,6 +7,8 @@ export default class Column extends React.PureComponent {
 
   static propTypes = {
     children: PropTypes.node,
+    extraClasses: PropTypes.string,
+    name: PropTypes.string,
   };
 
   scrollTop () {
@@ -40,10 +42,10 @@ export default class Column extends React.PureComponent {
   }
 
   render () {
-    const { children } = this.props;
+    const { children, extraClasses, name } = this.props;
 
     return (
-      <div role='region' className='column' ref={this.setRef}>
+      <div role='region' data-column={name} className={`column ${extraClasses || ''}`} ref={this.setRef}>
         {children}
       </div>
     );
diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js
index 8a60c4192..50c3bf11f 100644
--- a/app/javascript/mastodon/components/column_back_button.js
+++ b/app/javascript/mastodon/components/column_back_button.js
@@ -9,7 +9,8 @@ export default class ColumnBackButton extends React.PureComponent {
   };
 
   handleClick = () => {
-    if (window.history && window.history.length === 1) {
+    // if history is exhausted, or we would leave mastodon, just go to root.
+    if (window.history && (window.history.length === 1 || window.history.length === window._mastoInitialHistoryLen)) {
       this.context.router.history.push('/');
     } else {
       this.context.router.history.goBack();
diff --git a/app/javascript/mastodon/components/column_back_button_slim.js b/app/javascript/mastodon/components/column_back_button_slim.js
index 3b4f46d99..2cdf1b25b 100644
--- a/app/javascript/mastodon/components/column_back_button_slim.js
+++ b/app/javascript/mastodon/components/column_back_button_slim.js
@@ -9,8 +9,12 @@ export default class ColumnBackButtonSlim extends React.PureComponent {
   };
 
   handleClick = () => {
-    if (window.history && window.history.length === 1) this.context.router.history.push('/');
-    else this.context.router.history.goBack();
+    // if history is exhausted, or we would leave mastodon, just go to root.
+    if (window.history && (window.history.length === 1 || window.history.length === window._mastoInitialHistoryLen)) {
+      this.context.router.history.push('/');
+    } else {
+      this.context.router.history.goBack();
+    }
   }
 
   render () {
diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js
index e4fa8fa7a..c47296a51 100644
--- a/app/javascript/mastodon/components/column_header.js
+++ b/app/javascript/mastodon/components/column_header.js
@@ -1,13 +1,18 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import classNames from 'classnames';
-import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
+import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+// Glitch imports
+import NotificationPurgeButtonsContainer from '../../glitch/components/column/notif_cleaning_widget/container';
 
 const messages = defineMessages({
   show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
   hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
   moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' },
   moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
+  enterNotifCleaning : { id: 'notification_purge.start', defaultMessage: 'Enter notification cleaning mode' },
 });
 
 @injectIntl
@@ -22,14 +27,19 @@ export default class ColumnHeader extends React.PureComponent {
     title: PropTypes.node.isRequired,
     icon: PropTypes.string.isRequired,
     active: PropTypes.bool,
+    localSettings : ImmutablePropTypes.map,
     multiColumn: PropTypes.bool,
     focusable: PropTypes.bool,
     showBackButton: PropTypes.bool,
+    notifCleaning: PropTypes.bool, // true only for the notification column
+    notifCleaningActive: PropTypes.bool,
+    onEnterCleaningMode: PropTypes.func,
     children: PropTypes.node,
     pinned: PropTypes.bool,
     onPin: PropTypes.func,
     onMove: PropTypes.func,
     onClick: PropTypes.func,
+    intl: PropTypes.object.isRequired,
   };
 
   static defaultProps = {
@@ -39,6 +49,7 @@ export default class ColumnHeader extends React.PureComponent {
   state = {
     collapsed: true,
     animating: false,
+    animatingNCD: false,
   };
 
   handleToggleClick = (e) => {
@@ -59,17 +70,32 @@ export default class ColumnHeader extends React.PureComponent {
   }
 
   handleBackClick = () => {
-    if (window.history && window.history.length === 1) this.context.router.history.push('/');
-    else this.context.router.history.goBack();
+    // if history is exhausted, or we would leave mastodon, just go to root.
+    if (window.history && (window.history.length === 1 || window.history.length === window._mastoInitialHistoryLen)) {
+      this.context.router.history.push('/');
+    } else {
+      this.context.router.history.goBack();
+    }
   }
 
   handleTransitionEnd = () => {
     this.setState({ animating: false });
   }
 
+  handleTransitionEndNCD = () => {
+    this.setState({ animatingNCD: false });
+  }
+
+  onEnterCleaningMode = () => {
+    this.setState({ animatingNCD: true });
+    this.props.onEnterCleaningMode(!this.props.notifCleaningActive);
+  }
+
   render () {
-    const { title, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage } } = this.props;
-    const { collapsed, animating } = this.state;
+    const { intl, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive } = this.props;
+    const { collapsed, animating, animatingNCD } = this.state;
+
+    let title = this.props.title;
 
     const wrapperClassName = classNames('column-header__wrapper', {
       'active': active,
@@ -88,8 +114,20 @@ export default class ColumnHeader extends React.PureComponent {
       'active': !collapsed,
     });
 
+    const notifCleaningButtonClassName = classNames('column-header__button', {
+      'active': notifCleaningActive,
+    });
+
+    const notifCleaningDrawerClassName = classNames('ncd column-header__collapsible', {
+      'collapsed': !notifCleaningActive,
+      'animating': animatingNCD,
+    });
+
     let extraContent, pinButton, moveButtons, backButton, collapseButton;
 
+    //*glitch
+    const msgEnterNotifCleaning = intl.formatMessage(messages.enterNotifCleaning);
+
     if (children) {
       extraContent = (
         <div key='extra-content' className='column-header__collapsible__extra'>
@@ -138,13 +176,30 @@ export default class ColumnHeader extends React.PureComponent {
         <h1 tabIndex={focusable ? 0 : null} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
           <i className={`fa fa-fw fa-${icon} column-header__icon`} />
           {title}
-
           <div className='column-header__buttons'>
             {backButton}
+            { notifCleaning ? (
+              <button
+                aria-label={msgEnterNotifCleaning}
+                title={msgEnterNotifCleaning}
+                onClick={this.onEnterCleaningMode}
+                className={notifCleaningButtonClassName}
+              >
+                <i className='fa fa-eraser' />
+              </button>
+            ) : null}
             {collapseButton}
           </div>
         </h1>
 
+        { notifCleaning ? (
+          <div className={notifCleaningDrawerClassName} onTransitionEnd={this.handleTransitionEndNCD}>
+            <div className='column-header__collapsible-inner nopad-drawer'>
+              {(notifCleaningActive || animatingNCD) ? (<NotificationPurgeButtonsContainer />) : null }
+            </div>
+          </div>
+        ) : null}
+
         <div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}>
           <div className='column-header__collapsible-inner'>
             {(!collapsed || animating) && collapsedContent}
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
index d8e445cef..651b89566 100644
--- a/app/javascript/mastodon/components/icon_button.js
+++ b/app/javascript/mastodon/components/icon_button.js
@@ -20,8 +20,10 @@ export default class IconButton extends React.PureComponent {
     disabled: PropTypes.bool,
     inverted: PropTypes.bool,
     animate: PropTypes.bool,
+    flip: PropTypes.bool,
     overlay: PropTypes.bool,
     tabIndex: PropTypes.string,
+    label: PropTypes.string,
   };
 
   static defaultProps = {
@@ -42,14 +44,18 @@ export default class IconButton extends React.PureComponent {
   }
 
   render () {
-    const style = {
+    let style = {
       fontSize: `${this.props.size}px`,
-      width: `${this.props.size * 1.28571429}px`,
       height: `${this.props.size * 1.28571429}px`,
       lineHeight: `${this.props.size}px`,
       ...this.props.style,
       ...(this.props.active ? this.props.activeStyle : {}),
     };
+    if (!this.props.label) {
+      style.width = `${this.props.size * 1.28571429}px`;
+    } else {
+      style.textAlign = 'left';
+    }
 
     const {
       active,
@@ -72,6 +78,21 @@ export default class IconButton extends React.PureComponent {
       overlayed: overlay,
     });
 
+    const flipDeg = this.props.flip ? -180 : -360;
+    const rotateDeg = this.props.active ? flipDeg : 0;
+
+    const motionDefaultStyle = {
+      rotate: rotateDeg,
+    };
+
+    const springOpts = {
+      stiffness: this.props.flip ? 60 : 120,
+      damping: 7,
+    };
+    const motionStyle = {
+      rotate: this.props.animate ? spring(rotateDeg, springOpts) : 0,
+    };
+
     return (
       <Motion defaultStyle={{ rotate: active ? -360 : 0 }} style={{ rotate: animate ? spring(active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}>
         {({ rotate }) =>
@@ -86,6 +107,7 @@ export default class IconButton extends React.PureComponent {
             tabIndex={tabIndex}
           >
             <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
+            {this.props.label}
           </button>
         }
       </Motion>
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index fb71d8c5c..83cf8b871 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -1,3 +1,6 @@
+//  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
+//  SEE INSTEAD : glitch/components/status/gallery
+
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 70005436b..b9be20033 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -1,3 +1,6 @@
+//  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
+//  SEE INSTEAD : glitch/components/status
+
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index e952733f3..af152cc32 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -1,3 +1,6 @@
+//  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
+//  SEE INSTEAD : glitch/components/status/action_bar
+
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 63ce25865..8ad60b9d6 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -1,3 +1,6 @@
+//  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
+//  SEE INSTEAD : glitch/components/status/content
+
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index 58a7b228a..214955591 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import StatusContainer from '../containers/status_container';
+import StatusContainer from '../../glitch/components/status/container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import ScrollableList from './scrollable_list';