about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/extras.jsx14
-rw-r--r--app/assets/stylesheets/stream_entries.scss363
-rw-r--r--app/controllers/api/oembed_controller.rb4
-rw-r--r--app/controllers/stream_entries_controller.rb5
-rw-r--r--app/helpers/stream_entries_helper.rb4
-rw-r--r--app/views/api/oembed/show.json.rabl2
-rw-r--r--app/views/stream_entries/_content_spoiler.html.haml3
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml36
-rw-r--r--app/views/stream_entries/_simple_status.html.haml28
-rw-r--r--app/views/stream_entries/_status.html.haml25
-rw-r--r--app/views/stream_entries/embed.html.haml2
-rw-r--r--config/i18n-tasks.yml1
-rw-r--r--config/locales/en.yml19
13 files changed, 318 insertions, 188 deletions
diff --git a/app/assets/javascripts/extras.jsx b/app/assets/javascripts/extras.jsx
index 93f827044..9fd769c0b 100644
--- a/app/assets/javascripts/extras.jsx
+++ b/app/assets/javascripts/extras.jsx
@@ -1,8 +1,20 @@
 import emojify from './components/emoji'
 
 $(() => {
-  $.each($('.entry .content, .name, .account__header__content'), (_, content) => {
+  $.each($('.entry .content, .entry .status__content, .display-name, .name, .account__header__content'), (_, content) => {
     const $content = $(content);
     $content.html(emojify($content.html()));
   });
+
+  $('.video-player video').on('click', e => {
+    if (e.target.paused) {
+      e.target.play();
+    } else {
+      e.target.pause();
+    }
+  });
+
+  $('.media-spoiler').on('click', e => {
+    $(e.target).hide();
+  });
 });
diff --git a/app/assets/stylesheets/stream_entries.scss b/app/assets/stylesheets/stream_entries.scss
index 4df03b794..5cd140aac 100644
--- a/app/assets/stylesheets/stream_entries.scss
+++ b/app/assets/stylesheets/stream_entries.scss
@@ -3,232 +3,281 @@
   box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
 
   .entry {
-    border-bottom: 1px solid #d9e1e8;
-    background: #fff;
-    border-left: 2px solid #fff;
+    .status.light, .detailed-status.light {
+      border-bottom: 1px solid #d9e1e8;
+    }
 
-    &.entry-reblog {
-      border-left-color: #2b90d9;
+    &:last-child {
+      .status.light, .detailed-status.light {
+        border-bottom: 0;
+        border-radius: 0 0 4px 4px;
+      }
     }
 
-    &.entry-predecessor, &.entry-successor {
-      background: #d9e1e8;
-      border-left-color: #d9e1e8;
-      border-bottom-color: darken(#d9e1e8, 10%);
+    &:first-child {
+      .status.light, .detailed-status.light {
+        border-radius: 4px 4px 0 0;
+      }
 
-      .header {
-        .header__right {
-          .counter-btn {
-            color: darken(#d9e1e8, 15%);
-          }
+      &:last-child {
+        .status.light, .detailed-status.light {
+          border-radius: 4px;
         }
       }
     }
+  }
 
-    &.entry-center {
-      border-bottom-color: darken(#d9e1e8, 10%);
-    }
+  .status.light {
+    padding: 14px 14px 14px (48px + 14px*2);
+    position: relative;
+    min-height: 48px;
+    cursor: default;
+    background: lighten(#d9e1e8, 8%);
 
-    &.entry-follow, &.entry-favourite {
-      .content {
-        padding-top: 10px;
-        padding-bottom: 10px;
+    .status__header {
+      font-size: 15px;
 
-        strong {
-          font-weight: 500;
+      .status__meta {
+        float: right;
+        font-size: 14px;
+
+        .status__relative-time {
+          color: #9baec8;
         }
       }
     }
 
-    &:last-child {
-      border-bottom: 0;
-      border-radius: 0 0 4px 4px;
+    .status__display-name {
+      display: block;
+      max-width: 100%;
+      padding-right: 25px;
+      color: #282c37;
     }
-  }
 
-  .entry:first-child {
-    border-radius: 4px 4px 0 0;
+    .status__avatar {
+      position: absolute;
+      left: 14px;
+      top: 14px;
+      width: 48px;
+      height: 48px;
 
-    &:last-child {
-      border-radius: 4px;
+      & > div {
+        width: 48px;
+        height: 48px;
+      }
+
+      img {
+        display: block;
+        border-radius: 4px;
+      }
     }
-  }
 
-  @media screen and (max-width: 700px) {
-    border-radius: 0;
-    box-shadow: none;
+    .display-name {
+      display: block;
+      max-width: 100%;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
 
-    .entry {
-      &:last-child {
-        border-radius: 0;
+      strong {
+        font-weight: 500;
+        color: #282c37;
       }
 
-      &:first-child {
-        border-radius: 0;
+      span {
+        font-size: 14px;
+        color: #9baec8;
+      }
+    }
 
-        &:last-child {
-          border-radius: 0;
-        }
+    .status__content {
+      color: #282c37;
+
+      a {
+        color: #2b90d9;
       }
     }
-  }
 
-  .entry__container {
-    overflow: hidden;
+    .status__attachments {
+      margin-top: 8px;
+      overflow: hidden;
+      width: 100%;
+      box-sizing: border-box;
+      height: 110px;
+      display: flex;
+    }
   }
 
-  .avatar {
-    width: 56px;
-    padding: 15px 10px;
-    padding-right: 5px;
-    float: left;
+  .detailed-status.light {
+    padding: 14px;
+    background: #fff;
+    cursor: default;
 
-    img {
-      width: 56px;
-      height: 56px;
+    .detailed-status__display-name {
       display: block;
-      border-radius: 4px;
-    }
-  }
+      overflow: hidden;
+      margin-bottom: 15px;
 
-  .entry__container__container {
-    margin-left: 71px;
-  }
+      & > div {
+        float: left;
+        margin-right: 10px;
+      }
 
-  .header {
-    margin-bottom: 10px;
-    padding: 15px;
-    padding-bottom: 0;
-    padding-left: 8px;
-    display: flex;
+      .display-name {
+        display: block;
+        max-width: 100%;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+
+        strong {
+          font-weight: 500;
+          color: #282c37;
+        }
 
-    .header__left {
-      flex: 1;
+        span {
+          font-size: 14px;
+          color: #9baec8;
+        }
+      }
     }
 
-    .header__right {
+    .avatar {
+      width: 48px;
+      height: 48px;
 
+      img {
+        display: block;
+        border-radius: 4px;
+      }
     }
 
-    .name {
-      text-decoration: none;
+    .status__content {
+      color: #282c37;
+
+      a {
+        color: #2b90d9;
+      }
+    }
+
+    .detailed-status__meta {
+      margin-top: 15px;
       color: #9baec8;
+      font-size: 14px;
+      line-height: 18px;
 
-      strong {
-        color: #282c37;
-        font-weight: 500;
+      a {
+        color: inherit;
       }
 
-      &:hover {
-        strong {
-          text-decoration: underline;
-        }
+      span > span {
+        font-weight: 500;
+        font-size: 12px;
+        margin-left: 6px;
+        display: inline-block;
       }
     }
-  }
-
-  .pre-header {
-    border-bottom: 1px solid #d9e1e8;
-    color: #2b90d9;
-    padding: 5px 10px;
-    padding-left: 8px;
-    clear: both;
 
-    .name {
-      color: #2b90d9;
-      font-weight: 500;
-      text-decoration: none;
+    .detailed-status__attachments {
+      margin-top: 8px;
+      overflow: hidden;
+      width: 100%;
+      box-sizing: border-box;
+      height: 300px;
+      display: flex;
+    }
 
-      &:hover {
-        text-decoration: underline;
+    .video-player {
+      margin-top: 8px;
+      height: 300px;
+      overflow: hidden;
+
+      video {
+        position: relative;
+        z-index: 1;
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+        top: 50%;
+        transform: translateY(-50%);
       }
     }
   }
 
-  .content {
-    font-size: 14px;
-    padding: 0 15px;
-    padding-left: 8px;
-    padding-bottom: 15px;
-    color: #282c37;
-    word-wrap: break-word;
-    overflow: hidden;
-    white-space: pre-wrap;
-
-    p {
-      margin-bottom: 18px;
+  .media-item, .video-item {
+    box-sizing: border-box;
+    position: relative;
+    left: auto;
+    top: auto;
+    right: auto;
+    bottom: auto;
+    float: left;
+    border: medium none;
+    display: block;
+    flex: 1 1 auto;
+    height: 100%;
+    margin-right: 2px;
 
-      &:last-child {
-        margin-bottom: 0;
-      }
+    &:last-child {
+      margin-right: 0;
     }
 
     a {
-      color: #2b90d9;
+      display: block;
+      width: 100%;
+      height: 100%;
+      background: no-repeat scroll center center / cover;
       text-decoration: none;
+      cursor: zoom-in;
+    }
+  }
 
-      &:hover {
-        text-decoration: underline;
-      }
+  .video-item {
+    max-width: 196px;
 
-      &.mention {
-        &:hover {
-          text-decoration: none;
+    a {
+      cursor: pointer;
+    }
 
-          span {
-            text-decoration: underline;
-          }
-        }
-      }
+    .video-item__play {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      font-size: 36px;
+      transform: translate(-50%, -50%);
+      padding: 5px;
+      border-radius: 100px;
+      color: rgba(255, 255, 255, 0.8);
     }
   }
 
-  .time {
-    text-decoration: none;
-    color: #9baec8;
+  .media-spoiler {
+    background: #9baec8;
+    width: 100%;
+    height: 100%;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    text-align: center;
+    transition: all 100ms linear;
 
     &:hover {
-      text-decoration: underline;
+      background: darken(#9baec8, 5%);
     }
-  }
-
-  .media-attachments {
-    list-style: none;
-    margin: 0;
-    padding: 0;
-    display: block;
-    overflow: hidden;
-    padding-left: 10px;
-    margin-bottom: 15px;
 
-    li {
+    span {
       display: block;
-      float: left;
-      width: 120px;
-      height: 100px;
-      border-radius: 4px;
-      margin-right: 4px;
-      margin-bottom: 4px;
 
-      a {
-        display: block;
-        width: 120px;
-        height: 100px;
-        border-radius: 4px;
-        background-position: center;
-        background-repeat: none;
-        background-size: cover;
+      &:first-child {
+        font-size: 14px;
       }
-    }
-  }
 
-  @media screen and (max-width: 360px) {
-    .avatar {
-      display: none;
-    }
-
-    .entry__container__container {
-      margin-left: 7px;
+      &:last-child {
+        font-size: 11px;
+        font-weight: 500;
+      }
     }
   }
 }
diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb
index d30ae8152..2360061ff 100644
--- a/app/controllers/api/oembed_controller.rb
+++ b/app/controllers/api/oembed_controller.rb
@@ -5,8 +5,8 @@ class Api::OembedController < ApiController
 
   def show
     @stream_entry = stream_entry_from_url(params[:url])
-    @width        = [300, params[:maxwidth].to_i].max
-    @height       = [200, params[:maxheight].to_i].max
+    @width        = params[:maxwidth].present?  ? params[:maxwidth].to_i  : 400
+    @height       = params[:maxheight].present? ? params[:maxheight].to_i : 600
   end
 
   private
diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb
index 933bdf737..58dd423f7 100644
--- a/app/controllers/stream_entries_controller.rb
+++ b/app/controllers/stream_entries_controller.rb
@@ -9,8 +9,6 @@ class StreamEntriesController < ApplicationController
   before_action :check_account_suspension
 
   def show
-    @type = @stream_entry.activity_type.downcase
-
     respond_to do |format|
       format.html do
         return gone if @stream_entry.activity.nil?
@@ -27,7 +25,7 @@ class StreamEntriesController < ApplicationController
 
   def embed
     response.headers['X-Frame-Options'] = 'ALLOWALL'
-    @type = @stream_entry.activity_type.downcase
+    @external_links = true
 
     return gone if @stream_entry.activity.nil?
 
@@ -46,6 +44,7 @@ class StreamEntriesController < ApplicationController
 
   def set_stream_entry
     @stream_entry = @account.stream_entries.find(params[:id])
+    @type         = @stream_entry.activity_type.downcase
   end
 
   def check_account_suspension
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb
index 0aa7008be..5cd65008e 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/stream_entries_helper.rb
@@ -5,6 +5,10 @@ module StreamEntriesHelper
     account.display_name.blank? ? account.username : account.display_name
   end
 
+  def acct(account)
+    "@#{account.acct}#{@external_links && account.local? ? "@#{Rails.configuration.x.local_domain}" : ''}"
+  end
+
   def avatar_for_status_url(status)
     status.reblog? ? status.reblog.account.avatar.url( :original) : status.account.avatar.url( :original)
   end
diff --git a/app/views/api/oembed/show.json.rabl b/app/views/api/oembed/show.json.rabl
index 2bec9165e..f33b70ee5 100644
--- a/app/views/api/oembed/show.json.rabl
+++ b/app/views/api/oembed/show.json.rabl
@@ -9,6 +9,6 @@ node(:author_url) { |entry| account_url(entry.account) }
 node(:provider_name) { Rails.configuration.x.local_domain }
 node(:provider_url) { root_url }
 node(:cache_age) { 86_400 }
-node(:html) { |entry| "<div style=\"position: relative; height: 0; overflow: hidden; padding-top: 30px; padding-bottom: 56.25%\"><iframe src=\"#{embed_account_stream_entry_url(entry.account, entry)}\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden\" frameborder=\"0\" width=\"#{@width}\" scrolling=\"no\"></iframe></div>" }
+node(:html) { |entry| "<iframe src=\"#{embed_account_stream_entry_url(entry.account, entry)}\" style=\"width: 100%; overflow: hidden\" frameborder=\"0\" width=\"#{@width}\" height=\"#{@height}\" scrolling=\"no\"></iframe>" }
 node(:width) { @width }
 node(:height) { nil }
diff --git a/app/views/stream_entries/_content_spoiler.html.haml b/app/views/stream_entries/_content_spoiler.html.haml
new file mode 100644
index 000000000..d80ea46a0
--- /dev/null
+++ b/app/views/stream_entries/_content_spoiler.html.haml
@@ -0,0 +1,3 @@
+.media-spoiler
+  %span= t('stream_entries.sensitive_content')
+  %span= t('stream_entries.click_to_show')
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
new file mode 100644
index 000000000..94451d3bd
--- /dev/null
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -0,0 +1,36 @@
+.detailed-status.light
+  = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name', target: @external_links ? '_blank' : nil, rel: 'noopener' do
+    %div
+      %div.avatar
+        = image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: ''
+    %span.display-name
+      %strong= display_name(status.account)
+      %span= acct(status.account)
+
+  .status__content= Formatter.instance.format(status)
+
+  - unless status.media_attachments.empty?
+    - if status.media_attachments.first.video?
+      .video-player
+        - if status.sensitive?
+          = render partial: 'stream_entries/content_spoiler'
+        %video{ src: status.media_attachments.first.file.url(:original), loop: true }
+    - else
+      .detailed-status__attachments
+        - if status.sensitive?
+          = render partial: 'stream_entries/content_spoiler'
+        - status.media_attachments.each do |media|
+          .media-item
+            = link_to '', (media.remote_url.blank? ? media.file.url(:original) : media.remote_url), style: "background-image: url(#{media.file.url(:original)})", target: '_blank', rel: 'noopener'
+
+  %div.detailed-status__meta
+    = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: @external_links ? '_blank' : nil, rel: 'noopener' do
+      %span= l(status.created_at)
+    ·
+    %span
+      = fa_icon('retweet')
+      %span= status.reblogs.count
+    ·
+    %span
+      = fa_icon('star')
+      %span= status.favourites.count
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
new file mode 100644
index 000000000..da3bc0ccb
--- /dev/null
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -0,0 +1,28 @@
+.status.light
+  .status__header
+    .status__meta
+      = link_to time_ago_in_words(status.created_at), TagManager.instance.url_for(status), class: 'status__relative-time', title: l(status.created_at), target: @external_links ? '_blank' : nil, rel: 'noopener'
+
+    = link_to TagManager.instance.url_for(status.account), class: 'status__display-name', target: @external_links ? '_blank' : nil, rel: 'noopener' do
+      .status__avatar
+        %div
+          = image_tag status.account.avatar(:original), width: 48, height: 48, alt: ''
+      %span.display-name
+        %strong= display_name(status.account)
+        %span= acct(status.account)
+
+  .status__content= Formatter.instance.format(status)
+
+  - unless status.media_attachments.empty?
+    .status__attachments
+      - if status.sensitive?
+        = render partial: 'stream_entries/content_spoiler'
+      - if status.media_attachments.first.video?
+        .video-item
+          = link_to (status.media_attachments.first.remote_url.blank? ? status.media_attachments.first.file.url(:original) : status.media_attachments.first.remote_url), style: "background-image: url(#{status.media_attachments.first.file.url(:small)})", target: '_blank', rel: 'noopener' do
+            .video-item__play
+              = fa_icon('play')
+      - else
+        - status.media_attachments.each do |media|
+          .media-item
+            = link_to '', (media.remote_url.blank? ? media.file.url(:original) : media.remote_url), style: "background-image: url(#{media.file.url(:original)})", target: '_blank', rel: 'noopener'
diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml
index 8169b8178..67cb06a83 100644
--- a/app/views/stream_entries/_status.html.haml
+++ b/app/views/stream_entries/_status.html.haml
@@ -1,7 +1,7 @@
 - include_threads ||= false
 - is_predecessor  ||= false
 - is_successor    ||= false
-- centered          = include_threads && !is_predecessor && !is_successor
+- centered        ||= include_threads && !is_predecessor && !is_successor
 
 - if status.reply? && include_threads
   = render partial: 'status', collection: @ancestors, as: :status, locals: { is_predecessor: true }
@@ -13,28 +13,7 @@
       Shared by
       = link_to display_name(status.account), TagManager.instance.url_for(status.account), class: 'name'
 
-  .entry__container
-    .avatar
-      = image_tag avatar_for_status_url(status)
-
-    .entry__container__container
-      .header
-        .header__left
-          = link_to TagManager.instance.url_for(proper_status(status).account), class: 'name' do
-            %strong= display_name(proper_status(status).account)
-            = "@#{proper_status(status).account.acct}"
-
-        .header__right
-          = link_to TagManager.instance.url_for(proper_status(status)), class: 'time' do
-            %span{ title: proper_status(status).created_at }
-              = relative_time(proper_status(status).created_at)
-
-      .content= Formatter.instance.format(proper_status(status))
-
-      - if (status.reblog? ? status.reblog : status).media_attachments.size > 0
-        %ul.media-attachments
-          - (status.reblog? ? status.reblog : status).media_attachments.each do |media|
-            %li.transparent-background= link_to '', media.file.url( :original), style: "background-image: url(#{media.file.url( :small)})", target: '_blank'
+  = render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: proper_status(status) }
 
 - if include_threads
   = render partial: 'status', collection: @descendants, as: :status, locals: { is_successor: true }
diff --git a/app/views/stream_entries/embed.html.haml b/app/views/stream_entries/embed.html.haml
index 4a733d428..fd07fdd91 100644
--- a/app/views/stream_entries/embed.html.haml
+++ b/app/views/stream_entries/embed.html.haml
@@ -1,2 +1,2 @@
 .activity-stream.activity-stream-headless
-  = render partial: @type, locals: { @type.to_sym => @stream_entry.activity }
+  = render partial: @type, locals: { @type.to_sym => @stream_entry.activity, centered: true }
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 4dc6985b7..e72063844 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -33,6 +33,7 @@ search:
 ignore_unused:
   - 'activerecord.attributes.*'
   - '{devise,will_paginate,doorkeeper}.*'
+  - '{datetime,time}.*'
   - 'simple_form.{yes,no}'
   - 'simple_form.{placeholders,hints,labels}.*'
   - 'simple_form.{error_notification,required}.:'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 50a1f0e95..f58ce9a71 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -26,6 +26,20 @@ en:
     resend_confirmation: Resend confirmation instructions
     reset_password: Reset password
     set_new_password: Set new password
+  datetime:
+    distance_in_words:
+      about_x_hours: "%{count}h"
+      about_x_months: "%{count}mo"
+      about_x_years: "%{count}y"
+      almost_x_years: "%{count}y"
+      half_a_minute: Just now
+      less_than_x_minutes: "%{count}m"
+      less_than_x_seconds: Just now
+      over_x_years: "%{count}y"
+      x_days: "%{count}d"
+      x_minutes: "%{count}m"
+      x_months: "%{count}mo"
+      x_seconds: "%{count}s"
   generic:
     changes_saved_msg: Changes successfully saved!
     powered_by: powered by %{link}
@@ -53,8 +67,13 @@ en:
     edit_profile: Edit profile
     preferences: Preferences
   stream_entries:
+    click_to_show: Click to show
     favourited: favourited a post by
     is_now_following: is now following
+    sensitive_content: Sensitive content
+  time:
+    formats:
+      default: "%b %d, %Y, %H:%M"
   users:
     invalid_email: The e-mail address is invalid
   will_paginate: