about summary refs log tree commit diff
path: root/app/javascript/flavours
diff options
context:
space:
mode:
authorThibG <thib@sitedethib.com>2020-06-17 18:28:28 +0200
committerGitHub <noreply@github.com>2020-06-17 18:28:28 +0200
commitc7da2cc5a1821e589e7241aec21d8bb426722c2a (patch)
treee61b37ed8d8c5a8446cf656600c90c85ccdc983c /app/javascript/flavours
parent0a5cf075cdddbbe40e79ebad8ee2b1abd62d3200 (diff)
parent9a641a5a0e7a8d45bf6d3b8e4bfdc269def2dc06 (diff)
Merge pull request #1358 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
Diffstat (limited to 'app/javascript/flavours')
-rw-r--r--app/javascript/flavours/glitch/components/scrollable_list.js5
-rw-r--r--app/javascript/flavours/glitch/components/timeline_hint.js18
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js28
-rw-r--r--app/javascript/flavours/glitch/features/audio/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/followers/index.js26
-rw-r--r--app/javascript/flavours/glitch/features/following/index.js26
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss25
-rw-r--r--app/javascript/flavours/glitch/util/emoji/index.js15
8 files changed, 132 insertions, 12 deletions
diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js
index c022290a4..fae0a7393 100644
--- a/app/javascript/flavours/glitch/components/scrollable_list.js
+++ b/app/javascript/flavours/glitch/components/scrollable_list.js
@@ -32,6 +32,7 @@ export default class ScrollableList extends PureComponent {
     hasMore: PropTypes.bool,
     numPending: PropTypes.number,
     prepend: PropTypes.node,
+    append: PropTypes.node,
     alwaysPrepend: PropTypes.bool,
     emptyMessage: PropTypes.node,
     children: PropTypes.node,
@@ -272,7 +273,7 @@ export default class ScrollableList extends PureComponent {
   }
 
   render () {
-    const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
+    const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props;
     const { fullscreen } = this.state;
     const childrenCount = React.Children.count(children);
 
@@ -319,6 +320,8 @@ export default class ScrollableList extends PureComponent {
             ))}
 
             {loadMore}
+
+            {!hasMore && append}
           </div>
         </div>
       );
diff --git a/app/javascript/flavours/glitch/components/timeline_hint.js b/app/javascript/flavours/glitch/components/timeline_hint.js
new file mode 100644
index 000000000..fb55a62cc
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/timeline_hint.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+
+const TimelineHint = ({ resource, url }) => (
+  <div className='timeline-hint'>
+    <strong><FormattedMessage id='timeline_hint.remote_resource_not_displayed' defaultMessage='{resource} from other servers are not displayed.' values={{ resource }} /></strong>
+    <br />
+    <a href={url} target='_blank'><FormattedMessage id='account.browse_more_on_origin_server' defaultMessage='Browse more on the original profile' /></a>
+  </div>
+);
+
+TimelineHint.propTypes = {
+  resource: PropTypes.node.isRequired,
+  url: PropTypes.string.isRequired,
+};
+
+export default TimelineHint;
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index f25c82a00..a8e8aa7a8 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -15,11 +15,14 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { FormattedMessage } from 'react-intl';
 import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
 import MissingIndicator from 'flavours/glitch/components/missing_indicator';
+import TimelineHint from 'flavours/glitch/components/timeline_hint';
 
 const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
   const path = withReplies ? `${accountId}:with_replies` : accountId;
 
   return {
+    remote: !!state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username']),
+    remoteUrl: state.getIn(['accounts', accountId, 'url']),
     isAccount: !!state.getIn(['accounts', accountId]),
     statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
     featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
@@ -28,6 +31,14 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
   };
 };
 
+const RemoteHint = ({ url }) => (
+  <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.statuses' defaultMessage='Older toots' />} />
+);
+
+RemoteHint.propTypes = {
+  url: PropTypes.string.isRequired,
+};
+
 export default @connect(mapStateToProps)
 class AccountTimeline extends ImmutablePureComponent {
 
@@ -40,6 +51,8 @@ class AccountTimeline extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     withReplies: PropTypes.bool,
     isAccount: PropTypes.bool,
+    remote: PropTypes.bool,
+    remoteUrl: PropTypes.string,
     multiColumn: PropTypes.bool,
   };
 
