From: Tom Hughes Date: Sun, 2 Mar 2025 10:54:39 +0000 (+0000) Subject: Merge remote-tracking branch 'upstream/pull/5667' X-Git-Tag: live~237 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/b8b9c3d1e9e412178426e9ba1b612e14e09f2320?hp=5c9355fccc335eacb870c47498165324bd16f467 Merge remote-tracking branch 'upstream/pull/5667' --- diff --git a/.annotaterb.yml b/.annotaterb.yml new file mode 100644 index 000000000..eab2bb917 --- /dev/null +++ b/.annotaterb.yml @@ -0,0 +1,58 @@ +--- +:position: before +:position_in_additional_file_patterns: before +:position_in_class: before +:position_in_factory: before +:position_in_fixture: before +:position_in_routes: before +:position_in_serializer: before +:position_in_test: before +:classified_sort: false +:exclude_controllers: true +:exclude_factories: true +:exclude_fixtures: true +:exclude_helpers: true +:exclude_scaffolds: true +:exclude_serializers: true +:exclude_sti_subclasses: false +:exclude_tests: true +:force: false +:format_markdown: false +:format_rdoc: false +:format_yard: false +:frozen: false +:ignore_model_sub_dir: false +:ignore_unknown_models: false +:include_version: false +:show_check_constraints: false +:show_complete_foreign_keys: false +:show_foreign_keys: true +:show_indexes: true +:simple_indexes: false +:sort: false +:timestamp: false +:trace: false +:with_comment: true +:with_column_comments: true +:with_table_comments: true +:active_admin: false +:command: +:debug: false +:hide_default_column_types: '' +:hide_limit_column_types: 'integer,bigint,boolean' +:ignore_columns: +:ignore_routes: +:models: true +:routes: false +:skip_on_db_migrate: false +:target_action: :do_annotations +:wrapper: +:wrapper_close: +:wrapper_open: +:classes_default_to_s: [] +:additional_file_patterns: [] +:model_dir: +- app/models +:require: [] +:root_dir: +- '' diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 66c2aca51..8c6680bdc 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -19,7 +19,7 @@ jobs: - name: Setup ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.1 + ruby-version: 3.2 rubygems: 3.4.10 bundler-cache: true - name: Create base branch diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8383068b3..099d0abf8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,7 +6,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true env: - ruby: '3.1' + ruby: '3.2' jobs: rubocop: name: RuboCop @@ -86,14 +86,17 @@ jobs: ruby-version: ${{ env.ruby }} rubygems: 3.4.10 bundler-cache: true + - name: Configure rails + run: | + cp config/github.database.yml config/database.yml + cp config/example.storage.yml config/storage.yml - name: Setup database run: | sudo systemctl start postgresql sudo -u postgres createuser -s $(id -un) createdb openstreetmap - cp config/github.database.yml config/database.yml bundle exec rails db:schema:load - name: Run Annotate Models - run: bundle exec rails annotate_models + run: bundle exec annotaterb models - name: Fail if model annotations are out of date run: git diff --exit-code diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d1cf73eae..841155bf6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: name: Ruby ${{ matrix.ruby }} strategy: matrix: - ruby: ['3.1', '3.2', '3.3', '3.4'] + ruby: ['3.2', '3.3', '3.4'] runs-on: ubuntu-latest env: RAILS_ENV: test diff --git a/.overcommit.yml b/.overcommit.yml index eab8eb2ae..18bd5851f 100644 --- a/.overcommit.yml +++ b/.overcommit.yml @@ -22,7 +22,7 @@ PreCommit: enabled: true EsLint: enabled: true - command: ["bin/yarn", "eslint", "-c", "config/eslint.js"] + command: ["bin/yarn", "eslint", "-c", "config/eslint.config.mjs"] exclude: - vendor/**/*.js RailsSchemaUpToDate: diff --git a/.rubocop.yml b/.rubocop.yml index 0f15cb9a8..64a8d0a0f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,16 +1,18 @@ inherit_from: .rubocop_todo.yml -require: - - rubocop-capybara - - rubocop-factory_bot +plugins: - rubocop-minitest - rubocop-performance - rubocop-rails - rubocop-rake + +require: + - rubocop-capybara + - rubocop-factory_bot - ./.rubocop/specific_action_names.rb AllCops: - TargetRubyVersion: 3.1 + TargetRubyVersion: 3.2 NewCops: enable Exclude: - 'vendor/**/*' @@ -106,6 +108,10 @@ Style/MixinUsage: - 'bin/setup' - 'bin/update' +Style/RaiseArgs: + Exclude: + - 'lib/osm.rb' + Style/StringLiterals: EnforcedStyle: double_quotes @@ -127,7 +133,6 @@ Rails/SpecificActionNames: - app/controllers/**/*.rb Exclude: # This is a todo list, but is currently too long for `rubocop --auto-gen-config` - - 'app/controllers/api/changeset_comments_controller.rb' - 'app/controllers/api/changesets_controller.rb' - 'app/controllers/api/notes_controller.rb' - 'app/controllers/api/user_preferences_controller.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3d7cea500..a60cd9442 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Work around erblint issues. -# https://github.com/openstreetmap/openstreetmap-website/issues/2472 -require: - - rubocop-minitest - - rubocop-performance - - rubocop-rails - - rubocop-rake - # Offense count: 13 # Configuration parameters: Include, MaxAmount. # Include: **/*_spec.rb, **/spec/**/*, **/test/**/*, **/features/support/factories/**/*.rb diff --git a/.vscode/settings.json b/.vscode/settings.json index 8a3d341d4..3545f0727 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { "eslint.options": { - "overrideConfigFile": "config/eslint.js" + "overrideConfigFile": "config/eslint.config.mjs" } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46fe63bb2..20debf4d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ bundle exec rails test:all You can run javascript tests with: ``` -bundle exec teaspoon +RAILS_ENV=test bundle exec teaspoon ``` You can view test coverage statistics by browsing the `coverage` directory. diff --git a/Dockerfile b/Dockerfile index dae25be3f..5df13d717 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm +FROM ruby:3.2-bookworm ENV DEBIAN_FRONTEND=noninteractive diff --git a/Gemfile b/Gemfile index 2765b1ae7..ad6b3b3ef 100644 --- a/Gemfile +++ b/Gemfile @@ -55,7 +55,6 @@ gem "bootstrap_form", "~> 5.0" gem "cancancan" gem "config" gem "delayed_job_active_record" -gem "dry-schema", "< 1.14.0" # see https://github.com/openstreetmap/openstreetmap-website/issues/5482 gem "dry-validation" gem "frozen_record" gem "http_accept_language", "~> 2.1.1" @@ -139,9 +138,6 @@ gem "image_processing" # Used to validate widths gem "unicode-display_width" -# Lock some modules to old versions for ruby 3.1 support -gem "zeitwerk", "< 2.7" - # Gems useful for development group :development do gem "better_errors" @@ -180,7 +176,7 @@ group :test do end group :development, :test do - gem "annotate" + gem "annotaterb" gem "teaspoon" gem "teaspoon-mocha", "~> 2.3.3" diff --git a/Gemfile.lock b/Gemfile.lock index 0ca767c00..38328c280 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,9 +82,7 @@ GEM tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) - annotate (3.2.0) - activerecord (>= 3.2, < 8.0) - rake (>= 10.4, < 14.0) + annotaterb (4.14.0) argon2 (2.3.2) ffi (~> 1.15) ffi-compiler (~> 1.0) @@ -92,17 +90,17 @@ GEM autoprefixer-rails (10.4.19.0) execjs (~> 2) aws-eventstream (1.3.1) - aws-partitions (1.1050.0) - aws-sdk-core (3.218.1) + aws-partitions (1.1054.0) + aws-sdk-core (3.219.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) base64 jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.98.0) + aws-sdk-kms (1.99.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.181.0) + aws-sdk-s3 (1.182.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -231,13 +229,13 @@ GEM concurrent-ruby (~> 1.0) dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-schema (1.13.4) + dry-schema (1.14.0) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-logic (>= 1.4, < 2) - dry-types (>= 1.7, < 2) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.5) + dry-types (~> 1.8) zeitwerk (~> 2.6) dry-types (1.8.2) bigdecimal (~> 3.0) @@ -246,11 +244,11 @@ GEM dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) - dry-validation (1.10.0) + dry-validation (1.11.1) concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-schema (>= 1.12, < 2) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-schema (~> 1.14) zeitwerk (~> 2.6) erb_lint (0.9.0) activesupport @@ -372,7 +370,7 @@ GEM marcel (1.0.4) matrix (0.4.2) maxminddb (0.1.22) - mini_magick (5.1.2) + mini_magick (5.2.0) benchmark logger mini_mime (1.1.5) @@ -399,7 +397,7 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.4) - nokogiri (1.18.2) + nokogiri (1.18.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) oauth (1.1.0) @@ -456,7 +454,7 @@ GEM open4 (1.3.4) openstreetmap-deadlock_retry (1.3.1) ostruct (0.6.1) - overcommit (0.66.0) + overcommit (0.67.0) childprocess (>= 0.6.3, < 6) iniparse (~> 1.4) rexml (>= 3.3.9) @@ -548,13 +546,13 @@ GEM io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) - rexml (3.4.0) + rexml (3.4.1) rinku (2.0.6) rotp (6.3.0) rouge (4.5.1) rtlcss (0.2.1) mini_racer (>= 0.6.3) - rubocop (1.72.0) + rubocop (1.72.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -571,19 +569,23 @@ GEM rubocop (~> 1.41) rubocop-factory_bot (2.26.1) rubocop (~> 1.61) - rubocop-minitest (0.36.0) - rubocop (>= 1.61, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-performance (1.23.1) - rubocop (>= 1.48.1, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.29.1) + rubocop-minitest (0.37.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-performance (1.24.0) + lint_roller (~> 1.1) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rails (2.30.2) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.52.0, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rake (0.6.0) - rubocop (~> 1.0) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rake (0.7.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) ruby-openid (2.9.2) ruby-progressbar (1.13.0) ruby-vips (2.2.3) @@ -628,7 +630,7 @@ GEM actionpack (>= 6.1) activesupport (>= 6.1) sprockets (>= 3.0.0) - stringio (3.1.3) + stringio (3.1.5) strong_migrations (1.8.0) activerecord (>= 5.2) teaspoon (1.4.0) @@ -654,7 +656,7 @@ GEM i18n (>= 0.8.0) simpleidn vendorer (0.2.0) - version_gem (1.1.4) + version_gem (1.1.6) webmock (3.25.0) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -667,7 +669,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.18) + zeitwerk (2.7.2) PLATFORMS ruby @@ -678,7 +680,7 @@ DEPENDENCIES active_record_union activerecord-import addressable (~> 2.8) - annotate + annotaterb argon2 autoprefixer-rails aws-sdk-s3 @@ -704,7 +706,6 @@ DEPENDENCIES doorkeeper doorkeeper-i18n doorkeeper-openid_connect - dry-schema (< 1.14.0) dry-validation erb_lint factory_bot_rails @@ -778,7 +779,6 @@ DEPENDENCIES validates_email_format_of (>= 1.5.1) vendorer webmock - zeitwerk (< 2.7) BUNDLED WITH 2.5.22 diff --git a/INSTALL.md b/INSTALL.md index 8667fb512..cb54cb543 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -22,7 +22,7 @@ of packages required before you can get the various gems installed. ## Minimum requirements -* Ruby 3.1+ +* Ruby 3.2+ * PostgreSQL 13+ * Bundler (see note below about [developer Ruby setup](#rbenv)) * Javascript Runtime diff --git a/app/abilities/ability.rb b/app/abilities/ability.rb index dd377a727..7f47b578c 100644 --- a/app/abilities/ability.rb +++ b/app/abilities/ability.rb @@ -36,7 +36,7 @@ class Ability can [:read, :create, :update, :destroy], :oauth2_application can [:read, :destroy], :oauth2_authorized_application can [:read, :create, :destroy], :oauth2_authorization - can [:update, :destroy], :account + can [:read, :update, :destroy], :account can :update, :account_terms can :create, :account_pd_declaration can :read, :dashboard diff --git a/app/abilities/api_ability.rb b/app/abilities/api_ability.rb index edf051fae..ef852b69f 100644 --- a/app/abilities/api_ability.rb +++ b/app/abilities/api_ability.rb @@ -10,7 +10,7 @@ class ApiAbility can [:read, :feed, :search], Note can :create, Note unless user - can [:read, :download], Changeset + can :read, Changeset can :read, ChangesetComment can :read, Tracepoint can :read, User @@ -40,7 +40,7 @@ class ApiAbility end if user.moderator? - can [:destroy, :restore], ChangesetComment if scopes.include?("write_changeset_comments") + can [:create, :destroy], :changeset_comment_visibility if scopes.include?("write_changeset_comments") can :destroy, Note if scopes.include?("write_notes") diff --git a/app/assets/favicons/manifest.json.erb b/app/assets/favicons/manifest.json.erb index 0826e8841..2e97cebc3 100644 --- a/app/assets/favicons/manifest.json.erb +++ b/app/assets/favicons/manifest.json.erb @@ -1,43 +1,18 @@ { "name": "OpenStreetMap", - "icons": [ - { - "src": "<%= image_path("android-chrome-36x36.png").gsub("/", "\\/") %>", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "<%= image_path("android-chrome-48x48.png").gsub("/", "\\/") %>", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "<%= image_path("android-chrome-72x72.png").gsub("/", "\\/") %>", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "<%= image_path("android-chrome-96x96.png").gsub("/", "\\/") %>", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "<%= image_path("android-chrome-144x144.png").gsub("/", "\\/") %>", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, - { - "src": "<%= image_path("android-chrome-192x192.png").gsub("/", "\\/") %>", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" - } - ], + "short_name": "OSM", + "icons": <%= [36, 48, 72, 96, 144, 192].map { |res| { + src: image_path("android-chrome-#{res}x#{res}.png"), + sizes: "#{res}x#{res}", + type: "image/png", + density: res.to_f / 48 + } }.push({ + src: image_path("../images/osm_logo.svg"), + sizes: "any", + type: "image/svg+xml" + }).to_json %>, "start_url": "/", + "theme_color": "#7ebc6f", + "background_color": "#fff", "display": "minimal-ui" } diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index a3fd93e27..e07830d4e 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -84,7 +84,7 @@ window.updateLinks = function (loc, zoom, layers, object) { .toggleClass("disabled", editDisabled); }; -$(document).ready(function () { +$(function () { // NB: Turns Turbo Drive off by default. Turbo Drive must be opt-in on a per-link and per-form basis // See https://turbo.hotwired.dev/reference/drive#turbo.session.drive Turbo.session.drive = false; @@ -95,6 +95,9 @@ $(document).ready(function () { breakpointWidth = 768; let moreItemWidth = 0; + OSM.csrf = {}; + OSM.csrf[($("meta[name=csrf-param]").attr("content"))] = $("meta[name=csrf-token]").attr("content"); + function updateHeader() { const windowWidth = $(window).width(); diff --git a/app/assets/javascripts/auth_providers.js b/app/assets/javascripts/auth_providers.js index 18c3e38e0..3c2948253 100644 --- a/app/assets/javascripts/auth_providers.js +++ b/app/assets/javascripts/auth_providers.js @@ -1,4 +1,4 @@ -$(document).ready(function () { +$(function () { // Attach referer to authentication buttons $(".auth_button").each(function () { const params = new URLSearchParams(this.search); diff --git a/app/assets/javascripts/diary_entry.js b/app/assets/javascripts/diary_entry.js index e55d0900d..a87eb35f1 100644 --- a/app/assets/javascripts/diary_entry.js +++ b/app/assets/javascripts/diary_entry.js @@ -1,4 +1,4 @@ -$(document).ready(function () { +$(function () { let marker, map; function setLocation(e) { diff --git a/app/assets/javascripts/edit/id.js.erb b/app/assets/javascripts/edit/id.js.erb index 7c907ed3a..7d13dcfbb 100644 --- a/app/assets/javascripts/edit/id.js.erb +++ b/app/assets/javascripts/edit/id.js.erb @@ -1,4 +1,4 @@ -$(document).ready(function () { +$(function () { const id = $("#id-embed"), idData = id.data(); @@ -13,15 +13,18 @@ $(document).ready(function () { const hashArgs = OSM.parseHash(location.hash); const mapParams = OSM.mapParams(); const params = new URLSearchParams(); - let { zoom, lat, lon } = mapParams; + let zoom, lat, lon; + if (idData.lat && idData.lon) { + ({ zoom, lat, lon } = idData); + } else if (!mapParams.object) { + ({ zoom, lat, lon } = mapParams); + } if (mapParams.object) { params.set("id", mapParams.object.type + "/" + mapParams.object.id); if (hashArgs.center) ({ zoom, center: { lat, lng: lon } } = hashArgs); - } else if (idData.lat && idData.lon) { - ({ zoom, lat, lon } = { zoom: 16, ...idData }); } - params.set("map", [zoom || 17, lat, lon].join("/")); + if (lat && lon) params.set("map", [zoom || 17, lat, lon].join("/")); const passThroughKeys = ["background", "comment", "disable_features", "gpx", "hashtags", "locale", "maprules", "notes", "offset", "photo", "photo_dates", "photo_overlay", "photo_username", "presets", "source", "validationDisable", "validationWarning", "validationError", "walkthrough"]; for (const key of passThroughKeys) { diff --git a/app/assets/javascripts/embed.js.erb b/app/assets/javascripts/embed.js.erb index aff13add8..7596e89a4 100644 --- a/app/assets/javascripts/embed.js.erb +++ b/app/assets/javascripts/embed.js.erb @@ -16,7 +16,7 @@ I18n.default_locale = <%= I18n.default_locale.to_json %>; I18n.fallbacks = true; window.onload = function () { - const args = Object.fromEntries(new URLSearchParams(window.location.search)); + const args = Object.fromEntries(new URLSearchParams(location.search)); const tileOptions = { mapnik: { diff --git a/app/assets/javascripts/fixthemap.js b/app/assets/javascripts/fixthemap.js index 2b3101db4..46afb0f68 100644 --- a/app/assets/javascripts/fixthemap.js +++ b/app/assets/javascripts/fixthemap.js @@ -1,4 +1,4 @@ -$(document).ready(function () { +$(function () { const params = OSM.params(); let url = "/note/new"; diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 96870f600..c5bddbaec 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -24,7 +24,7 @@ //= require index/home //= require router -$(document).ready(function () { +$(function () { const map = new L.OSM.Map("map", { zoomControl: false, layerControl: false, @@ -311,7 +311,7 @@ $(document).ready(function () { }; function addObject(type, id, version, center) { - const hashParams = OSM.parseHash(window.location.hash); + const hashParams = OSM.parseHash(location.hash); map.addObject({ type: type, id: parseInt(id, 10), version: version && parseInt(version, 10) }, function (bounds) { if (!hashParams.center && bounds.isValid() && (center || !map.getBounds().contains(bounds))) { @@ -363,7 +363,7 @@ $(document).ready(function () { "/account/home": OSM.Home(map) }); - if (OSM.preferred_editor === "remote" && document.location.pathname === "/edit") { + if (OSM.preferred_editor === "remote" && location.pathname === "/edit") { remoteEditHandler(map.getBounds(), params.object); OSM.router.setCurrentPath("/"); } diff --git a/app/assets/javascripts/index/changeset.js b/app/assets/javascripts/index/changeset.js index 6c1c6a2df..6feefbc2d 100644 --- a/app/assets/javascripts/index/changeset.js +++ b/app/assets/javascripts/index/changeset.js @@ -12,7 +12,7 @@ OSM.Changeset = function (map) { const changesetData = content.find("[data-changeset]").data("changeset"); changesetData.type = "changeset"; - const hashParams = OSM.parseHash(window.location.hash); + const hashParams = OSM.parseHash(location.hash); initialize(); map.addObject(changesetData, function (bounds) { if (!hashParams.center && bounds.isValid()) { @@ -45,7 +45,7 @@ OSM.Changeset = function (map) { }); }) .then(() => { - OSM.loadSidebarContent(window.location.pathname, page.load); + OSM.loadSidebarContent(location.pathname, page.load); }) .catch(error => { content.find("button[data-method][data-url]").prop("disabled", false); diff --git a/app/assets/javascripts/index/directions-endpoint.js b/app/assets/javascripts/index/directions-endpoint.js index 90e95857f..0b52d3d4c 100644 --- a/app/assets/javascripts/index/directions-endpoint.js +++ b/app/assets/javascripts/index/directions-endpoint.js @@ -104,7 +104,13 @@ OSM.DirectionsEndpoint = function Endpoint(map, input, iconUrl, dragCallback, ch const viewbox = map.getBounds().toBBoxString(), // ,,, geocodeUrl = OSM.NOMINATIM_URL + "search?" + new URLSearchParams({ q: endpoint.value, format: "json", viewbox }); - endpoint.geocodeRequest = $.getJSON(geocodeUrl, function (json) { + endpoint.geocodeRequest = new AbortController(); + fetch(geocodeUrl, { signal: endpoint.geocodeRequest.signal }) + .then(r => r.json()) + .then(success) + .catch(() => {}); + + function success(json) { delete endpoint.geocodeRequest; if (json.length === 0) { input.addClass("is-invalid"); @@ -119,7 +125,7 @@ OSM.DirectionsEndpoint = function Endpoint(map, input, iconUrl, dragCallback, ch input.val(json[0].display_name); changeCallback(); - }); + } } function getReverseGeocode() { @@ -127,7 +133,13 @@ OSM.DirectionsEndpoint = function Endpoint(map, input, iconUrl, dragCallback, ch { lat, lng } = latlng, reverseGeocodeUrl = OSM.NOMINATIM_URL + "reverse?" + new URLSearchParams({ lat, lon: lng, format: "json" }); - endpoint.geocodeRequest = $.getJSON(reverseGeocodeUrl, function (json) { + endpoint.geocodeRequest = new AbortController(); + fetch(reverseGeocodeUrl, { signal: endpoint.geocodeRequest.signal }) + .then(r => r.json()) + .then(success) + .catch(() => {}); + + function success(json) { delete endpoint.geocodeRequest; if (!json || !json.display_name) { endpoint.cachedReverseGeocode = { latlng: latlng, notFound: true }; @@ -137,7 +149,7 @@ OSM.DirectionsEndpoint = function Endpoint(map, input, iconUrl, dragCallback, ch endpoint.value = json.display_name; input.val(json.display_name); endpoint.cachedReverseGeocode = { latlng: latlng, value: endpoint.value }; - }); + } } function setLatLng(ll) { diff --git a/app/assets/javascripts/index/directions.js b/app/assets/javascripts/index/directions.js index 2338f537f..40ca85d96 100644 --- a/app/assets/javascripts/index/directions.js +++ b/app/assets/javascripts/index/directions.js @@ -4,6 +4,7 @@ OSM.Directions = function (map) { let controller = null; // the AbortController for the current route request if a route request is in progress + let lastLocation = []; let chosenEngine; const popup = L.popup({ autoPanPadding: [100, 100] }); @@ -36,23 +37,14 @@ OSM.Directions = function (map) { OSM.DirectionsEndpoint(map, $("input[name='route_to']"), OSM.MARKER_RED, endpointDragCallback, endpointChangeCallback) ]; + let downloadURL = null; + const expiry = new Date(); expiry.setYear(expiry.getFullYear() + 10); - const engines = OSM.Directions.engines; - - engines.sort(function (a, b) { - const localised_a = I18n.t("javascripts.directions.engines." + a.id), - localised_b = I18n.t("javascripts.directions.engines." + b.id); - return localised_a.localeCompare(localised_b); - }); - + const modeGroup = $(".routing_modes"); const select = $("select.routing_engines"); - engines.forEach(function (engine, i) { - select.append(""); - }); - $(".directions_form .reverse_directions").on("click", function () { const coordFrom = endpoints[0].latlng, coordTo = endpoints[1].latlng; @@ -73,8 +65,8 @@ OSM.Directions = function (map) { $(".directions_form .btn-close").on("click", function (e) { e.preventDefault(); - $(".describe_location").toggle(!endpoints[0].value); - $(".search_form input[name='query']").val(endpoints[0].value); + $(".describe_location").toggle(!endpoints[1].value); + $(".search_form input[name='query']").val(endpoints[1].value); OSM.router.route("/" + OSM.formatHash(map)); }); @@ -96,15 +88,33 @@ OSM.Directions = function (map) { return h + ":" + (m < 10 ? "0" : "") + m; } - function findEngine(id) { - return engines.findIndex(function (engine) { - return engine.id === id; - }); - } + function setEngine(id) { + const engines = OSM.Directions.engines; + const desired = engines.find(engine => engine.id === id); + if (!desired || (chosenEngine && chosenEngine.id === id)) return; + chosenEngine = desired; + + const modes = engines + .filter(engine => engine.provider === chosenEngine.provider) + .map(engine => engine.mode); + modeGroup + .find("input[id]") + .prop("disabled", function () { + return !modes.includes(this.id); + }) + .prop("checked", function () { + return this.id === chosenEngine.mode; + }); - function setEngine(index) { - chosenEngine = engines[index]; - select.val(index); + const providers = engines + .filter(engine => engine.mode === chosenEngine.mode) + .map(engine => engine.provider); + select + .find("option[value]") + .prop("disabled", function () { + return !providers.includes(this.value); + }); + select.val(chosenEngine.provider); } function getRoute(fitRoute, reportErrors) { @@ -189,6 +199,16 @@ OSM.Directions = function (map) { turnByTurnTable.append(row); }); + const blob = new Blob([JSON.stringify(polyline.toGeoJSON())], { type: "application/json" }); + URL.revokeObjectURL(downloadURL); + downloadURL = URL.createObjectURL(blob); + + $("#sidebar_content").append(`

${ + I18n.t("javascripts.directions.download") + }

`); + $("#sidebar_content").append("

" + I18n.t("javascripts.directions.instructions.courtesy", { link: chosenEngine.creditline }) + "

"); @@ -218,14 +238,17 @@ OSM.Directions = function (map) { } } - let chosenEngineIndex = findEngine("fossgis_osrm_car"); - if (Cookies.get("_osm_directions_engine")) { - chosenEngineIndex = findEngine(Cookies.get("_osm_directions_engine")); - } - setEngine(chosenEngineIndex); + setEngine("fossgis_osrm_car"); + setEngine(Cookies.get("_osm_directions_engine")); + + modeGroup.on("change", "input[name='modes']", function (e) { + setEngine(chosenEngine.provider + "_" + e.target.id); + Cookies.set("_osm_directions_engine", chosenEngine.id, { secure: true, expires: expiry, path: "/", samesite: "lax" }); + getRoute(true, true); + }); select.on("change", function (e) { - chosenEngine = engines[e.target.selectedIndex]; + setEngine(e.target.value + "_" + chosenEngine.mode); Cookies.set("_osm_directions_engine", chosenEngine.id, { secure: true, expires: expiry, path: "/", samesite: "lax" }); getRoute(true, true); }); @@ -246,6 +269,21 @@ OSM.Directions = function (map) { } }); + function sendstartinglocation({ latlng: { lat, lng } }) { + map.fire("startinglocation", { latlng: [lat, lng] }); + } + + function startingLocationListener({ latlng }) { + if (endpoints[0].value) return; + endpoints[0].setValue(latlng.join(", ")); + } + + map.on("locationfound", ({ latlng: { lat, lng } }) => + lastLocation = [lat, lng] + ).on("locateactivate", () => { + map.once("startinglocation", startingLocationListener); + }); + const page = {}; page.pushstate = page.popstate = function () { @@ -268,21 +306,17 @@ OSM.Directions = function (map) { endpoints[type === "from" ? 0 : 1].setValue(llWithPrecision.join(", ")); }); + map.on("locationfound", sendstartinglocation); + endpoints[0].enable(); endpoints[1].enable(); const params = new URLSearchParams(location.search), route = (params.get("route") || "").split(";"); - if (params.has("engine")) { - const engineIndex = findEngine(params.get("engine")); - - if (engineIndex >= 0) { - setEngine(engineIndex); - } - } + if (params.has("engine")) setEngine(params.get("engine")); - endpoints[0].setValue(params.get("from") || route[0] || ""); + endpoints[0].setValue(params.get("from") || route[0] || lastLocation.join(", ")); endpoints[1].setValue(params.get("to") || route[1] || ""); map.setSidebarOverlaid(!endpoints[0].latlng || !endpoints[1].latlng); @@ -296,6 +330,7 @@ OSM.Directions = function (map) { $(".search_form").show(); $(".directions_form").hide(); $("#map").off("dragend dragover drop"); + map.off("locationfound", sendstartinglocation); endpoints[0].disable(); endpoints[1].disable(); @@ -311,7 +346,8 @@ OSM.Directions = function (map) { OSM.Directions.engines = []; OSM.Directions.addEngine = function (engine, supportsHTTPS) { - if (document.location.protocol === "http:" || supportsHTTPS) { + if (location.protocol === "http:" || supportsHTTPS) { + engine.id = engine.provider + "_" + engine.mode; OSM.Directions.engines.push(engine); } }; diff --git a/app/assets/javascripts/index/directions/fossgis_osrm.js b/app/assets/javascripts/index/directions/fossgis_osrm.js index cd1731247..2332049be 100644 --- a/app/assets/javascripts/index/directions/fossgis_osrm.js +++ b/app/assets/javascripts/index/directions/fossgis_osrm.js @@ -2,7 +2,7 @@ // Doesn't yet support hints (function () { - function FOSSGISOSRMEngine(id, vehicleType) { + function FOSSGISOSRMEngine(modeId, vehicleType) { let cachedHints = []; function _processDirections(route) { @@ -150,7 +150,8 @@ } return { - id: id, + mode: modeId, + provider: "fossgis_osrm", creditline: "OSRM (FOSSGIS)", draggable: true, @@ -181,7 +182,7 @@ }; } - OSM.Directions.addEngine(new FOSSGISOSRMEngine("fossgis_osrm_car", "car"), true); - OSM.Directions.addEngine(new FOSSGISOSRMEngine("fossgis_osrm_bike", "bike"), true); - OSM.Directions.addEngine(new FOSSGISOSRMEngine("fossgis_osrm_foot", "foot"), true); + OSM.Directions.addEngine(new FOSSGISOSRMEngine("car", "car"), true); + OSM.Directions.addEngine(new FOSSGISOSRMEngine("bicycle", "bike"), true); + OSM.Directions.addEngine(new FOSSGISOSRMEngine("foot", "foot"), true); }()); diff --git a/app/assets/javascripts/index/directions/fossgis_valhalla.js b/app/assets/javascripts/index/directions/fossgis_valhalla.js index 41ad6a972..11aa4470b 100644 --- a/app/assets/javascripts/index/directions/fossgis_valhalla.js +++ b/app/assets/javascripts/index/directions/fossgis_valhalla.js @@ -1,5 +1,5 @@ (function () { - function FOSSGISValhallaEngine(id, costing) { + function FOSSGISValhallaEngine(modeId, costing) { const INSTR_MAP = [ 0, // kNone = 0; 8, // kStart = 1; @@ -82,7 +82,8 @@ } return { - id: id, + mode: modeId, + provider: "fossgis_valhalla", creditline: "Valhalla (FOSSGIS)", draggable: false, @@ -110,7 +111,7 @@ }; } - OSM.Directions.addEngine(new FOSSGISValhallaEngine("fossgis_valhalla_car", "auto"), true); - OSM.Directions.addEngine(new FOSSGISValhallaEngine("fossgis_valhalla_bicycle", "bicycle"), true); - OSM.Directions.addEngine(new FOSSGISValhallaEngine("fossgis_valhalla_foot", "pedestrian"), true); + OSM.Directions.addEngine(new FOSSGISValhallaEngine("car", "auto"), true); + OSM.Directions.addEngine(new FOSSGISValhallaEngine("bicycle", "bicycle"), true); + OSM.Directions.addEngine(new FOSSGISValhallaEngine("foot", "pedestrian"), true); }()); diff --git a/app/assets/javascripts/index/directions/graphhopper.js b/app/assets/javascripts/index/directions/graphhopper.js index 729618f2d..b3194d16c 100644 --- a/app/assets/javascripts/index/directions/graphhopper.js +++ b/app/assets/javascripts/index/directions/graphhopper.js @@ -1,5 +1,5 @@ (function () { - function GraphHopperEngine(id, vehicleType) { + function GraphHopperEngine(modeId, vehicleType) { const GH_INSTR_MAP = { "-3": 7, // sharp left "-2": 6, // left @@ -47,7 +47,8 @@ } return { - id: id, + mode: modeId, + provider: "graphhopper", creditline: "GraphHopper", draggable: false, @@ -73,7 +74,7 @@ }; } - OSM.Directions.addEngine(new GraphHopperEngine("graphhopper_car", "car"), true); - OSM.Directions.addEngine(new GraphHopperEngine("graphhopper_bicycle", "bike"), true); - OSM.Directions.addEngine(new GraphHopperEngine("graphhopper_foot", "foot"), true); + OSM.Directions.addEngine(new GraphHopperEngine("car", "car"), true); + OSM.Directions.addEngine(new GraphHopperEngine("bicycle", "bike"), true); + OSM.Directions.addEngine(new GraphHopperEngine("foot", "foot"), true); }()); diff --git a/app/assets/javascripts/index/history.js b/app/assets/javascripts/index/history.js index c36ae5f18..6e4e73a23 100644 --- a/app/assets/javascripts/index/history.js +++ b/app/assets/javascripts/index/history.js @@ -58,7 +58,7 @@ OSM.History = function (map) { function update() { const data = new URLSearchParams(); - if (window.location.pathname === "/history") { + if (location.pathname === "/history") { data.set("bbox", map.getBounds().wrap().toBBoxString()); const feedLink = $("link[type=\"application/atom+xml\"]"), feedHref = feedLink.attr("href").split("?")[0]; @@ -67,7 +67,7 @@ OSM.History = function (map) { data.set("list", "1"); - fetch(window.location.pathname + "?" + data) + fetch(location.pathname + "?" + data) .then(response => response.text()) .then(function (html) { displayFirstChangesets(html); @@ -137,7 +137,7 @@ OSM.History = function (map) { updateBounds(); - if (window.location.pathname !== "/history") { + if (location.pathname !== "/history") { const bounds = group.getBounds(); if (bounds.isValid()) map.fitBounds(bounds); } @@ -150,7 +150,7 @@ OSM.History = function (map) { page.load = function () { map.addLayer(group); - if (window.location.pathname === "/history") { + if (location.pathname === "/history") { map.on("moveend", update); } diff --git a/app/assets/javascripts/index/note.js b/app/assets/javascripts/index/note.js index b9310a1af..3e8cbce1b 100644 --- a/app/assets/javascripts/index/note.js +++ b/app/assets/javascripts/index/note.js @@ -79,7 +79,7 @@ OSM.Note = function (map) { const data = $(".details").data(); if (data) { - const hashParams = OSM.parseHash(window.location.hash); + const hashParams = OSM.parseHash(location.hash); map.addObject({ type: "note", id: parseInt(id, 10), diff --git a/app/assets/javascripts/index/query.js b/app/assets/javascripts/index/query.js index ee9d3f415..c0395c701 100644 --- a/app/assets/javascripts/index/query.js +++ b/app/assets/javascripts/index/query.js @@ -326,7 +326,7 @@ OSM.Query = function (map) { const params = new URLSearchParams(path.substring(path.indexOf("?"))), latlng = L.latLng(params.get("lat"), params.get("lon")); - if (!window.location.hash && !noCentre && !map.getBounds().contains(latlng)) { + if (!location.hash && !noCentre && !map.getBounds().contains(latlng)) { OSM.router.withoutMoveListener(function () { map.setView(latlng, 15); }); diff --git a/app/assets/javascripts/index/search.js b/app/assets/javascripts/index/search.js index 42294576b..b3ef3ceb3 100644 --- a/app/assets/javascripts/index/search.js +++ b/app/assets/javascripts/index/search.js @@ -11,7 +11,7 @@ OSM.Search = function (map) { e.preventDefault(); const query = $(this).closest("form").find("input[name=query]").val(); let search = ""; - if (query) search = "?" + new URLSearchParams({ from: query }); + if (query) search = "?" + new URLSearchParams({ to: query }); OSM.router.route("/directions" + search + OSM.formatHash(map)); }); @@ -44,19 +44,14 @@ OSM.Search = function (map) { e.preventDefault(); e.stopPropagation(); - const div = $(this).parents(".search_more"), - csrf_param = $("meta[name=csrf-param]").attr("content"), - csrf_token = $("meta[name=csrf-token]").attr("content"), - params = new URLSearchParams(); + const div = $(this).parents(".search_more"); $(this).hide(); div.find(".loader").show(); - params.set(csrf_param, csrf_token); - fetch($(this).attr("href"), { method: "POST", - body: params + body: new URLSearchParams(OSM.csrf) }) .then(response => response.text()) .then(data => div.replaceWith(data)); @@ -120,20 +115,17 @@ OSM.Search = function (map) { page.load = function () { $(".search_results_entry").each(function (index) { - const entry = $(this), - csrf_param = $("meta[name=csrf-param]").attr("content"), - csrf_token = $("meta[name=csrf-token]").attr("content"), - params = new URLSearchParams({ - zoom: map.getZoom(), - minlon: map.getBounds().getWest(), - minlat: map.getBounds().getSouth(), - maxlon: map.getBounds().getEast(), - maxlat: map.getBounds().getNorth() - }); - params.set(csrf_param, csrf_token); + const entry = $(this); fetch(entry.data("href"), { method: "POST", - body: params + body: new URLSearchParams({ + zoom: map.getZoom(), + minlon: map.getBounds().getWest(), + minlat: map.getBounds().getSouth(), + maxlon: map.getBounds().getEast(), + maxlat: map.getBounds().getNorth(), + ...OSM.csrf + }) }) .then(response => response.text()) .then(function (html) { diff --git a/app/assets/javascripts/leaflet.key.js b/app/assets/javascripts/leaflet.key.js index 937d9b052..45d22feed 100644 --- a/app/assets/javascripts/leaflet.key.js +++ b/app/assets/javascripts/leaflet.key.js @@ -16,7 +16,10 @@ L.OSM.key = function (options) { function shown() { map.on("zoomend baselayerchange", update); - $section.load("/key", update); + fetch("/key") + .then(r => r.text()) + .then(html => { $section.html(html); }) + .then(update); } function hidden() { diff --git a/app/assets/javascripts/leaflet.map.js b/app/assets/javascripts/leaflet.map.js index 34c0bd449..6adf2138d 100644 --- a/app/assets/javascripts/leaflet.map.js +++ b/app/assets/javascripts/leaflet.map.js @@ -155,7 +155,7 @@ L.OSM.Map = L.Map.extend({ [params.mlat, params.mlon] = OSM.cropLocation(marker.getLatLng(), this.getZoom()); } - let url = window.location.protocol + "//" + OSM.SERVER_URL + "/"; + let url = location.protocol + "//" + OSM.SERVER_URL + "/"; const query = new URLSearchParams(params), hash = OSM.formatHash(this); @@ -176,7 +176,7 @@ L.OSM.Map = L.Map.extend({ // and drops the last 4 bits of the full 64 bit Morton code. c1 = interlace(x >>> 17, y >>> 17), c2 = interlace((x >>> 2) & 0x7fff, (y >>> 2) & 0x7fff); - let str = window.location.protocol + "//" + window.location.hostname.replace(/^www\.openstreetmap\.org/i, "osm.org") + "/go/"; + let str = location.protocol + "//" + location.hostname.replace(/^www\.openstreetmap\.org/i, "osm.org") + "/go/"; for (let i = 0; i < Math.ceil((zoom + 8) / 3.0) && i < 5; ++i) { const digit = (c1 >> (24 - (6 * i))) & 0x3f; diff --git a/app/assets/javascripts/leaflet.share.js b/app/assets/javascripts/leaflet.share.js index 325b168b2..d84f8820e 100644 --- a/app/assets/javascripts/leaflet.share.js +++ b/app/assets/javascripts/leaflet.share.js @@ -159,6 +159,7 @@ L.OSM.share = function (options) { .attr("class", "form-select w-auto") .append($("