about summary refs log tree commit diff
path: root/app/javascript/flavours
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours')
-rw-r--r--app/javascript/flavours/glitch/components/admin/Counter.js6
-rw-r--r--app/javascript/flavours/glitch/components/media_attachments.js119
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js12
-rw-r--r--app/javascript/flavours/glitch/features/report/components/status_check_box.js49
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js20
-rw-r--r--app/javascript/flavours/glitch/locales/af.js7
-rw-r--r--app/javascript/flavours/glitch/locales/ckb.js7
-rw-r--r--app/javascript/flavours/glitch/locales/es-MX.js7
-rw-r--r--app/javascript/flavours/glitch/locales/gd.js7
-rw-r--r--app/javascript/flavours/glitch/locales/kw.js7
-rw-r--r--app/javascript/flavours/glitch/locales/pa.js7
-rw-r--r--app/javascript/flavours/glitch/locales/si.js7
-rw-r--r--app/javascript/flavours/glitch/locales/szl.js7
-rw-r--r--app/javascript/flavours/glitch/locales/tai.js7
-rw-r--r--app/javascript/flavours/glitch/locales/tt.js7
-rw-r--r--app/javascript/flavours/glitch/locales/ug.js7
-rw-r--r--app/javascript/flavours/glitch/styles/admin.scss50
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss35
-rw-r--r--app/javascript/flavours/glitch/styles/components/modal.scss41
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/mastodon-light/diff.scss22
-rw-r--r--app/javascript/flavours/glitch/styles/tables.scss18
22 files changed, 361 insertions, 90 deletions
diff --git a/app/javascript/flavours/glitch/components/admin/Counter.js b/app/javascript/flavours/glitch/components/admin/Counter.js
index 2bc9ce482..ecb242950 100644
--- a/app/javascript/flavours/glitch/components/admin/Counter.js
+++ b/app/javascript/flavours/glitch/components/admin/Counter.js
@@ -68,12 +68,12 @@ export default class Counter extends React.PureComponent {
       );
     } else {
       const measure = data[0];
-      const percentChange = percIncrease(measure.previous_total * 1, measure.total * 1);
+      const percentChange = measure.previous_total && percIncrease(measure.previous_total * 1, measure.total * 1);
 
       content = (
         <React.Fragment>
-          <span className='sparkline__value__total'><FormattedNumber value={measure.total} /></span>
-          <span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>
+          <span className='sparkline__value__total'>{measure.human_value || <FormattedNumber value={measure.total} />}</span>
+          {measure.previous_total && (<span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>)}
         </React.Fragment>
       );
     }
