commit: 5ae1b39ec9b4d5269d2f01aeaa4304252b694519
parent: aed25932b528f16861c4e016cbeb7a3de6231fe7
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Sun, 18 Dec 2016 19:47:11 +0100
Adjusting public display of statuses to look similar to logged-in UI,
fix #361 with rich OEmbed display via iframe, fix #237 by hiding sensitive
content behind a spoiler on public pages
Diffstat:
13 files changed, 318 insertions(+), 188 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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: