diff --git a/.env.production.sample b/.env.production.sample index 3dd66abae4fc06..1faaf5b57cac22 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -50,7 +50,7 @@ OTP_SECRET= # Must be available (and set to same values) for all server processes # These are private/secret values, do not share outside hosting environment # Use `bin/rails db:encryption:init` to generate fresh secrets -# Do not change these secrets once in use, as this would cause data loss and other issues +# Do NOT change these secrets once in use, as this would cause data loss and other issues # ------------------ # ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY= # ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a3825484e2ae2..6bdacab4a76b60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,30 +9,51 @@ You can contribute in the following ways: - Contributing code to Mastodon by fixing bugs or implementing features - Improving the documentation -If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). - Please review the org-level [contribution guidelines] for high-level acceptance criteria guidance and the [DEVELOPMENT] guide for environment-specific details. -[contribution guidelines]: https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md - ## API Changes and Additions -Please note that any changes or additions made to the API should have an accompanying pull request on [our documentation repository](https://github.com/mastodon/documentation). +Any changes or additions made to the API should have an accompanying pull +request on our [documentation repository]. -## Bug reports +## Bug Reports -Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/mastodon/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected. +Bug reports and feature suggestions must use descriptive and concise titles and +be submitted to [GitHub Issues]. Please use the search function to make sure +there are not duplicate bug reports or feature requests. ## Translations -You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). They are periodically merged into the codebase. +Translations are community contributed via [Crowdin]. They are periodically +reviewed and merged into the codebase. [![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)](https://crowdin.com/project/mastodon) -## Pull requests +## Pull Requests + +### Size and Scope + +Our time is limited and PRs making large, unsolicited changes are unlikely to +get a response. Changes which link to an existing confirmed issue, or which come +from a "help wanted" issue or other request are more likely to be reviewed. -**Please use clean, concise titles for your pull requests.** Unless the pull request is about refactoring code, updating dependencies or other internal tasks, assume that the person reading the pull request title is not a programmer or Mastodon developer, but instead a Mastodon user or server administrator, and **try to describe your change or fix from their perspective**. We use commit squashing, so the final commit in the main branch will carry the title of the pull request, and commits from the main branch are fed into the changelog. The changelog is separated into [keepachangelog.com categories](https://keepachangelog.com/en/1.0.0/), and while that spec does not prescribe how the entries ought to be named, for easier sorting, start your pull request titles using one of the verbs "Add", "Change", "Deprecate", "Remove", or "Fix" (present tense). +The smaller and more narrowly focused the changes in a PR are, the easier they +are to review and potentially merge. If the change only makes sense in some +larger context of future ongoing work, note that in the description, but still +aim to keep each distinct PR to a "smallest viable change" chunk of work. + +### Description of Changes + +Unless the Pull Request is about refactoring code, updating dependencies or +other internal tasks, assume that the audience are not developers, but a +Mastodon user or server admin, and try to describe it from their perspective. + +The final commit in the main branch will carry the title from the PR. The main +branch is then fed into the changelog and ultimately into release notes. We try +to follow the [keepachangelog] spec, and while that does not prescribe how +exactly the entries ought to be named, starting titles using one of the verbs +"Add", "Change", "Deprecate", "Remove", or "Fix" (present tense) is helpful. Example: @@ -40,18 +61,25 @@ Example: | ------------------------------------ | ------------------------------------------------------------- | | Fixed NoMethodError in RemovalWorker | Fix nil error when removing statuses caused by race condition | -It is not always possible to phrase every change in such a manner, but it is desired. +### Technical Requirements -**The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable. - -**Pull requests that do not pass automated checks may not be reviewed**. In particular, you need to keep in mind: +Pull requests that do not pass automated checks on CI may not be reviewed. In +particular, please keep in mind: - Unit and integration tests (rspec, jest) - Code style rules (rubocop, eslint) - Normalization of locale files (i18n-tasks) +- Relevant accessibility or performance concerns ## Documentation -The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation). +The [Mastodon documentation] is a statically generated site that contains guides +and API docs. Improvements are made via PRs to the [documentation repository]. +[contribution guidelines]: https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md +[Crowdin]: https://crowdin.com/project/mastodon [DEVELOPMENT]: docs/DEVELOPMENT.md +[documentation repository]: https://github.com/mastodon/documentation +[GitHub Issues]: https://github.com/mastodon/mastodon/issues +[keepachangelog]: https://keepachangelog.com/en/1.0.0/ +[Mastodon documentation]: https://docs.joinmastodon.org diff --git a/Gemfile b/Gemfile index 5f277859cdddc3..dfb9d42a607609 100644 --- a/Gemfile +++ b/Gemfile @@ -105,19 +105,19 @@ 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.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.25.0', require: false - gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false - gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false - 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.34.0', require: false - gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false - gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false + gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false + gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false + gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false + gem 'opentelemetry-instrumentation-excon', '~> 0.23.0', require: false + gem 'opentelemetry-instrumentation-faraday', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-http', '~> 0.24.0', require: false + gem 'opentelemetry-instrumentation-http_client', '~> 0.23.0', require: false + gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false + gem 'opentelemetry-instrumentation-pg', '~> 0.30.0', require: false + gem 'opentelemetry-instrumentation-rack', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-rails', '~> 0.35.0', require: false + gem 'opentelemetry-instrumentation-redis', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-sidekiq', '~> 0.26.0', require: false gem 'opentelemetry-sdk', '~> 1.4', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index f7ab57961b62f6..0ece6da14e0fe0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -189,7 +189,7 @@ GEM climate_control (1.2.0) cocoon (1.2.15) color_diff (0.1) - concurrent-ruby (1.3.4) + concurrent-ruby (1.3.5) connection_pool (2.5.0) cose (1.3.1) cbor (~> 0.5.9) @@ -314,7 +314,7 @@ GEM hashie (5.0.0) hcaptcha (7.1.0) json - highline (3.1.1) + highline (3.1.2) reline hiredis (0.6.3) hiredis-client (0.22.2) @@ -335,7 +335,7 @@ GEM httplog (1.7.0) rack (>= 2.0) rainbow (>= 2.0.0) - i18n (1.14.6) + i18n (1.14.7) concurrent-ruby (~> 1.0) i18n-tasks (1.0.14) activesupport (>= 4.0.2) @@ -420,7 +420,7 @@ GEM llhttp-ffi (0.5.0) ffi-compiler (~> 1.0) rake (~> 13.0) - logger (1.6.4) + logger (1.6.5) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -442,7 +442,7 @@ GEM mime-types (3.6.0) logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.1203) + mime-types-data (3.2025.0107) mini_magick (4.13.2) mini_mime (1.1.5) mini_portile2 (2.8.8) @@ -452,7 +452,7 @@ GEM mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.5.4) + net-imap (0.5.5) date net-protocol net-ldap (0.19.0) @@ -463,7 +463,7 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.18.1) + nokogiri (1.18.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) oj (3.16.9) @@ -512,78 +512,78 @@ GEM opentelemetry-common (~> 0.20) opentelemetry-sdk (~> 1.2) opentelemetry-semantic_conventions - opentelemetry-helpers-sql-obfuscation (0.2.1) + opentelemetry-helpers-sql-obfuscation (0.3.0) opentelemetry-common (~> 0.21) - opentelemetry-instrumentation-action_mailer (0.3.0) + opentelemetry-instrumentation-action_mailer (0.4.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (~> 0.7) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-action_pack (0.10.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-action_pack (0.11.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-rack (~> 0.21) - opentelemetry-instrumentation-action_view (0.8.0) + opentelemetry-instrumentation-action_view (0.9.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (~> 0.7) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_job (0.7.8) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_job (0.8.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_model_serializers (0.21.1) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_model_serializers (0.22.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (>= 0.7.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_record (0.8.1) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_record (0.9.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_support (0.7.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_support (0.8.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-base (0.22.6) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-base (0.23.0) opentelemetry-api (~> 1.0) opentelemetry-common (~> 0.21) opentelemetry-registry (~> 0.1) - opentelemetry-instrumentation-concurrent_ruby (0.21.4) + opentelemetry-instrumentation-concurrent_ruby (0.22.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-excon (0.22.5) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-excon (0.23.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-faraday (0.25.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-faraday (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-http (0.23.5) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-http (0.24.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-http_client (0.22.8) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-http_client (0.23.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-net_http (0.22.8) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-net_http (0.23.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-pg (0.29.2) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-pg (0.30.0) opentelemetry-api (~> 1.0) opentelemetry-helpers-sql-obfuscation - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-rack (0.25.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-rack (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-rails (0.34.1) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-rails (0.35.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-action_mailer (~> 0.3.0) - opentelemetry-instrumentation-action_pack (~> 0.10.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.7.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-concurrent_ruby (~> 0.21.4) - opentelemetry-instrumentation-redis (0.25.7) + opentelemetry-instrumentation-action_mailer (~> 0.4.0) + opentelemetry-instrumentation-action_pack (~> 0.11.0) + opentelemetry-instrumentation-action_view (~> 0.9.0) + opentelemetry-instrumentation-active_job (~> 0.8.0) + opentelemetry-instrumentation-active_record (~> 0.9.0) + opentelemetry-instrumentation-active_support (~> 0.8.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) + opentelemetry-instrumentation-redis (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-sidekiq (0.25.7) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-sidekiq (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-registry (0.3.1) opentelemetry-api (~> 1.1) opentelemetry-sdk (1.6.0) @@ -600,7 +600,7 @@ GEM parallel (1.26.3) parallel_tests (4.2.1) parallel - parser (3.3.6.0) + parser (3.3.7.0) ast (~> 2.4.1) racc parslet (2.0.0) @@ -621,7 +621,7 @@ GEM activesupport (>= 7.0.0) rack railties (>= 7.0.0) - psych (5.2.2) + psych (5.2.3) date stringio public_suffix (6.0.1) @@ -705,7 +705,7 @@ GEM link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.7.0) rdf (~> 3.3) - rdoc (6.10.0) + rdoc (6.11.0) psych (>= 4.0.0) redcarpet (3.6.0) redis (4.8.1) @@ -781,7 +781,7 @@ GEM rack (>= 1.1) rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.3.0) + rubocop-rspec (3.4.0) rubocop (~> 1.61) rubocop-rspec_rails (2.30.0) rubocop (~> 1.61) @@ -808,7 +808,7 @@ GEM activerecord (>= 4.0.0) railties (>= 4.0.0) securerandom (0.4.1) - selenium-webdriver (4.27.0) + selenium-webdriver (4.28.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -902,7 +902,7 @@ GEM rbs (>= 1.8.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2024.2) + tzinfo-data (1.2025.1) tzinfo (>= 1.0.0) unf (0.1.4) unf_ext @@ -940,7 +940,8 @@ GEM semantic_range (>= 2.3.0) webrick (1.9.1) websocket (1.2.11) - websocket-driver (0.7.6) + websocket-driver (0.7.7) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) wisper (2.0.1) @@ -1034,19 +1035,19 @@ DEPENDENCIES omniauth_openid_connect (~> 0.6.1) opentelemetry-api (~> 1.4.0) opentelemetry-exporter-otlp (~> 0.29.0) - opentelemetry-instrumentation-active_job (~> 0.7.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.25.0) - opentelemetry-instrumentation-http (~> 0.23.2) - opentelemetry-instrumentation-http_client (~> 0.22.3) - opentelemetry-instrumentation-net_http (~> 0.22.4) - opentelemetry-instrumentation-pg (~> 0.29.0) - opentelemetry-instrumentation-rack (~> 0.25.0) - opentelemetry-instrumentation-rails (~> 0.34.0) - opentelemetry-instrumentation-redis (~> 0.25.3) - opentelemetry-instrumentation-sidekiq (~> 0.25.2) + opentelemetry-instrumentation-active_job (~> 0.8.0) + opentelemetry-instrumentation-active_model_serializers (~> 0.22.0) + opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) + opentelemetry-instrumentation-excon (~> 0.23.0) + opentelemetry-instrumentation-faraday (~> 0.26.0) + opentelemetry-instrumentation-http (~> 0.24.0) + opentelemetry-instrumentation-http_client (~> 0.23.0) + opentelemetry-instrumentation-net_http (~> 0.23.0) + opentelemetry-instrumentation-pg (~> 0.30.0) + opentelemetry-instrumentation-rack (~> 0.26.0) + opentelemetry-instrumentation-rails (~> 0.35.0) + opentelemetry-instrumentation-redis (~> 0.26.0) + opentelemetry-instrumentation-sidekiq (~> 0.26.0) opentelemetry-sdk (~> 1.4) ox (~> 2.14) parallel_tests @@ -1120,4 +1121,4 @@ RUBY VERSION ruby 3.4.1p0 BUNDLED WITH - 2.6.2 + 2.6.3 diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index a378425183b49d..1b64eb4ef2efcb 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -33,6 +33,7 @@ def account_params :discoverable, :hide_collections, :indexable, + attribution_domains: [], fields_attributes: [:name, :value] ) end diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb index 376a30c16fd6f5..5e7b14646a0848 100644 --- a/app/controllers/auth/setup_controller.rb +++ b/app/controllers/auth/setup_controller.rb @@ -35,6 +35,6 @@ def set_user end def user_params - params.require(:user).permit(:email) + params.expect(user: [:email]) end end diff --git a/app/controllers/settings/verifications_controller.rb b/app/controllers/settings/verifications_controller.rb index 4e0663253cc82e..9cc60ba2e88b13 100644 --- a/app/controllers/settings/verifications_controller.rb +++ b/app/controllers/settings/verifications_controller.rb @@ -18,7 +18,9 @@ def update private def account_params - params.require(:account).permit(:attribution_domains_as_text) + params.require(:account).permit(:attribution_domains).tap do |params| + params[:attribution_domains] = params[:attribution_domains].split if params[:attribution_domains] + end end def set_account diff --git a/app/javascript/images/reticle.png b/app/javascript/images/reticle.png deleted file mode 100644 index a724ac0bcdf535..00000000000000 Binary files a/app/javascript/images/reticle.png and /dev/null differ diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 4131236702f12b..3adcbebd436419 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -444,7 +444,7 @@ export function initMediaEditModal(id) { dispatch(openModal({ modalType: 'FOCAL_POINT', - modalProps: { id }, + modalProps: { mediaId: id }, })); }; } diff --git a/app/javascript/mastodon/actions/compose_typed.ts b/app/javascript/mastodon/actions/compose_typed.ts new file mode 100644 index 00000000000000..97f0d68c51439c --- /dev/null +++ b/app/javascript/mastodon/actions/compose_typed.ts @@ -0,0 +1,70 @@ +import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; + +import { apiUpdateMedia } from 'mastodon/api/compose'; +import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments'; +import type { MediaAttachment } from 'mastodon/models/media_attachment'; +import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; + +type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & { + unattached?: boolean; +}; + +const simulateModifiedApiResponse = ( + media: MediaAttachment, + params: { description?: string; focus?: string }, +): SimulatedMediaAttachmentJSON => { + const [x, y] = (params.focus ?? '').split(','); + + const data = { + ...media.toJS(), + ...params, + meta: { + focus: { + x: parseFloat(x ?? '0'), + y: parseFloat(y ?? '0'), + }, + }, + } as unknown as SimulatedMediaAttachmentJSON; + + return data; +}; + +export const changeUploadCompose = createDataLoadingThunk( + 'compose/changeUpload', + async ( + { + id, + ...params + }: { + id: string; + description: string; + focus: string; + }, + { getState }, + ) => { + const media = ( + (getState().compose as ImmutableMap).get( + 'media_attachments', + ) as ImmutableList + ).find((item) => item.get('id') === id); + + // Editing already-attached media is deferred to editing the post itself. + // For simplicity's sake, fake an API reply. + if (media && !media.get('unattached')) { + return new Promise((resolve) => { + resolve(simulateModifiedApiResponse(media, params)); + }); + } + + return apiUpdateMedia(id, params); + }, + (media: SimulatedMediaAttachmentJSON) => { + return { + media, + attached: typeof media.unattached !== 'undefined' && !media.unattached, + }; + }, + { + useLoadingBar: false, + }, +); diff --git a/app/javascript/mastodon/actions/modal.ts b/app/javascript/mastodon/actions/modal.ts index ab03e4676525be..49af176a111db0 100644 --- a/app/javascript/mastodon/actions/modal.ts +++ b/app/javascript/mastodon/actions/modal.ts @@ -9,6 +9,7 @@ export type ModalType = keyof typeof MODAL_COMPONENTS; interface OpenModalPayload { modalType: ModalType; modalProps: ModalProps; + previousModalProps?: ModalProps; } export const openModal = createAction('MODAL_OPEN'); diff --git a/app/javascript/mastodon/api/compose.ts b/app/javascript/mastodon/api/compose.ts new file mode 100644 index 00000000000000..757e9961c9206f --- /dev/null +++ b/app/javascript/mastodon/api/compose.ts @@ -0,0 +1,7 @@ +import { apiRequestPut } from 'mastodon/api'; +import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments'; + +export const apiUpdateMedia = ( + id: string, + params?: { description?: string; focus?: string }, +) => apiRequestPut(`v1/media/${id}`, params); diff --git a/app/javascript/mastodon/components/button.tsx b/app/javascript/mastodon/components/button.tsx index b349a83f2bbed2..a527468f6567ed 100644 --- a/app/javascript/mastodon/components/button.tsx +++ b/app/javascript/mastodon/components/button.tsx @@ -7,6 +7,7 @@ interface BaseProps extends Omit, 'children'> { block?: boolean; secondary?: boolean; + compact?: boolean; dangerous?: boolean; } @@ -27,6 +28,7 @@ export const Button: React.FC = ({ disabled, block, secondary, + compact, dangerous, className, title, @@ -47,6 +49,7 @@ export const Button: React.FC = ({ + + + + ); +}; + +const Preview: React.FC<{ + mediaId: string; + position: FocalPoint; + onPositionChange: (arg0: FocalPoint) => void; +}> = ({ mediaId, position, onPositionChange }) => { + const media = useAppSelector((state) => + ( + (state.compose as ImmutableMap).get( + 'media_attachments', + ) as ImmutableList + ).find((x) => x.get('id') === mediaId), + ); + const account = useAppSelector((state) => + me ? state.accounts.get(me) : undefined, + ); + + const [dragging, setDragging] = useState(false); + const [x, y] = position; + const nodeRef = useRef(null); + const draggingRef = useRef(false); + + const setRef = useCallback( + (e: HTMLImageElement | HTMLVideoElement | null) => { + nodeRef.current = e; + }, + [], + ); + + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + if (e.button !== 0) { + return; + } + + const { x, y } = getPointerPosition(nodeRef.current, e); + setDragging(true); + draggingRef.current = true; + onPositionChange([x, y]); + }, + [setDragging, onPositionChange], + ); + + const handleTouchStart = useCallback( + (e: React.TouchEvent) => { + const { x, y } = getPointerPosition(nodeRef.current, e); + setDragging(true); + draggingRef.current = true; + onPositionChange([x, y]); + }, + [setDragging, onPositionChange], + ); + + useEffect(() => { + const handleMouseUp = () => { + setDragging(false); + draggingRef.current = false; + }; + + const handleMouseMove = (e: MouseEvent) => { + if (draggingRef.current) { + const { x, y } = getPointerPosition(nodeRef.current, e); + onPositionChange([x, y]); + } + }; + + const handleTouchEnd = () => { + setDragging(false); + draggingRef.current = false; + }; + + const handleTouchMove = (e: TouchEvent) => { + if (draggingRef.current) { + const { x, y } = getPointerPosition(nodeRef.current, e); + onPositionChange([x, y]); + } + }; + + document.addEventListener('mouseup', handleMouseUp); + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('touchend', handleTouchEnd); + document.addEventListener('touchmove', handleTouchMove); + + return () => { + document.removeEventListener('mouseup', handleMouseUp); + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('touchend', handleTouchEnd); + document.removeEventListener('touchmove', handleTouchMove); + }; + }, [setDragging, onPositionChange]); + + if (!media) { + return null; + } + + if (media.get('type') === 'image') { + return ( +
+ +
+
+ ); + } else if (media.get('type') === 'gifv') { + return ( +
+ +
+
+ ); + } else if (media.get('type') === 'video') { + return ( +