diff --git a/app/javascript/flavours/glitch/components/media_attachments.js b/app/javascript/flavours/glitch/components/media_attachments.js
new file mode 100644
index 000000000..c8d133f09
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/media_attachments.js
@@ -0,0 +1,119 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { MediaGallery, Video, Audio } from 'flavours/glitch/util/async-components';
+import Bundle from 'flavours/glitch/features/ui/components/bundle';
+import noop from 'lodash/noop';
+
+export default class MediaAttachments extends ImmutablePureComponent {
+
+  static propTypes = {
+    status: ImmutablePropTypes.map.isRequired,
+    height: PropTypes.number,
+    width: PropTypes.number,
+    revealed: PropTypes.bool,
+  };
+
+  static defaultProps = {
+    height: 110,
+    width: 239,
+  };
+
+  updateOnProps = [
+    'status',
+  ];
+
+  renderLoadingMediaGallery = () => {
+    const { height, width } = this.props;
+
+    return (
+      <div className='media-gallery' style={{ height, width }} />
+    );
+  }
+
+  renderLoadingVideoPlayer = () => {
+    const { height, width } = this.props;
+
+    return (
+      <div className='video-player' style={{ height, width }} />
+    );
+  }
+
+  renderLoadingAudioPlayer = () => {
+    const { height, width } = this.props;
+
+    return (
+      <div className='audio-player' style={{ height, width }} />
+    );
+  }
+
+  render () {
+    const { status, width, height, revealed } = this.props;
+    const mediaAttachments = status.get('media_attachments');
+
+    if (mediaAttachments.size === 0) {
+      return null;
+    }
+
+    if (mediaAttachments.getIn([0, 'type']) === 'audio') {
+      const audio = mediaAttachments.get(0);
+
+      return (
+        <Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
+          {Component => (
+            <Component
+              src={audio.get('url')}
+              alt={audio.get('description')}
+              width={width}
+              height={height}
+              poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
+              backgroundColor={audio.getIn(['meta', 'colors', 'background'])}
+              foregroundColor={audio.getIn(['meta', 'colors', 'foreground'])}
+              accentColor={audio.getIn(['meta', 'colors', 'accent'])}
+              duration={audio.getIn(['meta', 'original', 'duration'], 0)}
+            />
+          )}
+        </Bundle>
+      );
+    } else if (mediaAttachments.getIn([0, 'type']) === 'video') {
+      const video = mediaAttachments.get(0);
+
+      return (
+        <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
+          {Component => (
+            <Component
+              preview={video.get('preview_url')}
+              frameRate={video.getIn(['meta', 'original', 'frame_rate'])}
+              blurhash={video.get('blurhash')}
+              src={video.get('url')}
+              alt={video.get('description')}
+              width={width}
+              height={height}
+              inline
+              sensitive={status.get('sensitive')}
+              revealed={revealed}
+              onOpenVideo={noop}
+            />
+          )}
+        </Bundle>
+      );
+    } else {
+      return (
+        <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
+          {Component => (
+            <Component
+              media={mediaAttachments}
+              sensitive={status.get('sensitive')}
+              defaultWidth={width}
+              revealed={revealed}
+              height={height}
+              onOpenMedia={noop}
+            />
+          )}
+        </Bundle>
+      );
+    }
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
index 142118cef..ede8907e5 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
@@ -40,7 +40,17 @@ class ColumnSettings extends React.PureComponent {
     }
   };
 
-  onSelect = mode => value => this.props.onChange(['tags', mode], value);
+  onSelect = mode => value => {
+    const oldValue = this.tags(mode);
+
+    // Prevent changes that add more than 4 tags, but allow removing
+    // tags that were already added before
+    if ((value.length > 4) && !(value < oldValue)) {
+      return;
+    }
+
+    this.props.onChange(['tags', mode], value);
+  };
 
   onToggle = () => {
     if (this.state.open && this.hasTags()) {
diff --git a/app/javascript/flavours/glitch/features/report/components/status_check_box.js b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
index adb5e77a7..76bf0eb85 100644
--- a/app/javascript/flavours/glitch/features/report/components/status_check_box.js
+++ b/app/javascript/flavours/glitch/features/report/components/status_check_box.js
@@ -1,14 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import noop from 'lodash/noop';
 import StatusContent from 'flavours/glitch/components/status_content';
-import { MediaGallery, Video } from 'flavours/glitch/util/async-components';
-import Bundle from 'flavours/glitch/features/ui/components/bundle';
 import Avatar from 'flavours/glitch/components/avatar';
 import DisplayName from 'flavours/glitch/components/display_name';
 import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
 import Option from './option';
+import MediaAttachments from 'flavours/glitch/components/media_attachments';
 
 export default class StatusCheckBox extends React.PureComponent {
 
@@ -27,53 +25,10 @@ export default class StatusCheckBox extends React.PureComponent {
   render () {
     const { status, checked } = this.props;
 
-    let media = null;
-
     if (status.get('reblog')) {
       return null;
     }
 
-    if (status.get('media_attachments').size > 0) {
-      if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
-
-      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
-        const video = status.getIn(['media_attachments', 0]);
-
-        media = (
-          <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
-            {Component => (
-              <Component
-                preview={video.get('preview_url')}
-                blurhash={video.get('blurhash')}
-                src={video.get('url')}
-                alt={video.get('description')}
-                width={239}
-                height={110}
-                inline
-                sensitive={status.get('sensitive')}
-                revealed={false}
-                onOpenVideo={noop}
-              />
-            )}
-          </Bundle>
-        );
-      } else {
-        media = (
-          <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
-            {Component => (
-              <Component
-                media={status.get('media_attachments')}
-                sensitive={status.get('sensitive')}
-                revealed={false}
-                height={110}
-                onOpenMedia={noop}
-              />
-            )}
-          </Bundle>
-        );
-      }
-    }
-
     const labelComponent = (
       <div className='status-check-box__status poll__option__text'>
         <div className='detailed-status__display-name'>
@@ -84,7 +39,7 @@ export default class StatusCheckBox extends React.PureComponent {
           <div><DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} /></div>
         </div>
 
-        <StatusContent status={status} media={media} />
+        <StatusContent status={status} media={<MediaAttachments status={status} revealed={false} />} />
       </div>
     );
 
diff --git a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js
index 198443221..8fd528da0 100644
--- a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js
@@ -9,6 +9,7 @@ import escapeTextContentForBrowser from 'escape-html';
 import InlineAccount from 'flavours/glitch/components/inline_account';
 import IconButton from 'flavours/glitch/components/icon_button';
 import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
+import MediaAttachments from 'flavours/glitch/components/media_attachments';
 
 const mapStateToProps = (state, { statusId }) => ({
   versions: state.getIn(['history', statusId, 'items']),
@@ -70,6 +71,25 @@ class CompareHistoryModal extends React.PureComponent {
             )}
 
             <div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
+
+            {!!currentVersion.get('poll') && (
+              <div className='poll'>
+                <ul>
+                  {currentVersion.getIn(['poll', 'options']).map(option => (
+                    <li key={option.get('title')}>
+                      <span className='poll__input disabled' />
+
+                      <span
+                        className='poll__option__text translate'
+                        dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
+                      />
+                    </li>
+                  ))}
+                </ul>
+              </div>
+            )}
+
+            <MediaAttachments status={currentVersion} />
           </div>
         </div>
       </div>
diff --git a/app/javascript/flavours/glitch/locales/af.js b/app/javascript/flavours/glitch/locales/af.js
new file mode 100644
index 000000000..4c97b644a
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/af.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/af.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/ckb.js b/app/javascript/flavours/glitch/locales/ckb.js
new file mode 100644
index 000000000..c2e177d5f
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/ckb.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/ckb.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/es-MX.js b/app/javascript/flavours/glitch/locales/es-MX.js
new file mode 100644
index 000000000..eaefa20ef
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/es-MX.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/es-MX.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/gd.js b/app/javascript/flavours/glitch/locales/gd.js
new file mode 100644
index 000000000..604ee86dc
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/gd.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/gd.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/kw.js b/app/javascript/flavours/glitch/locales/kw.js
new file mode 100644
index 000000000..1325ca825
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/kw.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/kw.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/pa.js b/app/javascript/flavours/glitch/locales/pa.js
new file mode 100644
index 000000000..c3e0e2b84
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/pa.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/pa.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/si.js b/app/javascript/flavours/glitch/locales/si.js
new file mode 100644
index 000000000..d43266254
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/si.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/si.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/szl.js b/app/javascript/flavours/glitch/locales/szl.js
new file mode 100644
index 000000000..0b50afe45
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/szl.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/szl.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/tai.js b/app/javascript/flavours/glitch/locales/tai.js
new file mode 100644
index 000000000..f26cec5bd
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/tai.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/tai.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/tt.js b/app/javascript/flavours/glitch/locales/tt.js
new file mode 100644
index 000000000..ff74f6c29
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/tt.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/tt.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/locales/ug.js b/app/javascript/flavours/glitch/locales/ug.js
new file mode 100644
index 000000000..ab7ee0761
--- /dev/null
+++ b/app/javascript/flavours/glitch/locales/ug.js
@@ -0,0 +1,7 @@
+import inherited from 'mastodon/locales/ug.json';
+
+const messages = {
+  //  No translations available.
+};
+
+export default Object.assign({}, inherited, messages);
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 0873ac300..40cd899b3 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -367,6 +367,21 @@ body,
     }
   }
 
+  .positive-hint,
+  .negative-hint,
+  .neutral-hint {
+    a {
+      color: inherit;
+      text-decoration: underline;
+
+      &:focus,
+      &:hover,
+      &:active {
+        text-decoration: none;
+      }
+    }
+  }
+
   .positive-hint {
     color: $valid-value-color;
     font-weight: 500;
@@ -1612,3 +1627,38 @@ a.sparkline {
     }
   }
 }
+
+.availability-indicator {
+  display: flex;
+  align-items: center;
+  margin-bottom: 30px;
+  font-size: 14px;
+  line-height: 21px;
+
+  &__hint {
+    padding: 0 15px;
+  }
+
+  &__graphic {
+    display: flex;
+    margin: 0 -2px;
+
+    &__item {
+      display: block;
+      flex: 0 0 auto;
+      width: 4px;
+      height: 21px;
+      background: lighten($ui-base-color, 8%);
+      margin: 0 2px;
+      border-radius: 2px;
+
+      &.positive {
+        background: $valid-value-color;
+      }
+
+      &.negative {
+        background: $error-value-color;
+      }
+    }
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 7364eba91..016e31c63 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1563,41 +1563,6 @@ button.icon-button.active i.fa-retweet {
   filter: none;
 }
 
-.compare-history-modal {
-  .report-modal__target {
-    border-bottom: 1px solid $ui-secondary-color;
-  }
-
-  &__container {
-    padding: 30px;
-    pointer-events: all;
-  }
-
-  .status__content {
-    color: $inverted-text-color;
-    font-size: 19px;
-    line-height: 24px;
-
-    .emojione {
-      width: 24px;
-      height: 24px;
-      margin: -1px 0 0;
-    }
-
-    a {
-      color: $highlight-text-color;
-    }
-
-    hr {
-      height: 0.25rem;
-      padding: 0;
-      background-color: $ui-secondary-color;
-      border: 0;
-      margin: 20px 0;
-    }
-  }
-}
-
 .loading-bar {
   background-color: $ui-highlight-color;
   height: 3px;
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index ae1afc320..7e6918356 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -1041,6 +1041,47 @@
   }
 }
 
+.compare-history-modal {
+  .report-modal__target {
+    border-bottom: 1px solid $ui-secondary-color;
+  }
+
+  &__container {
+    padding: 30px;
+    pointer-events: all;
+  }
+
+  .status__content {
+    color: $inverted-text-color;
+    font-size: 19px;
+    line-height: 24px;
+
+    .emojione {
+      width: 24px;
+      height: 24px;
+      margin: -1px 0 0;
+    }
+
+    a {
+      color: $highlight-text-color;
+    }
+
+    hr {
+      height: 0.25rem;
+      padding: 0;
+      background-color: $ui-secondary-color;
+      border: 0;
+      margin: 20px 0;
+    }
+  }
+
+  .media-gallery,
+  .audio-player,
+  .video-player {
+    margin-top: 15px;
+  }
+}
+
 .embed-modal {
   width: auto;
   max-width: 80vw;
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 77541ab74..b9dd3107b 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -542,7 +542,7 @@
   .media-gallery,
   .audio-player,
   .video-player {
-    margin-top: 8px;
+    margin-top: 15px;
     max-width: 250px;
   }
 
diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index 8f5309f2b..020d39aff 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -116,7 +116,8 @@
 }
 
 .dropdown-menu__item {
-  a {
+  a,
+  button {
     background: $ui-base-color;
     color: $ui-secondary-color;
   }
@@ -172,7 +173,15 @@
   }
 }
 
-.dropdown-menu__separator {
+.dropdown-menu__separator,
+.dropdown-menu__item.edited-timestamp__history__item,
+.dropdown-menu__container__header,
+.compare-history-modal .report-modal__target,
+.report-dialog-modal .poll__option.dialog-option {
+  border-bottom-color: lighten($ui-base-color, 12%);
+}
+
+.report-dialog-modal__container {
   border-bottom-color: lighten($ui-base-color, 12%);
 }
 
@@ -229,15 +238,22 @@
 .mute-modal,
 .block-modal,
 .report-modal,
+.report-dialog-modal,
 .embed-modal,
 .error-modal,
 .onboarding-modal,
+.compare-history-modal,
 .report-modal__comment .setting-text__wrapper,
-.report-modal__comment .setting-text {
+.report-modal__comment .setting-text,
+.report-dialog-modal__textarea {
   background: $white;
   border: 1px solid lighten($ui-base-color, 8%);
 }
 
+.report-dialog-modal .dialog-option .poll__input {
+  color: $white;
+}
+
 .report-modal__comment {
   border-right-color: lighten($ui-base-color, 8%);
 }
diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss
index 8b5933b7b..598c67034 100644
--- a/app/javascript/flavours/glitch/styles/tables.scss
+++ b/app/javascript/flavours/glitch/styles/tables.scss
@@ -65,6 +65,24 @@
     }
   }
 
+  &.horizontal-table {
+    border-collapse: collapse;
+    border-style: hidden;
+
+    & > tbody > tr > th,
+    & > tbody > tr > td {
+      padding: 11px 10px;
+      background: transparent;
+      border: 1px solid lighten($ui-base-color, 8%);
+      color: $secondary-text-color;
+    }
+
+    & > tbody > tr > th {
+      color: $darker-text-color;
+      font-weight: 600;
+    }
+  }
+
   &.batch-table {
     & > thead > tr > th {
       background: $ui-base-color;