From: Tom Hughes Date: Wed, 18 Dec 2024 13:54:12 +0000 (+0000) Subject: Merge remote-tracking branch 'upstream/pull/5410' X-Git-Tag: live~190 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/7d4cc85a312cb79fd1c4b0f22626722bea1d736d?hp=e89a78fff858d5db07f6959a39dd9655dc46008e Merge remote-tracking branch 'upstream/pull/5410' --- diff --git a/Gemfile.lock b/Gemfile.lock index 455ae2bda..0200f153e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,7 +92,7 @@ GEM autoprefixer-rails (10.4.19.0) execjs (~> 2) aws-eventstream (1.3.0) - aws-partitions (1.1019.0) + aws-partitions (1.1023.0) aws-sdk-core (3.214.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -101,7 +101,7 @@ GEM aws-sdk-kms (1.96.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.176.0) + aws-sdk-s3 (1.176.1) aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -328,7 +328,7 @@ GEM in_threads (1.6.0) iniparse (1.5.0) io-console (0.8.0) - irb (1.14.1) + irb (1.14.2) rdoc (>= 4.0.0) reline (>= 0.4.2) jbuilder (2.13.0) @@ -353,7 +353,7 @@ GEM listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.2) + logger (1.6.3) logstasher (2.1.5) activesupport (>= 5.2) request_store @@ -383,7 +383,7 @@ GEM nap (1.1.0) net-http (0.6.0) uri - net-imap (0.5.1) + net-imap (0.5.2) date net-protocol net-pop (0.1.2) @@ -393,7 +393,7 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.17.1) + nokogiri (1.17.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) oauth (1.1.0) @@ -509,7 +509,7 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.1) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-i18n (7.0.10) @@ -532,20 +532,20 @@ GEM rb-inotify (0.11.1) ffi (~> 1.0) rchardet (1.8.0) - rdoc (6.8.1) + rdoc (6.9.1) psych (>= 4.0.0) regexp_parser (2.9.3) - reline (0.5.12) + reline (0.6.0) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) - rexml (3.3.9) + rexml (3.4.0) rinku (2.0.6) rotp (6.3.0) rouge (4.5.1) rtlcss (0.2.1) mini_racer (>= 0.6.3) - rubocop (1.69.1) + rubocop (1.69.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -555,7 +555,7 @@ GEM rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.36.2) + rubocop-ast (1.37.0) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) @@ -589,7 +589,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - securerandom (0.4.0) + securerandom (0.4.1) selenium-webdriver (4.23.0) base64 (~> 0.2) logger (~> 1.4) @@ -631,7 +631,7 @@ GEM execjs (>= 0.3.0, < 3) thor (1.3.2) tilt (2.4.0) - timeout (0.4.2) + timeout (0.4.3) turbo-rails (2.0.11) actionpack (>= 6.0.0) railties (>= 6.0.0) diff --git a/app/assets/images/social_icons/email.svg b/app/assets/images/social_icons/email.svg new file mode 100644 index 000000000..d35d51d60 --- /dev/null +++ b/app/assets/images/social_icons/email.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/images/social_icons/facebook.svg b/app/assets/images/social_icons/facebook.svg new file mode 100644 index 000000000..80241473e --- /dev/null +++ b/app/assets/images/social_icons/facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/images/social_icons/linkedin.svg b/app/assets/images/social_icons/linkedin.svg new file mode 100644 index 000000000..908c86751 --- /dev/null +++ b/app/assets/images/social_icons/linkedin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/images/social_icons/mastodon.svg b/app/assets/images/social_icons/mastodon.svg new file mode 100644 index 000000000..a8b4bc44e --- /dev/null +++ b/app/assets/images/social_icons/mastodon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/assets/images/social_icons/telegram.svg b/app/assets/images/social_icons/telegram.svg new file mode 100644 index 000000000..84c087929 --- /dev/null +++ b/app/assets/images/social_icons/telegram.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/assets/images/social_icons/x.svg b/app/assets/images/social_icons/x.svg new file mode 100644 index 000000000..937dcc732 --- /dev/null +++ b/app/assets/images/social_icons/x.svg @@ -0,0 +1 @@ + diff --git a/app/assets/javascripts/social_share_button.js b/app/assets/javascripts/social_share_button.js new file mode 100644 index 000000000..409b2c58a --- /dev/null +++ b/app/assets/javascripts/social_share_button.js @@ -0,0 +1,21 @@ +function openShareUrl(url, initialWidth = 640, initialHeight = 480) { + const width = Math.max(100, Math.min(screen.width, initialWidth)); + const height = Math.max(100, Math.min(screen.height, initialHeight)); + + const left = (screen.width / 2) - (width / 2); + const top = (screen.height * 0.3) - (height / 2); + const opts = `width=${width},height=${height},left=${left},top=${top},menubar=no,status=no,location=no`; + + window.open(url, "popup", opts); +} + +$(document).ready(function () { + $(".ssb-icon").on("click", function (e) { + const shareUrl = $(this).attr("href"); + if (!shareUrl.startsWith("mailto:")) { + e.preventDefault(); + openShareUrl(shareUrl); + } + }); +}); + diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index c47a3abfb..c40e776b4 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -44,5 +44,7 @@ class NotesController < ApplicationController render :template => "browse/not_found", :status => :not_found end - def new; end + def new + render :action => :new_readonly if api_status != "online" + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fcf253289..5558e69fb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,6 @@ module ApplicationHelper require "rexml/document" + include SocialShareButtonHelper def linkify(text) if text.html_safe? @@ -75,4 +76,32 @@ module ApplicationHelper rescue StandardError flash.inspect if Rails.env.development? end + + # Generates a set of social share buttons based on the specified options. + def render_social_share_buttons(opts = {}) + sites = opts.fetch(:allow_sites, []) + valid_sites, invalid_sites = SocialShareButtonHelper.filter_allowed_sites(sites) + + # Log invalid sites + invalid_sites.each do |invalid_site| + Rails.logger.error("Invalid site or icon not configured: #{invalid_site}") + end + + tag.div( + :class => "social-share-button d-flex gap-1 align-items-end flex-wrap mb-3" + ) do + valid_sites.map do |site| + link_options = { + :rel => ["nofollow", opts[:rel]].compact, + :class => "ssb-icon rounded-circle", + :title => I18n.t("application.share.#{site}.title"), + :target => "_blank" + } + + link_to SocialShareButtonHelper.generate_share_url(site, opts), link_options do + image_tag(SocialShareButtonHelper.icon_path(site), :alt => I18n.t("application.share.#{site}.alt"), :size => 28) + end + end.join.html_safe + end + end end diff --git a/app/views/diary_entries/_diary_entry.html.erb b/app/views/diary_entries/_diary_entry.html.erb index 62e701d14..2d8243d1b 100644 --- a/app/views/diary_entries/_diary_entry.html.erb +++ b/app/views/diary_entries/_diary_entry.html.erb @@ -44,4 +44,5 @@ <% end %> + diff --git a/app/views/diary_entries/show.html.erb b/app/views/diary_entries/show.html.erb index e80a44f09..14b1576f0 100644 --- a/app/views/diary_entries/show.html.erb +++ b/app/views/diary_entries/show.html.erb @@ -1,3 +1,7 @@ +<% content_for :head do %> + <%= javascript_include_tag "social_share_button" %> +<% end %> + <% content_for :heading do %>
@@ -11,6 +15,10 @@ <% end %> <%= render @entry %> +<%= render_social_share_buttons({ + :title => @entry.title, + :url => diary_entry_url(@entry.user, @entry) + }) %>
diff --git a/app/views/notes/new_readonly.html.erb b/app/views/notes/new_readonly.html.erb new file mode 100644 index 000000000..033530c71 --- /dev/null +++ b/app/views/notes/new_readonly.html.erb @@ -0,0 +1,7 @@ +<% set_title(t(".title")) %> + +<%= render "sidebar_header", :title => t(".title") %> + +
+

