From 648cdbc04a21580a89d337edb0f45308aff1b93f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 30 Jul 2019 13:10:40 +0200 Subject: [PATCH 01/20] Add hashtag score for better sorting of autosuggestions (#11427) * Add hashtag score for better sorting of autosuggestions * Do not use `~<~` operator with no text_pattern_ops index --- app/javascript/mastodon/reducers/compose.js | 4 ++-- app/models/tag.rb | 3 ++- app/models/trending_tags.rb | 7 ++++++- db/migrate/20190729185330_add_score_to_tags.rb | 5 +++++ db/schema.rb | 3 ++- 5 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20190729185330_add_score_to_tags.rb diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index e683a9c1a..7b0cdd5a5 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -153,9 +153,9 @@ const sortHashtagsByUse = (state, tags) => { if (usedA === usedB) { return 0; } else if (usedA && !usedB) { - return 1; - } else { return -1; + } else { + return 1; } }); }; diff --git a/app/models/tag.rb b/app/models/tag.rb index 46e3a3ec0..a2d6078f4 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -7,6 +7,7 @@ # name :string default(""), not null # created_at :datetime not null # updated_at :datetime not null +# score :integer # class Tag < ApplicationRecord @@ -78,7 +79,7 @@ class Tag < ApplicationRecord pattern = sanitize_sql_like(normalize(term.strip)) + '%' Tag.where(arel_table[:name].lower.matches(pattern.mb_chars.downcase.to_s)) - .order(:name) + .order(Arel.sql('length(name) ASC, score DESC, name ASC')) .limit(limit) .offset(offset) end diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb index 148535c21..34a4abbc5 100644 --- a/app/models/trending_tags.rb +++ b/app/models/trending_tags.rb @@ -48,12 +48,17 @@ class TrendingTags redis.zrem(key, tag_id.to_s) else score = ((observed - expected)**2) / expected - redis.zadd(key, score, tag_id.to_s) + added = redis.zadd(key, score, tag_id.to_s) + bump_tag_score!(tag_id) if added == 1 end redis.expire(key, EXPIRE_TRENDS_AFTER) end + def bump_tag_score!(tag_id) + Tag.where(id: tag_id).update_all('score = COALESCE(score, 0) + 1') + end + def disallowed_hashtags return @disallowed_hashtags if defined?(@disallowed_hashtags) diff --git a/db/migrate/20190729185330_add_score_to_tags.rb b/db/migrate/20190729185330_add_score_to_tags.rb new file mode 100644 index 000000000..75fee4b57 --- /dev/null +++ b/db/migrate/20190729185330_add_score_to_tags.rb @@ -0,0 +1,5 @@ +class AddScoreToTags < ActiveRecord::Migration[5.2] + def change + add_column :tags, :score, :int + end +end diff --git a/db/schema.rb b/db/schema.rb index 2d83d8b76..e3af9c31a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_07_28_084117) do +ActiveRecord::Schema.define(version: 2019_07_29_185330) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -659,6 +659,7 @@ ActiveRecord::Schema.define(version: 2019_07_28_084117) do t.string "name", default: "", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "score" t.index "lower((name)::text)", name: "index_tags_on_name_lower", unique: true end From ff789a751a1c730e4d808410411196b76caff39c Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 30 Jul 2019 13:18:23 +0200 Subject: [PATCH 02/20] Fix boosting & unboosting preventing a boost from appearing in the TL (#11405) * Fix boosting & unboosting preventing a boost from appearing in the TL * Add tests * Avoids side effects when aggregate_reblogs isn't true --- app/lib/feed_manager.rb | 14 +++++++++----- spec/lib/feed_manager_spec.rb | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index ed3ce6112..482b64867 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -35,7 +35,7 @@ class FeedManager end def unpush_from_home(account, status) - return false unless remove_from_feed(:home, account.id, status) + return false unless remove_from_feed(:home, account.id, status, account.user&.aggregates_reblogs?) redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s)) true end @@ -53,7 +53,7 @@ class FeedManager end def unpush_from_list(list, status) - return false unless remove_from_feed(:list, list.id, status) + return false unless remove_from_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?) redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s)) true end @@ -105,7 +105,7 @@ class FeedManager oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0 from_account.statuses.select('id, reblog_of_id').where('id > ?', oldest_home_score).reorder(nil).find_each do |status| - remove_from_feed(:home, into_account.id, status) + remove_from_feed(:home, into_account.id, status, into_account.user&.aggregates_reblogs?) end end @@ -275,10 +275,11 @@ class FeedManager # with reblogs, and returning true if a status was removed. As with # `add_to_feed`, this does not trigger push updates, so callers must # do so if appropriate. - def remove_from_feed(timeline_type, account_id, status) + def remove_from_feed(timeline_type, account_id, status, aggregate_reblogs = true) timeline_key = key(timeline_type, account_id) + reblog_key = key(timeline_type, account_id, 'reblogs') - if status.reblog? + if status.reblog? && (aggregate_reblogs.nil? || aggregate_reblogs) # 1. If the reblogging status is not in the feed, stop. status_rank = redis.zrevrank(timeline_key, status.id) return false if status_rank.nil? @@ -287,6 +288,7 @@ class FeedManager reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}") redis.srem(reblog_set_key, status.id) + redis.zrem(reblog_key, status.reblog_of_id) # 3. Re-insert another reblog or original into the feed if one # remains in the set. We could pick a random element, but this # set should generally be small, and it seems ideal to show the @@ -294,12 +296,14 @@ class FeedManager other_reblog = redis.smembers(reblog_set_key).map(&:to_i).min redis.zadd(timeline_key, other_reblog, other_reblog) if other_reblog + redis.zadd(reblog_key, other_reblog, status.reblog_of_id) if other_reblog # 4. Remove the reblogging status from the feed (as normal) # (outside conditional) else # If the original is getting deleted, no use for reblog references redis.del(key(timeline_type, account_id, "reblogs:#{status.id}")) + redis.srem(reblog_key, status.id) end redis.zrem(timeline_key, status.id) diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb index 9bdb675e1..b996997b1 100644 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@ -247,6 +247,23 @@ RSpec.describe FeedManager do expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be false end + it 'saves a new reblog of a recently-reblogged status when previous reblog has been deleted' do + account = Fabricate(:account) + reblogged = Fabricate(:status) + old_reblog = Fabricate(:status, reblog: reblogged) + + # The first reblog should be accepted + expect(FeedManager.instance.push_to_home(account, old_reblog)).to be true + + # The first reblog should be successfully removed + expect(FeedManager.instance.unpush_from_home(account, old_reblog)).to be true + + reblog = Fabricate(:status, reblog: reblogged) + + # The second reblog should be accepted + expect(FeedManager.instance.push_to_home(account, reblog)).to be true + end + it 'does not save a new reblog of a multiply-reblogged-then-unreblogged status' do account = Fabricate(:account) reblogged = Fabricate(:status) From 92de439c04785530ce15f55cae18590136c75216 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 30 Jul 2019 20:29:50 +0200 Subject: [PATCH 03/20] Change hashtag search to only return results that have trended in the past (#11448) * Change hashtag search to only return results that have trended in the past A way to eliminate typos and other one-off "junk" results * Fix excluding exact matches that don't have a score * Fix tests --- app/models/tag.rb | 6 ++++-- spec/models/tag_spec.rb | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/models/tag.rb b/app/models/tag.rb index a2d6078f4..c7f0af86d 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -76,9 +76,11 @@ class Tag < ApplicationRecord end def search_for(term, limit = 5, offset = 0) - pattern = sanitize_sql_like(normalize(term.strip)) + '%' + normalized_term = normalize(term.strip).mb_chars.downcase.to_s + pattern = sanitize_sql_like(normalized_term) + '%' - Tag.where(arel_table[:name].lower.matches(pattern.mb_chars.downcase.to_s)) + Tag.where(arel_table[:name].lower.matches(pattern)) + .where(arel_table[:score].gt(0).or(arel_table[:name].lower.eq(normalized_term))) .order(Arel.sql('length(name) ASC, score DESC, name ASC')) .limit(limit) .offset(offset) diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 5f07fd618..9d700849b 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -136,8 +136,8 @@ RSpec.describe Tag, type: :model do end it 'finds the exact matching tag as the first item' do - similar_tag = Fabricate(:tag, name: "matchlater") - tag = Fabricate(:tag, name: "match") + similar_tag = Fabricate(:tag, name: "matchlater", score: 1) + tag = Fabricate(:tag, name: "match", score: 1) results = Tag.search_for("match") From e46e9c9a8ec047825712e1ffa80d179facfb78e0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 31 Jul 2019 09:23:30 +0200 Subject: [PATCH 04/20] Fix delete regression (#11450) Regression from ff789a751a1c730e4d808410411196b76caff39c --- app/lib/feed_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 482b64867..ca3d890a8 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -303,7 +303,7 @@ class FeedManager else # If the original is getting deleted, no use for reblog references redis.del(key(timeline_type, account_id, "reblogs:#{status.id}")) - redis.srem(reblog_key, status.id) + redis.zrem(reblog_key, status.id) end redis.zrem(timeline_key, status.id) From c4043ba2f22f2a21fb2c6bc2febb6535c14ff878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel=20=28ariasuni=29?= Date: Wed, 31 Jul 2019 10:06:58 +0200 Subject: [PATCH 05/20] Fix jumping of toot date when clicking spoiler button (#11449) * Fix jumping of toot date when clicking spoiler button * Fix lint --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index c5d3f2e78..e65af7304 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -731,7 +731,7 @@ white-space: pre-wrap; &:last-child { - margin-bottom: 2px; + margin-bottom: 0; } } From 706a48ee1f2075ffb35ad4ad9cfc2f23fffbffcb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 1 Aug 2019 12:26:58 +0200 Subject: [PATCH 06/20] Fix column header scrolling with the page (#11458) Regression from aa22b38 --- .../mastodon/components/column_header.js | 12 +++++-- .../mastodon/features/status/index.js | 6 ++-- .../features/ui/components/column_loading.js | 2 +- .../features/ui/components/tabs_bar.js | 10 ++++-- app/javascript/styles/mastodon/basics.scss | 2 +- .../styles/mastodon/components.scss | 36 +++++++++++++++---- 6 files changed, 53 insertions(+), 15 deletions(-) diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index f33c689e7..89c5fe723 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { createPortal } from 'react-dom'; import classNames from 'classnames'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import Icon from 'mastodon/components/icon'; @@ -28,6 +29,7 @@ class ColumnHeader extends React.PureComponent { showBackButton: PropTypes.bool, children: PropTypes.node, pinned: PropTypes.bool, + placeholder: PropTypes.bool, onPin: PropTypes.func, onMove: PropTypes.func, onClick: PropTypes.func, @@ -79,7 +81,7 @@ class ColumnHeader extends React.PureComponent { } render () { - const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props; + const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder } = this.props; const { collapsed, animating } = this.state; const wrapperClassName = classNames('column-header__wrapper', { @@ -146,7 +148,7 @@ class ColumnHeader extends React.PureComponent { const hasTitle = icon && title; - return ( + const component = (

{hasTitle && ( @@ -172,6 +174,12 @@ class ColumnHeader extends React.PureComponent {

); + + if (multiColumn || placeholder) { + return component; + } else { + return createPortal(component, document.getElementById('tabs-bar__portal')); + } } } diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 0422111ae..6c8cf3f1b 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -146,6 +146,7 @@ class Status extends ImmutablePureComponent { descendantsIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, askReplyConfirmation: PropTypes.bool, + multiColumn: PropTypes.bool, domain: PropTypes.string.isRequired, }; @@ -437,13 +438,13 @@ class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain } = this.props; + const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain, multiColumn } = this.props; const { fullscreen } = this.state; if (status === null) { return ( - + ); @@ -473,6 +474,7 @@ class Status extends ImmutablePureComponent { )} diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.js index 9503a7a1a..0cdfd05d8 100644 --- a/app/javascript/mastodon/features/ui/components/column_loading.js +++ b/app/javascript/mastodon/features/ui/components/column_loading.js @@ -21,7 +21,7 @@ export default class ColumnLoading extends ImmutablePureComponent { let { title, icon } = this.props; return ( - +
); diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index 29583d3d7..1911da8ba 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -73,9 +73,13 @@ class TabsBar extends React.PureComponent { const { intl: { formatMessage } } = this.props; return ( - +
+ + +
+
); } diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 7df76bdff..7b983efab 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -39,7 +39,7 @@ body { &.layout-single-column { height: auto; - min-height: 100%; + min-height: 100vh; overflow-y: scroll; } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index e65af7304..b979c9b9a 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1852,6 +1852,26 @@ a.account__display-name { } } +.tabs-bar__wrapper { + background: darken($ui-base-color, 8%); + position: sticky; + top: 0; + z-index: 2; + padding-top: 0; + + @media screen and (min-width: $no-gap-breakpoint) { + padding-top: 10px; + } + + .tabs-bar { + margin-bottom: 0; + + @media screen and (min-width: $no-gap-breakpoint) { + margin-bottom: 10px; + } + } +} + .react-swipeable-view-container { &, .columns-area, @@ -1949,9 +1969,6 @@ a.account__display-name { background: lighten($ui-base-color, 8%); flex: 0 0 auto; overflow-y: auto; - position: sticky; - top: 0; - z-index: 3; } .tabs-bar__link { @@ -2014,6 +2031,14 @@ a.account__display-name { padding: 0; } + //.column { + // margin-top: 0; + + // @media screen and (min-width: $no-gap-breakpoint) { + // margin-top: 10px; + // } + //} + .autosuggest-textarea__textarea { font-size: 16px; } @@ -2039,6 +2064,7 @@ a.account__display-name { @media screen and (min-width: $no-gap-breakpoint) { padding: 10px 0; + padding-top: 0; } @media screen and (min-width: 630px) { @@ -2153,13 +2179,11 @@ a.account__display-name { @media screen and (min-width: $no-gap-breakpoint) { .tabs-bar { - margin: 10px auto; - margin-bottom: 0; width: 100%; } .react-swipeable-view-container .columns-area--mobile { - height: calc(100% - 20px) !important; + height: calc(100% - 10px) !important; } .getting-started__wrapper, From 8b9d0a05337b6bcf57b51abf45e21d9474bf2684 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 1 Aug 2019 19:14:02 +0200 Subject: [PATCH 07/20] Remove XML version of Webfinger and remove links to Atom feeds (#11460) Fix #11453 --- .../well_known/webfinger_controller.rb | 11 +--- app/serializers/webfinger_serializer.rb | 1 - app/views/accounts/show.html.haml | 1 - app/views/well_known/webfinger/show.xml.ruby | 51 ------------------- .../well_known/webfinger_controller_spec.rb | 11 ---- spec/requests/webfinger_request_spec.rb | 17 ------- 6 files changed, 1 insertion(+), 91 deletions(-) delete mode 100644 app/views/well_known/webfinger/show.xml.ruby diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 53f7f1e27..50bace217 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -9,17 +9,8 @@ module WellKnown def show @account = Account.find_local!(username_from_resource) - respond_to do |format| - format.any(:json, :html) do - render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json' - end - - format.xml do - render content_type: 'application/xrd+xml' - end - end - expires_in 3.days, public: true + render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json' rescue ActiveRecord::RecordNotFound head 404 end diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb index 008d0c182..c67363b8f 100644 --- a/app/serializers/webfinger_serializer.rb +++ b/app/serializers/webfinger_serializer.rb @@ -26,7 +26,6 @@ class WebfingerSerializer < ActiveModel::Serializer else [ { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) }, - { rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(object, format: 'atom') }, { rel: 'self', type: 'application/activity+json', href: account_url(object) }, { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, ] diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 6846abeb6..bf7a9f5f7 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -7,7 +7,6 @@ - if @account.user&.setting_noindex %meta{ name: 'robots', content: 'noindex, noarchive' }/ - %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/ %link{ rel: 'alternate', type: 'application/rss+xml', href: account_url(@account, format: 'rss') }/ %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ diff --git a/app/views/well_known/webfinger/show.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby deleted file mode 100644 index f5a54052a..000000000 --- a/app/views/well_known/webfinger/show.xml.ruby +++ /dev/null @@ -1,51 +0,0 @@ -doc = Ox::Document.new(version: '1.0') - -doc << Ox::Element.new('XRD').tap do |xrd| - xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0' - - xrd << (Ox::Element.new('Subject') << @account.to_webfinger_s) - - if @account.instance_actor? - xrd << (Ox::Element.new('Alias') << instance_actor_url) - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://webfinger.net/rel/profile-page' - link['type'] = 'text/html' - link['href'] = about_more_url(instance_actor: true) - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'self' - link['type'] = 'application/activity+json' - link['href'] = instance_actor_url - end - else - xrd << (Ox::Element.new('Alias') << short_account_url(@account)) - xrd << (Ox::Element.new('Alias') << account_url(@account)) - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://webfinger.net/rel/profile-page' - link['type'] = 'text/html' - link['href'] = short_account_url(@account) - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://schemas.google.com/g/2010#updates-from' - link['type'] = 'application/atom+xml' - link['href'] = account_url(@account, format: 'atom') - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'self' - link['type'] = 'application/activity+json' - link['href'] = account_url(@account) - end - - xrd << Ox::Element.new('Link').tap do |link| - link['rel'] = 'http://ostatus.org/schema/1.0/subscribe' - link['template'] = "#{authorize_interaction_url}?acct={uri}" - end - end -end - -('' + Ox.dump(doc, effort: :tolerant)).force_encoding('UTF-8') diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb index b05745ea3..20275aa63 100644 --- a/spec/controllers/well_known/webfinger_controller_spec.rb +++ b/spec/controllers/well_known/webfinger_controller_spec.rb @@ -56,17 +56,6 @@ PEM expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') end - it 'returns JSON when account can be found' do - get :show, params: { resource: alice.to_webfinger_s }, format: :xml - - xml = Nokogiri::XML(response.body) - - expect(response).to have_http_status(200) - expect(response.content_type).to eq 'application/xrd+xml' - expect(xml.at_xpath('//xmlns:Subject').content).to eq 'acct:alice@cb6e6126.ngrok.io' - expect(xml.xpath('//xmlns:Alias').map(&:content)).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') - end - it 'returns http not found when account cannot be found' do get :show, params: { resource: 'acct:not@existing.com' }, format: :json diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb index 7f9e1162e..48823714e 100644 --- a/spec/requests/webfinger_request_spec.rb +++ b/spec/requests/webfinger_request_spec.rb @@ -12,23 +12,6 @@ describe 'The webfinger route' do end end - describe 'asking for xml format' do - it 'returns an xml response for xml format' do - get webfinger_url(resource: alice.to_webfinger_s, format: :xml) - - expect(response).to have_http_status(200) - expect(response.content_type).to eq 'application/xrd+xml' - end - - it 'returns an xml response for xml accept header' do - headers = { 'HTTP_ACCEPT' => 'application/xrd+xml' } - get webfinger_url(resource: alice.to_webfinger_s), headers: headers - - expect(response).to have_http_status(200) - expect(response.content_type).to eq 'application/xrd+xml' - end - end - describe 'asking for json format' do it 'returns a json response for json format' do get webfinger_url(resource: alice.to_webfinger_s, format: :json) From 2dee293c4c98486d387105224023fad02b8b0d96 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 1 Aug 2019 19:17:17 +0200 Subject: [PATCH 08/20] Fix scroll to top in single column UI (#11463) --- app/javascript/mastodon/components/column.js | 15 ++++++++++++--- .../mastodon/components/column_back_button.js | 15 ++++++++++++++- .../mastodon/features/account_gallery/index.js | 5 +++-- .../mastodon/features/account_timeline/index.js | 2 +- app/javascript/mastodon/features/blocks/index.js | 2 +- .../mastodon/features/community_timeline/index.js | 2 +- .../mastodon/features/direct_timeline/index.js | 2 +- .../mastodon/features/domain_blocks/index.js | 2 +- .../features/favourited_statuses/index.js | 2 +- .../mastodon/features/favourites/index.js | 2 +- .../mastodon/features/follow_requests/index.js | 2 +- .../mastodon/features/followers/index.js | 2 +- .../mastodon/features/following/index.js | 2 +- .../mastodon/features/getting_started/index.js | 2 +- .../mastodon/features/hashtag_timeline/index.js | 2 +- .../mastodon/features/home_timeline/index.js | 2 +- .../mastodon/features/keyboard_shortcuts/index.js | 4 ++-- .../mastodon/features/list_timeline/index.js | 4 ++-- app/javascript/mastodon/features/lists/index.js | 2 +- app/javascript/mastodon/features/mutes/index.js | 2 +- .../mastodon/features/notifications/index.js | 2 +- .../mastodon/features/pinned_statuses/index.js | 2 +- .../mastodon/features/public_timeline/index.js | 2 +- app/javascript/mastodon/features/reblogs/index.js | 2 +- app/javascript/mastodon/features/status/index.js | 2 +- app/javascript/styles/mastodon/components.scss | 2 ++ 26 files changed, 55 insertions(+), 30 deletions(-) diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js index d45387463..55e3bfd5e 100644 --- a/app/javascript/mastodon/components/column.js +++ b/app/javascript/mastodon/components/column.js @@ -8,10 +8,11 @@ export default class Column extends React.PureComponent { static propTypes = { children: PropTypes.node, label: PropTypes.string, + bindToDocument: PropTypes.bool, }; scrollTop () { - const scrollable = this.node.querySelector('.scrollable'); + const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); if (!scrollable) { return; @@ -33,11 +34,19 @@ export default class Column extends React.PureComponent { } componentDidMount () { - this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); + if (this.props.bindToDocument) { + document.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); + } else { + this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); + } } componentWillUnmount () { - this.node.removeEventListener('wheel', this.handleWheel); + if (this.props.bindToDocument) { + document.removeEventListener('wheel', this.handleWheel); + } else { + this.node.removeEventListener('wheel', this.handleWheel); + } } render () { diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js index f41045787..cc0e5c07c 100644 --- a/app/javascript/mastodon/components/column_back_button.js +++ b/app/javascript/mastodon/components/column_back_button.js @@ -2,6 +2,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import Icon from 'mastodon/components/icon'; +import { createPortal } from 'react-dom'; export default class ColumnBackButton extends React.PureComponent { @@ -9,6 +10,10 @@ export default class ColumnBackButton extends React.PureComponent { router: PropTypes.object, }; + static propTypes = { + multiColumn: PropTypes.bool, + }; + handleClick = () => { if (window.history && window.history.length === 1) { this.context.router.history.push('/'); @@ -18,12 +23,20 @@ export default class ColumnBackButton extends React.PureComponent { } render () { - return ( + const { multiColumn } = this.props; + + const component = ( ); + + if (multiColumn) { + return component; + } else { + return createPortal(component, document.getElementById('tabs-bar__portal')); + } } } diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 5d6a53e18..f1a665d8f 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -56,6 +56,7 @@ class AccountGallery extends ImmutablePureComponent { isLoading: PropTypes.bool, hasMore: PropTypes.bool, isAccount: PropTypes.bool, + multiColumn: PropTypes.bool, }; state = { @@ -116,7 +117,7 @@ class AccountGallery extends ImmutablePureComponent { } render () { - const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props; + const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount, multiColumn } = this.props; const { width } = this.state; if (!isAccount) { @@ -143,7 +144,7 @@ class AccountGallery extends ImmutablePureComponent { return ( - +
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 9914b7e65..69bab1e86 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -100,7 +100,7 @@ class AccountTimeline extends ImmutablePureComponent { return ( - + } diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js index 8fb0f051b..051431ed2 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.js @@ -57,7 +57,7 @@ class Blocks extends ImmutablePureComponent { const emptyMessage = ; return ( - + + + ; return ( - + ; return ( - + - + ; return ( - + - + - + + {multiColumn &&