@@ -78,7 +91,7 @@ class AccountTimeline extends ImmutablePureComponent {
   }
 
   render () {
-    const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount, multiColumn } = this.props;
+    const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount, multiColumn, remote, remoteUrl } = this.props;
 
     if (!isAccount) {
       return (
@@ -97,6 +110,16 @@ class AccountTimeline extends ImmutablePureComponent {
       );
     }
 
+    let emptyMessage;
+
+    if (remote && statusIds.isEmpty()) {
+      emptyMessage = <RemoteHint url={remoteUrl} />;
+    } else {
+      emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
+    }
+
+    const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
+
     return (
       <Column ref={this.setRef} name='account'>
         <ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
@@ -104,13 +127,14 @@ class AccountTimeline extends ImmutablePureComponent {
         <StatusList
           prepend={<HeaderContainer accountId={this.props.params.accountId} />}
           alwaysPrepend
+          append={remoteMessage}
           scrollKey='account_timeline'
           statusIds={statusIds}
           featuredStatusIds={featuredStatusIds}
           isLoading={isLoading}
           hasMore={hasMore}
           onLoadMore={this.handleLoadMore}
-          emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />}
+          emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
           timelineId='account'
         />
diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js
index 49e91227f..ba3534492 100644
--- a/app/javascript/flavours/glitch/features/audio/index.js
+++ b/app/javascript/flavours/glitch/features/audio/index.js
@@ -125,6 +125,7 @@ class Audio extends React.PureComponent {
         this.wavesurfer.createPeakCache();
         this.wavesurfer.load(this.props.src);
         this.wavesurfer.toggleInteraction();
+        this.wavesurfer.setVolume(this.state.volume);
         this.loaded = true;
       }
 
diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js
index bf41f3b98..8ae46be94 100644
--- a/app/javascript/flavours/glitch/features/followers/index.js
+++ b/app/javascript/flavours/glitch/features/followers/index.js
@@ -17,14 +17,25 @@ import HeaderContainer from 'flavours/glitch/features/account_timeline/container
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import MissingIndicator from 'flavours/glitch/components/missing_indicator';
 import ScrollableList from 'flavours/glitch/components/scrollable_list';
+import TimelineHint from 'flavours/glitch/components/timeline_hint';
 
 const mapStateToProps = (state, props) => ({
+  remote: !!state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username']),
+  remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
   isAccount: !!state.getIn(['accounts', props.params.accountId]),
   accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
   hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
   isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true),
 });
 
+const RemoteHint = ({ url }) => (
+  <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.followers' defaultMessage='Followers' />} />
+);
+
+RemoteHint.propTypes = {
+  url: PropTypes.string.isRequired,
+};
+
 export default @connect(mapStateToProps)
 class Followers extends ImmutablePureComponent {
 
@@ -35,6 +46,8 @@ class Followers extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     isLoading: PropTypes.bool,
     isAccount: PropTypes.bool,
+    remote: PropTypes.bool,
+    remoteUrl: PropTypes.string,
     multiColumn: PropTypes.bool,
   };
 
@@ -65,7 +78,7 @@ class Followers extends ImmutablePureComponent {
   }
 
   render () {
-    const { accountIds, hasMore, isAccount, multiColumn, isLoading } = this.props;
+    const { accountIds, hasMore, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
 
     if (!isAccount) {
       return (
@@ -83,7 +96,15 @@ class Followers extends ImmutablePureComponent {
       );
     }
 
-    const emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
+    let emptyMessage;
+
+    if (remote && accountIds.isEmpty()) {
+      emptyMessage = <RemoteHint url={remoteUrl} />;
+    } else {
+      emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
+    }
+
+    const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
 
     return (
       <Column ref={this.setRef}>
@@ -96,6 +117,7 @@ class Followers extends ImmutablePureComponent {
           onLoadMore={this.handleLoadMore}
           prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
           alwaysPrepend
+          append={remoteMessage}
           emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
         >
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js
index f090900cc..e06eaa8a6 100644
--- a/app/javascript/flavours/glitch/features/following/index.js
+++ b/app/javascript/flavours/glitch/features/following/index.js
@@ -17,14 +17,25 @@ import HeaderContainer from 'flavours/glitch/features/account_timeline/container
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import MissingIndicator from 'flavours/glitch/components/missing_indicator';
 import ScrollableList from 'flavours/glitch/components/scrollable_list';
+import TimelineHint from 'flavours/glitch/components/timeline_hint';
 
 const mapStateToProps = (state, props) => ({
+  remote: !!state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username']),
+  remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
   isAccount: !!state.getIn(['accounts', props.params.accountId]),
   accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
   hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
   isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true),
 });
 
+const RemoteHint = ({ url }) => (
+  <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.follows' defaultMessage='Follows' />} />
+);
+
+RemoteHint.propTypes = {
+  url: PropTypes.string.isRequired,
+};
+
 export default @connect(mapStateToProps)
 class Following extends ImmutablePureComponent {
 
@@ -35,6 +46,8 @@ class Following extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     isLoading: PropTypes.bool,
     isAccount: PropTypes.bool,
+    remote: PropTypes.bool,
+    remoteUrl: PropTypes.string,
     multiColumn: PropTypes.bool,
   };
 
@@ -65,7 +78,7 @@ class Following extends ImmutablePureComponent {
   }
 
   render () {
-    const { accountIds, hasMore, isAccount, multiColumn, isLoading } = this.props;
+    const { accountIds, hasMore, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
 
     if (!isAccount) {
       return (
@@ -83,7 +96,15 @@ class Following extends ImmutablePureComponent {
       );
     }
 
-    const emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
+    let emptyMessage;
+
+    if (remote && accountIds.isEmpty()) {
+      emptyMessage = <RemoteHint url={remoteUrl} />;
+    } else {
+      emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
+    }
+
+    const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
 
     return (
       <Column ref={this.setRef}>
@@ -96,6 +117,7 @@ class Following extends ImmutablePureComponent {
           onLoadMore={this.handleLoadMore}
           prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
           alwaysPrepend
+          append={remoteMessage}
           emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
         >
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 50cea8b26..a37cef795 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1093,6 +1093,31 @@
   border-bottom: 1px solid lighten($ui-base-color, 8%);
 }
 
+.timeline-hint {
+  text-align: center;
+  color: $darker-text-color;
+  padding: 15px;
+  box-sizing: border-box;
+  width: 100%;
+  cursor: default;
+
+  strong {
+    font-weight: 500;
+  }
+
+  a {
+    color: lighten($ui-highlight-color, 8%);
+    text-decoration: none;
+
+    &:hover,
+    &:focus,
+    &:active {
+      text-decoration: underline;
+      color: lighten($ui-highlight-color, 12%);
+    }
+  }
+}
+
 .missing-indicator {
   padding-top: 20px + 48px;
 
diff --git a/app/javascript/flavours/glitch/util/emoji/index.js b/app/javascript/flavours/glitch/util/emoji/index.js
index 22b657b05..61f211c92 100644
--- a/app/javascript/flavours/glitch/util/emoji/index.js
+++ b/app/javascript/flavours/glitch/util/emoji/index.js
@@ -6,13 +6,18 @@ const trie = new Trie(Object.keys(unicodeMapping));
 
 const assetHost = process.env.CDN_HOST || '';
 
+// Convert to file names from emojis. (For different variation selector emojis)
+const emojiFilenames = (emojis) => {
+  return emojis.map(v => unicodeMapping[v].filename);
+};
+
 // Emoji requiring extra borders depending on theme
-const darkEmoji = '🎱🐜⚫🖤⬛◼️◾◼️✒️▪️💣🎳📷📸♣️🕶️✴️🔌💂‍♀️📽️🍳🦍💂🔪🕳️🕹️🕋🖊️🖋️💂‍♂️🎤🎓🎥🎼♠️🎩🦃📼📹🎮🐃🏴';
-const lightEmoji = '👽⚾🐔☁️💨🕊️👀🍥👻🐐❕❔⛸️🌩️🔊🔇📃🌧️🐏🍚🍙🐓🐑💀☠️🌨️🔉🔈💬💭🏐🏳️⚪⬜◽◻️▫️';
+const darkEmoji = emojiFilenames(['🎱', '🐜', '⚫', '🖤', '⬛', '◼️', '◾', '◼️', '✒️', '▪️', '💣', '🎳', '📷', '📸', '♣️', '🕶️', '✴️', '🔌', '💂‍♀️', '📽️', '🍳', '🦍', '💂', '🔪', '🕳️', '🕹️', '🕋', '🖊️', '🖋️', '💂‍♂️', '🎤', '🎓', '🎥', '🎼', '♠️', '🎩', '🦃', '📼', '📹', '🎮', '🐃', '🏴']);
+const lightEmoji = emojiFilenames(['👽', '⚾', '🐔', '☁️', '💨', '🕊️', '👀', '🍥', '👻', '🐐', '❕', '❔', '⛸️', '🌩️', '🔊', '🔇', '📃', '🌧️', '🐏', '🍚', '🍙', '🐓', '🐑', '💀', '☠️', '🌨️', '🔉', '🔈', '💬', '💭', '🏐', '🏳️', '⚪', '⬜', '◽', '◻️', '▫️']);
 
-const emojiFilename = (filename, match) => {
+const emojiFilename = (filename) => {
   const borderedEmoji = (document.body && document.body.classList.contains('skin-mastodon-light')) ? lightEmoji : darkEmoji;
-  return borderedEmoji.includes(match) ? (filename + '_border') : filename;
+  return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
 };
 
 const emojify = (str, customEmojis = {}) => {
@@ -69,7 +74,7 @@ const emojify = (str, customEmojis = {}) => {
     } else if (!useSystemEmojiFont) { // matched to unicode emoji
       const { filename, shortCode } = unicodeMapping[match];
       const title = shortCode ? `:${shortCode}:` : '';
-      replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename, match)}.svg" />`;
+      replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename)}.svg" />`;
       rend = i + match.length;
       // If the matched character was followed by VS15 (for selecting text presentation), skip it.
       if (str.codePointAt(rend) === 65038) {