]> git.openstreetmap.org Git - rails.git/commitdiff
Merge pull request #5293 from AntonKhorev/user-roles-resourceful-routes
authorAndy Allan <git@gravitystorm.co.uk>
Wed, 13 Nov 2024 18:45:16 +0000 (18:45 +0000)
committerGitHub <noreply@github.com>
Wed, 13 Nov 2024 18:45:16 +0000 (18:45 +0000)
Use resourceful routes for granting/revoking user roles

37 files changed:
.github/workflows/danger.yml
CONFIGURE.md
CONTRIBUTING.md
DOCKER.md
FAQ.md
Gemfile
Gemfile.lock
Vagrantfile
app/assets/javascripts/index.js
app/assets/javascripts/index/new_note.js
app/assets/javascripts/index/query.js
app/assets/javascripts/richtext.js
app/assets/stylesheets/common.scss
app/controllers/api/notes_controller.rb
app/controllers/sessions_controller.rb
app/controllers/user_blocks_controller.rb
app/models/user.rb
app/views/sessions/new.html.erb
app/views/shared/_richtext_field.html.erb
config/locales/az.yml
config/locales/bn.yml
config/locales/cy.yml
config/locales/da.yml
config/locales/he.yml
config/locales/my.yml
config/locales/sc.yml
config/locales/sr.yml
config/settings.yml
config/settings/test.yml
db/migrate/20241023004427_backfill_note_subscriptions.rb [new file with mode: 0644]
db/structure.sql
script/vagrant/setup/provision.sh
test/controllers/api/changeset_comments_controller_test.rb
test/controllers/api/notes_controller_test.rb
test/controllers/sessions_controller_test.rb
test/controllers/user_blocks_controller_test.rb
test/factories/note_subscriptions.rb [new file with mode: 0644]

index 8999fca5b5af7b8f9603e1cebf6aae4cbe8ec210..67a676d87c11eea4b1879c2a715443237ea250ee 100644 (file)
@@ -22,6 +22,12 @@ jobs:
           ruby-version: 3.1
           rubygems: 3.4.10
           bundler-cache: true
+      - name: Create base branch
+        run: |
+          git fetch ${{ github.event.pull_request.base.repo.clone_url }} ${{ github.event.pull_request.base.ref }}:danger_base
+      - name: Create head branch
+        run: |
+          git fetch ${{ github.event.pull_request.head.repo.clone_url }} ${{ github.event.pull_request.head.ref }}:danger_head
       - name: Danger
         env:
           DANGER_GITHUB_BEARER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
index dcc8ae2ac1d20a5001ae568a2dbf17b18735ab48..29d1daad8bc513f82b46a43210cf90047c1c2a6f 100644 (file)
@@ -76,7 +76,7 @@ For iD, do the following:
 
 An example excerpt from settings.local.yml:
 
-```
+```yaml
 # Default editor
 default_editor: "id"
 # OAuth 2 Client ID for iD
