diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index 572f34fa02..0af0935e6c 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -66,6 +66,15 @@ class Header extends ImmutablePureComponent {
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
+ onMention: PropTypes.func.isRequired,
+ onDirect: PropTypes.func.isRequired,
+ onReport: PropTypes.func.isRequired,
+ onReblogToggle: PropTypes.func.isRequired,
+ onMute: PropTypes.func.isRequired,
+ onBlockDomain: PropTypes.func.isRequired,
+ onUnblockDomain: PropTypes.func.isRequired,
+ onEndorseToggle: PropTypes.func.isRequired,
+ onAddToList: PropTypes.func.isRequired,
onEditAccountNote: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired,
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
index 1bab05c721..8195735a13 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
@@ -24,7 +24,6 @@ export default class Header extends ImmutablePureComponent {
onUnblockDomain: PropTypes.func.isRequired,
onEndorseToggle: PropTypes.func.isRequired,
onAddToList: PropTypes.func.isRequired,
- onEditAccountNote: PropTypes.func.isRequired,
hideTabs: PropTypes.bool,
domain: PropTypes.string.isRequired,
};
diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js
index 4e85e3c581..33e67fcbe3 100644
--- a/app/javascript/flavours/glitch/features/audio/index.js
+++ b/app/javascript/flavours/glitch/features/audio/index.js
@@ -254,8 +254,9 @@ class Audio extends React.PureComponent {
}
_initAudioContext () {
- const context = new AudioContext();
- const source = context.createMediaElementSource(this.audio);
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
+ const context = new AudioContext();
+ const source = context.createMediaElementSource(this.audio);
this.visualizer.setAudioContext(context, source);
source.connect(context.destination);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
index b9b0a2644e..ea970c61f4 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
@@ -6,7 +6,22 @@ import { FormattedMessage } from 'react-intl';
import { me } from 'flavours/glitch/util/initial_state';
import { profileLink, termsLink } from 'flavours/glitch/util/backend_links';
-const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
+const HASHTAG_SEPARATORS = "_\\u00b7\\u200c";
+const ALPHA = '\\p{L}\\p{M}';
+const WORD = '\\p{L}\\p{M}\\p{N}\\p{Pc}';
+const APPROX_HASHTAG_RE = new RegExp(
+ '(?:^|[^\\/\\)\\w])#((' +
+ '[' + WORD + '_]' +
+ '[' + WORD + HASHTAG_SEPARATORS + ']*' +
+ '[' + ALPHA + HASHTAG_SEPARATORS + ']' +
+ '[' + WORD + HASHTAG_SEPARATORS +']*' +
+ '[' + WORD + '_]' +
+ ')|(' +
+ '[' + WORD + '_]*' +
+ '[' + ALPHA + ']' +
+ '[' + WORD + '_]*' +
+ '))', 'iu'
+);
const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 9613b0b9ed..61ecf045d1 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -66,6 +66,16 @@ class Header extends ImmutablePureComponent {
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
+ onMention: PropTypes.func.isRequired,
+ onDirect: PropTypes.func.isRequired,
+ onReport: PropTypes.func.isRequired,
+ onReblogToggle: PropTypes.func.isRequired,
+ onMute: PropTypes.func.isRequired,
+ onBlockDomain: PropTypes.func.isRequired,
+ onUnblockDomain: PropTypes.func.isRequired,
+ onEndorseToggle: PropTypes.func.isRequired,
+ onAddToList: PropTypes.func.isRequired,
+ onEditAccountNote: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired,
};
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index 4e1b27466b..abb15edcc7 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -23,7 +23,6 @@ export default class Header extends ImmutablePureComponent {
onUnblockDomain: PropTypes.func.isRequired,
onEndorseToggle: PropTypes.func.isRequired,
onAddToList: PropTypes.func.isRequired,
- onEditAccountNote: PropTypes.func.isRequired,
hideTabs: PropTypes.bool,
domain: PropTypes.string.isRequired,
};
diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js
index 1ab1c3117d..a4e00ba96c 100644
--- a/app/javascript/mastodon/features/audio/index.js
+++ b/app/javascript/mastodon/features/audio/index.js
@@ -269,8 +269,9 @@ class Audio extends React.PureComponent {
}
_initAudioContext () {
- const context = new AudioContext();
- const source = context.createMediaElementSource(this.audio);
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
+ const context = new AudioContext();
+ const source = context.createMediaElementSource(this.audio);
this.visualizer.setAudioContext(context, source);
source.connect(context.destination);
diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js
index 8200a319f7..947d20c5a5 100644
--- a/app/javascript/mastodon/features/compose/containers/warning_container.js
+++ b/app/javascript/mastodon/features/compose/containers/warning_container.js
@@ -5,7 +5,22 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { me } from '../../../initial_state';
-const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
+const HASHTAG_SEPARATORS = "_\\u00b7\\u200c";
+const ALPHA = '\\p{L}\\p{M}';
+const WORD = '\\p{L}\\p{M}\\p{N}\\p{Pc}';
+const APPROX_HASHTAG_RE = new RegExp(
+ '(?:^|[^\\/\\)\\w])#((' +
+ '[' + WORD + '_]' +
+ '[' + WORD + HASHTAG_SEPARATORS + ']*' +
+ '[' + ALPHA + HASHTAG_SEPARATORS + ']' +
+ '[' + WORD + HASHTAG_SEPARATORS +']*' +
+ '[' + WORD + '_]' +
+ ')|(' +
+ '[' + WORD + '_]*' +
+ '[' + ALPHA + ']' +
+ '[' + WORD + '_]*' +
+ '))', 'iu'
+);
const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
diff --git a/app/javascript/styles/mastodon/boost.scss b/app/javascript/styles/mastodon/boost.scss
index 3489428f83..4b6c9b82ed 100644
--- a/app/javascript/styles/mastodon/boost.scss
+++ b/app/javascript/styles/mastodon/boost.scss
@@ -15,5 +15,8 @@ button.icon-button i.fa-retweet {
}
button.icon-button.disabled i.fa-retweet {
- background-image: url("data:image/svg+xml;utf8,");
+ &,
+ &:hover {
+ background-image: url("data:image/svg+xml;utf8,");
+ }
}
diff --git a/chart/values.yaml.template b/chart/values.yaml.template
index 694bc4d422..ff680b81f7 100644
--- a/chart/values.yaml.template
+++ b/chart/values.yaml.template
@@ -4,7 +4,7 @@ image:
repository: tootsuite/mastodon
pullPolicy: Always
# https://hub.docker.com/r/tootsuite/mastodon/tags
- tag: v3.1.5
+ tag: v3.2.0
# alternatively, use `latest` for the latest release or `edge` for the image
# built from the most recent commit
#
diff --git a/lib/paperclip/response_with_limit_adapter.rb b/lib/paperclip/response_with_limit_adapter.rb
index 7d897b8d67..8711b13497 100644
--- a/lib/paperclip/response_with_limit_adapter.rb
+++ b/lib/paperclip/response_with_limit_adapter.rb
@@ -19,7 +19,7 @@ module Paperclip
@original_filename = filename_from_content_disposition || filename_from_path || 'data'
@size = @target.response.content_length
@tempfile = copy_to_tempfile(@target)
- @content_type = @target.response.mime_type || ContentTypeDetector.new(@tempfile.path).detect
+ @content_type = ContentTypeDetector.new(@tempfile.path).detect
end
def copy_to_tempfile(source)
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index 2ac4acc12a..51e0b8caff 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe ActivityPub::Activity::Create do
stub_request(:get, 'http://example.com/attachment.png').to_return(request_fixture('avatar.txt'))
stub_request(:get, 'http://example.com/emoji.png').to_return(body: attachment_fixture('emojo.png'))
+ stub_request(:get, 'http://example.com/emojib.png').to_return(body: attachment_fixture('emojo.png'), headers: { 'Content-Type' => 'application/octet-stream' })
end
describe '#perform' do
@@ -451,6 +452,32 @@ RSpec.describe ActivityPub::Activity::Create do
end
end
+ context 'with emojis served with invalid content-type' do
+ let(:object_json) do
+ {
+ id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
+ type: 'Note',
+ content: 'Lorem ipsum :tinkong:',
+ tag: [
+ {
+ type: 'Emoji',
+ icon: {
+ url: 'http://example.com/emojib.png',
+ },
+ name: 'tinkong',
+ },
+ ],
+ }
+ end
+
+ it 'creates status' do
+ status = sender.statuses.first
+
+ expect(status).to_not be_nil
+ expect(status.emojis.map(&:shortcode)).to include('tinkong')
+ end
+ end
+
context 'with emojis missing name' do
let(:object_json) do
{