diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 1d462103bc..e613f829f6 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -11,9 +11,11 @@ class StatusContent extends React.PureComponent {
constructor (props, context) {
super(props, context);
+
this.state = {
hidden: true
};
+
this.onMentionClick = this.onMentionClick.bind(this);
this.onHashtagClick = this.onHashtagClick.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this)
@@ -36,8 +38,6 @@ class StatusContent extends React.PureComponent {
link.setAttribute('title', mention.get('acct'));
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
- } else if (media) {
- link.innerHTML = '
';
} else {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener');
@@ -70,11 +70,11 @@ class StatusContent extends React.PureComponent {
const [ startX, startY ] = this.startXY;
const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
- if (e.target.localName === 'a' || (e.target.parentNode && e.target.parentNode.localName === 'a')) {
+ if (e.target.localName === 'button' || e.target.localName === 'a' || (e.target.parentNode && e.target.parentNode.localName === 'a')) {
return;
}
- if (deltaX + deltaY < 5 && e.button === 0) {
+ if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
this.props.onClick();
}
@@ -95,7 +95,7 @@ class StatusContent extends React.PureComponent {
const { hidden } = this.state;
const content = { __html: emojify(status.get('content')) };
- const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) };
+ const spoilerContent = { __html: emojify(status.get('spoiler_text', '')) };
const directionStyle = { direction: 'ltr' };
if (isRtl(status.get('content'))) {
@@ -118,14 +118,19 @@ class StatusContent extends React.PureComponent {
}
return (
-
+
);
} else if (this.props.onClick) {
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index 384b47c8f8..3bee65385f 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -76,7 +76,10 @@ class ActionBar extends React.PureComponent {
-
+
+
+
+
);
}
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 6aef2ffeef..bbeb0a9ecf 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -3,9 +3,12 @@ import { length } from 'stringz';
import { default as dateFormat } from 'date-fns/format';
import distanceInWordsStrict from 'date-fns/distance_in_words_strict';
import { delegate } from 'rails-ujs';
+import Rails from 'rails-ujs';
require.context('../images/', true);
+Rails.start();
+
const parseFormat = (format) => format.replace(/%(\w)/g, (_, modifier) => {
switch (modifier) {
case '%':
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
index cededdcddd..3d133d4d9f 100644
--- a/app/javascript/styles/components.scss
+++ b/app/javascript/styles/components.scss
@@ -474,15 +474,18 @@
}
}
-a.status__content__spoiler-link {
+.status__content__spoiler-link {
display: inline-block;
border-radius: 2px;
+ background: transparent;
+ border: 0;
color: lighten($ui-base-color, 8%);
font-weight: 500;
font-size: 11px;
padding: 0 6px;
text-transform: uppercase;
line-height: inherit;
+ cursor: pointer;
}
.status__prepend-icon-wrapper {
@@ -608,6 +611,34 @@ a.status__content__spoiler-link {
width: 18px;
}
+.detailed-status__action-bar-dropdown {
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+
+ .dropdown {
+ display: block;
+ width: 18px;
+ height: 18px;
+ }
+
+ .dropdown--active {
+ .dropdown__content.dropdown__left {
+ left: 20px;
+ right: initial;
+ }
+
+ &::after {
+ bottom: initial;
+ margin-left: 7px;
+ margin-top: -7px;
+ right: initial;
+ }
+ }
+}
+
.detailed-status {
background: lighten($ui-base-color, 4%);
padding: 14px 10px;
@@ -2165,6 +2196,7 @@ button.icon-button.active i.fa-retweet {
display: flex;
flex: 1 1 auto;
align-items: center;
+ justify-content: center;
a {
color: $ui-highlight-color;
diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb
index 920eac31b8..6b0faf75f5 100644
--- a/app/lib/atom_serializer.rb
+++ b/app/lib/atom_serializer.rb
@@ -332,7 +332,7 @@ class AtomSerializer
end
def serialize_status_attributes(entry, status)
- append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text?
+ append_element(entry, 'summary', Formatter.instance.format(status.proper, :spoiler_text, false).to_str, 'xml:lang': status.language, type: 'html') if status.spoiler_text?
append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html', 'xml:lang': status.language)
status.mentions.each do |mentioned|
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 5b008278c6..5a1b63c0a2 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -9,13 +9,15 @@ class Formatter
include ActionView::Helpers::TextHelper
- def format(status)
- return reformat(status.content) unless status.local?
+ def format(status, attribute = :text, paragraphize = true)
+ raw_content = status.public_send(attribute)
- html = status.text
+ return '' if raw_content.blank?
+ return reformat(raw_content) unless status.local?
+
+ html = raw_content
html = encode_and_link_urls(html, status.mentions)
-
- html = simple_format(html, {}, sanitize: false)
+ html = simple_format(html, {}, sanitize: false) if paragraphize
html = html.delete("\n")
html.html_safe # rubocop:disable Rails/OutputSafety
@@ -25,18 +27,6 @@ class Formatter
sanitize(html, Sanitize::Config::MASTODON_STRICT).html_safe # rubocop:disable Rails/OutputSafety
end
- def format_spoiler(status)
- return reformat(status.spoiler_text) unless status.local?
-
- html = status.spoiler_text
- html = encode_and_link_urls(html)
-
- html = simple_format(html, {}, sanitize: false)
- html = html.delete("\n")
-
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
def plaintext(status)
return status.text if status.local?
strip_tags(status.text)
diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb
index eab033d50d..8904e2cf81 100644
--- a/app/services/process_hashtags_service.rb
+++ b/app/services/process_hashtags_service.rb
@@ -2,7 +2,7 @@
class ProcessHashtagsService < BaseService
def call(status, tags = [])
- text = [status.text, status.spoiler_text].reject(&:empty?).join(' ')
+ text = [status.text, status.spoiler_text].reject(&:blank?).join(' ')
tags = text.scan(Tag::HASHTAG_RE).map(&:first) if status.local?
tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |tag|
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index aa0a4d71bb..fd45810fb9 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -10,7 +10,9 @@ class ProcessMentionsService < BaseService
def call(status)
return unless status.local?
- status.text.scan(Account::MENTION_RE).each do |match|
+ text = [status.text, status.spoiler_text].reject(&:blank?).join(' ')
+
+ text.scan(Account::MENTION_RE).each do |match|
username, domain = match.first.split('@')
mentioned_account = Account.find_remote(username, domain)
diff --git a/app/views/api/v1/statuses/_show.rabl b/app/views/api/v1/statuses/_show.rabl
index 54e8a86d81..33bf394588 100644
--- a/app/views/api/v1/statuses/_show.rabl
+++ b/app/views/api/v1/statuses/_show.rabl
@@ -1,7 +1,8 @@
-attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, :sensitive, :spoiler_text, :visibility
+attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, :sensitive, :visibility
node(:uri) { |status| TagManager.instance.uri_for(status) }
node(:content) { |status| Formatter.instance.format(status) }
+node(:spoiler_text) { |status| Formatter.instance.format(status, :spoiler_text, false) }
node(:url) { |status| TagManager.instance.url_for(status) }
node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : status.reblogs_count }
node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites_count }
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 4cf94c83cf..943d768dda 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -10,7 +10,7 @@
.status__content.p-name.emojify<
- if status.spoiler_text?
%p{ style: 'margin-bottom: 0' }<
- %span.p-summary> #{Formatter.instance.format_spoiler(status)}
+ %span.p-summary> #{Formatter.instance.format(status, :spoiler_text, false)}
%a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
.e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 583bb24f36..c076142955 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -16,7 +16,7 @@
.status__content.p-name.emojify<
- if status.spoiler_text?
%p{ style: 'margin-bottom: 0' }<
- %span.p-summary> #{Formatter.instance.format_spoiler(status)}
+ %span.p-summary> #{Formatter.instance.format(status, :spoiler_text, false)}
%a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
.e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index 791bcce866..15b4e1d960 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -69,11 +69,9 @@ RSpec.describe Formatter do
end
end
-=begin
- it 'matches a URL without closing paranthesis' do
+ xit 'matches a URL without closing paranthesis' do
expect(subject.match('(http://google.com/)')[0]).to eq 'http://google.com'
end
-=end
context 'matches a URL without exclamation point' do
let(:local_text) { 'http://www.google.com!' }