Merge pull request #1905 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
		
						commit
						b2a25d446a
					
				| 
						 | 
					@ -51,6 +51,15 @@ class LanguageDropdownMenu extends React.PureComponent {
 | 
				
			||||||
    document.addEventListener('click', this.handleDocumentClick, false);
 | 
					    document.addEventListener('click', this.handleDocumentClick, false);
 | 
				
			||||||
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
					    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
				
			||||||
    this.setState({ mounted: true });
 | 
					    this.setState({ mounted: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
 | 
				
			||||||
 | 
					    // to wait for a frame before focusing
 | 
				
			||||||
 | 
					    requestAnimationFrame(() => {
 | 
				
			||||||
 | 
					      if (this.node) {
 | 
				
			||||||
 | 
					        const element = this.node.querySelector('input[type="search"]');
 | 
				
			||||||
 | 
					        if (element) element.focus();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillUnmount () {
 | 
					  componentWillUnmount () {
 | 
				
			||||||
| 
						 | 
					@ -226,7 +235,7 @@ class LanguageDropdownMenu extends React.PureComponent {
 | 
				
			||||||
          // react-overlays
 | 
					          // react-overlays
 | 
				
			||||||
          <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
 | 
					          <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
 | 
				
			||||||
            <div className='emoji-mart-search'>
 | 
					            <div className='emoji-mart-search'>
 | 
				
			||||||
              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
 | 
					              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
 | 
				
			||||||
              <button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
 | 
					              <button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -573,6 +573,7 @@ export default function compose(state = initialState, action) {
 | 
				
			||||||
        'advanced_options',
 | 
					        'advanced_options',
 | 
				
			||||||
        map => map.merge(new ImmutableMap({ do_not_federate }))
 | 
					        map => map.merge(new ImmutableMap({ do_not_federate }))
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					      map.set('id', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (action.status.get('spoiler_text').length > 0) {
 | 
					      if (action.status.get('spoiler_text').length > 0) {
 | 
				
			||||||
        map.set('spoiler', true);
 | 
					        map.set('spoiler', true);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -268,7 +268,8 @@ html {
 | 
				
			||||||
.status__content .status__content__spoiler-link {
 | 
					.status__content .status__content__spoiler-link {
 | 
				
			||||||
  background: $ui-base-color;
 | 
					  background: $ui-base-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &:hover {
 | 
					  &:hover,
 | 
				
			||||||
 | 
					  &:focus {
 | 
				
			||||||
    background: lighten($ui-base-color, 4%);
 | 
					    background: lighten($ui-base-color, 4%);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,6 +51,15 @@ class LanguageDropdownMenu extends React.PureComponent {
 | 
				
			||||||
    document.addEventListener('click', this.handleDocumentClick, false);
 | 
					    document.addEventListener('click', this.handleDocumentClick, false);
 | 
				
			||||||
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
					    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
				
			||||||
    this.setState({ mounted: true });
 | 
					    this.setState({ mounted: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
 | 
				
			||||||
 | 
					    // to wait for a frame before focusing
 | 
				
			||||||
 | 
					    requestAnimationFrame(() => {
 | 
				
			||||||
 | 
					      if (this.node) {
 | 
				
			||||||
 | 
					        const element = this.node.querySelector('input[type="search"]');
 | 
				
			||||||
 | 
					        if (element) element.focus();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillUnmount () {
 | 
					  componentWillUnmount () {
 | 
				
			||||||
| 
						 | 
					@ -226,7 +235,7 @@ class LanguageDropdownMenu extends React.PureComponent {
 | 
				
			||||||
          // react-overlays
 | 
					          // react-overlays
 | 
				
			||||||
          <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
 | 
					          <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
 | 
				
			||||||
            <div className='emoji-mart-search'>
 | 
					            <div className='emoji-mart-search'>
 | 
				
			||||||
              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
 | 
					              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
 | 
				
			||||||
              <button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
 | 
					              <button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -452,6 +452,7 @@ export default function compose(state = initialState, action) {
 | 
				
			||||||
      map.set('idempotencyKey', uuid());
 | 
					      map.set('idempotencyKey', uuid());
 | 
				
			||||||
      map.set('sensitive', action.status.get('sensitive'));
 | 
					      map.set('sensitive', action.status.get('sensitive'));
 | 
				
			||||||
      map.set('language', action.status.get('language'));
 | 
					      map.set('language', action.status.get('language'));
 | 
				
			||||||
 | 
					      map.set('id', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (action.status.get('spoiler_text').length > 0) {
 | 
					      if (action.status.get('spoiler_text').length > 0) {
 | 
				
			||||||
        map.set('spoiler', true);
 | 
					        map.set('spoiler', true);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -268,7 +268,8 @@ html {
 | 
				
			||||||
.status__content .status__content__spoiler-link {
 | 
					.status__content .status__content__spoiler-link {
 | 
				
			||||||
  background: $ui-base-color;
 | 
					  background: $ui-base-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &:hover {
 | 
					  &:hover,
 | 
				
			||||||
 | 
					  &:focus {
 | 
				
			||||||
    background: lighten($ui-base-color, 4%);
 | 
					    background: lighten($ui-base-color, 4%);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,8 +62,6 @@ class Request
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    begin
 | 
					    begin
 | 
				
			||||||
      response = response.extend(ClientLimit)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # If we are using a persistent connection, we have to
 | 
					      # If we are using a persistent connection, we have to
 | 
				
			||||||
      # read every response to be able to move forward at all.
 | 
					      # read every response to be able to move forward at all.
 | 
				
			||||||
      # However, simply calling #to_s or #flush may not be safe,
 | 
					      # However, simply calling #to_s or #flush may not be safe,
 | 
				
			||||||
| 
						 | 
					@ -181,6 +179,14 @@ class Request
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if ::HTTP::Response.methods.include?(:body_with_limit) && !Rails.env.production?
 | 
				
			||||||
 | 
					    abort 'HTTP::Response#body_with_limit is already defined, the monkey patch will not be applied'
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    class ::HTTP::Response
 | 
				
			||||||
 | 
					      include Request::ClientLimit
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  class Socket < TCPSocket
 | 
					  class Socket < TCPSocket
 | 
				
			||||||
    class << self
 | 
					    class << self
 | 
				
			||||||
      def open(host, *args)
 | 
					      def open(host, *args)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -139,7 +139,12 @@ class AccountStatusesCleanupPolicy < ApplicationRecord
 | 
				
			||||||
    # Filtering on `id` rather than `min_status_age` ago will treat
 | 
					    # Filtering on `id` rather than `min_status_age` ago will treat
 | 
				
			||||||
    # non-snowflake statuses as older than they really are, but Mastodon
 | 
					    # non-snowflake statuses as older than they really are, but Mastodon
 | 
				
			||||||
    # has switched to snowflake IDs significantly over 2 years ago anyway.
 | 
					    # has switched to snowflake IDs significantly over 2 years ago anyway.
 | 
				
			||||||
    max_id = [max_id, Mastodon::Snowflake.id_at(min_status_age.seconds.ago, with_random: false)].compact.min
 | 
					    snowflake_id = Mastodon::Snowflake.id_at(min_status_age.seconds.ago, with_random: false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if max_id.nil? || snowflake_id < max_id
 | 
				
			||||||
 | 
					      max_id = snowflake_id
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Status.where(Status.arel_table[:id].lteq(max_id))
 | 
					    Status.where(Status.arel_table[:id].lteq(max_id))
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,6 +63,8 @@ class FeaturedTag < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def validate_featured_tags_limit
 | 
					  def validate_featured_tags_limit
 | 
				
			||||||
 | 
					    return unless account.local?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= LIMIT
 | 
					    errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= LIMIT
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,21 +51,17 @@ class ActivityPub::FetchFeaturedTagsCollectionService < BaseService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def process_items(items)
 | 
					  def process_items(items)
 | 
				
			||||||
    names     = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.map { |name| HashtagNormalizer.new.normalize(name) }
 | 
					    names            = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.take(FeaturedTag::LIMIT)
 | 
				
			||||||
    to_remove = []
 | 
					    tags             = names.index_by { |name| HashtagNormalizer.new.normalize(name) }
 | 
				
			||||||
    to_add    = names
 | 
					    normalized_names = tags.keys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FeaturedTag.where(account: @account).map(&:name).each do |name|
 | 
					    FeaturedTag.includes(:tag).references(:tag).where(account: @account).where.not(tag: { name: normalized_names }).delete_all
 | 
				
			||||||
      if names.include?(name)
 | 
					
 | 
				
			||||||
        to_add.delete(name)
 | 
					    FeaturedTag.includes(:tag).references(:tag).where(account: @account, tag: { name: normalized_names }).each do |featured_tag|
 | 
				
			||||||
      else
 | 
					      featured_tag.update(name: tags.delete(featured_tag.tag.name))
 | 
				
			||||||
        to_remove << name
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FeaturedTag.includes(:tag).where(account: @account, tags: { name: to_remove }).delete_all unless to_remove.empty?
 | 
					    tags.each_value do |name|
 | 
				
			||||||
 | 
					 | 
				
			||||||
    to_add.each do |name|
 | 
					 | 
				
			||||||
      FeaturedTag.create!(account: @account, name: name)
 | 
					      FeaturedTag.create!(account: @account, name: name)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
  = f.input :return_to, as: :hidden
 | 
					  = f.input :return_to, as: :hidden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .field-group
 | 
					  .field-group
 | 
				
			||||||
    = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off', :autofocus => true }, label: t('challenge.prompt'), required: true
 | 
					    = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password', :autofocus => true }, label: t('challenge.prompt'), required: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .actions
 | 
					  .actions
 | 
				
			||||||
    = f.button :button, t('challenge.confirm'), type: :submit
 | 
					    = f.button :button, t('challenge.confirm'), type: :submit
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,9 +8,9 @@
 | 
				
			||||||
    = f.input :reset_password_token, as: :hidden
 | 
					    = f.input :reset_password_token, as: :hidden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true
 | 
					      = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, required: true
 | 
					      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, required: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .actions
 | 
					    .actions
 | 
				
			||||||
      = f.button :button, t('auth.set_new_password'), type: :submit
 | 
					      = f.button :button, t('auth.set_new_password'), type: :submit
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,13 +13,13 @@
 | 
				
			||||||
      .fields-row__column.fields-group.fields-row__column-6
 | 
					      .fields-row__column.fields-group.fields-row__column-6
 | 
				
			||||||
        = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
 | 
					        = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
 | 
				
			||||||
      .fields-row__column.fields-group.fields-row__column-6
 | 
					      .fields-row__column.fields-group.fields-row__column-6
 | 
				
			||||||
        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true, disabled: current_account.suspended?, hint: false
 | 
					        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'current-password' }, required: true, disabled: current_account.suspended?, hint: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fields-row
 | 
					    .fields-row
 | 
				
			||||||
      .fields-row__column.fields-group.fields-row__column-6
 | 
					      .fields-row__column.fields-group.fields-row__column-6
 | 
				
			||||||
        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
 | 
					        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
 | 
				
			||||||
      .fields-row__column.fields-group.fields-row__column-6
 | 
					      .fields-row__column.fields-group.fields-row__column-6
 | 
				
			||||||
        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, disabled: current_account.suspended?
 | 
					        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, disabled: current_account.suspended?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .actions
 | 
					    .actions
 | 
				
			||||||
      = f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
 | 
					      = f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
      - else
 | 
					      - else
 | 
				
			||||||
        = f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
 | 
					        = f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false
 | 
					      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'current-password' }, hint: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .actions
 | 
					    .actions
 | 
				
			||||||
      = f.button :button, t('auth.login'), type: :submit
 | 
					      = f.button :button, t('auth.login'), type: :submit
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@
 | 
				
			||||||
  %hr.spacer/
 | 
					  %hr.spacer/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - if current_user.encrypted_password.present?
 | 
					  - if current_user.encrypted_password.present?
 | 
				
			||||||
    = f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_password')
 | 
					    = f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, hint: t('deletes.confirm_password')
 | 
				
			||||||
  - else
 | 
					  - else
 | 
				
			||||||
    = f.input :username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_username')
 | 
					    = f.input :username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_username')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fields-row__column.fields-group.fields-row__column-6
 | 
					    .fields-row__column.fields-group.fields-row__column-6
 | 
				
			||||||
      - if current_user.encrypted_password.present?
 | 
					      - if current_user.encrypted_password.present?
 | 
				
			||||||
        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
 | 
					        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true
 | 
				
			||||||
      - else
 | 
					      - else
 | 
				
			||||||
        = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
 | 
					        = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fields-row__column.fields-group.fields-row__column-6
 | 
					    .fields-row__column.fields-group.fields-row__column-6
 | 
				
			||||||
      - if current_user.encrypted_password.present?
 | 
					      - if current_user.encrypted_password.present?
 | 
				
			||||||
        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
 | 
					        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true, disabled: on_cooldown?
 | 
				
			||||||
      - else
 | 
					      - else
 | 
				
			||||||
        = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
 | 
					        = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,95 @@
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service do
 | 
				
			||||||
 | 
					  let(:collection_url) { 'https://example.com/account/tags' }
 | 
				
			||||||
 | 
					  let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/account') }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:items) do
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					      { type: 'Hashtag', href: 'https://example.com/account/tagged/foo', name: 'Foo' },
 | 
				
			||||||
 | 
					      { type: 'Hashtag', href: 'https://example.com/account/tagged/bar', name: 'bar' },
 | 
				
			||||||
 | 
					      { type: 'Hashtag', href: 'https://example.com/account/tagged/baz', name: 'baZ' },
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:payload) do
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      '@context': 'https://www.w3.org/ns/activitystreams',
 | 
				
			||||||
 | 
					      type: 'Collection',
 | 
				
			||||||
 | 
					      id: collection_url,
 | 
				
			||||||
 | 
					      items: items,
 | 
				
			||||||
 | 
					    }.with_indifferent_access
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  subject { described_class.new }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  shared_examples 'sets featured tags' do
 | 
				
			||||||
 | 
					    before do
 | 
				
			||||||
 | 
					      subject.call(actor, collection_url)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'sets expected tags as pinned tags' do
 | 
				
			||||||
 | 
					      expect(actor.featured_tags.map(&:display_name)).to match_array ['Foo', 'bar', 'baZ']
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe '#call' do
 | 
				
			||||||
 | 
					    context 'when the endpoint is a Collection' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it_behaves_like 'sets featured tags'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when the account already has featured tags' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        actor.featured_tags.create!(name: 'FoO')
 | 
				
			||||||
 | 
					        actor.featured_tags.create!(name: 'baz')
 | 
				
			||||||
 | 
					        actor.featured_tags.create!(name: 'oh').update(name: nil)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it_behaves_like 'sets featured tags'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when the endpoint is an OrderedCollection' do
 | 
				
			||||||
 | 
					      let(:payload) do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          '@context': 'https://www.w3.org/ns/activitystreams',
 | 
				
			||||||
 | 
					          type: 'OrderedCollection',
 | 
				
			||||||
 | 
					          id: collection_url,
 | 
				
			||||||
 | 
					          orderedItems: items,
 | 
				
			||||||
 | 
					        }.with_indifferent_access
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it_behaves_like 'sets featured tags'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when the endpoint is a paginated Collection' do
 | 
				
			||||||
 | 
					      let(:payload) do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          '@context': 'https://www.w3.org/ns/activitystreams',
 | 
				
			||||||
 | 
					          type: 'Collection',
 | 
				
			||||||
 | 
					          id: collection_url,
 | 
				
			||||||
 | 
					          first: {
 | 
				
			||||||
 | 
					            type: 'CollectionPage',
 | 
				
			||||||
 | 
					            partOf: collection_url,
 | 
				
			||||||
 | 
					            items: items,
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }.with_indifferent_access
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it_behaves_like 'sets featured tags'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Loading…
	
		Reference in New Issue