@@ -99,7 +99,7 @@ To allow [Notes](https://wiki.openstreetmap.org/wiki/Notes) and changeset discus
 
 An example excerpt from settings.local.yml:
 
-```
+```yaml
 # OAuth 2 Client ID for the web site
 oauth_application: "SGm8QJ6tmoPXEaUPIZzLUmm1iujltYZVWCp9hvGsqXg"
 # OAuth 2 Client Secret for the web site
index 2571b93459147e8e635302b24353465b63da8887..e298c944f5819d2b72d8bf14cfb34e6a692de7a1 100644 (file)
@@ -1,6 +1,15 @@
+# Contributing
+
 * https://www.ruby-lang.org/ - The homepage of Ruby which has more links and some great tutorials.
 * https://rubyonrails.org/ - The homepage of Rails, also has links and tutorials.
 
+## Assigning Issues
+
+We don't assign issues to individual contributors. You are welcome to work on any
+issue, and there's no need to ask first.
+
+For more details see [our FAQ](FAQ.md)]
+
 ## Coding style
 
 We use [Rubocop](https://github.com/rubocop-hq/rubocop) (for ruby files)
index 4804d09b31403d24a73c95cb17437e606624e308..b93bf6d508346fff56c5c572b40f5ae241932b40 100644 (file)
--- a/DOCKER.md
+++ b/DOCKER.md
@@ -24,27 +24,37 @@ Use [Docker Engine](https://docs.docker.com/engine/install/ubuntu/) with the [do
 
 The first step is to fork/clone the repo to your local machine:
 
-    git clone https://github.com/openstreetmap/openstreetmap-website.git
+```
+git clone https://github.com/openstreetmap/openstreetmap-website.git
+```
 
 Now change working directory to the `openstreetmap-website`:
 
-    cd openstreetmap-website
+```
+cd openstreetmap-website
+```
 
 ## Initial Setup
 
 ### Storage
 
-    cp config/example.storage.yml config/storage.yml
+```
+cp config/example.storage.yml config/storage.yml
+```
 
 ### Database
 
-    cp config/docker.database.yml config/database.yml
+```
+cp config/docker.database.yml config/database.yml
+```
 
 ## Prepare local settings file
 
 This is a workaround. [See issues/2185 for details](https://github.com/openstreetmap/openstreetmap-website/issues/2185#issuecomment-508676026).
 
-    touch config/settings.local.yml
+```
+touch config/settings.local.yml
+```
 
 **Windows users:** `touch` is not an availible command in Windows so just create a `settings.local.yml` file in the `config` directory, or if you have WSL you can run `wsl touch config/settings.local.yml`.
 
@@ -52,13 +62,17 @@ This is a workaround. [See issues/2185 for details](https://github.com/openstree
 
 To build local Docker images run from the root directory of the repository:
 
-    docker compose build
+```
+docker compose build
+```
 
 If this is your first time running or you have removed cache this will take some time to complete. Once the Docker images have finished building you can launch the images as containers.
 
 To launch the app run:
 
-    docker compose up -d
+```
+docker compose up -d
+```
 
 This will launch one Docker container for each 'service' specified in `docker-compose.yml` and run them in the background. There are two options for inspecting the logs of these running containers:
 
@@ -69,21 +83,29 @@ This will launch one Docker container for each 'service' specified in `docker-co
 
 Run the Rails database migrations:
 
-    docker compose run --rm web bundle exec rails db:migrate
+```
+docker compose run --rm web bundle exec rails db:migrate
+```
 
 ### Tests
 
 Prepare the test database:
 
-    docker compose run --rm web bundle exec rails db:test:prepare
+```
+docker compose run --rm web bundle exec rails db:test:prepare
+```
 
 Run the test suite:
 
-    docker compose run --rm web bundle exec rails test:all
+```
+docker compose run --rm web bundle exec rails test:all
+```
 
 If you encounter errors about missing assets, precompile the assets:
 
-    docker compose run --rm web bundle exec rake assets:precompile
+```
+docker compose run --rm web bundle exec rake assets:precompile
+```
 
 ### Loading an OSM extract
 
@@ -91,31 +113,37 @@ This installation comes with no geographic data loaded. You can either create ne
 
 For example, let's download the District of Columbia from Geofabrik or [any other region](https://download.geofabrik.de):
 
-    wget https://download.geofabrik.de/north-america/us/district-of-columbia-latest.osm.pbf
+```
+wget https://download.geofabrik.de/north-america/us/district-of-columbia-latest.osm.pbf
+```
 
 You can now use Docker to load this extract into your local Docker-based OSM instance:
 
-    docker compose run --rm web osmosis \
-        -verbose    \
-        --read-pbf district-of-columbia-latest.osm.pbf \
-        --log-progress \
-        --write-apidb \
-            host="db" \
-            database="openstreetmap" \
-            user="openstreetmap" \
-            validateSchemaVersion="no"
+```
+docker compose run --rm web osmosis \
+    -verbose    \
+    --read-pbf district-of-columbia-latest.osm.pbf \
+    --log-progress \
+    --write-apidb \
+        host="db" \
+        database="openstreetmap" \
+        user="openstreetmap" \
+        validateSchemaVersion="no"
+```
 
 **Windows users:** Powershell uses `` ` `` and CMD uses `^` at the end of each line, e.g.:
 
-    docker compose run --rm web osmosis `
-        -verbose    `
-        --read-pbf district-of-columbia-latest.osm.pbf `
-        --log-progress `
-        --write-apidb `
-            host="db" `
-            database="openstreetmap" `
-            user="openstreetmap" `
-            validateSchemaVersion="no"
+```
+docker compose run --rm web osmosis `
+    -verbose    `
+    --read-pbf district-of-columbia-latest.osm.pbf `
+    --log-progress `
+    --write-apidb `
+        host="db" `
+        database="openstreetmap" `
+        user="openstreetmap" `
+        validateSchemaVersion="no"
+```
 
 Once you have data loaded for Washington, DC you should be able to navigate to [`http://localhost:3000/#map=12/38.8938/-77.0146`](http://localhost:3000/#map=12/38.8938/-77.0146) to begin working with your local instance.
 
@@ -127,12 +155,18 @@ See [`CONFIGURE.md`](CONFIGURE.md) for information on how to manage users and en
 
 If you want to get into a web container and run specific commands you can fire up a throwaway container to run bash in via:
 
-    docker compose run --rm web bash
+```
+docker compose run --rm web bash
+```
 
 Alternatively, if you want to use the already-running `web` container then you can `exec` into it via:
 
-    docker compose exec web bash
+```
+docker compose exec web bash
+```
 
 Similarly, if you want to `exec` in the db container use:
 
-    docker compose exec db bash
+```
+docker compose exec db bash
+```
diff --git a/FAQ.md b/FAQ.md
index d4ac1fc9fbd9959ca4e2a02a4562c66e8bbc80c8..e53c8dddb4195288797355a975395b41dcaa7ac5 100644 (file)
--- a/FAQ.md
+++ b/FAQ.md
@@ -1,3 +1,5 @@
+# Frequently Asked Questions
+
 ## How do I create a banner to promote my OpenStreetMap event?
 
 We occasionally display banner images on the main page of [openstreetmap.org](https://www.openstreetmap.org/) to
@@ -23,3 +25,13 @@ drive.  This is a great way to reach a lot of people!
 
 See [PR #1296](https://github.com/openstreetmap/openstreetmap-website/pull/1296)
 as an example.
+
+## Why don't you assign issues?
+
+We don't assign issues to volunteers for several reasons. The main reasons are that it discourages other volunteers from working on the issue, and the process turns into an unproductive administrative overhead for our team.
+
+There's no need to ask for an issue to be assigned before anyone starts working on it. Everyone is welcome to work on any issue at any time.
+
+In our experience, most people who ask for an issue to be assigned to them never create a pull request. So we would need to keep track of the assigned issues, and remember to unassign them a week or two into the future, when it is likely that they will not be making a PR. Assigned developers might feel bad if they perceive that we're unhappy with their progress, further discouraging them from contributing. Or we will get drawn into discussions about needing more time, or re-assigning them again, or so on. So it is best not to assign in the first place.
+
+The risk that two people are both genuinely working on the same task in the same hour or two is vanishingly remote, and doesn't outweigh the downsides described above. A better approach is to encourage people to simply work on the task and create a pull request, at which point everyone knows that they are actually working on the issue and not just planning/hoping/wishing to do so.
diff --git a/Gemfile b/Gemfile
index b83011542a24d216995f7c60f154d537182e8609..277346b8388df908c3e110cff5b9bbb55a18cf84 100644 (file)
--- a/Gemfile
+++ b/Gemfile
@@ -148,7 +148,7 @@ gem "zeitwerk", "< 2.7"
 group :development do
   gem "better_errors"
   gem "binding_of_caller"
-  gem "danger", :github => "tomhughes/danger", :ref => "pull-request-target"
+  gem "danger"
   gem "danger-auto_label"
   gem "debug_inspector"
   gem "i18n-tasks"
index d3a61841ade9f977be8ba708039e9e38f8887f53..5d15e550c55d1dd8bcfdf3bec73091af8ae130bf 100644 (file)
@@ -1,23 +1,3 @@
-GIT
-  remote: https://github.com/tomhughes/danger.git
-  revision: a265cf74d2f464a25796b48d95697f5eed553454
-  ref: pull-request-target
-  specs:
-    danger (9.5.1)
-      base64 (~> 0.2)
-      claide (~> 1.0)
-      claide-plugins (>= 0.9.2)
-      colored2 (~> 3.1)
-      cork (~> 0.1)
-      faraday (>= 0.9.0, < 3.0)
-      faraday-http-cache (~> 2.0)
-      git (~> 1.13)
-      kramdown (~> 2.3)
-      kramdown-parser-gfm (~> 1.0)
-      octokit (>= 4.0)
-      pstore (~> 0.1)
-      terminal-table (>= 1, < 4)
-
 GEM
   remote: https://rubygems.org/
   specs:
@@ -112,8 +92,8 @@ GEM
     autoprefixer-rails (10.4.19.0)
       execjs (~> 2)
     aws-eventstream (1.3.0)
-    aws-partitions (1.1001.0)
-    aws-sdk-core (3.211.0)
+    aws-partitions (1.1004.0)
+    aws-sdk-core (3.212.0)
       aws-eventstream (~> 1, >= 1.3.0)
       aws-partitions (~> 1, >= 1.992.0)
       aws-sigv4 (~> 1.9)
@@ -121,14 +101,14 @@ GEM
     aws-sdk-kms (1.95.0)
       aws-sdk-core (~> 3, >= 3.210.0)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.169.0)
+    aws-sdk-s3 (1.170.1)
       aws-sdk-core (~> 3, >= 3.210.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)
     aws-sigv4 (1.10.1)
       aws-eventstream (~> 1, >= 1.0.2)
     base64 (0.2.0)
-    benchmark (0.3.0)
+    benchmark (0.4.0)
     better_errors (2.10.1)
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
@@ -190,6 +170,20 @@ GEM
       rexml
     crass (1.0.6)
     dalli (3.2.8)
+    danger (9.5.1)
+      base64 (~> 0.2)
+      claide (~> 1.0)
+      claide-plugins (>= 0.9.2)
+      colored2 (~> 3.1)
+      cork (~> 0.1)
+      faraday (>= 0.9.0, < 3.0)
+      faraday-http-cache (~> 2.0)
+      git (~> 1.13)
+      kramdown (~> 2.3)
+      kramdown-parser-gfm (~> 1.0)
+      octokit (>= 4.0)
+      pstore (~> 0.1)
+      terminal-table (>= 1, < 4)
     danger-auto_label (1.3.1)
       danger-plugin-api (~> 1.0)
     danger-plugin-api (1.0.0)
@@ -208,10 +202,10 @@ GEM
       reline (>= 0.3.8)
     debug_inspector (1.2.0)
     deep_merge (1.2.2)
-    delayed_job (4.1.12)
-      activesupport (>= 3.0, < 8.0)
-    delayed_job_active_record (4.1.10)
-      activerecord (>= 3.0, < 8.0)
+    delayed_job (4.1.13)
+      activesupport (>= 3.0, < 9.0)
+    delayed_job_active_record (4.1.11)
+      activerecord (>= 3.0, < 9.0)
       delayed_job (>= 3.0, < 5)
     docile (1.4.1)
     doorkeeper (5.7.1)
@@ -297,7 +291,7 @@ GEM
     globalid (1.2.1)
       activesupport (>= 6.1)
     google-protobuf (3.25.5)
-    hashdiff (1.1.1)
+    hashdiff (1.1.2)
     hashie (5.0.0)
     highline (3.1.1)
       reline
@@ -345,7 +339,7 @@ GEM
       rails-dom-testing (>= 1, < 3)
       railties (>= 4.2.0)
       thor (>= 0.14, < 2.0)
-    json (2.7.6)
+    json (2.8.1)
     jwt (2.9.3)
       base64
     kgio (2.11.4)
@@ -382,14 +376,14 @@ GEM
     minitest (5.25.1)
     minitest-focus (1.4.0)
       minitest (>= 4, < 6)
-    msgpack (1.7.3)
+    msgpack (1.7.5)
     multi_json (1.15.0)
     multi_xml (0.7.1)
       bigdecimal (~> 3.1)
     nap (1.1.0)
-    net-http (0.4.1)
+    net-http (0.5.0)
       uri
-    net-imap (0.5.0)
+    net-imap (0.5.1)
       date
       net-protocol
     net-pop (0.1.2)
@@ -455,7 +449,7 @@ GEM
       omniauth (~> 2.0)
     open4 (1.3.4)
     openstreetmap-deadlock_retry (1.3.1)
-    ostruct (0.6.0)
+    ostruct (0.6.1)
     overcommit (0.64.1)
       childprocess (>= 0.6.3, < 6)
       iniparse (~> 1.4)
@@ -468,7 +462,7 @@ GEM
     popper_js (2.11.8)
     progress (3.6.0)
     pstore (0.1.3)
-    psych (5.1.2)
+    psych (5.2.0)
       stringio
     public_suffix (6.0.1)
     puma (5.6.9)
@@ -540,14 +534,14 @@ GEM
     rdoc (6.7.0)
       psych (>= 4.0.0)
     regexp_parser (2.9.2)
-    reline (0.5.10)
+    reline (0.5.11)
       io-console (~> 0.5)
     request_store (1.7.0)
       rack (>= 1.4)
     rexml (3.3.9)
     rinku (2.0.6)
     rotp (6.3.0)
-    rouge (4.4.0)
+    rouge (4.5.1)
     rtlcss (0.2.1)
       mini_racer (>= 0.6.3)
     rubocop (1.68.0)
@@ -560,7 +554,7 @@ GEM
       rubocop-ast (>= 1.32.2, < 2.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (>= 2.4.0, < 3.0)
-    rubocop-ast (1.34.0)
+    rubocop-ast (1.35.0)
       parser (>= 3.3.1.0)
     rubocop-capybara (2.21.0)
       rubocop (~> 1.41)
@@ -594,7 +588,7 @@ GEM
     sawyer (0.9.2)
       addressable (>= 2.3.5)
       faraday (>= 0.17.3, < 3)
-    securerandom (0.3.1)
+    securerandom (0.3.2)
     selenium-webdriver (4.23.0)
       base64 (~> 0.2)
       logger (~> 1.4)
@@ -636,7 +630,7 @@ GEM
       execjs (>= 0.3.0, < 3)
     thor (1.3.2)
     tilt (2.4.0)
-    timeout (0.4.1)
+    timeout (0.4.2)
     turbo-rails (2.0.11)
       actionpack (>= 6.0.0)
       railties (>= 6.0.0)
@@ -690,7 +684,7 @@ DEPENDENCIES
   config
   connection_pool
   dalli
-  danger!
+  danger
   danger-auto_label
   dartsass-sprockets
   debug
index c2869cd5f661c38846251efee9d222a23de08ecd..617bd7b4d0884707e8f94fc1af38ffd5671523ba 100644 (file)
@@ -2,9 +2,11 @@
 # vi: set ft=ruby :
 
 Vagrant.configure("2") do |config|
-  # use official ubuntu image for virtualbox
+  # use official debian image
+  config.vm.box = "debian/bookworm64"
+
+  # configure virtualbox provider
   config.vm.provider "virtualbox" do |vb, override|
-    override.vm.box = "ubuntu/noble64"
     override.vm.synced_folder ".", "/srv/openstreetmap-website"
     vb.customize ["modifyvm", :id, "--memory", "4096"]
     vb.customize ["modifyvm", :id, "--cpus", "2"]
@@ -14,16 +16,16 @@ Vagrant.configure("2") do |config|
   # Use sshfs sharing if available, otherwise NFS sharing
   sharing_type = Vagrant.has_plugin?("vagrant-sshfs") ? "sshfs" : "nfs"
 
-  # use third party image and sshfs or NFS sharing for lxc
+  # configure lxc provider
   config.vm.provider "lxc" do |_, override|
-    override.vm.box = "generic/ubuntu2404"
     override.vm.synced_folder ".", "/srv/openstreetmap-website", :type => sharing_type
   end
 
-  # use third party image and sshfs or NFS sharing for libvirt
-  config.vm.provider "libvirt" do |_, override|
-    override.vm.box = "generic/ubuntu2404"
+  # configure libvirt provider
+  config.vm.provider "libvirt" do |libvirt, override|
     override.vm.synced_folder ".", "/srv/openstreetmap-website", :type => sharing_type
+    libvirt.memory = 4096
+    libvirt.cpus = 2
   end
 
   # configure shared package cache if possible
index 2b29992e909892802c38092bbe36f611b8095be4..4dfc849feb5d9d24361566c50de3793d1c65ebd6 100644 (file)
@@ -25,8 +25,6 @@
 //= require qs/dist/qs
 
 $(document).ready(function () {
-  var loaderTimeout;
-
   var map = new L.OSM.Map("map", {
     zoomControl: false,
     layerControl: false,
@@ -39,11 +37,7 @@ $(document).ready(function () {
 
     map.setSidebarOverlaid(false);
 
-    clearTimeout(loaderTimeout);
-
-    loaderTimeout = setTimeout(function () {
-      $("#sidebar_loader").show();
-    }, 200);
+    $("#sidebar_loader").show().addClass("delayed-fade-in");
 
     // IE<10 doesn't respect Vary: X-Requested-With header, so
     // prevent caching the XHR response as a full-page URL.
@@ -60,9 +54,8 @@ $(document).ready(function () {
       url: content_path,
       dataType: "html",
       complete: function (xhr) {
-        clearTimeout(loaderTimeout);
         $("#flash").empty();
-        $("#sidebar_loader").hide();
+        $("#sidebar_loader").removeClass("delayed-fade-in").hide();
 
         var content = $(xhr.responseText);
 
index 59fbeeb1d6aa35146a0808c950923373c3d9f10b..887ba043b12b00bda5478b4e2dfd3aeec305efd3 100644 (file)
@@ -139,8 +139,6 @@ OSM.NewNote = function (map) {
 
     newNote.on("remove", function () {
       addNoteButton.removeClass("active");
-    }).on("dragstart", function () {
-      $(newNote).stopTime("removenote");
     }).on("dragend", function () {
       content.find("textarea").focus();
     });
index 7d63280ee849638a9f7acd0d69bbc8b9129a5cec..09e4de31e0c6e78faf57444534b55fa3c5653292 100644 (file)
@@ -289,18 +289,10 @@ OSM.Query = function (map) {
       .hide();
 
     if (marker) map.removeLayer(marker);
-    marker = L.circle(latlng, radius, featureStyle).addTo(map);
-
-    $(document).everyTime(75, "fadeQueryMarker", function (i) {
-      if (i === 10) {
-        map.removeLayer(marker);
-      } else {
-        marker.setStyle({
-          opacity: 1 - (i * 0.1),
-          fillOpacity: 0.5 - (i * 0.05)
-        });
-      }
-    }, 10);
+    marker = L.circle(latlng, Object.assign({
+      radius: radius,
+      className: "query-marker"
+    }, featureStyle)).addTo(map);
 
     runQuery(latlng, radius, nearby, $("#query-nearby"), false);
     runQuery(latlng, radius, isin, $("#query-isin"), true, compareSize);
index 56aad8c73a2c8642f09d4cc1d6b6f54f5b5a6f4d..bd00d937e2313068b310681a2be733f04dff0201 100644 (file)
@@ -6,8 +6,10 @@
    */
   $(document).on("change", ".richtext_container textarea", function () {
     var container = $(this).closest(".richtext_container");
+    var preview = container.find(".tab-pane[id$='_preview']");
 
-    container.find(".tab-pane[id$='_preview']").empty();
+    preview.children(".richtext_placeholder").attr("hidden", true).removeClass("delayed-fade-in");
+    preview.children(".richtext").empty();
   });
 
   /*
     var editor = container.find("textarea");
     var preview = container.find(".tab-pane[id$='_preview']");
 
-    if (preview.contents().length === 0) {
-      preview.oneTime(500, "loading", function () {
-        preview.addClass("loading");
-      });
+    if (preview.children(".richtext").contents().length === 0) {
+      preview.children(".richtext_placeholder").removeAttr("hidden").addClass("delayed-fade-in");
 
-      preview.load(editor.data("previewUrl"), { text: editor.val() }, function () {
-        preview.stopTime("loading");
-        preview.removeClass("loading");
+      preview.children(".richtext").load(editor.data("previewUrl"), { text: editor.val() }, function () {
+        preview.children(".richtext_placeholder").attr("hidden", true).removeClass("delayed-fade-in");
       });
     }
   });
index d551462b2202ec34defa80b70c13be334e08553f..73f8521d7fbc3f2a816deaa75661c81783df846d 100644 (file)
@@ -70,6 +70,18 @@ time[title] {
   }
 }
 
+/* Utility for delayed loading spinner */
+
+.delayed-fade-in {
+  animation: 300ms linear forwards delayed-fade-in;
+}
+
+@keyframes delayed-fade-in {
+  0%   { opacity: 0 }
+  66%  { opacity: 0 }
+  100% { opacity: 1 }
+}
+
 /* Rules for the header */
 
 #menu-icon {
@@ -368,6 +380,14 @@ body.small-nav {
     .leaflet-marker-draggable {
       cursor: move;
     }
+
+    .query-marker {
+      animation: 1500ms forwards query-marker-fade;
+
+      @keyframes query-marker-fade {
+        to { opacity: 0 }
+      }
+    }
   }
 
   #map-ui {
index c141bdf46a08146047c7c68f6f6673886202f5db..7e2e7fb793babed86952313cc1d0703043d2f760 100644 (file)
@@ -398,8 +398,10 @@ module Api
 
       comment = note.comments.create!(attributes)
 
-      note.comments.map(&:author).uniq.each do |user|
-        UserMailer.note_comment_notification(comment, user).deliver_later if notify && user && user != current_user && user.visible?
+      if notify
+        note.subscribers.visible.each do |user|
+          UserMailer.note_comment_notification(comment, user).deliver_later if current_user != user
+        end
       end
 
       NoteSubscription.find_or_create_by(:note => note, :user => current_user) if current_user
index a3e6f42f03db4b172607bc26285d6c79c3b0ee8b..abbaf5e921e45aedf89e60d7057023b9a0374143 100644 (file)
@@ -20,7 +20,7 @@ class SessionsController < ApplicationController
   end
 
   def create
-    session[:remember_me] ||= params[:remember_me]
+    session[:remember_me] = params[:remember_me] == "yes"
 
     referer = safe_referer(params[:referer]) if params[:referer]
 
index c42c2659db0b09aae46b17430b3cac0e25283790..d427e5fa515bee1b2a4454cc1b1624a112d794cf 100644 (file)
@@ -29,7 +29,7 @@ class UserBlocksController < ApplicationController
   end
 
   def show
-    if current_user && current_user == @user_block.user
+    if current_user && current_user == @user_block.user && !@user_block.deactivates_at
       @user_block.needs_view = false
       @user_block.deactivates_at = [@user_block.ends_at, Time.now.utc].max
       @user_block.save!
index d8e8771d84b976d9ef0aa561849323d300d748ca..917faca2184f38f79999d7ea5e31f5768727d0d7 100644 (file)
@@ -423,8 +423,8 @@ class User < ApplicationRecord
     if moderator?
       Settings.moderator_changeset_comments_per_hour
     else
-      previous_comments = changeset_comments.limit(200).count
-      max_comments = previous_comments / 200.0 * Settings.max_changeset_comments_per_hour
+      previous_comments = changeset_comments.limit(Settings.comments_to_max_changeset_comments).count
+      max_comments = previous_comments / Settings.comments_to_max_changeset_comments.to_f * Settings.max_changeset_comments_per_hour
       max_comments = max_comments.floor.clamp(Settings.initial_changeset_comments_per_hour, Settings.max_changeset_comments_per_hour)
       max_comments /= 2**active_reports
       max_comments.floor.clamp(Settings.min_changeset_comments_per_hour, Settings.max_changeset_comments_per_hour)
index 9d05d4af80d44b7bf956824ee163dc66d4db225f..c2d96b63cc37ae2b9def6ba868cf8742927c3212 100644 (file)
@@ -40,7 +40,7 @@
   <%= f.password_field :password, :autocomplete => "on", :tabindex => 2, :value => "", :skip_label => true %>
 
   <%= f.form_group do %>
-    <%= f.check_box :remember_me, { :label => t(".remember"), :tabindex => 3, :checked => (params[:remember_me] == "yes") }, "yes" %>
+    <%= f.check_box :remember_me, { :label => t(".remember"), :tabindex => 3, :checked => (params[:remember_me] == "true") }, "yes" %>
   <% end %>
 
   <div class="mb-3">
index 5b84fd6ed889bce1d774cdbb746082a946b8056a..eb11aa13cab3cfa0d927666f54209572056c74da 100644 (file)
       <div id="<%= id %>_edit" class="tab-pane show active">
         <%= builder.text_area(attribute, options.merge(:wrapper => false, "data-preview-url" => preview_url(:type => type))) %>
       </div>
-      <div id="<%= id %>_preview" class="tab-pane richtext text-break"></div>
+      <div id="<%= id %>_preview" class="tab-pane">
+        <div class="richtext_placeholder text-center py-5" hidden>
+          <div class="spinner-border" role="status">
+            <span class="visually-hidden"><%= t("browse.start_rjs.loading") %></span>
+          </div>
+        </div>
+        <div class="richtext text-break"></div>
+      </div>
       <div id="<%= id %>_help" class="tab-pane">
         <div class="card bg-body-tertiary h-100">
           <div class="card-body">
index 945d9172fcbbbec01d49cd8ee9c9d6dd6569fb55..917cf11bdef9cb11d28da213a0579c90eacfffad 100644 (file)
@@ -13,6 +13,7 @@
 # Author: Vugar 1981
 # Author: Wertuose
 # Author: Şeyx Şamil
+# Author: Əkrəm Cəfər
 ---
 az:
   time:
@@ -134,7 +135,7 @@ az:
         other: təxminən %{count} saat əvvəl
       half_a_minute: yarım dəqiqə əvvəl
   editor:
-    default: Susmaya görə (hal-hazırda %{name})
+    default: Defolt (hal-hazırda %{name})
     id:
       name: iD
       description: iD (brauzerdaxili redaktə)
index bd6e870821731ec073e9a7db4964f2f0dbf18f9d..cf9c4da1be3d8b1390940b496d2ace121eb9939f 100644 (file)
@@ -66,6 +66,12 @@ bn:
         create: বাধা তৈরি করুন
         update: বাধা হালনাগাদ করুন
   activerecord:
+    errors:
+      messages:
+        display_name_is_user_n: n আপনার ব্যবহারকারী আইডি না হলে user_n হবে না
+      models:
+        user_mute:
+          is_already_muted: ইতিমধ্যেই মিউট করা হয়েছে
     models:
       acl: প্রবেশাধিকার নিয়ন্ত্রণ তালিকা
       changeset: পরিবর্তনসমূহ
@@ -252,6 +258,7 @@ bn:
         reopened_at_by_html: '%{when} %{user} দ্বারা পুনর্সক্রিয়'
       rss:
         title: ওপেনস্ট্রিটম্যাপ টীকা
+        description_all: রিপোর্ট করা, মন্তব্য করা বা বন্ধ করা নোটের তালিকা
         description_area: নোটের তালিকা, রিপোর্ট করা, মন্তব্য করা বা আপনার এলাকায়
           বন্ধ করা [(%{min_lat}|%{min_lon}) -- (%{max_lat}|%{max_lon})]
         description_item: নোট %{id}-এর জন্য একটি আরএসএস চারণ
@@ -286,6 +293,8 @@ bn:
         retain_changeset_discussions: আপনার পরিবর্তন ধার্য আলোচনাগুলি, যদি করে থাকেন,
           রয়ে যাবে।
         retain_email: আপমার ইমেইল ঠিকানা রয়ে যাবে।
+        recent_editing_html: আপনি সম্প্রতি সম্পাদনা করেছেন বলে আপনার অ্যাকাউন্টটি
+          বর্তমানে মুছে ফেলা যাবে না। %{time} পরে মুছে ফেলা সম্ভব হবে।
         confirm_delete: আপনি কি নিশ্চিত?
         cancel: বাতিল করুন
   accounts:
@@ -337,6 +346,7 @@ bn:
     deleted_ago_by_html: '%{user} কর্তৃক %{time_ago} অপসারিত'
     edited_ago_by_html: '%{user} কর্তৃক %{time_ago} সম্পাদিত'
     version: সংস্করণ
+    redacted_version: সংশোধিত সংস্করণ
     in_changeset: পরিবর্তনসমূহ
     anonymous: নামহীন
     no_comment: (কোনো মন্তব্য নেই)
@@ -349,6 +359,7 @@ bn:
       other: '%{count}টি রাস্তা'
     download_xml: XML ডাউনলোড করুন
     view_history: ইতিহাস দেখুন
+    view_unredacted_history: অসংশোধিত ইতিহাস দেখুন
     view_details: বিস্তারিত দেখুন
     location: 'অবস্থান:'
     node:
@@ -470,6 +481,7 @@ bn:
     index:
       title: পরিবর্তনধার্য
       title_user: '%{user} কর্তৃক পরিবর্তন ধার্য'
+      title_user_link_html: '%{user_link}-এর দ্বারা পরিবর্তনগুলি'
       title_friend: আমার বন্ধুদের দ্বারা পরিবর্তনসেট
       title_nearby: কাছকাছি ব্যবহারকারীর পরিবর্তনধার্য
       empty: কোনো পরিবর্তনধার্য পাওয়া যায়নি।
@@ -485,8 +497,16 @@ bn:
         created: তৈরি হয়েছে
         closed: বন্ধ হয়েছে
         belongs_to: লেখক
+    subscribe:
+      button: আলোচনায় সাবস্ক্রাইব করুন
+    unsubscribe:
+      button: আলোচনায় আনসাবস্ক্রাইব করুন
+    no_such_entry:
+      heading: '%{id} আইডির কোনো ভুক্তি নেই'
     show:
       title: 'পরিবর্তনধার্য: %{id}'
+      created: 'তৈরি করা হয়েছে: %{when}'
+      closed: 'বন্ধ করা হয়েছে: %{when}'
       created_ago_html: '%{time_ago} তৈরি'
       closed_ago_html: '%{time_ago} বন্ধকৃত'
       created_ago_by_html: '%{user} কর্তৃক %{time_ago} তৈরিকৃত'
@@ -495,6 +515,8 @@ bn:
       join_discussion: আলোচনায় যোগ দিতে প্রবেশ করুন
       still_open: চেঞ্জসেট এখনও খোলা - চেঞ্জসেট বন্ধ হয়ে গেলে আলোচনা খোলা হবে।
       hidden_comment_by_html: '%{user} %{time_ago} মন্তব্যটি লুকিয়েছেন'
+      hide_comment: লুকান
+      unhide_comment: দৃশ্যমান করুন
       comment: মন্তব্য
       changesetxml: পরিবর্তনধার্য এক্সএমএল
       osmchangexml: osmChange এক্সএমএল
@@ -618,7 +640,10 @@ bn:
         create:
           notice: অ্যাপ্লিকেশন নিবন্ধিত।
     scopes:
+      address: আপনার আসল ঠিকানা দেখুন
       email: আপনার ইমেইল ঠিকানা দেখুন
+      phone: আপনার ফোন নম্বর দেখুন
+      profile: আপনার প্রোফাইলের তথ্য দেখুন
   errors:
     contact:
       contact: যোগাযোগ
@@ -1196,28 +1221,35 @@ bn:
           fashion: ফ্যাশন সামগ্রীর দোকান
           florist: ফুলওয়ালা
           food: খাবারের দোকান
+          frame: ফ্রেমের দোকান
           funeral_directors: অন্ত্যেষ্টিক্রিয়া পরিচালকবৃন্দ
           furniture: আসবাবপত্র
           garden_centre: বাগান কেন্দ্র
+          gas: গ্যাসের দোকান
           general: সাধারণ দোকান
           gift: উপহারের দোকান
           greengrocer: সবজিওয়ালা
           grocery: মুদি দোকান
           hairdresser: নাপিত
           hardware: যন্ত্রাংশের দোকান
+          health_food: স্বাস্থ্যকর খাবারের দোকান
           hearing_aids: শ্রবণসহায়ক যন্ত্র
+          herbalist: ভেষজ বিশেষজ্ঞ
           hifi: হাই-ফাই দোকান
           houseware: ঘরোয়া জিনিসের দোকান
           ice_cream: আইসক্রিমের দোকান
           interior_decoration: ভিতরের সজ্জা
           jewelry: গহনার দোকান
+          kiosk: কিয়স্কের দোকান
           kitchen: রান্নাঘরের দোকান
           laundry: ধোপার দোকান
           locksmith: চাবিওয়ালা
           lottery: লটারি
           mall: বিপণী বিতান
           massage: ম্যাসেজ
+          medical_supply: চিকিৎসা সামগ্রীর দোকান
           mobile_phone: মোবাইল ফোনের দোকান
+          money_lender: ঋণদাতা
           motorcycle: মোটোরসইকেলের দোকান
           motorcycle_repair: মোটরসাইকেল মেরামতের দোকান
           music: সঙ্গীতের দোকান
@@ -1226,8 +1258,11 @@ bn:
           nutrition_supplements: পুষ্টি সম্পূরক
           optician: চশমা বিক্রেতা
           organic: জৈব খাদ্যের দোকান
+          outdoor: আউটডোর দোকান
           paint: রঙের দোকান
+          pastry: পেস্ট্রির দোকান
           perfumery: সুগন্ধিশালা
+          pet: পোষা প্রাণীর দোকান
           photo: ছবির দোকান
           seafood: সামুদ্রিক খাবার
           second_hand: পুরনো-সামগ্রীর দোকান
@@ -1251,6 +1286,7 @@ bn:
           wine: মদের দোকান
           "yes": দোকান
         tourism:
+          artwork: শিল্পকর্ম
           attraction: আকর্ষণ
           cabin: পর্যটক কেবিন
           camp_site: ক্যাম্পের স্থল
@@ -1287,6 +1323,7 @@ bn:
           "yes": জলপথ
       admin_levels:
         level2: রাষ্ট্রের সীমানা
+        level3: অঞ্চলের সীমানা
         level4: রাজ্যের সীমানা
         level5: অঞ্চলের সীমানা
         level6: প্রদেশের সীমানা
@@ -1304,10 +1341,12 @@ bn:
       select_type: প্রকার নির্বাচন করুন
       search: অনুসন্ধান
       states:
+        ignored: উপেক্ষিত
         open: খুলুন
         resolved: মীমাংসিত
     page:
       user_not_found: ব্যবহারকারীর অস্তিত্ব নেই
+      issues_not_found: এই ধরনের কোনও ইস্যু পাওয়া যায়নি
       status: স্থিতি
       reports: অভিযোগ
       last_updated: সর্বশেষ হালনাগাদ
@@ -1315,6 +1354,9 @@ bn:
       reports_count:
         one: '%{count}টি প্রতিবেদন'
         other: '%{count}টি প্রতিবেদন'
+      states:
+        open: খুলুন
+        resolved: মীমাংসিত
     show:
       resolve: মীমাংসা করুন
       ignore: উপেক্ষা করুন
@@ -1322,6 +1364,7 @@ bn:
       reports_of_this_issue: এই সমস্যা প্রতিবেদন করুন
       read_reports: প্রতিবেদন পড়ুন
       new_reports: নতুন প্রতিবেদন
+      comments_on_this_issue: এই বিষয়ে মন্তব্যসমূহ
     helper:
       reportable_title:
         note: 'টীকা #%{note_id}'
@@ -1542,20 +1585,29 @@ bn:
       delete image: বর্তমান ছবিটি অপসারণ করুন
       replace image: বর্তমান ছবি বদল করুন
       home location: বাড়ির অবস্থান
+      show: দেখাও
+      delete: অপসারণ
     update:
       success: প্রোফাইল পরিবর্তিত হয়েছে।
   sessions:
     new:
-      tab_title: প্রবেশ
+      tab_title: প্রবেশ করুন
       email or username: ইমেইল ঠিকানা অথবা ব্যবহারকারী নাম
       password: পাসওয়ার্ড
       remember: আমাকে মনে রাখো
       lost password link: পাসওয়ার্ড ভুলে গেছেন?
       login_button: প্রবেশ করুন
+      or: অথবা
       auth failure: দুঃখিত, এই তথ্য দিয়ে প্রবেশ করানো যাচ্ছে না।
     destroy:
       title: প্রস্থান
+      heading: ওপেনস্ট্রিটম্যাপ থেকে লগআউট করুন
       logout_button: প্রস্থান
+    suspended_flash:
+      suspended: দুঃখিত, সন্দেহজনক কার্যকলাপের কারণে আপনার অ্যাকাউন্ট স্থগিত করা হয়েছে।
+      contact_support_html: আপনি যদি এই বিষয়ে আলোচনা করতে চান তাহলে অনুগ্রহ করে %{support_link}-এর
+        সাথে যোগাযোগ করুন।
+      support: সহায়তা
   shared:
     markdown_help:
       heading_html: '%{kramdown_link} দিয়ে পার্স করা হয়েছে'
@@ -1573,6 +1625,7 @@ bn:
     richtext_field:
       edit: সম্পাদনা
       preview: প্রাকদর্শন
+      help: সাহায্য
     pagination:
       diary_comments:
         older: পুরাতন মন্তব্য
index 282b2ec50fda5f2ddbe32065d641ad91c8ce7d55..fd2297d509001c47382ff498776ec6ac803a1be5 100644 (file)
@@ -533,14 +533,18 @@ cy:
         closed: Caëwyd
         belongs_to: Awdur
     subscribe:
+      heading: Tanysgrifio i'r drafodaeth grŵp newid ganlynol?
       button: Tanysgrifio i drafodaeth
     unsubscribe:
+      heading: Dad-danysgrifio i'r drafodaeth grŵp newid ganlynol?
       button: Dad-danysgrifio o'r drafodaeth
     heading:
       title: Grŵp newid %{id}
       created_by_html: Crëwyd gan %{link_user} ar %{created}.
     no_such_entry:
       heading: 'Dim cofnod gyda''r id: %{id}'
+      body: Mae'n ddrwg gennym, nid oes grŵp newid gyda'r id %{id}. Gwiriwch eich
+        sillafu, neu efallai bod y ddolen rydych chi wedi ei chlicio arni'n anghywir.
     show:
       title: 'Grŵp newid: %{id}'
       created: 'Crëwyd: %{when}'
@@ -668,6 +672,7 @@ cy:
         title: Cofnodion dyddiadur OpenStreetMap
         description: Cofnodion dyddiadur diweddar gan ddefnyddwyr OpenStreetMap
     subscribe:
+      heading: Tanysgrifio i'r drafodaeth cofnod dyddiadur ganlynol?
       button: Tanysgrifio i drafodaeth
     unsubscribe:
       heading: Dad-danysgrifio o'r drafodaeth cofnod dyddiadur ganlynol?
@@ -1263,7 +1268,7 @@ cy:
           educational_institution: Sefydliad Addysgol
           employment_agency: Asiantaeth Cyflogi
           energy_supplier: Swyddfa Gyflenwr Ynni
-          estate_agent: Gwerthwr Tai
+          estate_agent: Asiant Eiddo
           financial: Swyddfa Gyllid
           government: Swyddfa Llywodraeth
           insurance: Swyddfa Yswiriant
@@ -1379,7 +1384,7 @@ cy:
           e-cigarette: Siop E-Sigaréts
           electronics: Siop Electroneg
           erotic: Siop Erotig
-          estate_agent: Gwerthwr Tai
+          estate_agent: Asiant Eiddo
           fabric: Siop Ddeunydd
           farm: Siop Fferm
           fashion: Siop Ffasiwn
@@ -1783,6 +1788,10 @@ cy:
         partial_changeset_without_comment: dim sylw
       details: 'Ateb neu ddysgu mwy am y grŵp newid: %{url}.'
       details_html: 'Ateb neu ddysgu mwy am y grŵp newid: %{url}.'
+      unsubscribe: Gallwch ddad-danysgrifio o ddiweddariadau i'r grŵp newid hwn yn
+        %{url}.
+      unsubscribe_html: Gallwch ddad-danysgrifio o ddiweddariadau i'r grŵp newid hwn
+        yn %{url}.
   confirmations:
     confirm:
       heading: Gwiriwch eich e-byst!
@@ -2657,6 +2666,7 @@ cy:
       application: Ap
       permissions: Caniatadau
       last_authorized: Awdurdodwyd Ddiweddaf
+      no_applications_html: Nid ydych wedi awdurdodi unrhyw apiau %{oauth2} eto.
     application:
       revoke: Dirymu Mynediad
       confirm_revoke: Dirymu mynediad ar gyfer yr ap hwn?
index f692feaacd48707646dff1b02c984c6faa82de66..e6715c975f6451814b5a5a62b03add157a71d070 100644 (file)
@@ -2552,7 +2552,7 @@ da:
     show:
       title: Viser spor %{name}
       heading: Viser spor %{name}
-      pending: VENTENDE
+      pending: AFVENTENDE
       filename: 'Filnavn:'
       download: hent
       uploaded: 'Overført:'
@@ -2571,7 +2571,7 @@ da:
       visibility: 'Synlighed:'
       confirm_delete: Slet dette spor?
     trace:
-      pending: VENTENDE
+      pending: AFVENTENDE
       count_points:
         one: '%{count} punkt'
         other: '%{count} punkter'
index e1f99baee5d065aac522d43bad587ce2f4b40b47..134a954f7f3e099a86f972a638cc02046067ed2d 100644 (file)
@@ -156,7 +156,7 @@ he:
         longitude: קו אורך
         public: ציבורי
         description: תיאור
-        gpx_file: ×\91×\97×\99רת קובץ מסלול GPS
+        gpx_file: × ×\90 ×\9c×\91×\97×\95ר קובץ מסלול GPS
         visibility: נִראוּת
         tagstring: תגים
       message:
@@ -1698,14 +1698,14 @@ he:
       befriend_them: באפשרותך לסמנו כחבר בכתובת %{befriendurl}.
       befriend_them_html: באפשרותך לסמנו כחבר בכתובת %{befriendurl}.
     gpx_description:
-      description_with_tags: '×\96×\94 × ×¨×\90×\94 ×\9b×\9e×\95 הקובץ שלך %{trace_name} עם התיאור %{trace_description}
+      description_with_tags: 'נר×\90×\94 ×©הקובץ שלך %{trace_name} עם התיאור %{trace_description}
         ועם התגים הבאים: %{tags}'
-      description_with_tags_html: '×\96×\94 × ×¨×\90×\94 ×\9b×\9e×\95 הקובץ שלך %{trace_name} עם התיאור %{trace_description}
+      description_with_tags_html: 'נר×\90×\94 ×©הקובץ שלך %{trace_name} עם התיאור %{trace_description}
         ועם התגים הבאים: %{tags}'
-      description_with_no_tags: זה נראה כמו הקובץ שלך %{trace_name} עם התיאור %{trace_description}
+      description_with_no_tags: נראה שהקובץ שלך %{trace_name} עם התיאור %{trace_description}
+        וללא תגים
+      description_with_no_tags_html: נראה שהקובץ שלך %{trace_name} עם התיאור %{trace_description}
         וללא תגים
-      description_with_no_tags_html: זה נראה כמו הקובץ שלך %{trace_name} עם התיאור
-        %{trace_description} וללא תגים
     gpx_failure:
       hi: שלום %{to_user},
       failed_to_import: 'הייבוא כקובץ מסלול GPS נכשל. נא לוודא שהקובץ שלך הוא קובץ
index 9cfc23f2c103ce2e57023113dadfd824911ee2c1..d214bd5ce559af385c03740212947e7a6f77f513 100644 (file)
@@ -3,10 +3,16 @@
 # Export driver: phpyaml
 # Author: Dr Lotus Black
 # Author: Ninjastrikers
+# Author: Zyh333222
 # Author: ခွန်ပညာႏ(တောင်ႏကီꩻ)
 # Author: သူထွန်း
 ---
 my:
+  time:
+    formats:
+      friendly: |-
+        %e %B %Y
+         at %H:%M
   helpers:
     file:
       prompt: ဖိုင်ရွေးပါ
index 955d9a7fadd74c2238b0adabb1915e8ff50d4cdd..31f37fa7e8b7ff54c923cba16b895e119173469a 100644 (file)
@@ -2267,44 +2267,61 @@ sc:
           cycleway_local: Pista tziclàbile locale
           footway: Caminu pro pedones
           rail: Ferrovia
+          train: Trenu
           subway: Metropolitana
+          light_rail: Trenu lèbiu/metropolitana lèbia
+          tram: Tram
+          bus: Postale
           cable_car: Funivia
           chair_lift: ascensore carrotzina
           runway: Pista de aeroportu
           taxiway: carrera de furriada
           apron: Àrea de parchègiu de sos aèreos
           admin: Làcana amministrativa
+          capital: Capitale
+          city: Tzitade
+          vineyard: Bìngia
           forest: Litu
           wood: Buscu
+          farmland: Terras de coltivu
+          grass: Erba
+          meadow: Pradu
+          bare_rock: Roca nuda
+          sand: Arena
           golf: Campu de golf
           park: Parcu
           common: Comunu
+          built_up: Zona fraigada
           resident: Àrea de residèntzia
           retail: Àrea cummertziale
           industrial: Àrea industriale
           commercial: Àrea cummertziale
           heathland: Istruvina
           lake: Lagu
-          reservoir: riserva de abba
+          reservoir: Riserva de abba
           farm: Fatoria
           brownfield: Terrinu industriale abbandonadu
           cemetery: Campusantu
           allotments: Giardinos familiares
           pitch: Campu isportivu
           centre: Tzentru isportivu
+          beach: Marina
           reserve: Reserva naturale
           military: Zona militare
           school: Iscola
-          university: universidade
+          university: Universidade
+          hospital: Ispidale
           building: Edifìtziu significativu
           station: Istatzione ferroviària
+          tram_stop: Firmada de su tram
           summit: Cùcuru de monte
-          peak: cùcuru
+          peak: Cùcuru
           tunnel: Lìnia trategiada = galleria
           bridge: Oros nieddos = ponte
           private: Atzessu privadu
           destination: Atzessu pro sa destinatzione
           construction: Caminos in costrutzione
+          bus_stop: Firmada de su postale
           bicycle_shop: Butega de bitzicletas
           bicycle_parking: Parchègiu pro bitzicletas
           toilets: Còmodu
@@ -2505,24 +2522,26 @@ sc:
       oauth2_applications: Aplicatziones OAuth 2
       oauth2_authorizations: Autorizatziones OAuth 2
     auth_providers:
+      openid_url: URL OpenID
+      openid_login_button: Sighi
       openid:
         title: Intra cun OpenID
-        alt: Intra cun un'URL de OpenID
+        alt: Logotipu de OpenID
       google:
         title: Intra cun Google
-        alt: Intra cun un'OpenID de Google
+        alt: Logotipu de Google
       facebook:
         title: Intra cun Facebook
-        alt: Intra cun unu contu de Facebook
+        alt: Logotipu de Facebook
       microsoft:
         title: Intra cun Microsoft
-        alt: Intra cun unu contu de Microsoft
+        alt: Logotipu de Microsoft
       github:
         title: Intra cun GitHub
-        alt: Intra cun unu contu de GitHub
+        alt: Logotipu de GitHub
       wikipedia:
         title: Intra cun Wikipedia
-        alt: Intra cun unu contu de Wikipedia
+        alt: Logotipu de Wikipedia
   oauth:
     permissions:
       missing: No as cuntzèdidu s'atzessu a custa caraterìstica  a s'aplicatzione
@@ -2582,6 +2601,7 @@ sc:
       title: Sas aplicatziones autorizadas meas
       application: Aplicatzione
       permissions: Permissos
+      last_authorized: Ùrtima autorizatzione
       no_applications_html: No as galu autorizadu peruna aplicatzione %{oauth2}.
     application:
       revoke: Rèvoca s'atzessu
@@ -2589,6 +2609,8 @@ sc:
   users:
     new:
       title: Iscrie·ti
+      tab_title: Registrati·ti
+      signup_to_authorize_html: Registra·ti cun OpenStreetMap pro atzèdere a %{client_app_name}.
       no_auto_account_create: A dolu mannu como non semus in gradu de creare unu contu
         pro tene in automàticu.
       please_contact_support_html: Cuntata %{support_link} pro fàghere in manera chi
@@ -2596,12 +2618,15 @@ sc:
         su prus in presse chi podimus.
       support: assistèntzia
       about:
-        header: Lìbera e modificàbile
+        header: Lìbera e modificàbile.
         paragraph_1: A diferèntzia de àteras mapas, OpenStreetMap est totu realizada
           dae persones che a tie e chie si siat la podet currègere, agiornare, iscarrigare
           o impreare in manera lìbera.
-        paragraph_2: Registra·ti pro incumintzare a contribuire. T'amus a imbiare
-          una lìtera eletrònica pro cunfirmare su contu tuo.
+        paragraph_2: Registra·ti pro incumintzare a contribuire.
+        welcome: Bene bènnidu in OpenStreetMap
+      duplicate_social_email: Si tenes giai unu contu de OpenStreetMap e boles impreare
+        unu frunidore de identidade esternu, intra impreende sa crae tua e muda sas
+        impostaduras de su contu tuo.
       display name description: Su nùmene de utente chi s'at a mustrare in manera
         pùblica. Lu podes cambiare prus a tardu in sas preferèntzias.
       by_signing_up:
@@ -2836,6 +2861,12 @@ sc:
       reason: Resone de su blocu
       status: Istadu
       revoker_name: Revocadu dae
+    navigation:
+      all_blocks: Totu sos blocos
+      blocks_on_me: Blocos subra a mene
+      blocks_by_me: Blocos fatos dae mene
+      block: 'Blocu #%{id}'
+      new_block: Blocu nou
   notes:
     index:
       title: Notas insertadas o cummentadas dae %{user}
@@ -2884,12 +2915,16 @@ sc:
       intro: As agatadu una faddina o carchi cosa chi mancat? Informa sos àteros mapadores
         a manera chi lu potzant acontzare. Move su marcadore a sa positzione curreta
         e iscrie una nota pro descrìere su problema.
+      anonymous_warning_log_in: intra
+      anonymous_warning_sign_up: registra·ti
       advice: Sa nota tua est pùblica e si podet impreare pro atualizare sa mapa,
         duncas non nch'insertes peruna informatzione personale o informatzione de
         mapas cun deretu de autore o elencos.
       add: Annanghe una nota
     notes_paging_nav:
       showing_page: Pàgina %{page}
+      next: Imbeniente
+      previous: Antepostu
   javascripts:
     close: Serra
     share:
index f54160404ebfce64b3cb4fba3112066c96c0405e..2a6d11089e9c630c3daf5be1a0c70f0184dbb5e1 100644 (file)
@@ -1522,8 +1522,8 @@ sr:
       destroy_button: Обриши
       back: Назад
       wrong_user: Пријављени сте као '%{user}', али порука коју сте желели да прочитате
-        Ð½Ð¸Ñ\98е Ð¿Ð¾Ñ\81лаÑ\82а Ñ\82ом ÐºÐ¾Ñ\80иÑ\81никÑ\83. Ð\9fÑ\80иÑ\98авиÑ\82е Ñ\81е ÐºÐ°Ð¾ Ð¸Ñ\81пÑ\80аван ÐºÐ¾Ñ\80иÑ\81ник Ð´Ð° Ð±Ð¸Ñ\81Ñ\82е Ñ\98е
-        прочитали.
+        Ð½Ð¸Ñ\81Ñ\82е Ð¿Ð¾Ñ\81лали Ð²Ð¸ Ð¸Ð»Ð¸ Ñ\82ом ÐºÐ¾Ñ\80иÑ\81никÑ\83. Ð\9fÑ\80иÑ\98авиÑ\82е Ñ\81е ÐºÐ°Ð¾ Ð¸Ñ\81пÑ\80аван ÐºÐ¾Ñ\80иÑ\81ник Ð´Ð°
+        Ð±Ð¸Ñ\81Ñ\82е Ñ\98е Ð¿Ñ\80оÑ\87иÑ\82али.
     sent_message_summary:
       destroy_button: Обриши
     heading:
index b5a565b133dc4df6697cdeab8e299c5a4f2d2758..db871775e784d4cfe5ceb29a71bfbdee7c89b2ed 100644 (file)
@@ -69,6 +69,7 @@ max_friends_per_hour: 60
 min_changeset_comments_per_hour: 1
 initial_changeset_comments_per_hour: 6
 max_changeset_comments_per_hour: 60
+comments_to_max_changeset_comments: 200
 moderator_changeset_comments_per_hour: 36000
 # Rate limit for changes
 min_changes_per_hour: 100
index fe5aa5d897341348905eb8b349ad8bd601016107..b0e2f461392cce37eb7221ad9f72e0601f7a361c 100644 (file)
@@ -21,6 +21,7 @@ trace_image_storage: "test"
 trace_icon_storage: "test"
 # Lower some rate limits for testing
 max_changeset_comments_per_hour: 10
+comments_to_max_changeset_comments: 20
 moderator_changeset_comments_per_hour: 15
 # Private key for signing id_tokens
 doorkeeper_signing_key: |
diff --git a/db/migrate/20241023004427_backfill_note_subscriptions.rb b/db/migrate/20241023004427_backfill_note_subscriptions.rb
new file mode 100644 (file)
index 0000000..3c9a2ef
--- /dev/null
@@ -0,0 +1,15 @@
+class BackfillNoteSubscriptions < ActiveRecord::Migration[7.2]
+  class NoteComment < ApplicationRecord; end
+  class NoteSubscription < ApplicationRecord; end
+
+  disable_ddl_transaction!
+
+  def up
+    attrs = %w[user_id note_id]
+
+    NoteComment.in_batches(:of => 1000) do |note_comments|
+      rows = note_comments.distinct.where.not(:author_id => nil).pluck(:author_id, :note_id)
+      NoteSubscription.upsert_all(rows.map { |r| attrs.zip(r).to_h })
+    end
+  end
+end
index 35ef5e2b3107518378de971a2691f8413c7271fa..9679e0b92561617b120b347632a2373bfa9aebcc 100644 (file)
@@ -3397,6 +3397,7 @@ INSERT INTO "schema_migrations" (version) VALUES
 ('23'),
 ('22'),
 ('21'),
+('20241023004427'),
 ('20241022141247'),
 ('20240913171951'),
 ('20240912181413'),
index f19234a8ec0f04ba8b4ac380d85b83734e1c1067..f6ecd4ed5742ab0a28b07a5017c541fdfdd55b92 100644 (file)
@@ -3,12 +3,6 @@
 # abort on error
 set -e
 
-# set locale to UTF-8 compatible. apologies to non-english speakers...
-locale-gen en_GB.utf8
-update-locale LANG=en_GB.utf8 LC_ALL=en_GB.utf8
-export LANG=en_GB.utf8
-export LC_ALL=en_GB.utf8
-
 # make sure we have up-to-date packages
 apt-get update
 
@@ -18,7 +12,7 @@ apt-get upgrade -y
 # install packages as explained in INSTALL.md
 apt-get install -y ruby ruby-dev ruby-bundler \
                      libxml2-dev libxslt1-dev nodejs npm \
-                     build-essential git-core \
+                     build-essential git-core firefox-esr \
                      postgresql postgresql-contrib libpq-dev libvips-dev libyaml-dev \
                      libsasl2-dev libffi-dev libgd-dev libarchive-dev libbz2-dev
 npm install --global yarn
index 35e45e8c840f4eb2a17d71b6bb01928d3888447b..21f30714c2213034fef4e4d4041ce2e05273ef68 100644 (file)
@@ -158,7 +158,7 @@ module Api
     def test_create_comment_experienced_user_rate_limit
       changeset = create(:changeset, :closed)
       user = create(:user)
-      create_list(:changeset_comment, 200, :author_id => user.id, :created_at => Time.now.utc - 1.day)
+      create_list(:changeset_comment, Settings.comments_to_max_changeset_comments, :author_id => user.id, :created_at => Time.now.utc - 1.day)
 
       auth_header = bearer_authorization_header user
 
index f814d8c6e6424c13ec0788a5d5f4e170d6ca3da3..5f69e6a2ac025e9eadf11a85795d040c349202c6 100644 (file)
@@ -271,6 +271,58 @@ module Api
       assert_equal user.display_name, js["properties"]["comments"].last["user"]
     end
 
+    def test_comment_without_notifications_success
+      # Ensure that emails are sent to users
+      first_user = create(:user)
+      second_user = create(:user)
+      third_user = create(:user)
+
+      note_with_comments_by_users = create(:note) do |note|
+        create(:note_comment, :note => note, :author => first_user)
+        create(:note_comment, :note => note, :author => second_user)
+      end
+
+      auth_header = bearer_authorization_header third_user
+
+      assert_difference "NoteComment.count", 1 do
+        assert_difference "NoteSubscription.count", 1 do
+          assert_no_difference "ActionMailer::Base.deliveries.size" do
+            perform_enqueued_jobs do
+              post comment_api_note_path(note_with_comments_by_users, :text => "This is an additional comment", :format => "json"), :headers => auth_header
+            end
+          end
+        end
+      end
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal note_with_comments_by_users.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 3, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_equal third_user.display_name, js["properties"]["comments"].last["user"]
+
+      subscription = NoteSubscription.last
+      assert_equal third_user, subscription.user
+      assert_equal note_with_comments_by_users, subscription.note
+
+      get api_note_path(note_with_comments_by_users, :format => "json")
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal note_with_comments_by_users.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 3, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_equal third_user.display_name, js["properties"]["comments"].last["user"]
+
+      ActionMailer::Base.deliveries.clear
+    end
+
     def test_comment_with_notifications_success
       # Ensure that emails are sent to users
       first_user = create(:user)
@@ -281,6 +333,8 @@ module Api
         create(:note_comment, :note => note, :author => first_user)
         create(:note_comment, :note => note, :author => second_user)
       end
+      create(:note_subscription, :note => note_with_comments_by_users, :user => first_user)
+      create(:note_subscription, :note => note_with_comments_by_users, :user => second_user)
 
       auth_header = bearer_authorization_header third_user
 
index 914a4ab5608d32ce7fe343a2d6d5556ba47d3f31..f490b748c4a6c763699d2759909ba06f52944e69 100644 (file)
@@ -54,6 +54,24 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest
     assert_redirected_to root_path
   end
 
+  def test_login_remembered
+    user = create(:user)
+
+    post login_path, :params => { :username => user.display_name, :password => "test", :remember_me => "yes" }
+    assert_redirected_to root_path
+
+    assert_equal 28 * 86400, session[:_remember_for]
+  end
+
+  def test_login_not_remembered
+    user = create(:user)
+
+    post login_path, :params => { :username => user.display_name, :password => "test", :remember_me => "0" }
+    assert_redirected_to root_path
+
+    assert_nil session[:_remember_for]
+  end
+
   def test_logout_without_referer
     post logout_path
     assert_redirected_to root_path
index 1d89476ec6d10aea552cd3c8e283385064af1be1..2ab90364ec63672457b24070f17b48fc2c372909 100644 (file)
@@ -147,14 +147,72 @@ class UserBlocksControllerTest < ActionDispatch::IntegrationTest
     assert_select "h1 a[href='#{user_path active_block.user}']", :text => active_block.user.display_name
     assert_select "h1 a[href='#{user_path active_block.creator}']", :text => active_block.creator.display_name
     assert UserBlock.find(active_block.id).needs_view
+  end
 
-    # Login as the blocked user
-    session_for(active_block.user)
+  ##
+  # test clearing needs_view by showing a zero-hour block to the blocked user
+  def test_show_sets_deactivates_at_for_zero_hour_block
+    user = create(:user)
+    session_for(user)
 
-    # Now viewing it should mark it as seen
-    get user_block_path(:id => active_block)
-    assert_response :success
-    assert_not UserBlock.find(active_block.id).needs_view
+    freeze_time do
+      block = create(:user_block, :needs_view, :zero_hour, :user => user)
+      assert block.needs_view
+      assert_nil block.deactivates_at
+
+      travel 1.hour
+
+      get user_block_path(block)
+      assert_response :success
+      block.reload
+      assert_not block.needs_view
+      assert_equal Time.now.utc, block.deactivates_at
+
+      travel 1.hour
+
+      get user_block_path(block)
+      assert_response :success
+      block.reload
+      assert_not block.needs_view
+      assert_equal Time.now.utc - 1.hour, block.deactivates_at
+    end
+  end
+
+  ##
+  # test clearing needs_view by showing a timed block to the blocked user
+  def test_show_sets_deactivates_at_for_timed_block
+    user = create(:user)
+    session_for(user)
+
+    freeze_time do
+      block = create(:user_block, :needs_view, :created_at => Time.now.utc, :ends_at => Time.now.utc + 24.hours, :user => user)
+      assert block.needs_view
+      assert_nil block.deactivates_at
+
+      travel 1.hour
+
+      get user_block_path(block)
+      assert_response :success
+      block.reload
+      assert_not block.needs_view
+      assert_equal Time.now.utc + 23.hours, block.deactivates_at
+
+      travel 1.hour
+
+      get user_block_path(block)
+      assert_response :success
+      block.reload
+      assert_not block.needs_view
+      assert_equal Time.now.utc + 22.hours, block.deactivates_at
+
+      travel 24.hours
+
+      get user_block_path(block)
+      assert_response :success
+      block.reload
+      assert_not block.needs_view
+      assert_equal Time.now.utc - 2.hours, block.deactivates_at
+    end
   end
 
   ##
diff --git a/test/factories/note_subscriptions.rb b/test/factories/note_subscriptions.rb
new file mode 100644 (file)
index 0000000..5f09ec3
--- /dev/null
@@ -0,0 +1,3 @@
+FactoryBot.define do
+  factory :note_subscription
+end