<%= t(".warning") %>

+
diff --git a/config/locales/en.yml b/config/locales/en.yml index 94fc77247..d24987aef 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1001,6 +1001,14 @@ en: wayside_shrine: "Wayside Shrine" wreck: "Wreck" "yes": "Historic Site" + information: + guidepost: "Guidepost" + board: "Information Board" + map: "Map" + office: "Tourist Office" + terminal: "Information Terminal" + sign: "Information Sign" + stele: "Information Stele" junction: "yes": "Junction" landuse: @@ -1073,6 +1081,8 @@ en: track: "Running Track" water_park: "Water Park" "yes": "Leisure" + lock: + "yes": "Lock" man_made: adit: "Adit" advertising: "Advertising" @@ -1413,6 +1423,17 @@ en: building_passage: "Building Passage" culvert: "Culvert" "yes": "Tunnel" + water: + lake: "Lake" + pond: "Pond" + reservoir: "Reservoir" + basin: "Water basin" + fishpond: "Fish Pond" + lagoon: "Lagoon" + wastewater: "Waste Water" + oxbow: "Oxbow" + stream_pool: "Stream Pool" + lock: "Lock" waterway: artificial: "Artificial Waterway" boatyard: "Boatyard" @@ -2607,6 +2628,25 @@ en: wikipedia: title: Log in with Wikipedia alt: Wikipedia logo + share: + email: + title: Share via Email + alt: Email icon + facebook: + title: Share via Facebook + alt: Facebook Icon + linkedin: + title: Share via LinkedIn + alt: LinkedIn Icon + mastodon: + title: Share on Mastodon + alt: Mastodon Icon + telegram: + title: Share on Telegram + alt: Telegram Icon + x: + title: Share on X + alt: X Icon oauth: permissions: missing: "You have not permitted the application access to this facility" @@ -3032,6 +3072,9 @@ en: anonymous_warning_sign_up: "sign up" advice: "Your note is public and may be used to update the map, so don't enter personal information, or information from copyrighted maps or directory listings." add: Add Note + new_readonly: + title: "New Note" + warning: "New notes cannot be created because the OpenStreetMap API is currently in read-only mode." notes_paging_nav: showing_page: "Page %{page}" next: "Next" diff --git a/lib/social_share_button_helper.rb b/lib/social_share_button_helper.rb new file mode 100644 index 000000000..fe47c6ca1 --- /dev/null +++ b/lib/social_share_button_helper.rb @@ -0,0 +1,51 @@ +module SocialShareButtonHelper + require "uri" + + SOCIAL_SHARE_CONFIG = { + :email => "social_icons/email.svg", + :facebook => "social_icons/facebook.svg", + :linkedin => "social_icons/linkedin.svg", + :mastodon => "social_icons/mastodon.svg", + :telegram => "social_icons/telegram.svg", + :x => "social_icons/x.svg" + }.freeze + + def self.filter_allowed_sites(sites) + valid_sites = sites.empty? ? SOCIAL_SHARE_CONFIG.keys : sites.select { |site| valid_site?(site) } + invalid_sites = sites - valid_sites + [valid_sites, invalid_sites] + end + + def self.icon_path(site) + SOCIAL_SHARE_CONFIG[site.to_sym] || "" + end + + def self.valid_site?(site) + SOCIAL_SHARE_CONFIG.key?(site.to_sym) + end + + def self.generate_share_url(site, params) + site = site.to_sym + case site + when :email + to = params[:to] || "" + subject = CGI.escape(params[:title]) + body = CGI.escape(params[:url]) + "mailto:#{to}?subject=#{subject}&body=#{body}" + when :x + via_str = params[:via] ? "&via=#{URI.encode_www_form_component(params[:via])}" : "" + hashtags_str = params[:hashtags] ? "&hashtags=#{URI.encode_www_form_component(params[:hashtags].join(','))}" : "" + "https://x.com/intent/tweet?url=#{URI.encode_www_form_component(params[:url])}&text=#{URI.encode_www_form_component(params[:title])}#{hashtags_str}#{via_str}" + when :linkedin + "https://www.linkedin.com/sharing/share-offsite/?url=#{URI.encode_www_form_component(params[:url])}" + when :facebook + "https://www.facebook.com/sharer/sharer.php?u=#{URI.encode_www_form_component('params[:url]')}&t=#{URI.encode_www_form_component(params[:title])}" + when :mastodon + "https://mastodonshare.com/?text=#{URI.encode_www_form_component(params[:title])}&url=#{URI.encode_www_form_component(params[:url])}" + when :telegram + "https://t.me/share/url?url=#{URI.encode_www_form_component(params[:url])}&text=#{URI.encode_www_form_component(params[:title])}" + else + raise ArgumentError, "Unsupported platform: #{platform}" + end + end +end diff --git a/test/helpers/social_share_button_helper_test.rb b/test/helpers/social_share_button_helper_test.rb new file mode 100644 index 000000000..9c7569334 --- /dev/null +++ b/test/helpers/social_share_button_helper_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class SocialShareButtonHelperTest < ActionView::TestCase + include SocialShareButtonHelper + include ApplicationHelper + + def setup + @options = { + :allow_sites => %w[x facebook linkedin], + :title => "Test Title", + :url => "https://example.com", + :desc => "Test Description", + :via => "testuser" + } + end + + def test_render_social_share_buttons_with_valid_sites + result = render_social_share_buttons(@options) + assert_includes result, "x" + assert_includes result, "facebook" + assert_includes result, "linkedin" + end + + def test_render_social_share_buttons_with_invalid_site + @options[:allow_sites] << "invalid_site" + result = render_social_share_buttons(@options) + assert_not_includes result, "invalid_site" + end + + def test_render_social_share_buttons_with_no_sites + @options[:allow_sites] = [] + result = render_social_share_buttons(@options) + SocialShareButtonHelper::SOCIAL_SHARE_CONFIG.each_key do |site| + assert_includes result, site.to_s # Convert symbol to string + end + end + + def test_filter_allowed_sites + valid_sites, invalid_sites = SocialShareButtonHelper.filter_allowed_sites(%w[x facebook invalid_site]) + assert_equal %w[x facebook], valid_sites + assert_equal %w[invalid_site], invalid_sites + end + + def test_icon_path + assert_equal "social_icons/x.svg", SocialShareButtonHelper.icon_path("x") + assert_equal "", SocialShareButtonHelper.icon_path("invalid_site") + end +end diff --git a/test/system/create_note_test.rb b/test/system/create_note_test.rb new file mode 100644 index 000000000..ccb2ed32f --- /dev/null +++ b/test/system/create_note_test.rb @@ -0,0 +1,23 @@ +require "application_system_test_case" + +class CreateNoteTest < ApplicationSystemTestCase + test "can create note" do + visit new_note_path(:anchor => "map=18/0/0") + + assert_button "Add Note", :disabled => true + + fill_in "text", :with => "Some newly added note description" + click_on "Add Note" + + assert_content "Unresolved note ##{Note.last.id}" + assert_content "Some newly added note description" + end + + test "cannot create note when api is readonly" do + with_settings(:status => "api_readonly") do + visit new_note_path(:anchor => "map=18/0/0") + + assert_no_button "Add Note", :disabled => true + end + end +end diff --git a/yarn.lock b/yarn.lock index 45a3058f7..28ca8971f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,10 +48,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.16.0": - version "9.16.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.16.0.tgz#3df2b2dd3b9163056616886c86e4082f45dbf3f4" - integrity sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg== +"@eslint/js@9.17.0": + version "9.17.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.17.0.tgz#1523e586791f80376a6f8398a3964455ecc651ec" + integrity sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w== "@eslint/object-schema@^2.1.4": version "2.1.4" @@ -189,10 +189,10 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -cross-spawn@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82" - integrity sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug== +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -265,16 +265,16 @@ eslint-visitor-keys@^4.2.0: integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== eslint@^9.0.0: - version "9.16.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.16.0.tgz#66832e66258922ac0a626f803a9273e37747f2a6" - integrity sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA== + version "9.17.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.17.0.tgz#faa1facb5dd042172fdc520106984b5c2421bb0c" + integrity sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" "@eslint/config-array" "^0.19.0" "@eslint/core" "^0.9.0" "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.16.0" + "@eslint/js" "9.17.0" "@eslint/plugin-kit" "^0.2.3" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" @@ -283,7 +283,7 @@ eslint@^9.0.0: "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" - cross-spawn "^7.0.5" + cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" eslint-scope "^8.2.0" @@ -583,9 +583,9 @@ optionator@^0.9.3: type-check "^0.4.0" osm-community-index@^5.2.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/osm-community-index/-/osm-community-index-5.8.0.tgz#59fbf71ab9f359b307fcd0415fbce6f43dda94f6" - integrity sha512-7hg+D7u2vooujuihGTgKWDYjPUidlWuTjBrMP/drsHP/cVJI94ehsDulNkcKH0Od05n6vBl/ZyJJjdJvqcrMaA== + version "5.9.0" + resolved "https://registry.yarnpkg.com/osm-community-index/-/osm-community-index-5.9.0.tgz#cf70a560baca5ec029cd16eecab8c2aa84d6bbc2" + integrity sha512-/P09ig/iCtD17AvmihBcvz6617c1NCJkqNNk3G6veY4MBWH6Djhl+xz69FLOGy8Xt4jHV57zP1PU6UATqby5aQ== dependencies: diacritics "^1.3.0"