diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 4d1fcddcb22..1e782e46c62 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -18,7 +18,7 @@ jobs: with: fetch-depth: 0 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: 3.3 bundler-cache: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2d881f5157b..7222e5528e1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ env.ruby }} bundler-cache: true @@ -33,7 +33,7 @@ jobs: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ env.ruby }} bundler-cache: true @@ -47,14 +47,14 @@ jobs: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ env.ruby }} bundler-cache: true - name: Run herb parser run: bundle exec herb analyze app/ - name: Cache node modules - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: cache: yarn - name: Install node modules @@ -71,12 +71,12 @@ jobs: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ env.ruby }} bundler-cache: true - name: Cache node modules - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: cache: yarn - name: Install node modules @@ -97,7 +97,7 @@ jobs: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ env.ruby }} bundler-cache: true @@ -113,7 +113,7 @@ jobs: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ env.ruby }} bundler-cache: true @@ -122,7 +122,7 @@ jobs: cp config/github.database.yml config/database.yml cp config/example.storage.yml config/storage.yml - name: Cache node modules - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: cache: yarn - name: Install node modules @@ -151,7 +151,7 @@ jobs: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ env.ruby }} bundler-cache: true @@ -160,7 +160,7 @@ jobs: cp config/github.database.yml config/database.yml cp config/example.storage.yml config/storage.yml - name: Cache node modules - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: cache: yarn - name: Install node modules diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d9a5f9e64ce..8a9c168deaa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,13 +25,13 @@ jobs: - name: Checkout source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1.328.0 with: ruby-version: ${{ matrix.ruby }} rubygems: 3.4.10 bundler-cache: true - name: Cache node modules - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: cache: yarn - name: Install packages @@ -76,7 +76,7 @@ jobs: env: DISPLAY: ":99" - name: Upload screenshots - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: failure() with: name: screenshots diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6c94be970d7..48254c6cbe5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -57,7 +57,7 @@ Metrics/BlockNesting: # Offense count: 23 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 352 + Max: 353 # Offense count: 72 # Configuration parameters: AllowedMethods, AllowedPatterns. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b23d890119..f323de1a308 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,7 @@ you do communicate, early and often, so that you can get feedback quickly. This will help you get buy-in from the maintainers, which will translate into less waste for everyone and a much easier time getting your code merged. -Bug fixes should be more straighforward than new features, but the same +Bug fixes should be more straightforward than new features, but the same guidance applies. If it turns out to be more complex than initially expected, stop for a moment and seek feedback, be it in an issue or in a draft PR. diff --git a/FAQ.md b/FAQ.md index b55b26b2825..c5a59f84312 100644 --- a/FAQ.md +++ b/FAQ.md @@ -7,8 +7,8 @@ promote a large OpenStreetMap focused conference or host a worldwide donation drive. This is a great way to reach a lot of people! 1. Please review the Operations Working Group's [Banner Policy](https://operations.osmfoundation.org/policies/banner/) to know whether your event qualifies for a front-page banner. -2. Create the banner image. The image needs to: - * be exactly 350px wide and at most 350px tall, +2. Create the banner image in two sizes for 1× and 2× resolution. The images need to: + * be exactly 350px wide and at most 350px tall, or double that for 2×, * be in PNG format, * have nothing important in the top-right 60x60px corner of the banner which has a close 'X' button overlayed, and * have sufficient visual contrast with the colours #cccccc and #999999 so the 'X' can be seen. diff --git a/Gemfile b/Gemfile index 85e2cc01309..8a3cc7dc0bc 100644 --- a/Gemfile +++ b/Gemfile @@ -12,10 +12,8 @@ gem "pg" # Use SCSS for stylesheets gem "dartsass-sprockets" -# Pin the dependent sass-embedded to avoid deprecation warnings in bootstrap -gem "sass-embedded", "~> 1.64.0" -# Pin uri to avoid errors in dartsass-ruby -gem "uri", "< 1.0.0" +# Don't allow upgrade to 3.0.0 as some deprecations will become errors +gem "sass-embedded", "< 3.0.0" # Use Terser as compressor for JavaScript assets gem "terser" @@ -53,6 +51,7 @@ gem "benchmark" gem "actionpack-page_caching" gem "activerecord-import" gem "active_record_union" +gem "active_storage_validations" gem "bootstrap" gem "bootstrap_form" gem "cancancan" diff --git a/Gemfile.lock b/Gemfile.lock index 04c49eb463b..df42bd26c6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,12 @@ GEM rails-html-sanitizer (~> 1.6) active_record_union (1.4.0) activerecord (>= 6.0) + active_storage_validations (4.1.1) + activejob (>= 7.0.1) + activemodel (>= 7.0.1) + activestorage (>= 7.0.1) + activesupport (>= 7.0.1) + marcel (>= 1.0.3) activejob (8.1.4) activesupport (= 8.1.4) globalid (>= 0.3.6) @@ -119,10 +125,10 @@ GEM attr_required (1.0.2) auth-sanitizer (0.2.3) version_gem (~> 1.1, >= 1.1.14) - autoprefixer-rails (10.4.21.0) + autoprefixer-rails (10.6.1.0) execjs (~> 2) aws-eventstream (1.4.0) - aws-partitions (1.1291.0) + aws-partitions (1.1296.0) aws-sdk-core (3.257.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -135,7 +141,7 @@ GEM aws-sdk-kms (1.132.0) aws-sdk-core (~> 3, >= 3.256.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.232.2) + aws-sdk-s3 (1.233.2) aws-sdk-core (~> 3, >= 3.256.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -165,7 +171,7 @@ GEM bootstrap_form (5.6.1) actionpack (>= 7.2) activemodel (>= 7.2) - brakeman (8.0.6) + brakeman (8.1.0) racc brotli (0.8.0) builder (3.3.0) @@ -201,7 +207,7 @@ GEM bigdecimal rexml crass (1.0.7) - dalli (5.1.1) + dalli (5.2.2) logger danger (9.6.1) base64 (~> 0.2) @@ -221,17 +227,15 @@ GEM danger-plugin-api (~> 1.0) danger-plugin-api (1.0.0) danger (> 2.0) - dartsass-ruby (3.0.2) - sass-embedded (~> 1.54, < 1.67) - dartsass-sprockets (3.0.0) - dartsass-ruby (~> 3.0) + dartsass-sprockets (3.2.1) railties (>= 4.0.0) + sassc-embedded (~> 1.80.1) sprockets (> 3.0) sprockets-rails tilt - database_consistency (3.0.13) + database_consistency (3.0.15) activerecord (>= 3.2) - date (3.5.1) + date (3.6.0) debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) @@ -244,7 +248,7 @@ GEM delayed_job_active_record (4.1.11) activerecord (>= 3.0, < 9.0) delayed_job (>= 3.0, < 5) - digest (3.2.1) + digest (3.3.0) doorkeeper (5.9.9) railties (>= 5) doorkeeper-i18n (5.2.9) @@ -333,9 +337,11 @@ GEM glob (0.5.0) globalid (1.4.0) activesupport (>= 6.1) - google-protobuf (3.25.8) - googleapis-common-protos-types (1.20.0) - google-protobuf (>= 3.18, < 5.a) + google-protobuf (4.36.2) + bigdecimal + rake (~> 13.3) + googleapis-common-protos-types (1.23.0) + google-protobuf (~> 4.26) hashdiff (1.2.1) hashie (5.1.0) logger @@ -371,7 +377,7 @@ GEM image_optim (~> 0.24) railties sprockets - image_processing (2.1.0) + image_processing (2.2.0) image_size (3.6.0) in_threads (1.6.0) iniparse (1.5.0) @@ -392,7 +398,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (3.0.2) + json (3.1.0) json-jwt (1.17.2) activesupport (>= 4.2) aes_key_wrap @@ -410,7 +416,7 @@ GEM libv8-node (24.12.0.1) libxml-ruby (6.0.0) lint_roller (1.1.0) - listen (3.10.0) + listen (3.10.1) logger rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -441,7 +447,8 @@ GEM multi_xml (0.9.1) bigdecimal (>= 3.1, < 5) nap (1.1.0) - net-http (0.9.1) + net-http (0.10.0) + net-protocol (>= 0.3.0) uri (>= 0.11.1) net-imap (0.6.7) date @@ -456,8 +463,8 @@ GEM nokogiri (1.19.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) - noticed (3.0.0) - rails (>= 6.1.0) + noticed (3.1.0) + rails (>= 7.1.0) oauth (1.1.8) anonymous_loader (~> 0.1, >= 0.1.3) auth-sanitizer (~> 0.2, >= 0.2.3) @@ -518,10 +525,14 @@ GEM omniauth-rails_csrf_protection (2.0.1) actionpack (>= 4.2) omniauth (~> 2.0) +<<<<<<< HEAD omniauth_openid_connect (0.8.0) omniauth (>= 1.9, < 3) openid_connect (~> 2.2) open3 (0.2.1) +======= + open3 (0.3.0) +>>>>>>> upstream-tip open4 (1.3.4) openid_connect (2.5.0) activemodel @@ -538,26 +549,27 @@ GEM openstreetmap-deadlock_retry (1.3.1) opentelemetry-api (1.11.1) logger - opentelemetry-common (0.25.1) + opentelemetry-common (0.25.2) opentelemetry-api (~> 1.0) - opentelemetry-exporter-otlp (0.36.0) + opentelemetry-exporter-otlp (0.37.0) google-protobuf (>= 3.18) googleapis-common-protos-types (~> 1.3) opentelemetry-api (~> 1.1) opentelemetry-common (~> 0.20) - opentelemetry-exporter-otlp-common + opentelemetry-exporter-otlp-common (~> 0.2) opentelemetry-sdk (~> 1.10) opentelemetry-semantic_conventions - opentelemetry-exporter-otlp-common (0.2.0) + opentelemetry-exporter-otlp-common (0.3.0) google-protobuf (>= 3.18) googleapis-common-protos-types (~> 1.3) opentelemetry-api (~> 1.1) + opentelemetry-common (~> 0.20) opentelemetry-helpers-mysql (0.6.0) opentelemetry-api (~> 1.7) opentelemetry-common (~> 0.21) opentelemetry-helpers-sql (0.4.0) opentelemetry-api (~> 1.7) - opentelemetry-helpers-sql-processor (0.5.0) + opentelemetry-helpers-sql-processor (0.5.1) opentelemetry-api (~> 1.0) opentelemetry-common (~> 0.21) opentelemetry-instrumentation-action_mailer (0.8.1) @@ -576,7 +588,7 @@ GEM opentelemetry-instrumentation-active_support (~> 0.10) opentelemetry-instrumentation-active_support (0.12.1) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-all (0.96.0) + opentelemetry-instrumentation-all (0.98.0) opentelemetry-instrumentation-active_model_serializers (~> 0.25.0) opentelemetry-instrumentation-anthropic (~> 0.5.0) opentelemetry-instrumentation-aws_lambda (~> 0.7.0) @@ -596,10 +608,11 @@ GEM opentelemetry-instrumentation-http_client (~> 0.29.0) opentelemetry-instrumentation-httpx (~> 0.8.0) opentelemetry-instrumentation-koala (~> 0.24.0) - opentelemetry-instrumentation-lmdb (~> 0.26.0) + opentelemetry-instrumentation-lmdb (~> 0.27.0) opentelemetry-instrumentation-mongo (~> 0.26.0) opentelemetry-instrumentation-mysql2 (~> 0.34.0) opentelemetry-instrumentation-net_http (~> 0.29.0) + opentelemetry-instrumentation-openai (~> 0.1.0) opentelemetry-instrumentation-pg (~> 0.37.0) opentelemetry-instrumentation-que (~> 0.13.0) opentelemetry-instrumentation-racecar (~> 0.7.0) @@ -616,13 +629,15 @@ GEM opentelemetry-instrumentation-trilogy (~> 0.69.0) opentelemetry-instrumentation-anthropic (0.5.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-aws_lambda (0.7.0) + opentelemetry-instrumentation-aws_lambda (0.7.1) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-aws_sdk (0.13.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-base (0.26.1) + opentelemetry-instrumentation-base (0.27.0) opentelemetry-api (~> 1.7) opentelemetry-common (~> 0.21) + opentelemetry-logs-api (~> 0.6) + opentelemetry-metrics-api (~> 0.9) opentelemetry-registry (~> 0.1) opentelemetry-instrumentation-bunny (0.25.0) opentelemetry-instrumentation-base (~> 0.25) @@ -638,7 +653,7 @@ GEM opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-faraday (0.33.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-grape (0.7.1) + opentelemetry-instrumentation-grape (0.7.2) opentelemetry-instrumentation-rack (~> 0.29) opentelemetry-instrumentation-graphql (0.32.0) opentelemetry-instrumentation-base (~> 0.25) @@ -654,26 +669,28 @@ GEM opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-koala (0.24.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-lmdb (0.26.1) + opentelemetry-instrumentation-lmdb (0.27.0) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-mongo (0.26.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-mysql2 (0.34.0) + opentelemetry-instrumentation-mysql2 (0.34.1) opentelemetry-helpers-mysql opentelemetry-helpers-sql - opentelemetry-helpers-sql-processor + opentelemetry-helpers-sql-processor (>= 0.5.1, < 1.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-net_http (0.29.1) + opentelemetry-instrumentation-net_http (0.29.2) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-pg (0.37.0) + opentelemetry-instrumentation-openai (0.1.0) + opentelemetry-instrumentation-base (~> 0.27) + opentelemetry-instrumentation-pg (0.37.1) opentelemetry-helpers-sql - opentelemetry-helpers-sql-processor + opentelemetry-helpers-sql-processor (>= 0.5.1, < 1.0) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-que (0.13.0) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-racecar (0.7.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-rack (0.31.1) + opentelemetry-instrumentation-rack (0.31.2) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-rails (0.42.0) opentelemetry-instrumentation-action_mailer (~> 0.7) @@ -698,14 +715,18 @@ GEM opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-sidekiq (0.29.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-sinatra (0.30.0) + opentelemetry-instrumentation-sinatra (0.30.1) opentelemetry-instrumentation-rack (~> 0.29) - opentelemetry-instrumentation-trilogy (0.69.0) + opentelemetry-instrumentation-trilogy (0.69.1) opentelemetry-helpers-mysql opentelemetry-helpers-sql - opentelemetry-helpers-sql-processor + opentelemetry-helpers-sql-processor (>= 0.5.1, < 1.0) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-semantic_conventions (>= 1.8.0) + opentelemetry-logs-api (0.7.0) + opentelemetry-api (~> 1.0) + opentelemetry-metrics-api (0.10.0) + opentelemetry-api (~> 1.0) opentelemetry-registry (0.6.3) opentelemetry-api (~> 1.1) opentelemetry-sdk (1.13.1) @@ -721,17 +742,17 @@ GEM childprocess (>= 0.6.3, < 6) iniparse (~> 1.4) rexml (>= 3.4.2) - parallel (2.2.0) + parallel (2.3.0) parser (3.3.12.0) ast (~> 2.4.1) racc - pg (1.6.3) - pg_query (6.2.2) - google-protobuf (>= 3.25.3) + pg (1.7.0) + pg_query (6.2.5) + google-protobuf (>= 4.34.0) popper_js (2.11.8) pp (0.6.4) prettyprint - prettyprint (0.2.0) + prettyprint (0.2.1) prism (1.9.0) process_executer (4.1.1) track_open_instances (~> 0.1) @@ -821,7 +842,7 @@ GEM prism (>= 1.6.0) rbs (>= 4.0.0) tsort - regexp_parser (2.13.0) + regexp_parser (2.13.1) reline (0.7.0) io-console (~> 0.5) rexml (3.4.4) @@ -835,14 +856,14 @@ GEM strscan (~> 3.1) rtlcss (0.2.1) mini_racer (>= 0.6.3) - rubocop (1.91.0) + rubocop (1.92.0) json (>= 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) + regexp_parser (>= 2.11.3, < 3.0) rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) @@ -855,9 +876,9 @@ GEM rubocop-factory_bot (2.28.0) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) - rubocop-minitest (0.40.0) + rubocop-minitest (0.41.0) lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) + rubocop (>= 1.89.0, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) rubocop-performance (1.27.0) lint_roller (~> 1.1) @@ -880,26 +901,27 @@ GEM sanitize (7.0.0) crass (~> 1.0.2) nokogiri (>= 1.16.8) - sass-embedded (1.64.2) - google-protobuf (~> 3.23) - rake (>= 13.0.0) + sass-embedded (1.105.1) + google-protobuf (~> 4.31) + rake (~> 13.3) + sassc-embedded (1.80.10) + sass-embedded (~> 1.80) sawyer (0.9.3) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) securerandom (0.4.1) - selenium-webdriver (4.49.0) + selenium-webdriver (4.51.0) base64 (~> 0.2) logger (~> 1.4) - rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) - sentry-delayed_job (7.0.0) + sentry-delayed_job (7.1.0) delayed_job (>= 4.0) - sentry-ruby (~> 7.0.0) - sentry-rails (7.0.0) + sentry-ruby (~> 7.1.0) + sentry-rails (7.1.0) railties (>= 5.2.0) - sentry-ruby (~> 7.0.0) - sentry-ruby (7.0.0) + sentry-ruby (~> 7.1.0) + sentry-ruby (7.1.0) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) logger @@ -950,7 +972,7 @@ GEM unicode-display_width (3.3.0) unicode-emoji (~> 4.3) unicode-emoji (4.3.0) - uri (0.13.3) + uri (1.1.2) useragent (0.16.11) validate_url (1.0.15) activemodel (>= 3.0.0) @@ -960,11 +982,15 @@ GEM simpleidn vendorer (0.2.0) version_gem (1.1.15) +<<<<<<< HEAD webfinger (2.1.3) activesupport faraday (~> 2.0) faraday-follow_redirects webmock (3.26.4) +======= + webmock (3.29.0) +>>>>>>> upstream-tip addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -985,6 +1011,7 @@ DEPENDENCIES aasm actionpack-page_caching active_record_union + active_storage_validations activerecord-import activerecord-postgis addressable @@ -1086,7 +1113,7 @@ DEPENDENCIES rubocop-rake ruby-vips sanitize - sass-embedded (~> 1.64.0) + sass-embedded (< 3.0.0) selenium-webdriver sentry-delayed_job sentry-rails @@ -1101,11 +1128,10 @@ DEPENDENCIES timeout turbo-rails unicode-display_width - uri (< 1.0.0) validates_email_format_of vendorer webmock webrick BUNDLED WITH - 4.0.3 + 4.0.20 diff --git a/app/assets/javascripts/index_modules/changeset.js b/app/assets/javascripts/index_modules/changeset.js index d2bfd56a7a6..c9d126abeca 100644 --- a/app/assets/javascripts/index_modules/changeset.js +++ b/app/assets/javascripts/index_modules/changeset.js @@ -1,6 +1,5 @@ export default function (map) { - const page = {}, - content = $("#sidebar_content"); + const content = $("#sidebar_content"); content.on("turbo:before-frame-render", "turbo-frame", function () { $(this).find(".numbered_pagination").trigger("numbered_pagination:disable"); @@ -10,15 +9,17 @@ export default function (map) { $(this).find(".numbered_pagination").trigger("numbered_pagination:enable"); }); - page.load = function (path) { - OSM.loadSidebarContent(path) - .then(page.init); - }; + return { + load(path) { + OSM.loadSidebarContent(path) + .then(this.init); + }, - page.init = function () { - const changesetData = content.find("[data-changeset]").data("changeset"); - changesetData.type = "changeset"; + init() { + const changesetData = content.find("[data-changeset]").data("changeset"); + changesetData.type = "changeset"; +<<<<<<< HEAD const hashParams = OSM.parseHash(); initialize(); map.addObject(changesetData, function (bounds) { @@ -32,60 +33,63 @@ export default function (map) { addOpenHistoricalMapTimeSlider(map); }; +======= + const hashParams = OSM.parseHash(); + content.find("button[data-method][data-url]").on("click", e => { + e.preventDefault(); + const { method, url } = $(e.target).data(); + const data = new URLSearchParams(); +>>>>>>> upstream-tip - function updateChangeset(method, url, include_data) { - const data = new URLSearchParams(); - - content.find("#comment-error").prop("hidden", true); - content.find("button[data-method][data-url]").prop("disabled", true); + content.find("#comment-error").prop("hidden", true); + content.find("button[data-method][data-url]").prop("disabled", true); - if (include_data) { - data.set("text", content.find("textarea").val()); - } + if (e.target.name === "comment") { + data.set("text", content.find("textarea").val()); + } - fetch(url, { - method: method, - headers: { ...OSM.oauth }, - body: data - }) - .then(response => { - if (response.ok) return response; - return response.text().then(text => { - throw new Error(text); - }); - }) - .then(() => OSM.loadSidebarContent(location.pathname)) - .then(page.init) - .catch(error => { - content.find("button[data-method][data-url]").prop("disabled", false); - content.find("#comment-error") - .text(error.message) - .prop("hidden", false) - .get(0).scrollIntoView({ block: "nearest" }); + fetch(url, { + method: method, + headers: { ...OSM.oauth }, + body: data + }) + .then(response => { + if (response.ok) return response; + return response.text().then(text => { + throw new Error(text); + }); + }) + .then(() => OSM.loadSidebarContent(location.pathname)) + .then(this.init) + .catch(error => { + content.find("button[data-method][data-url]").prop("disabled", false); + content.find("#comment-error") + .text(error.message) + .prop("hidden", false) + .get(0).scrollIntoView({ block: "nearest" }); + }); }); - } - - function initialize() { - content.find("button[data-method][data-url]").on("click", function (e) { - e.preventDefault(); - const data = $(e.target).data(); - const include_data = e.target.name === "comment"; - updateChangeset(data.method, data.url, include_data); - }); - content.find("textarea").on("input", function (e) { - const form = e.target.form, - disabled = $(e.target).val() === ""; - form.comment.disabled = disabled; - }); + content.find("textarea").on("input", function (e) { + const form = e.target.form, + disabled = $(e.target).val() === ""; + form.comment.disabled = disabled; + }); - content.find("textarea").val("").trigger("input"); - } + content.find("textarea").val("").trigger("input"); + map.addObject(changesetData, function (bounds) { + if (!hashParams.center && bounds.isValid()) { + OSM.router.withoutMoveListener(function () { + map.fitBounds(bounds); + }); + } + }); + $(".numbered_pagination").trigger("numbered_pagination:enable"); + }, - page.unload = function () { - map.removeObject(); - $(".numbered_pagination").trigger("numbered_pagination:disable"); + unload() { + map.removeObject(); + $(".numbered_pagination").trigger("numbered_pagination:disable"); + } }; - - return page; } diff --git a/app/assets/javascripts/index_modules/directions.js b/app/assets/javascripts/index_modules/directions.js index 3c663321d68..a3b43a543cb 100644 --- a/app/assets/javascripts/index_modules/directions.js +++ b/app/assets/javascripts/index_modules/directions.js @@ -241,8 +241,6 @@ export default function (map) { endpoints[1].enableListeners(); } - const page = {}; - function sidebarLoaded() { if ($("#directions_route").length) { sidebarReadyPromise = null; @@ -253,37 +251,43 @@ export default function (map) { return sidebarReadyPromise ??= OSM.loadSidebarContent("/directions"); } - page.load = page.init = function () { - initializeFromParams(); + return { + load() { this.init(); }, - $(".search_form").hide(); - $(".directions_form").show(); + init() { + initializeFromParams(); - sidebarLoaded().then(enableListeners); + $(".search_form").hide(); + $(".directions_form").show(); +<<<<<<< HEAD map.setSidebarOverlaid(!endpoints[0].latlng || !endpoints[1].latlng); addOpenHistoricalMapTimeSlider(map); - }; +======= + sidebarLoaded().then(enableListeners); - page.unload = function () { - $(".search_form").show(); - $(".directions_form").hide(); + map.setSidebarOverlaid(!endpoints[0].latlng || !endpoints[1].latlng); + }, - $("#sidebar .sidebar-close-controls button").off("click", closeButtonListener); - $("#map").off("dragend dragover drop"); - map.off("locationfound", sendStartingLocation); + unload() { + $(".search_form").show(); + $(".directions_form").hide(); - endpoints[0].disableListeners(); - endpoints[1].disableListeners(); + $("#sidebar .sidebar-close-controls button").off("click", closeButtonListener); + $("#map").off("dragend dragover drop"); + map.off("locationfound", sendStartingLocation); - endpoints[0].clearValue(); - endpoints[1].clearValue(); + endpoints[0].disableListeners(); + endpoints[1].disableListeners(); - routeOutput.remove(); + endpoints[0].clearValue(); + endpoints[1].clearValue(); - sidebarReadyPromise = null; - }; + routeOutput.remove(); - return page; + sidebarReadyPromise = null; + } +>>>>>>> upstream-tip + }; } diff --git a/app/assets/javascripts/index_modules/element.js b/app/assets/javascripts/index_modules/element.js index 2bba3bb5283..1cc2b3ec587 100644 --- a/app/assets/javascripts/index_modules/element.js +++ b/app/assets/javascripts/index_modules/element.js @@ -5,36 +5,36 @@ const isOfExpectedLanguage = ({ language }) => languagesToRequest[0].startsWith( export function element(type) { return function () { - const page = {}; - - page.load = function (path, id, version) { - OSM.loadSidebarContent(path) - .then(() => page.init(path, id, version, true)); - }; - - page.init = function (path, id, version, keepViewport) { - page._addObject(type, id, version, keepViewport); - $(".numbered_pagination").trigger("numbered_pagination:enable"); - abortController = new AbortController(); - }; - - page.unload = function () { - page._removeObject(); - $(".numbered_pagination").trigger("numbered_pagination:disable"); - abortController?.abort(); + return { + load(path, id, version) { + OSM.loadSidebarContent(path) + .then(() => this.init(path, id, version, true)); + }, + + init(path, id, version, keepViewport) { + this._addObject(type, id, version, keepViewport); + $(".numbered_pagination").trigger("numbered_pagination:enable"); + abortController = new AbortController(); + }, + + unload() { + this._removeObject(); + $(".numbered_pagination").trigger("numbered_pagination:disable"); + abortController?.abort(); + }, + + _addObject() {}, + _removeObject() {} }; - - page._addObject = function () {}; - page._removeObject = function () {}; - - return page; }; }; export function mappedElement(type) { return function (map) { - const page = element(type)(map); + return { + ...element(type)(map), +<<<<<<< HEAD page._addObject = function (type, id, version, keepViewport) { const hashParams = OSM.parseHash(); map.addObject({ type: type, id: parseInt(id, 10), version: version && parseInt(version, 10) }, function (bounds) { @@ -48,13 +48,24 @@ export function mappedElement(type) { addOpenHistoricalMapTimeSlider(map); addOpenHistoricalMapInspector(); +======= + _addObject(type, id, version, keepViewport) { + const hashParams = OSM.parseHash(); + map.addObject({ type: type, id: parseInt(id, 10), version: version && parseInt(version, 10) }, function (bounds) { + if (!hashParams.center && bounds.isValid() && + (!keepViewport || !map.getBounds().contains(bounds))) { + OSM.router.withoutMoveListener(function () { + map.fitBounds(bounds); + }); + } + }); + }, + + _removeObject() { + map.removeObject(); + } +>>>>>>> upstream-tip }; - - page._removeObject = function () { - map.removeObject(); - }; - - return page; }; }; @@ -65,6 +76,10 @@ function previewWikidataValue($btn) { const items = $btn.data("qids"); if (!items?.length) return; $btn.prop("disabled", true); + const fetchOptions = { + headers: { "Api-User-Agent": "OSM-TagPreview (https://github.com/openstreetmap/openstreetmap-website)" }, + signal: abortController?.signal + }; fetch(OSM.WIKIDATA_API_URL + "?" + new URLSearchParams({ action: "wbgetentities", format: "json", @@ -74,10 +89,7 @@ function previewWikidataValue($btn) { languages: languagesToRequest.join("|"), languagefallback: 1, sitefilter: wikisToRequest.join("|") - }), { - headers: { "Api-User-Agent": "OSM-TagPreview (https://github.com/openstreetmap/openstreetmap-website)" }, - signal: abortController?.signal - }) + }), fetchOptions) .then(response => response.ok ? response.json() : Promise.reject(response)) .then(({ entities }) => { if (!entities) return Promise.reject(entities); @@ -88,7 +100,7 @@ function previewWikidataValue($btn) { .filter(qid => entities[qid]) .map(qid => getLocalizedResponse(entities[qid])) .filter(data => data.label || data.icon || data.description || data.article) - .map(data => renderWikidataResponse(data, $btn.siblings(`a[href*="wikidata.org/entity/${data.qid}"]`))) + .map(data => renderWikidataResponse(data, $btn.siblings(`a[href*="wikidata.org/entity/${data.qid}"]`), fetchOptions)) ); }) .catch(() => $btn.prop("disabled", false)); @@ -115,20 +127,22 @@ function getLocalizedResponse(entity) { return data; } -function renderWikidataResponse({ icon, label, article, description }, $link) { +function renderWikidataResponse({ icon, label, article, description }, $link, fetchOptions) { const localeName = new Intl.DisplayNames(OSM.preferred_languages, { type: "language" }); const cell = $("") .attr("colspan", 2) .addClass("bg-body-tertiary"); if (icon && OSM.WIKIMEDIA_COMMONS_URL) { - let src = OSM.WIKIMEDIA_COMMONS_URL + "Special:Redirect/file/" + encodeURIComponent(icon) + "?mobileaction=toggle_view_desktop"; - if (!icon.endsWith(".svg")) src += "&width=128"; - $("") - .attr("href", OSM.WIKIMEDIA_COMMONS_URL + "File:" + encodeURIComponent(icon) + `?uselang=${OSM.i18n.locale}`) - .append($("").attr({ src, height: "32" })) - .addClass("float-end mb-1 ms-2") - .appendTo(cell); + fetchCommonsThumbnail(icon, fetchOptions) + .then(imageAttributes => { + $("") + .attr("href", OSM.WIKIMEDIA_COMMONS_URL + "/wiki/File:" + encodeURIComponent(icon) + `?uselang=${OSM.i18n.locale}`) + .append($("").attr(imageAttributes)) + .addClass("float-end mb-1 ms-2") + .prependTo(cell); + }) + .catch(() => {}); } if (label) { const link = $link.clone() @@ -168,6 +182,7 @@ function renderWikidataResponse({ icon, label, article, description }, $link) { return $("").append(cell); } +<<<<<<< HEAD // add the enhanced inspector (class comes from the external ohm-inspector // script loaded in layouts/_head.html.erb) function addOpenHistoricalMapInspector() { @@ -182,4 +197,36 @@ function addOpenHistoricalMapInspector() { apiBaseUrl: "/api" // no trailing / }); inspector.selectFeatureFromUrl(); +======= +function fetchCommonsThumbnail(filename, fetchOptions) { + const isVectorImage = filename.toLowerCase().endsWith(".svg"); + return fetch(OSM.WIKIMEDIA_COMMONS_URL + "/w/api.php?" + new URLSearchParams({ + action: "query", + format: "json", + origin: "*", + prop: "imageinfo", + titles: "File:" + filename, + iiprop: "url|thumburls", + iiurlheight: "32" + }), fetchOptions) + .then(response => response.ok ? response.json() : Promise.reject(response)) + .then(({ query }) => { + const page = Object.values(query.pages)[0]; + const imageInfo = page.imageinfo?.[0]; + const imageUrl = isVectorImage ? imageInfo?.url : imageInfo?.thumburl; + if (!imageUrl) return Promise.reject(page); + + const imageAttributes = { src: imageUrl, height: "32", alt: filename }; + if (!isVectorImage) { + const thumbUrls = Object.entries(imageInfo.thumburls ?? {}); + if (thumbUrls.length) { + imageAttributes.srcset = thumbUrls + .map(([width, thumbnail]) => `${thumbnail.url} ${width}w`) + .join(", "); + imageAttributes.sizes = `${imageInfo.thumbwidth}px`; + } + } + return imageAttributes; + }); +>>>>>>> upstream-tip } diff --git a/app/assets/javascripts/index_modules/export.js b/app/assets/javascripts/index_modules/export.js index a83060aa69e..9f14eab2e78 100644 --- a/app/assets/javascripts/index_modules/export.js +++ b/app/assets/javascripts/index_modules/export.js @@ -1,8 +1,6 @@ //= require download_util export default function (map) { - const page = {}; - const locationFilter = new L.LocationFilter({ enableButton: false, adjustButton: false @@ -79,52 +77,57 @@ export default function (map) { if (getBounds().getSize() > OSM.MAX_REQUEST_AREA) e.preventDefault(); } - page.load = function (path) { - OSM.loadSidebarContent(path) - .then(page.init); - }; - - page.init = function () { - map - .addLayer(locationFilter) - .on("moveend", update); - - $("#maxlat, #minlon, #maxlon, #minlat").change(boundsChanged); - $("#drag_box").click(enableFilter); - $(".export_form").on("submit", checkSubmit); - - document.querySelector(".export_form") - .addEventListener("turbo:submit-end", OSM.getTurboBlobHandler("map.osm")); - - document.querySelector(".export_form") - .addEventListener("turbo:before-fetch-response", OSM.turboHtmlResponseHandler); - - document.querySelector(".export_form") - .addEventListener("turbo:before-fetch-request", function (event) { - event.detail.fetchOptions.headers.Accept = "application/xml"; + return { + load(path) { + OSM.loadSidebarContent(path) + .then(this.init); + }, + + init() { + map + .addLayer(locationFilter) + .on("moveend", update); + + $("#maxlat, #minlon, #maxlon, #minlat").change(boundsChanged); + $("#drag_box").click(enableFilter); + $(".export_form").on("submit", checkSubmit); + + document.querySelector(".export_form") + .addEventListener("turbo:submit-end", OSM.getTurboBlobHandler("map.osm")); + + document.querySelector(".export_form") + .addEventListener("turbo:before-fetch-response", OSM.turboHtmlResponseHandler); + + document.querySelector(".export_form") + .addEventListener("turbo:before-fetch-request", function (event) { + event.detail.fetchOptions.headers.Accept = "application/xml"; + }); + + $("#export_overpass").on("click", async function (event) { + event.preventDefault(); + const downloadUrl = $(this).attr("href"); + const confirmed = await showConfirmationModal(); + if (confirmed) { + window.location.href = downloadUrl; + } }); - $("#export_overpass").on("click", async function (event) { - event.preventDefault(); - const downloadUrl = $(this).attr("href"); - const confirmed = await showConfirmationModal(); - if (confirmed) { - window.location.href = downloadUrl; - } - }); + update(); + return map.getState(); + }, +<<<<<<< HEAD update(); addOpenHistoricalMapTimeSlider(map); return map.getState(); +======= + unload() { + map + .removeLayer(locationFilter) + .off("moveend", update); + } +>>>>>>> upstream-tip }; - - page.unload = function () { - map - .removeLayer(locationFilter) - .off("moveend", update); - }; - - return page; } diff --git a/app/assets/javascripts/index_modules/history.js b/app/assets/javascripts/index_modules/history.js index 7722b909dfb..b05515543d4 100644 --- a/app/assets/javascripts/index_modules/history.js +++ b/app/assets/javascripts/index_modules/history.js @@ -2,8 +2,6 @@ /* global ChangesetsLayer */ export default function (map) { - const page = {}; - $("#sidebar_content") .on("click", ".changeset_more a", loadMoreChangesets) .on("mouseover", "[data-changeset]", function () { @@ -287,11 +285,13 @@ export default function (map) { } } - page.load = function (path) { - OSM.loadSidebarContent(path) - .then(page.init); - }; + return { + load(path) { + OSM.loadSidebarContent(path) + .then(this.init); + }, +<<<<<<< HEAD page.init = function () { map.addLayer(changesetsLayer); map.on("moveend", moveEndListener); @@ -300,13 +300,20 @@ export default function (map) { addOpenHistoricalMapTimeSlider(map); }; - - page.unload = function () { - map.removeLayer(changesetsLayer); - map.off("moveend", moveEndListener); - map.off("zoomend", zoomEndListener); - disableChangesetIntersectionObserver(); +======= + init() { + map.addLayer(changesetsLayer); + map.on("moveend", moveEndListener); + map.on("zoomend", zoomEndListener); + loadFirstChangesets(); + }, +>>>>>>> upstream-tip + + unload() { + map.removeLayer(changesetsLayer); + map.off("moveend", moveEndListener); + map.off("zoomend", zoomEndListener); + disableChangesetIntersectionObserver(); + } }; - - return page; } diff --git a/app/assets/javascripts/index_modules/home.js b/app/assets/javascripts/index_modules/home.js index 3a1d43896a6..66c69789612 100644 --- a/app/assets/javascripts/index_modules/home.js +++ b/app/assets/javascripts/index_modules/home.js @@ -6,8 +6,6 @@ export default function (map) { marker = null; } - const page = {}; - function show(panToHome) { map.setSidebarOverlaid(true); clearMarker(); @@ -31,18 +29,18 @@ export default function (map) { } } - page.load = function () { - show(true); - }; + return { + load() { + show(true); + }, - page.init = function () { - show(!OSM.parseHash(location.hash).center); - }; + init() { + show(!OSM.parseHash(location.hash).center); + }, - page.unload = function () { - clearMarker(); - $("#browse_status").empty(); + unload() { + clearMarker(); + $("#browse_status").empty(); + } }; - - return page; } diff --git a/app/assets/javascripts/index_modules/index.js b/app/assets/javascripts/index_modules/index.js index 7e3ba55b41b..c1a1819d6d7 100644 --- a/app/assets/javascripts/index_modules/index.js +++ b/app/assets/javascripts/index_modules/index.js @@ -1,21 +1,23 @@ export default function (map) { - const page = {}; + return { + load() { + map.setSidebarOverlaid(true); + document.title = OSM.i18n.t("layouts.project_name.title"); + }, - page.load = function () { - map.setSidebarOverlaid(true); - document.title = OSM.i18n.t("layouts.project_name.title"); - }; - - page.init = function () { - const params = new URLSearchParams(location.search); - if (params.has("query")) { - $("#sidebar .search_form input[name=query]").value(params.get("query")); + init() { + const params = new URLSearchParams(location.search); + if (params.has("query")) { + $("#sidebar .search_form input[name=query]").value(params.get("query")); + } + return map.getState(); } +<<<<<<< HEAD addOpenHistoricalMapTimeSlider(map); return map.getState(); +======= +>>>>>>> upstream-tip }; - - return page; }; diff --git a/app/assets/javascripts/index_modules/new_note.js b/app/assets/javascripts/index_modules/new_note.js index e7fb7929c0b..4d556e71cc2 100644 --- a/app/assets/javascripts/index_modules/new_note.js +++ b/app/assets/javascripts/index_modules/new_note.js @@ -1,7 +1,6 @@ export default function (map) { const noteLayer = map.noteLayer, content = $("#sidebar_content"), - page = {}, control = $(".control-note"), addNoteButton = control.find(".control-button"); let newNoteMarker, @@ -98,24 +97,82 @@ export default function (map) { if (newNoteMarker) newNoteMarker.setOpacity(zoomedOut ? 0.5 : 0.9); } - page.load = function (path) { - OSM.loadSidebarContent(path) - .then(() => page.init(path)); - }; + return { + load(path) { + OSM.loadSidebarContent(path) + .then(() => this.init(path)); + }, + + init(path) { + control.addClass("active"); + + map.addLayer(noteLayer); - page.init = function (path) { - control.addClass("active"); + const params = new URLSearchParams(path.substring(path.indexOf("?"))); + let markerLatlng; - map.addLayer(noteLayer); + if (params.has("lat") && params.has("lon")) { + markerLatlng = { lat: params.get("lat"), lng: params.get("lon") }; + } else { + markerLatlng = map.getCenter(); + } - const params = new URLSearchParams(path.substring(path.indexOf("?"))); - let markerLatlng; + map.panInside(markerLatlng, { + padding: [50, 50] + }); + + addNewNoteMarker(markerLatlng); + + content.find("textarea") + .on("input", updateControls) + .attr("readonly", "readonly") // avoid virtual keyboard popping up on focus + .trigger("focus") + .removeAttr("readonly"); + + content.find("input[type=submit]").on("click", function (e) { + const location = newNoteMarker.getLatLng().wrap(); + const text = content.find("textarea").val(); + + errorPanel = content.find(".new-note-error"); + errorPanel.addClass("d-none"); + errorPanelDetail = errorPanel.find(".new-note-error-detail"); + + e.preventDefault(); + $(this).prop("disabled", true); + newNoteMarker.options.draggable = false; + newNoteMarker.dragging.disable(); + + createNote(location, text) + .then(feature => { + if (typeof OSM.user === "undefined") { + const anonymousNotesCount = Number(OSM.cookies.get("_osm_anonymous_notes_count")) || 0; + OSM.cookies.set("_osm_anonymous_notes_count", anonymousNotesCount + 1, { expires: 14 }); + } + content.find("textarea").val(""); + addCreatedNoteMarker(feature); + OSM.router.route("/note/" + feature.properties.id); + }) + .catch(err => { + errorPanel.removeClass("d-none"); + errorPanelDetail.text(err.message || err); + updateControls(); + }); + }); - if (params.has("lat") && params.has("lon")) { - markerLatlng = { lat: params.get("lat"), lng: params.get("lon") }; - } else { - markerLatlng = map.getCenter(); + map.on("click", moveNewNoteMarkerToClick); + addNoteButton.on("disabled enabled", updateControls); + updateControls(); + + return map.getState(); + }, + + unload() { + map.off("click", moveNewNoteMarkerToClick); + addNoteButton.off("disabled enabled", updateControls); + removeNewNoteMarker(); + control.removeClass("active"); } +<<<<<<< HEAD map.panInside(markerLatlng, { padding: [50, 50] @@ -166,14 +223,7 @@ export default function (map) { addOpenHistoricalMapTimeSlider(map); return map.getState(); +======= +>>>>>>> upstream-tip }; - - page.unload = function () { - map.off("click", moveNewNoteMarkerToClick); - addNoteButton.off("disabled enabled", updateControls); - removeNewNoteMarker(); - control.removeClass("active"); - }; - - return page; } diff --git a/app/assets/javascripts/index_modules/note.js b/app/assets/javascripts/index_modules/note.js index f8f37058e41..7e54d09fe21 100644 --- a/app/assets/javascripts/index_modules/note.js +++ b/app/assets/javascripts/index_modules/note.js @@ -1,4 +1,5 @@ export default function (map) { +<<<<<<< HEAD const content = $("#sidebar_content"), page = {}; @@ -15,6 +16,9 @@ export default function (map) { initialize(path, id); addOpenHistoricalMapTimeSlider(map); }; +======= + const content = $("#sidebar_content"); +>>>>>>> upstream-tip function initialize(path, id, skipMoveToNote) { content.find("button[name]").on("click", function (e) { @@ -92,9 +96,22 @@ export default function (map) { } } - page.unload = function () { - map.removeObject(); - }; + return { + load(path, id) { + OSM.loadSidebarContent(path).then(function () { + const data = $(".details").data(); + if (!data) return; + const [lat, lng] = data.coordinates.split(",").map(parseFloat); + initialize(path, id, map.getBounds().contains({ lat, lng })); + }); + }, + + init(path, id) { + initialize(path, id); + }, - return page; + unload() { + map.removeObject(); + } + }; } diff --git a/app/assets/javascripts/index_modules/query.js b/app/assets/javascripts/index_modules/query.js index a7ca4485bfc..54acd208b6b 100644 --- a/app/assets/javascripts/index_modules/query.js +++ b/app/assets/javascripts/index_modules/query.js @@ -298,23 +298,26 @@ export default function (map) { runQuery(isin, $("#query-isin"), true, (feature1, feature2) => size(feature1.bounds) - size(feature2.bounds), jsDateFilter); } - const page = {}; - - page.load = function (path) { - OSM.loadSidebarContent(path) - .then(() => page.init(path, true)); - }; - - page.init = function (path, noCentre) { - const params = new URLSearchParams(path.substring(path.indexOf("?"))), - latlng = L.latLng(params.get("lat"), params.get("lon")); + return { + load(path) { + OSM.loadSidebarContent(path) + .then(() => this.init(path, true)); + }, + + init(path, noCentre) { + const params = new URLSearchParams(path.substring(path.indexOf("?"))), + latlng = L.latLng(params.get("lat"), params.get("lon")); + + if (!location.hash && !noCentre && !map.getBounds().contains(latlng)) { + OSM.router.withoutMoveListener(function () { + map.setView(latlng, 15); + }); + } - if (!location.hash && !noCentre && !map.getBounds().contains(latlng)) { - OSM.router.withoutMoveListener(function () { - map.setView(latlng, 15); - }); - } + queryOverpass([params.get("lat"), params.get("lon")]); + }, +<<<<<<< HEAD queryOverpass([params.get("lat"), params.get("lon")]); addOpenHistoricalMapTimeSlider(map); @@ -323,8 +326,12 @@ export default function (map) { page.unload = function (sameController) { if (!sameController) { $("#sidebar_content .query-results a.selected").each(hideResultGeometry); +======= + unload(sameController) { + if (!sameController) { + $("#sidebar_content .query-results a.selected").each(hideResultGeometry); + } +>>>>>>> upstream-tip } }; - - return page; } diff --git a/app/assets/javascripts/index_modules/search.js b/app/assets/javascripts/index_modules/search.js index 26ad7ca2647..e28fe273864 100644 --- a/app/assets/javascripts/index_modules/search.js +++ b/app/assets/javascripts/index_modules/search.js @@ -84,43 +84,45 @@ export default function (map) { e.stopPropagation(); } - const page = {}; - - page.load = function (path) { - const params = new URLSearchParams(path.substring(path.indexOf("?"))); - if (params.has("query")) { - $(".search_form input[name=query]").val(params.get("query")); - } else if (params.has("lat") && params.has("lon")) { - $(".search_form input[name=query]").val(params.get("lat") + ", " + params.get("lon")); - } - OSM.loadSidebarContent(path) - .then(page.init); - }; - - page.init = function () { - $(".search_results_entry[data-href]").each(function (index) { - const entry = $(this); - fetchReplace(this.dataset, entry.children().first()) - .then(() => { - // go to first result of first geocoder - if (index === 0) { - const firstResult = entry.find("*[data-lat][data-lon]:first").first(); - if (firstResult.length) { - panToSearchResult(firstResult.data()); + return { + load(path) { + const params = new URLSearchParams(path.substring(path.indexOf("?"))); + if (params.has("query")) { + $(".search_form input[name=query]").val(params.get("query")); + } else if (params.has("lat") && params.has("lon")) { + $(".search_form input[name=query]").val(params.get("lat") + ", " + params.get("lon")); + } + OSM.loadSidebarContent(path) + .then(this.init); + }, + + init() { + $(".search_results_entry[data-href]").each(function (index) { + const entry = $(this); + fetchReplace(this.dataset, entry.children().first()) + .then(() => { + // go to first result of first geocoder + if (index === 0) { + const firstResult = entry.find("*[data-lat][data-lon]:first").first(); + if (firstResult.length) { + panToSearchResult(firstResult.data()); + } } - } - }); - }); + }); + }); +<<<<<<< HEAD addOpenHistoricalMapTimeSlider(map); return map.getState(); - }; +======= + return map.getState(); + }, - page.unload = function () { - markers.clearLayers(); - processedResults = 0; + unload() { + markers.clearLayers(); + processedResults = 0; + } +>>>>>>> upstream-tip }; - - return page; } diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index f02dc05fa17..da5b8e78984 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -2,6 +2,7 @@ //= require ./home_location_name-endpoint $(function () { + const defaultHomeZoom = 11; let map, marker, deleted_lat, deleted_lon, deleted_home_name, homeLocationNameGeocoder, savedLat, savedLon; if ($("#social_links").length) { @@ -111,7 +112,7 @@ $(function () { const lat = $("#home_lat").val(), lon = $("#home_lon").val(); - map.flyTo({ center: [lon, lat], zoom: OSM.MapLibre.defaultHomeZoom }); + map.flyTo({ center: [lon, lat], zoom: defaultHomeZoom }); }); $("#home_delete").click(function () { diff --git a/app/assets/stylesheets/maplibre-gl-all.scss b/app/assets/stylesheets/maplibre-gl-all.scss index 742061d6222..a33c5364b31 100644 --- a/app/assets/stylesheets/maplibre-gl-all.scss +++ b/app/assets/stylesheets/maplibre-gl-all.scss @@ -51,6 +51,9 @@ } .maplibregl-map .maplibregl-canvas-container { + // Keep marker z-indexes below sibling popups and map controls. + isolation: isolate; + .maplibregl-user-location-accuracy-circle { background-color: $green; opacity: 0.2 !important; diff --git a/app/controllers/api/changesets_controller.rb b/app/controllers/api/changesets_controller.rb index 6cc673810f2..ed5dba358d1 100644 --- a/app/controllers/api/changesets_controller.rb +++ b/app/controllers/api/changesets_controller.rb @@ -94,7 +94,7 @@ def create # changesets are not (yet?) versioned, so we don't have to deal with # history tables here. changesets are locked to a single user, however. # - # after succesful update, returns the XML of the changeset. + # after successful update, returns the XML of the changeset. def update @changeset = Changeset.find(params.expect(:id)) new_changeset = Changeset.from_xml(request.raw_post) diff --git a/app/controllers/api/maps_controller.rb b/app/controllers/api/maps_controller.rb index 019d9af664b..0784667d179 100644 --- a/app/controllers/api/maps_controller.rb +++ b/app/controllers/api/maps_controller.rb @@ -46,9 +46,9 @@ def show else way_nodes = WayNode.where(:node_id => node_ids) way_ids = way_nodes.collect { |way_node| way_node.id[0] } - ways = Way.preload(:nodes, :element_tags, :changeset => :user).find(way_ids) + ways = Way.preload(:way_nodes, :element_tags, :changeset => :user).find(way_ids) - list_of_way_nodes = ways.flat_map { |way| way.way_nodes.map(&:node_id) } + list_of_way_nodes = ways.flat_map(&:nds) end # - [0] in case some thing links to node 0 which doesn't exist. Shouldn't actually ever happen but it does. FIXME: file a ticket for this diff --git a/app/controllers/api/nodes/ways_controller.rb b/app/controllers/api/nodes/ways_controller.rb index ad7b667fab1..9d4e66f589b 100644 --- a/app/controllers/api/nodes/ways_controller.rb +++ b/app/controllers/api/nodes/ways_controller.rb @@ -14,7 +14,7 @@ class WaysController < ApiController def index @ways = Way .visible - .includes(:nodes, :changeset => :user) + .includes(:way_nodes, :changeset => :user) .where(:id => WayNode.where( :node_id => params[:node_id] ).select(:way_id)) diff --git a/app/controllers/api/notes_controller.rb b/app/controllers/api/notes_controller.rb index ffc749ab2ac..0ead52933cd 100644 --- a/app/controllers/api/notes_controller.rb +++ b/app/controllers/api/notes_controller.rb @@ -32,7 +32,7 @@ def index # Check that the boundaries are valid bbox.check_boundaries - # Check the the bounding box is not too big + # Check that the bounding box is not too big bbox.check_size(Settings.max_note_request_area) @min_lon = bbox.min_lon @min_lat = bbox.min_lat diff --git a/app/controllers/api/ways_controller.rb b/app/controllers/api/ways_controller.rb index d1989be66b4..62cbeb63fbb 100644 --- a/app/controllers/api/ways_controller.rb +++ b/app/controllers/api/ways_controller.rb @@ -18,7 +18,7 @@ def index raise OSM::APIBadUserInput, "No ways were given to search for" if ids.empty? - @ways = Way.includes(:element_tags, :changeset => :user, :nodes => { :changeset => :user }).find(ids) + @ways = Way.includes(:way_nodes, :element_tags, :changeset => :user).find(ids) # Render the result respond_to do |format| @@ -29,7 +29,7 @@ def index def show @way = Way - @way = @way.includes(:nodes, :element_tags) + @way = @way.includes(:way_nodes, :element_tags) @way = @way.includes(:nodes => [:element_tags, { :changeset => :user }]) if params[:full] @way = @way.find(params.expect(:id)) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3e6fa3a0af7..784c6c14db2 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -253,9 +253,10 @@ def map_layout policy = request.content_security_policy.clone policy.connect_src(*policy.connect_src, "http://127.0.0.1:8111", "https://vector.openstreetmap.org", "https://api.maptiler.com", "https://tile.thunderforest.com", "https://render.openstreetmap.org", Settings.nominatim_url, Settings.overpass_url, - Settings.fossgis_osrm_url, Settings.graphhopper_url, Settings.fossgis_valhalla_url, Settings.wikidata_api_url) + Settings.fossgis_osrm_url, Settings.graphhopper_url, Settings.fossgis_valhalla_url, Settings.wikidata_api_url, + Settings.wikimedia_commons_url) policy.form_action(*policy.form_action, "render.openstreetmap.org", "tile.thunderforest.com") - policy.img_src(*policy.img_src, Settings.wikimedia_commons_url, "upload.wikimedia.org") + policy.img_src(*policy.img_src, Settings.wikimedia_commons_url, "upload.wikimedia.org", "thumb.wikimedia.org") policy.script_src(*policy.script_src, :wasm_unsafe_eval) policy.style_src(*policy.style_src, :unsafe_inline) diff --git a/app/controllers/concerns/nominatim_methods.rb b/app/controllers/concerns/nominatim_methods.rb index 3fd7cfe2465..cb5de2c9f94 100644 --- a/app/controllers/concerns/nominatim_methods.rb +++ b/app/controllers/concerns/nominatim_methods.rb @@ -46,10 +46,12 @@ def nominatim_reverse_query_url(format: nil) parameters = { "format" => format, "lat" => lat, - "lon" => lon, - "zoom" => zoom + "lon" => lon } + # add any zoom; a blank value is sent as "zoom=" and Nominatim rejects it + parameters["zoom"] = zoom if zoom.present? + # build url nominatim_url("reverse", parameters) end diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 7dd799d313c..22cc7291c0a 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -72,25 +72,25 @@ def confirm_resend def confirm_email if request.post? - self.current_user = User.find_by_token_for(:new_email, params[:confirm_string]) - - if current_user&.new_email? - current_user.email = current_user.new_email - current_user.new_email = nil - current_user.email_valid = true - gravatar_enabled = current_user.gravatar_enable! - if current_user.save + user = User.find_by_token_for(:new_email, params[:confirm_string]) + + if user&.new_email? + user.email = user.new_email + user.new_email = nil + user.email_valid = true + gravatar_enabled = user.gravatar_enable! + if user.save flash[:notice] = if gravatar_enabled - "#{t('.success')} #{gravatar_status_message(current_user)}" + "#{t('.success')} #{gravatar_status_message(user)}" else t(".success") end else - flash[:errors] = current_user.errors + flash[:errors] = user.errors end - session[:user] = current_user.id - session[:fingerprint] = current_user.fingerprint - elsif current_user + session[:user] = user.id + session[:fingerprint] = user.fingerprint + elsif user flash[:error] = t ".failure" else flash[:error] = t ".unknown_token" diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index aa300b4925b..3e740742cac 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -21,9 +21,9 @@ def edit @title = t ".title" if params[:token] - self.current_user = User.find_by_token_for(:password_reset, params[:token]) + @user = User.find_by_token_for(:password_reset, params[:token]) - if current_user.nil? + if @user.nil? flash[:error] = t ".flash token bad" redirect_to :action => "new" end @@ -52,19 +52,19 @@ def create def update if params[:token] - self.current_user = User.find_by_token_for(:password_reset, params[:token]) + @user = User.find_by_token_for(:password_reset, params[:token]) - if current_user + if @user if params[:user] - current_user.pass_crypt = params[:user][:pass_crypt] - current_user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation] - current_user.activate if current_user.may_activate? - current_user.email_valid = true + @user.pass_crypt = params[:user][:pass_crypt] + @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation] + @user.activate if @user.may_activate? + @user.email_valid = true - if current_user.save - session[:fingerprint] = current_user.fingerprint + if @user.save + session[:fingerprint] = @user.fingerprint flash[:notice] = t ".flash changed" - successful_login(current_user) + successful_login(@user) else render :edit end diff --git a/app/controllers/profiles/images_controller.rb b/app/controllers/profiles/images_controller.rb index 911b31e0449..b79a4a26d20 100644 --- a/app/controllers/profiles/images_controller.rb +++ b/app/controllers/profiles/images_controller.rb @@ -17,7 +17,11 @@ def update_profile current_user.image_use_gravatar = true end - current_user.save + saved_user = current_user.save + + current_user.attachment_changes.delete("avatar") unless saved_user + + saved_user end end end diff --git a/app/controllers/users/lists_controller.rb b/app/controllers/users/lists_controller.rb index 44d40b7dbeb..ba70028b0cb 100644 --- a/app/controllers/users/lists_controller.rb +++ b/app/controllers/users/lists_controller.rb @@ -35,8 +35,8 @@ def show def update ids = params.fetch(:user, {}).keys.collect(&:to_i) - User.where(:id => ids).each(&:confirm!) if params[:confirm] - User.where(:id => ids).each(&:suspend_if_possible!) if params[:suspend] + User.where(:id => ids).preload(:avatar_attachment).each(&:confirm!) if params[:confirm] + User.where(:id => ids).preload(:avatar_attachment).each(&:suspend_if_possible!) if params[:suspend] redirect_to url_for(params.permit(:status, :username, :ip, :edits, :before, :after)) end diff --git a/app/helpers/user_helper.rb b/app/helpers/user_helper.rb index f27772892b0..37575295f47 100644 --- a/app/helpers/user_helper.rb +++ b/app/helpers/user_helper.rb @@ -3,43 +3,18 @@ module UserHelper # User images - def user_image(user, options = {}) - options[:class] ||= "user_image border border-secondary-subtle bg-body" - options[:alt] ||= "" + USER_IMAGE_SIZES = { :user_thumbnail_tiny => 25, :user_thumbnail => 50, :user_image => 100 }.freeze - if user.image_use_gravatar - user_gravatar_tag(user, options) - elsif user.avatar.attached? - user_avatar_variant_tag(user, { :resize_to_limit => [100, 100] }, options) - else - image_tag "avatar.svg", options.merge(:width => 100, :height => 100) - end + def user_image(user, options = {}) + user_image_tag(user, :user_image, options) end def user_thumbnail(user, options = {}) - options[:class] ||= "user_thumbnail border border-secondary-subtle bg-body" - options[:alt] ||= "" - - if user.image_use_gravatar - user_gravatar_tag(user, options.merge(:size => 50)) - elsif user.avatar.attached? - user_avatar_variant_tag(user, { :resize_to_limit => [50, 50] }, options) - else - image_tag "avatar.svg", options.merge(:width => 50, :height => 50) - end + user_image_tag(user, :user_thumbnail, options) end def user_thumbnail_tiny(user, options = {}) - options[:class] ||= "user_thumbnail_tiny border border-secondary-subtle bg-body" - options[:alt] ||= "" - - if user.image_use_gravatar - user_gravatar_tag(user, options.merge(:size => 50)) - elsif user.avatar.attached? - user_avatar_variant_tag(user, { :resize_to_limit => [50, 50] }, options) - else - image_tag "avatar.svg", options.merge(:width => 50, :height => 50) - end + user_image_tag(user, :user_thumbnail_tiny, options) end def user_image_url(user) @@ -54,6 +29,21 @@ def user_image_url(user) private + def user_image_tag(user, size, options = {}) + options = { :class => "border border-secondary-subtle bg-body", :alt => "" }.merge(options) + options[:class] = "#{size} " + options[:class] + + size = USER_IMAGE_SIZES[size] + + if user.image_use_gravatar + user_gravatar_tag(user, options.merge(:size => size)) + elsif user.avatar.attached? + user_avatar_variant_tag(user, { :resize_to_limit => [size, size] }, options) + else + image_tag "avatar.svg", options.merge(:width => size, :height => size) + end + end + # Local avatar support def user_avatar_variant_tag(user, variant_options, options) if user.avatar.variable? @@ -92,7 +82,7 @@ def user_gravatar_url(user, options = {}) def user_gravatar_tag(user, options = {}) url = user_gravatar_url(user, options) - options[:height] = options[:width] = options.delete(:size) || 100 + options[:height] = options[:width] = options.delete(:size) image_tag url, options end end diff --git a/app/models/relation.rb b/app/models/relation.rb index 32a0bb5f7c4..dac6e3eb876 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -207,7 +207,7 @@ def create_with_history(user) def preconditions_ok?(good_members = []) raise OSM::APITooManyRelationMembersError.new(id, members.length, Settings.max_number_of_relation_members) if members.length > Settings.max_number_of_relation_members - # These are hastables that store an id in the index of all + # These are hashtables that store an id in the index of all # the nodes/way/relations that have already been added. # If the member is valid and visible then we add it to the # relevant hash table, with the value true as a cache. diff --git a/app/models/user.rb b/app/models/user.rb index 520450c7ef5..4386a794409 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -124,8 +124,8 @@ class User < ApplicationRecord validates :preferred_editor, :inclusion => Editors::ALL_EDITORS, :allow_nil => true validates :auth_uid, :unless => proc { |u| u.auth_provider.nil? }, :uniqueness => { :scope => :auth_provider } - validates :avatar, :if => proc { |u| u.attachment_changes["avatar"] }, - :image => true + validates :avatar, :image => { :if => proc { |u| u.attachment_changes["avatar"] } }, + :processable_file => true validates :description, :length => 0..65536 validates_email_format_of :email, :if => proc { |u| u.email_changed? } diff --git a/app/notifiers/gpx_import_success_notifier.rb b/app/notifiers/gpx_import_success_notifier.rb index b0beda515be..1bd80e89c11 100644 --- a/app/notifiers/gpx_import_success_notifier.rb +++ b/app/notifiers/gpx_import_success_notifier.rb @@ -8,6 +8,6 @@ class GpxImportSuccessNotifier < ApplicationNotifier deliver_by :email do |config| config.mailer = "UserMailer" config.method = "gpx_success" - config.if = -> { recipient.notification_preferences.gpx_import_success.include?("email") } + config.if = -> { record && recipient.notification_preferences.gpx_import_success.include?("email") } end end diff --git a/app/notifiers/new_follower_notifier.rb b/app/notifiers/new_follower_notifier.rb index 12c9e0a691b..139925250ca 100644 --- a/app/notifiers/new_follower_notifier.rb +++ b/app/notifiers/new_follower_notifier.rb @@ -8,6 +8,6 @@ class NewFollowerNotifier < ApplicationNotifier deliver_by :email do |config| config.mailer = "UserMailer" config.method = "follow_notification" - config.if = -> { recipient.notification_preferences.new_follower.include?("email") } + config.if = -> { record && recipient.notification_preferences.new_follower.include?("email") } end end diff --git a/app/views/api/ways/_way.json.jbuilder b/app/views/api/ways/_way.json.jbuilder index 27813f769f4..8b3786cfc04 100644 --- a/app/views/api/ways/_way.json.jbuilder +++ b/app/views/api/ways/_way.json.jbuilder @@ -10,6 +10,6 @@ json.uid way.changeset.user_id json.visible way.visible unless way.visible -json.nodes way.nodes.ids unless way.nodes.ids.empty? +json.nodes way.nds unless way.nds.empty? json.tags way.tags unless way.tags.empty? diff --git a/app/views/api/ways/_way.xml.builder b/app/views/api/ways/_way.xml.builder index 73e4cf9a043..c4a560b5b6e 100644 --- a/app/views/api/ways/_way.xml.builder +++ b/app/views/api/ways/_way.xml.builder @@ -11,8 +11,8 @@ attrs = { } xml.way(attrs) do |w| - way.nodes.each do |n| - w.nd(:ref => n.id) + way.nds.each do |n| + w.nd(:ref => n) end way.tags.each do |k, v| diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 07f7388c589..8e2c277f20a 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -49,7 +49,7 @@ <% if current_user && current_user.id %>