diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml index 1d4395e9ac..5bb67b108c 100644 --- a/.github/workflows/lint-ruby.yml +++ b/.github/workflows/lint-ruby.yml @@ -12,6 +12,7 @@ on: - 'Gemfile*' - '.rubocop*.yml' - '.ruby-version' + - 'bin/rubocop' - 'config/brakeman.ignore' - '**/*.rb' - '**/*.rake' @@ -22,6 +23,7 @@ on: - 'Gemfile*' - '.rubocop*.yml' - '.ruby-version' + - 'bin/rubocop' - 'config/brakeman.ignore' - '**/*.rb' - '**/*.rake' diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index d8e6cd11d2..1df87ebbdf 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -170,7 +170,7 @@ jobs: - name: Upload coverage reports to Codecov if: matrix.ruby-version == '.ruby-version' - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: files: coverage/lcov/*.lcov env: @@ -256,7 +256,7 @@ jobs: - name: Upload coverage reports to Codecov if: matrix.ruby-version == '.ruby-version' - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: files: coverage/lcov/mastodon.lcov env: diff --git a/Gemfile b/Gemfile index 6abb075c1c..b626b5511c 100644 --- a/Gemfile +++ b/Gemfile @@ -105,7 +105,7 @@ gem 'opentelemetry-api', '~> 1.4.0' group :opentelemetry do gem 'opentelemetry-exporter-otlp', '~> 0.29.0', require: false gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false - gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false + gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.21.0', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false @@ -114,7 +114,7 @@ group :opentelemetry do gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false - gem 'opentelemetry-instrumentation-rails', '~> 0.33.0', require: false + gem 'opentelemetry-instrumentation-rails', '~> 0.34.0', require: false gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false gem 'opentelemetry-sdk', '~> 1.4', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 6d125f4a96..3367e04685 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,7 +94,7 @@ GEM ast (2.4.2) attr_required (1.0.2) aws-eventstream (1.3.0) - aws-partitions (1.1017.0) + aws-partitions (1.1025.0) aws-sdk-core (3.214.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -109,7 +109,7 @@ GEM aws-sigv4 (~> 1.5) aws-sigv4 (1.10.1) aws-eventstream (~> 1, >= 1.0.2) - azure-blob (0.5.3) + azure-blob (0.5.4) rexml base64 (0.2.0) bcp47_spec (0.2.1) @@ -168,15 +168,15 @@ GEM bigdecimal rexml crass (1.0.6) - css_parser (1.19.1) + css_parser (1.21.0) addressable - csv (3.3.0) + csv (3.3.1) database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) date (3.4.1) - debug (1.9.2) + debug (1.10.0) irb (~> 1.10) reline (>= 0.3.8) debug_inspector (1.2.0) @@ -201,7 +201,7 @@ GEM domain_name (0.6.20240107) doorkeeper (5.8.1) railties (>= 5) - dotenv (3.1.5) + dotenv (3.1.7) drb (2.2.1) elasticsearch (7.17.11) elasticsearch-api (= 7.17.11) @@ -224,14 +224,14 @@ GEM fabrication (2.31.0) faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (2.12.0) - faraday-net_http (>= 2.0, < 3.4) + faraday (2.12.2) + faraday-net_http (>= 2.0, < 3.5) json logger faraday-httpclient (2.0.1) httpclient (>= 2.2) - faraday-net_http (3.3.0) - net-http + faraday-net_http (3.4.0) + net-http (>= 0.5.0) fast_blank (1.0.1) fastimage (2.3.1) ffi (1.17.0) @@ -279,7 +279,7 @@ GEM rainbow rubocop (>= 1.0) sysexits (~> 1.1) - hashdiff (1.1.1) + hashdiff (1.1.2) hashie (5.0.0) hcaptcha (7.1.0) json @@ -294,7 +294,7 @@ GEM http-cookie (~> 1.0) http-form_data (~> 2.2) llhttp-ffi (~> 0.5.0) - http-cookie (1.0.5) + http-cookie (1.0.8) domain_name (~> 0.5) http-form_data (2.3.0) http_accept_language (2.1.1) @@ -318,8 +318,8 @@ GEM inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) - io-console (0.7.2) - irb (1.14.2) + io-console (0.8.0) + irb (1.14.3) rdoc (>= 4.0.0) reline (>= 0.4.2) jd-paperclip-azure (3.0.0) @@ -327,7 +327,7 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.9.0) + json (2.9.1) json-canonicalization (1.0.0) json-jwt (1.15.3.1) activesupport (>= 4.2) @@ -384,7 +384,7 @@ GEM llhttp-ffi (0.5.0) ffi-compiler (~> 1.0) rake (~> 13.0) - logger (1.6.2) + logger (1.6.3) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -406,7 +406,7 @@ GEM mime-types (3.6.0) logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.1105) + mime-types-data (3.2024.1203) mini_mime (1.1.5) mini_portile2 (2.8.8) minitest (5.25.4) @@ -415,7 +415,7 @@ GEM mutex_m (0.3.0) net-http (0.5.0) uri - net-imap (0.5.1) + net-imap (0.5.2) date net-protocol net-ldap (0.19.0) @@ -429,7 +429,7 @@ GEM nokogiri (1.17.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) - oj (3.16.7) + oj (3.16.8) bigdecimal (>= 3.0) ostruct (>= 0.2) omniauth (2.1.2) @@ -460,7 +460,7 @@ GEM validate_email validate_url webfinger (~> 1.2) - openssl (3.2.0) + openssl (3.2.1) openssl-signature_algorithm (1.3.0) openssl (> 2.0) opentelemetry-api (1.4.0) @@ -475,29 +475,29 @@ GEM opentelemetry-semantic_conventions opentelemetry-helpers-sql-obfuscation (0.2.1) opentelemetry-common (~> 0.21) - opentelemetry-instrumentation-action_mailer (0.2.0) + opentelemetry-instrumentation-action_mailer (0.3.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-active_support (~> 0.1) + opentelemetry-instrumentation-active_support (~> 0.7) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-action_pack (0.10.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-rack (~> 0.21) - opentelemetry-instrumentation-action_view (0.7.3) + opentelemetry-instrumentation-action_view (0.8.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-active_support (~> 0.6) + opentelemetry-instrumentation-active_support (~> 0.7) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-active_job (0.7.8) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_model_serializers (0.20.3) + opentelemetry-instrumentation-active_model_serializers (0.21.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-active_support (>= 0.6.0) + opentelemetry-instrumentation-active_support (>= 0.7.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-active_record (0.8.1) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_support (0.6.0) + opentelemetry-instrumentation-active_support (0.7.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (0.22.6) @@ -510,7 +510,7 @@ GEM opentelemetry-instrumentation-excon (0.22.5) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-faraday (0.24.7) + opentelemetry-instrumentation-faraday (0.24.8) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-http (0.23.5) @@ -529,14 +529,14 @@ GEM opentelemetry-instrumentation-rack (0.25.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-rails (0.33.1) + opentelemetry-instrumentation-rails (0.34.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-action_mailer (~> 0.2.0) + opentelemetry-instrumentation-action_mailer (~> 0.3.0) opentelemetry-instrumentation-action_pack (~> 0.10.0) - opentelemetry-instrumentation-action_view (~> 0.7.0) + opentelemetry-instrumentation-action_view (~> 0.8.0) opentelemetry-instrumentation-active_job (~> 0.7.0) opentelemetry-instrumentation-active_record (~> 0.8.0) - opentelemetry-instrumentation-active_support (~> 0.6.0) + opentelemetry-instrumentation-active_support (~> 0.7.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-redis (0.25.7) opentelemetry-api (~> 1.0) @@ -579,7 +579,7 @@ GEM activesupport (>= 7.0.0) rack railties (>= 7.0.0) - psych (5.2.1) + psych (5.2.2) date stringio public_suffix (6.0.1) @@ -634,7 +634,7 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.1) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-i18n (7.0.10) @@ -656,7 +656,7 @@ GEM link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.7.0) rdf (~> 3.3) - rdoc (6.7.0) + rdoc (6.10.0) psych (>= 4.0.0) redcarpet (3.6.0) redis (4.8.1) @@ -665,14 +665,14 @@ GEM redlock (1.3.2) redis (>= 3.0.0, < 6.0) regexp_parser (2.9.3) - reline (0.5.12) + reline (0.6.0) io-console (~> 0.5) - request_store (1.6.0) + request_store (1.7.0) rack (>= 1.4) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.9) + rexml (3.4.0) rotp (6.3.0) rouge (4.5.1) rpam2 (4.0.2) @@ -707,7 +707,7 @@ GEM rspec-expectations (~> 3.0) rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) - rspec-support (3.13.1) + rspec-support (3.13.2) rubocop (1.69.2) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -718,7 +718,7 @@ GEM rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.36.2) + rubocop-ast (1.37.0) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) @@ -744,8 +744,8 @@ GEM ffi (~> 1.12) logger rubyzip (2.3.2) - rufus-scheduler (3.9.1) - fugit (~> 1.1, >= 1.1.6) + rufus-scheduler (3.9.2) + fugit (~> 1.1, >= 1.11.1) safety_net_attestation (0.4.0) jwt (~> 2.0) sanitize (6.1.3) @@ -754,14 +754,14 @@ GEM scenic (1.8.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - securerandom (0.3.2) + securerandom (0.4.1) selenium-webdriver (4.27.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - semantic_range (3.0.0) + semantic_range (3.1.0) shoulda-matchers (6.4.0) activesupport (>= 5.2.0) sidekiq (6.5.12) @@ -808,10 +808,10 @@ GEM unicode-display_width (>= 1.1.1, < 3) terrapin (1.0.1) climate_control - test-prof (1.4.2) + test-prof (1.4.3) thor (1.3.2) tilt (2.4.0) - timeout (0.4.2) + timeout (0.4.3) tpm-key_attestation (0.12.1) bindata (~> 2.4) openssl (> 2.0) @@ -837,7 +837,7 @@ GEM unf_ext unf_ext (0.0.9.1) unicode-display_width (2.6.0) - uri (0.13.1) + uri (1.0.2) useragent (0.16.11) validate_email (0.1.6) activemodel (>= 3.0) @@ -959,7 +959,7 @@ DEPENDENCIES opentelemetry-api (~> 1.4.0) opentelemetry-exporter-otlp (~> 0.29.0) opentelemetry-instrumentation-active_job (~> 0.7.1) - opentelemetry-instrumentation-active_model_serializers (~> 0.20.1) + opentelemetry-instrumentation-active_model_serializers (~> 0.21.0) opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2) opentelemetry-instrumentation-excon (~> 0.22.0) opentelemetry-instrumentation-faraday (~> 0.24.1) @@ -968,7 +968,7 @@ DEPENDENCIES opentelemetry-instrumentation-net_http (~> 0.22.4) opentelemetry-instrumentation-pg (~> 0.29.0) opentelemetry-instrumentation-rack (~> 0.25.0) - opentelemetry-instrumentation-rails (~> 0.33.0) + opentelemetry-instrumentation-rails (~> 0.34.0) opentelemetry-instrumentation-redis (~> 0.25.3) opentelemetry-instrumentation-sidekiq (~> 0.25.2) opentelemetry-sdk (~> 1.4) @@ -1036,4 +1036,4 @@ RUBY VERSION ruby 3.3.6p108 BUNDLED WITH - 2.5.23 + 2.6.1 diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb index ad1b82cb52..2833687a38 100644 --- a/app/controllers/api/v1/polls/votes_controller.rb +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -15,7 +15,7 @@ class Api::V1::Polls::VotesController < Api::BaseController private def set_poll - @poll = Poll.attached.find(params[:poll_id]) + @poll = Poll.find(params[:poll_id]) authorize @poll.status, :show? rescue Mastodon::NotPermittedError not_found diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb index ffc70a8496..b4c25476e8 100644 --- a/app/controllers/api/v1/polls_controller.rb +++ b/app/controllers/api/v1/polls_controller.rb @@ -15,7 +15,7 @@ class Api::V1::PollsController < Api::BaseController private def set_poll - @poll = Poll.attached.find(params[:id]) + @poll = Poll.find(params[:id]) authorize @poll.status, :show? rescue Mastodon::NotPermittedError not_found diff --git a/app/javascript/mastodon/actions/search.ts b/app/javascript/mastodon/actions/search.ts index 7ee432f782..7dd174e202 100644 --- a/app/javascript/mastodon/actions/search.ts +++ b/app/javascript/mastodon/actions/search.ts @@ -79,15 +79,12 @@ export const expandSearch = createDataLoadingThunk( export const openURL = createDataLoadingThunk( 'search/openURL', - ({ url }: { url: string }, { getState }) => { - const signedIn = !!getState().meta.get('me'); - - return apiGetSearch({ + ({ url }: { url: string }) => + apiGetSearch({ q: url, - resolve: signedIn, + resolve: true, limit: 1, - }); - }, + }), (data, { dispatch }) => { if (data.accounts.length > 0) { dispatch(importFetchedAccounts(data.accounts)); diff --git a/app/javascript/mastodon/components/regeneration_indicator.jsx b/app/javascript/mastodon/components/regeneration_indicator.jsx deleted file mode 100644 index d42a7d7c72..0000000000 --- a/app/javascript/mastodon/components/regeneration_indicator.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FormattedMessage } from 'react-intl'; - -import illustration from '@/images/elephant_ui_working.svg'; - -const RegenerationIndicator = () => ( -
-
- -
- -
- - -
-
-); - -export default RegenerationIndicator; diff --git a/app/javascript/mastodon/components/regeneration_indicator.tsx b/app/javascript/mastodon/components/regeneration_indicator.tsx new file mode 100644 index 0000000000..e26b93eb4f --- /dev/null +++ b/app/javascript/mastodon/components/regeneration_indicator.tsx @@ -0,0 +1,26 @@ +import { FormattedMessage } from 'react-intl'; + +import { GIF } from './gif'; + +export const RegenerationIndicator: React.FC = () => ( +
+ + +
+ + + + +
+
+); diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx index c6cacbd2b2..3091e2a2a0 100644 --- a/app/javascript/mastodon/components/status_list.jsx +++ b/app/javascript/mastodon/components/status_list.jsx @@ -6,7 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { debounce } from 'lodash'; import { TIMELINE_GAP, TIMELINE_SUGGESTIONS } from 'mastodon/actions/timelines'; -import RegenerationIndicator from 'mastodon/components/regeneration_indicator'; +import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator'; import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions'; import StatusContainer from '../containers/status_container'; diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx deleted file mode 100644 index 6b3a0c857c..0000000000 --- a/app/javascript/mastodon/features/interaction_modal/index.jsx +++ /dev/null @@ -1,433 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; - -import classNames from 'classnames'; - -import { connect } from 'react-redux'; - -import { throttle, escapeRegExp } from 'lodash'; - -import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; -import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react'; -import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; -import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; -import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; -import StarIcon from '@/material-icons/400-24px/star.svg?react'; -import { openModal, closeModal } from 'mastodon/actions/modal'; -import api from 'mastodon/api'; -import { Button } from 'mastodon/components/button'; -import { Icon } from 'mastodon/components/icon'; -import { registrationsOpen, sso_redirect } from 'mastodon/initial_state'; - -const messages = defineMessages({ - loginPrompt: { id: 'interaction_modal.login.prompt', defaultMessage: 'Domain of your home server, e.g. mastodon.social' }, -}); - -const mapStateToProps = (state, { accountId }) => ({ - displayNameHtml: state.getIn(['accounts', accountId, 'display_name_html']), - signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up', -}); - -const mapDispatchToProps = (dispatch) => ({ - onSignupClick() { - dispatch(closeModal({ - modalType: undefined, - ignoreFocus: false, - })); - dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' })); - }, -}); - -const PERSISTENCE_KEY = 'mastodon_home'; - -const isValidDomain = value => { - const url = new URL('https:///path'); - url.hostname = value; - return url.hostname === value; -}; - -const valueToDomain = value => { - // If the user starts typing an URL - if (/^https?:\/\//.test(value)) { - try { - const url = new URL(value); - - // Consider that if there is a path, the URL is more meaningful than a bare domain - if (url.pathname.length > 1) { - return ''; - } - - return url.host; - } catch { - return undefined; - } - // If the user writes their full handle including username - } else if (value.includes('@')) { - if (value.replace(/^@/, '').split('@').length > 2) { - return undefined; - } - return ''; - } - - return value; -}; - -const addInputToOptions = (value, options) => { - value = value.trim(); - - if (value.includes('.') && isValidDomain(value)) { - return [value].concat(options.filter((x) => x !== value)); - } - - return options; -}; - -class LoginForm extends React.PureComponent { - - static propTypes = { - resourceUrl: PropTypes.string, - intl: PropTypes.object.isRequired, - }; - - state = { - value: localStorage ? (localStorage.getItem(PERSISTENCE_KEY) || '') : '', - expanded: false, - selectedOption: -1, - isLoading: false, - isSubmitting: false, - error: false, - options: [], - networkOptions: [], - }; - - setRef = c => { - this.input = c; - }; - - isValueValid = (value) => { - let likelyAcct = false; - let url = null; - - if (value.startsWith('/')) { - return false; - } - - if (value.startsWith('@')) { - value = value.slice(1); - likelyAcct = true; - } - - // The user is in the middle of typing something, do not error out - if (value === '') { - return true; - } - - if (/^https?:\/\//.test(value) && !likelyAcct) { - url = value; - } else { - url = `https://${value}`; - } - - try { - new URL(url); - return true; - } catch { - return false; - } - }; - - handleChange = ({ target }) => { - const error = !this.isValueValid(target.value); - this.setState(state => ({ error, value: target.value, isLoading: true, options: addInputToOptions(target.value, state.networkOptions) }), () => this._loadOptions()); - }; - - handleMessage = (event) => { - const { resourceUrl } = this.props; - - if (event.origin !== window.origin || event.source !== this.iframeRef.contentWindow) { - return; - } - - if (event.data?.type === 'fetchInteractionURL-failure') { - this.setState({ isSubmitting: false, error: true }); - } else if (event.data?.type === 'fetchInteractionURL-success') { - if (/^https?:\/\//.test(event.data.template)) { - try { - const url = new URL(event.data.template.replace('{uri}', encodeURIComponent(resourceUrl))); - - if (localStorage) { - localStorage.setItem(PERSISTENCE_KEY, event.data.uri_or_domain); - } - - window.location.href = url; - } catch (e) { - console.error(e); - this.setState({ isSubmitting: false, error: true }); - } - } else { - this.setState({ isSubmitting: false, error: true }); - } - } - }; - - componentDidMount () { - window.addEventListener('message', this.handleMessage); - } - - componentWillUnmount () { - window.removeEventListener('message', this.handleMessage); - } - - handleSubmit = () => { - const { value } = this.state; - - this.setState({ isSubmitting: true }); - - this.iframeRef.contentWindow.postMessage({ - type: 'fetchInteractionURL', - uri_or_domain: value.trim(), - }, window.origin); - }; - - setIFrameRef = (iframe) => { - this.iframeRef = iframe; - }; - - handleFocus = () => { - this.setState({ expanded: true }); - }; - - handleBlur = () => { - this.setState({ expanded: false }); - }; - - handleKeyDown = (e) => { - const { options, selectedOption } = this.state; - - switch(e.key) { - case 'ArrowDown': - e.preventDefault(); - - if (options.length > 0) { - this.setState({ selectedOption: Math.min(selectedOption + 1, options.length - 1) }); - } - - break; - case 'ArrowUp': - e.preventDefault(); - - if (options.length > 0) { - this.setState({ selectedOption: Math.max(selectedOption - 1, -1) }); - } - - break; - case 'Enter': - e.preventDefault(); - - if (selectedOption === -1) { - this.handleSubmit(); - } else if (options.length > 0) { - this.setState({ value: options[selectedOption], error: false }, () => this.handleSubmit()); - } - - break; - } - }; - - handleOptionClick = e => { - const index = Number(e.currentTarget.getAttribute('data-index')); - const option = this.state.options[index]; - - e.preventDefault(); - this.setState({ selectedOption: index, value: option, error: false }, () => this.handleSubmit()); - }; - - _loadOptions = throttle(() => { - const { value } = this.state; - - const domain = valueToDomain(value.trim()); - - if (typeof domain === 'undefined') { - this.setState({ options: [], networkOptions: [], isLoading: false, error: true }); - return; - } - - if (domain.length === 0) { - this.setState({ options: [], networkOptions: [], isLoading: false }); - return; - } - - api().get('/api/v1/peers/search', { params: { q: domain } }).then(({ data }) => { - if (!data) { - data = []; - } - - this.setState((state) => ({ networkOptions: data, options: addInputToOptions(state.value, data), isLoading: false })); - }).catch(() => { - this.setState({ isLoading: false }); - }); - }, 200, { leading: true, trailing: true }); - - render () { - const { intl } = this.props; - const { value, expanded, options, selectedOption, error, isSubmitting } = this.state; - const domain = (valueToDomain(value) || '').trim(); - const domainRegExp = new RegExp(`(${escapeRegExp(domain)})`, 'gi'); - const hasPopOut = domain.length > 0 && options.length > 0; - - return ( -
- -