From 86dd9bfd6beb490a7680fcbee923fcfa0a52639b Mon Sep 17 00:00:00 2001 From: nertc Date: Thu, 22 May 2025 12:11:19 +0400 Subject: [PATCH] Add social profile links --- .rubocop_todo.yml | 2 +- .../images/social_link_icons/bluesky.svg | 4 + .../images/social_link_icons/discord.svg | 3 + .../images/social_link_icons/facebook.svg | 3 + .../images/social_link_icons/flickr.svg | 4 + .../images/social_link_icons/github.svg | 3 + .../images/social_link_icons/gitlab.svg | 3 + .../images/social_link_icons/instagram.svg | 3 + app/assets/images/social_link_icons/line.svg | 3 + .../images/social_link_icons/linkedin.svg | 3 + .../images/social_link_icons/mastodon.svg | 3 + .../images/social_link_icons/medium.svg | 3 + app/assets/images/social_link_icons/ogf.svg | 20 + app/assets/images/social_link_icons/ohm.svg | 289 ++ .../images/social_link_icons/osm_forum.svg | 3380 +++++++++++++++++ .../images/social_link_icons/osm_wiki.svg | 352 ++ app/assets/images/social_link_icons/other.svg | 3 + app/assets/images/social_link_icons/quora.svg | 3 + .../images/social_link_icons/reddit.svg | 4 + app/assets/images/social_link_icons/skype.svg | 3 + app/assets/images/social_link_icons/slack.svg | 3 + .../images/social_link_icons/snapchat.svg | 3 + .../social_link_icons/stackoverflow.svg | 4 + .../images/social_link_icons/strava.svg | 3 + .../images/social_link_icons/substack.svg | 3 + .../images/social_link_icons/telegram.svg | 3 + .../images/social_link_icons/threads.svg | 3 + .../images/social_link_icons/tiktok.svg | 3 + .../images/social_link_icons/twitch.svg | 4 + .../images/social_link_icons/twitter_x.svg | 3 + app/assets/images/social_link_icons/vimeo.svg | 3 + .../images/social_link_icons/whatsapp.svg | 3 + .../images/social_link_icons/wikidata.svg | 6 + .../images/social_link_icons/wikimedia.svg | 18 + .../images/social_link_icons/wikipedia.svg | 3 + .../images/social_link_icons/wikivoyage.svg | 11 + .../images/social_link_icons/youtube.svg | 3 + app/assets/javascripts/user.js | 29 + app/controllers/profiles_controller.rb | 3 + app/models/social_link.rb | 90 + app/models/user.rb | 3 + app/views/profiles/edit.html.erb | 16 + app/views/social_links/_show.html.erb | 14 + app/views/users/show.html.erb | 15 +- config/locales/en.yml | 13 + .../20250217140049_create_social_links.rb | 10 + db/structure.sql | 63 + test/controllers/profiles_controller_test.rb | 9 + test/factories/social_link.rb | 6 + test/models/social_link_test.rb | 72 + 50 files changed, 4510 insertions(+), 3 deletions(-) create mode 100644 app/assets/images/social_link_icons/bluesky.svg create mode 100644 app/assets/images/social_link_icons/discord.svg create mode 100644 app/assets/images/social_link_icons/facebook.svg create mode 100644 app/assets/images/social_link_icons/flickr.svg create mode 100644 app/assets/images/social_link_icons/github.svg create mode 100644 app/assets/images/social_link_icons/gitlab.svg create mode 100644 app/assets/images/social_link_icons/instagram.svg create mode 100644 app/assets/images/social_link_icons/line.svg create mode 100644 app/assets/images/social_link_icons/linkedin.svg create mode 100644 app/assets/images/social_link_icons/mastodon.svg create mode 100644 app/assets/images/social_link_icons/medium.svg create mode 100644 app/assets/images/social_link_icons/ogf.svg create mode 100644 app/assets/images/social_link_icons/ohm.svg create mode 100644 app/assets/images/social_link_icons/osm_forum.svg create mode 100644 app/assets/images/social_link_icons/osm_wiki.svg create mode 100644 app/assets/images/social_link_icons/other.svg create mode 100644 app/assets/images/social_link_icons/quora.svg create mode 100644 app/assets/images/social_link_icons/reddit.svg create mode 100644 app/assets/images/social_link_icons/skype.svg create mode 100644 app/assets/images/social_link_icons/slack.svg create mode 100644 app/assets/images/social_link_icons/snapchat.svg create mode 100644 app/assets/images/social_link_icons/stackoverflow.svg create mode 100644 app/assets/images/social_link_icons/strava.svg create mode 100644 app/assets/images/social_link_icons/substack.svg create mode 100644 app/assets/images/social_link_icons/telegram.svg create mode 100644 app/assets/images/social_link_icons/threads.svg create mode 100644 app/assets/images/social_link_icons/tiktok.svg create mode 100644 app/assets/images/social_link_icons/twitch.svg create mode 100644 app/assets/images/social_link_icons/twitter_x.svg create mode 100644 app/assets/images/social_link_icons/vimeo.svg create mode 100644 app/assets/images/social_link_icons/whatsapp.svg create mode 100644 app/assets/images/social_link_icons/wikidata.svg create mode 100644 app/assets/images/social_link_icons/wikimedia.svg create mode 100644 app/assets/images/social_link_icons/wikipedia.svg create mode 100644 app/assets/images/social_link_icons/wikivoyage.svg create mode 100644 app/assets/images/social_link_icons/youtube.svg create mode 100644 app/models/social_link.rb create mode 100644 app/views/social_links/_show.html.erb create mode 100644 db/migrate/20250217140049_create_social_links.rb create mode 100644 test/factories/social_link.rb create mode 100644 test/models/social_link_test.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4b830aaa8..f1aa874c3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -66,7 +66,7 @@ Metrics/BlockNesting: # Offense count: 26 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 330 + Max: 332 # Offense count: 58 # Configuration parameters: AllowedMethods, AllowedPatterns. diff --git a/app/assets/images/social_link_icons/bluesky.svg b/app/assets/images/social_link_icons/bluesky.svg new file mode 100644 index 000000000..0cbe91f4d --- /dev/null +++ b/app/assets/images/social_link_icons/bluesky.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/discord.svg b/app/assets/images/social_link_icons/discord.svg new file mode 100644 index 000000000..5f5bdd6aa --- /dev/null +++ b/app/assets/images/social_link_icons/discord.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/facebook.svg b/app/assets/images/social_link_icons/facebook.svg new file mode 100644 index 000000000..e9e796354 --- /dev/null +++ b/app/assets/images/social_link_icons/facebook.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/flickr.svg b/app/assets/images/social_link_icons/flickr.svg new file mode 100644 index 000000000..54b080805 --- /dev/null +++ b/app/assets/images/social_link_icons/flickr.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/github.svg b/app/assets/images/social_link_icons/github.svg new file mode 100644 index 000000000..51b1a8001 --- /dev/null +++ b/app/assets/images/social_link_icons/github.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/gitlab.svg b/app/assets/images/social_link_icons/gitlab.svg new file mode 100644 index 000000000..760ea7c6c --- /dev/null +++ b/app/assets/images/social_link_icons/gitlab.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/instagram.svg b/app/assets/images/social_link_icons/instagram.svg new file mode 100644 index 000000000..2724fbd5b --- /dev/null +++ b/app/assets/images/social_link_icons/instagram.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/line.svg b/app/assets/images/social_link_icons/line.svg new file mode 100644 index 000000000..3808c88e2 --- /dev/null +++ b/app/assets/images/social_link_icons/line.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/linkedin.svg b/app/assets/images/social_link_icons/linkedin.svg new file mode 100644 index 000000000..59f2e4533 --- /dev/null +++ b/app/assets/images/social_link_icons/linkedin.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/mastodon.svg b/app/assets/images/social_link_icons/mastodon.svg new file mode 100644 index 000000000..44a2730f3 --- /dev/null +++ b/app/assets/images/social_link_icons/mastodon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/medium.svg b/app/assets/images/social_link_icons/medium.svg new file mode 100644 index 000000000..b0c359ec8 --- /dev/null +++ b/app/assets/images/social_link_icons/medium.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/ogf.svg b/app/assets/images/social_link_icons/ogf.svg new file mode 100644 index 000000000..88b26eb02 --- /dev/null +++ b/app/assets/images/social_link_icons/ogf.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/social_link_icons/ohm.svg b/app/assets/images/social_link_icons/ohm.svg new file mode 100644 index 000000000..b05ab593c --- /dev/null +++ b/app/assets/images/social_link_icons/ohm.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/social_link_icons/osm_forum.svg b/app/assets/images/social_link_icons/osm_forum.svg new file mode 100644 index 000000000..c6e7485b9 --- /dev/null +++ b/app/assets/images/social_link_icons/osm_forum.svg @@ -0,0 +1,3380 @@ + + + OpenStreetMap logo 2011 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + OpenStreetMap logo 2011 + + + Ken Vermette + + + + April 2011 + + + OpenStreetMap.org + + + Replacement logo for OpenStreetMap Foundation + + + OSM openstreetmap logo + + + http://wiki.openstreetmap.org/wiki/File:Public-images-osm_logo.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 010110010011010110010011 + 010110010011010110010011 + + + diff --git a/app/assets/images/social_link_icons/osm_wiki.svg b/app/assets/images/social_link_icons/osm_wiki.svg new file mode 100644 index 000000000..29fb9d186 --- /dev/null +++ b/app/assets/images/social_link_icons/osm_wiki.svg @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + OpenStreetMap Wiki logo 2019 + + + Ken Vermette; addition of Wiki by Bert Araali; SVG conversion of Wiki by Chris2map + + + + June 2023 + + + OpenStreetMap.org + + + Logo for OpenStreetMap Wiki, based on OpenStreetMap logo 2011 + + + OSM openstreetmap wiki logo + + + http://wiki.openstreetmap.org/wiki/File:Public-images-osm_logo.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/social_link_icons/other.svg b/app/assets/images/social_link_icons/other.svg new file mode 100644 index 000000000..b78b4c73d --- /dev/null +++ b/app/assets/images/social_link_icons/other.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/quora.svg b/app/assets/images/social_link_icons/quora.svg new file mode 100644 index 000000000..e18e02e3f --- /dev/null +++ b/app/assets/images/social_link_icons/quora.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/reddit.svg b/app/assets/images/social_link_icons/reddit.svg new file mode 100644 index 000000000..dba2cb3f4 --- /dev/null +++ b/app/assets/images/social_link_icons/reddit.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/skype.svg b/app/assets/images/social_link_icons/skype.svg new file mode 100644 index 000000000..ff4833d9d --- /dev/null +++ b/app/assets/images/social_link_icons/skype.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/slack.svg b/app/assets/images/social_link_icons/slack.svg new file mode 100644 index 000000000..469831447 --- /dev/null +++ b/app/assets/images/social_link_icons/slack.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/snapchat.svg b/app/assets/images/social_link_icons/snapchat.svg new file mode 100644 index 000000000..a19a51993 --- /dev/null +++ b/app/assets/images/social_link_icons/snapchat.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/stackoverflow.svg b/app/assets/images/social_link_icons/stackoverflow.svg new file mode 100644 index 000000000..24aaca0ac --- /dev/null +++ b/app/assets/images/social_link_icons/stackoverflow.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/strava.svg b/app/assets/images/social_link_icons/strava.svg new file mode 100644 index 000000000..8e8625c16 --- /dev/null +++ b/app/assets/images/social_link_icons/strava.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/substack.svg b/app/assets/images/social_link_icons/substack.svg new file mode 100644 index 000000000..d0d6d6d3a --- /dev/null +++ b/app/assets/images/social_link_icons/substack.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/telegram.svg b/app/assets/images/social_link_icons/telegram.svg new file mode 100644 index 000000000..1b77dce2a --- /dev/null +++ b/app/assets/images/social_link_icons/telegram.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/threads.svg b/app/assets/images/social_link_icons/threads.svg new file mode 100644 index 000000000..99ec96289 --- /dev/null +++ b/app/assets/images/social_link_icons/threads.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/tiktok.svg b/app/assets/images/social_link_icons/tiktok.svg new file mode 100644 index 000000000..18f99ae1e --- /dev/null +++ b/app/assets/images/social_link_icons/tiktok.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/twitch.svg b/app/assets/images/social_link_icons/twitch.svg new file mode 100644 index 000000000..8e3ac6fbf --- /dev/null +++ b/app/assets/images/social_link_icons/twitch.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/twitter_x.svg b/app/assets/images/social_link_icons/twitter_x.svg new file mode 100644 index 000000000..d51006674 --- /dev/null +++ b/app/assets/images/social_link_icons/twitter_x.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/vimeo.svg b/app/assets/images/social_link_icons/vimeo.svg new file mode 100644 index 000000000..13c9fcf58 --- /dev/null +++ b/app/assets/images/social_link_icons/vimeo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/whatsapp.svg b/app/assets/images/social_link_icons/whatsapp.svg new file mode 100644 index 000000000..e24000ea4 --- /dev/null +++ b/app/assets/images/social_link_icons/whatsapp.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/wikidata.svg b/app/assets/images/social_link_icons/wikidata.svg new file mode 100644 index 000000000..546a06ae1 --- /dev/null +++ b/app/assets/images/social_link_icons/wikidata.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/wikimedia.svg b/app/assets/images/social_link_icons/wikimedia.svg new file mode 100644 index 000000000..6a06c2cd4 --- /dev/null +++ b/app/assets/images/social_link_icons/wikimedia.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/wikipedia.svg b/app/assets/images/social_link_icons/wikipedia.svg new file mode 100644 index 000000000..bbfc4659d --- /dev/null +++ b/app/assets/images/social_link_icons/wikipedia.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/social_link_icons/wikivoyage.svg b/app/assets/images/social_link_icons/wikivoyage.svg new file mode 100644 index 000000000..f9367b03c --- /dev/null +++ b/app/assets/images/social_link_icons/wikivoyage.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/assets/images/social_link_icons/youtube.svg b/app/assets/images/social_link_icons/youtube.svg new file mode 100644 index 000000000..38b2704b0 --- /dev/null +++ b/app/assets/images/social_link_icons/youtube.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index 7d2dc23d0..16fc5ff85 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -11,6 +11,35 @@ $(function () { const defaultHomeZoom = 12; let map, marker, deleted_lat, deleted_lon, deleted_home_name, homeLocationNameGeocoder, savedLat, savedLon; + if ($("#social_links").length) { + $("#add-social-link").click(function (event) { + event.preventDefault(); + const newIndex = -Date.now(); + const socialLinkForm = $(` + + `); + + socialLinkForm.find("button").click(function () { + $(this).parent().remove(); + }); + + $("#social_links").append(socialLinkForm); + }); + + $(".social_link_destroy input[type='checkbox']").change(function () { + $(this).parent().parent().addClass("d-none"); + }); + + $(".social_link_destroy input[type='checkbox']:checked").each(function () { + $(this).parent().parent().addClass("d-none"); + }); + } + if ($("#map").length) { map = L.map("map", { attributionControl: false, diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 9d050bd75..89e154e6b 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -12,6 +12,9 @@ class ProfilesController < ApplicationController def edit; end def update + social_links_params = params.permit(:user => [:social_links_attributes => [:id, :url, :_destroy]]) + current_user.assign_attributes(social_links_params[:user]) + if params[:user][:description] != current_user.description current_user.description = params[:user][:description] current_user.description_format = "markdown" diff --git a/app/models/social_link.rb b/app/models/social_link.rb new file mode 100644 index 000000000..b109d5cd3 --- /dev/null +++ b/app/models/social_link.rb @@ -0,0 +1,90 @@ +# == Schema Information +# +# Table name: social_links +# +# id :bigint not null, primary key +# user_id :bigint not null +# url :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_social_links_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# + +class SocialLink < ApplicationRecord + belongs_to :user + + validates :url, :format => { :with => %r{\A(https?://.+|@([a-zA-Z0-9_]+)@([\w\-\.]+))\z}, :message => :http_parse_error } + + URL_PATTERNS = { + :bluesky => %r{\Ahttps?://(?:www\.)?bsky\.app/profile/([a-zA-Z0-9\._-]+)}, + :discord => %r{\Ahttps?://(?:www\.)?discord\.com/users/(\d+)}, + :facebook => %r{\Ahttps?://(?:www\.)?facebook\.com/([a-zA-Z0-9.]+)}, + :flickr => %r{\Ahttps?://(?:www\.)?flickr\.com/people/([a-zA-Z0-9@._-]+)}, + :github => %r{\Ahttps?://(?:www\.)?github\.com/([a-zA-Z0-9_-]+)}, + :gitlab => %r{\Ahttps?://(?:www\.)?gitlab\.com/([a-zA-Z0-9_-]+)}, + :instagram => %r{\Ahttps?://(?:www\.)?instagram\.com/([a-zA-Z0-9._]+)}, + :linkedin => %r{\Ahttps?://(?:www\.)?linkedin\.com/in/([a-zA-Z0-9_-]+)}, + :line => %r{\Ahttps?://(?:www\.)?line\.me/ti/p/([a-zA-Z0-9_-]+)}, + :mastodon => %r{\Ahttps?://(?:(?:www\.)?(mastodon\.social|en\.osm\.town))/(@[a-zA-Z0-9_]+)}, + :mastodon_general => /\A@([a-zA-Z0-9_]+)@([\w\-\.]+)/, + :medium => %r{\Ahttps?://(?:www\.)?medium\.com/@([a-zA-Z0-9_]+)}, + :ogf => %r{\Ahttps?://wiki\.opengeofiction\.net/index\.php/User:([a-zA-Z0-9_-]+)}, + :ohm => %r{\Ahttps?://(?:www\.)?openhistoricalmap\.org/user/(\S+)}, + :osm_forum => %r{\Ahttps?://community\.openstreetmap\.org/u/(\S+)}, + :osm_wiki => %r{\Ahttps?://wiki\.openstreetmap\.org/wiki/User:([a-zA-Z0-9_-]+)}, + :quora => %r{\Ahttps?://(?:www\.)?quora\.com/profile/([a-zA-Z0-9_-]+)}, + :reddit => %r{\Ahttps?://(?:www\.)?reddit\.com/user/([a-zA-Z0-9_-]+)}, + :skype => %r{\Ahttps?://join\.skype\.com/invite/([a-zA-Z0-9_-]+)}, + :slack => %r{\Ahttps?://join\.slack\.com/shareDM/([a-zA-Z0-9_~-]+)}, + :snapchat => %r{\Ahttps?://(?:www\.)?snapchat\.com/add/([a-zA-Z0-9_-]+)}, + :stackoverflow => %r{\Ahttps?://(?:www\.)?stackoverflow\.com/users/\d+/([a-zA-Z0-9_-]+)}, + :strava => %r{\Ahttps?://(?:www\.)?strava\.com/athletes/([a-zA-Z0-9_-]+)}, + :substack => %r{\Ahttps?://(?:www\.)?substack\.com/@([a-zA-Z0-9_-]+)}, + :telegram => %r{\Ahttps?://(?:www\.)?t\.me/([a-zA-Z0-9_]+)}, + :threads => %r{\Ahttps?://(?:www\.)?threads\.net/@([a-zA-Z0-9_]+)}, + :tiktok => %r{\Ahttps?://(?:www\.)?tiktok\.com/@([a-zA-Z0-9_]+)}, + :twitch => %r{\Ahttps?://(?:www\.)?twitch\.tv/([a-zA-Z0-9_]+)}, + :twitter_x => %r{\Ahttps?://(?:www\.)?(?:twitter|x)\.com/([a-zA-Z0-9_]+)}, + :vimeo => %r{\Ahttps?://(?:www\.)?vimeo\.com/([a-zA-Z0-9_]+)}, + :whatsapp => %r{\Ahttps?://wa\.me/(\d+)}, + :wikidata => %r{\Ahttps?://(?:www\.)?wikidata\.org/wiki/User:([a-zA-Z0-9_-]+)}, + :wikimedia => %r{\Ahttps?://commons\.wikimedia\.org/wiki/User:([a-zA-Z0-9_-]+)}, + :wikipedia => %r{\Ahttps?://(?:[a-zA-Z]+\.)?wikipedia\.org/wiki/User:([a-zA-Z0-9_-]+)}, + :wikivoyage => %r{\Ahttps?://(?:[a-zA-Z]+\.)?wikivoyage\.org/wiki/User:([a-zA-Z0-9_-]+)}, + :youtube => %r{\Ahttps?://(?:www\.)?youtube\.com/@([a-zA-Z0-9_-]+)} + }.freeze + + NO_USERNAME_PLATFORMS = %w[discord line skype slack].freeze + + def parsed + URL_PATTERNS.each do |platform, pattern| + names = url.match(pattern) + next unless names + + if platform == :mastodon_general + return { + :url => "https://#{names[2]}/@#{names[1]}", + :platform => "mastodon", + :name => "@#{names[1]}@#{names[2]}" + } + end + + name = names[2].nil? ? names[1] : "#{names[2]}@#{names[1]}" + name = platform.to_s.capitalize if NO_USERNAME_PLATFORMS.include?(platform.to_s) + + return { + :url => url, + :platform => platform.to_s, + :name => name + } + end + { :url => url, :platform => nil, :name => url.gsub(%r{https?://}, "") } + end +end diff --git a/app/models/user.rb b/app/models/user.rb index eb5345098..5e5478fff 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -88,6 +88,9 @@ class User < ApplicationRecord has_many :reports + has_many :social_links + accepts_nested_attributes_for :social_links, :allow_destroy => true + scope :visible, -> { where(:status => %w[pending active confirmed]) } scope :active, -> { where(:status => %w[active confirmed]) } scope :identifiable, -> { where(:data_public => true) } diff --git a/app/views/profiles/edit.html.erb b/app/views/profiles/edit.html.erb index a9c46f1ed..d09242024 100644 --- a/app/views/profiles/edit.html.erb +++ b/app/views/profiles/edit.html.erb @@ -11,6 +11,22 @@ <%= bootstrap_form_for current_user, :url => { :action => :update }, :html => { :multipart => true, :autocomplete => :off } do |f| %> <%= f.richtext_field :description, :cols => 80, :rows => 20 %> +
+ + <%= f.label t(".social_links.title"), :class => "form-label" %> + + + +
+
<%= f.label t(".image") %>
diff --git a/app/views/social_links/_show.html.erb b/app/views/social_links/_show.html.erb new file mode 100644 index 000000000..8d2572a7d --- /dev/null +++ b/app/views/social_links/_show.html.erb @@ -0,0 +1,14 @@ +
    + <% social_links.each do |social_link| %> +
  • + <%= link_to social_link.parsed[:url], :class => "icon-link mw-100 text-body-secondary mb-3", :rel => "nofollow me" do %> + <%= image_tag "social_link_icons/#{social_link.parsed[:platform].nil? ? 'other' : social_link.parsed[:platform]}.svg", + :alt => social_link.parsed[:platform].nil? ? t(".other") : social_link.parsed[:platform], + :size => "16" %> + + <%= social_link.parsed[:name] %> + + <% end %> +
  • + <% end %> +
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 9a04ce50e..c239c48a9 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -243,8 +243,19 @@
<% end %> - -
<%= @user.description.to_html %>
+<% if @user.social_links.empty? %> +
<%= @user.description.to_html %>
+<% else %> +
+
+ <%= render "social_links/show", + :social_links => @user.social_links %> +
+
+
<%= @user.description.to_html %>
+
+
+<% end %> <% if @heatmap_data[:count].positive? %>

<%= t("users.show.contributions", :count => @heatmap_data[:count]) %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index f6f4aa41c..50a8fbe1b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -45,6 +45,10 @@ en: subject: format: "%{message}" is_already_muted: "is already muted" + social_link: + attributes: + url: + http_parse_error: Url should start with http:// or https:// # Translates all the model names, which is used in error handling on the website models: acl: "Access Control List" @@ -1982,6 +1986,10 @@ en: show: "Show" delete: "Delete" undelete: "Undo delete" + social_links: + title: Social Profile Links + remove: Remove + add: Add Social Link update: success: Profile updated. failure: Couldn't update profile. @@ -2557,6 +2565,9 @@ en: community group. Anyone can set up or join these. Read more on the %{communities_wiki_link}. communities_wiki: Communities wiki page communities_wiki_url: https://wiki.openstreetmap.org/wiki/User_group + social_links: + show: + other: Other map_keys: show: entries: @@ -3335,6 +3346,8 @@ en: queryfeature_tooltip: Query features queryfeature_disabled_tooltip: Zoom in to query features embed_html_disabled: HTML embedding is not available for this map layer + social_links: + remove: Remove edit_help: Move the map and zoom in on a location you want to edit, then click here. directions: distance_in_units: diff --git a/db/migrate/20250217140049_create_social_links.rb b/db/migrate/20250217140049_create_social_links.rb new file mode 100644 index 000000000..65a527aae --- /dev/null +++ b/db/migrate/20250217140049_create_social_links.rb @@ -0,0 +1,10 @@ +class CreateSocialLinks < ActiveRecord::Migration[7.2] + def change + create_table :social_links do |t| + t.references :user, :null => false, :foreign_key => true + t.string :url, :null => false + + t.timestamps + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 5e5b92dc5..c04b56ca4 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1368,6 +1368,38 @@ CREATE TABLE public.schema_migrations ( ); +-- +-- Name: social_links; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.social_links ( + id bigint NOT NULL, + user_id bigint NOT NULL, + url character varying NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: social_links_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.social_links_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: social_links_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.social_links_id_seq OWNED BY public.social_links.id; + + -- -- Name: user_blocks; Type: TABLE; Schema: public; Owner: - -- @@ -1763,6 +1795,13 @@ ALTER TABLE ONLY public.redactions ALTER COLUMN id SET DEFAULT nextval('public.r ALTER TABLE ONLY public.reports ALTER COLUMN id SET DEFAULT nextval('public.reports_id_seq'::regclass); +-- +-- Name: social_links id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.social_links ALTER COLUMN id SET DEFAULT nextval('public.social_links_id_seq'::regclass); + + -- -- Name: user_blocks id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2127,6 +2166,14 @@ ALTER TABLE ONLY public.schema_migrations ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); +-- +-- Name: social_links social_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.social_links + ADD CONSTRAINT social_links_pkey PRIMARY KEY (id); + + -- -- Name: user_blocks user_blocks_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2653,6 +2700,13 @@ CREATE INDEX index_reports_on_issue_id ON public.reports USING btree (issue_id); CREATE INDEX index_reports_on_user_id ON public.reports USING btree (user_id); +-- +-- Name: index_social_links_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_social_links_on_user_id ON public.social_links USING btree (user_id); + + -- -- Name: index_user_blocks_on_creator_id_and_id; Type: INDEX; Schema: public; Owner: - -- @@ -3048,6 +3102,14 @@ ALTER TABLE ONLY public.user_mutes ADD CONSTRAINT fk_rails_591dad3359 FOREIGN KEY (owner_id) REFERENCES public.users(id); +-- +-- Name: social_links fk_rails_6034fd4f62; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.social_links + ADD CONSTRAINT fk_rails_6034fd4f62 FOREIGN KEY (user_id) REFERENCES public.users(id); + + -- -- Name: oauth_access_tokens fk_rails_732cb83ab7; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -3453,6 +3515,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('21'), ('20250304172798'), ('20250304172758'), +('20250217140049'), ('20250212160355'), ('20250206202905'), ('20250121191749'), diff --git a/test/controllers/profiles_controller_test.rb b/test/controllers/profiles_controller_test.rb index f388349ab..b5ea4b54a 100644 --- a/test/controllers/profiles_controller_test.rb +++ b/test/controllers/profiles_controller_test.rb @@ -59,5 +59,14 @@ class ProfilesControllerTest < ActionDispatch::IntegrationTest get edit_profile_path assert_select "form > fieldset > div > div.col-sm-10 > div > input[name=avatar_action][checked]", false assert_select "form > fieldset > div > div.col-sm-10 > div > div.form-check > input[name=avatar_action][checked]", false + + # Updating social links should work + put profile_path, :params => { :user => { :description => user.description, :social_links_attributes => [{ :url => "https://test.com/test" }] } } + assert_redirected_to user_path(user) + follow_redirect! + assert_response :success + assert_template :show + assert_select ".alert-success", /^Profile updated./ + assert_select "a", "test.com/test" end end diff --git a/test/factories/social_link.rb b/test/factories/social_link.rb new file mode 100644 index 000000000..fb6f7f6e1 --- /dev/null +++ b/test/factories/social_link.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :social_link do + sequence(:url) { |n| "https://test.com/#{n}" } + user + end +end diff --git a/test/models/social_link_test.rb b/test/models/social_link_test.rb new file mode 100644 index 000000000..267d8bc13 --- /dev/null +++ b/test/models/social_link_test.rb @@ -0,0 +1,72 @@ +require "test_helper" + +class SocialLinkTest < ActiveSupport::TestCase + def test_user_required + social_link = create(:social_link) + + assert_predicate social_link, :valid? + social_link.user = nil + assert_not_predicate social_link, :valid? + end + + def test_url_required + social_link = create(:social_link) + + assert_predicate social_link, :valid? + social_link.url = nil + assert_not_predicate social_link, :valid? + end + + def test_url_https_valid + social_link = create(:social_link) + + assert_predicate social_link, :valid? + social_link.url = "test" + assert_not_predicate social_link, :valid? + end + + def test_parsed_platform + social_link = create(:social_link, :url => "https://github.com/test") + + assert_equal "github", social_link.parsed[:platform] + assert_equal "test", social_link.parsed[:name] + end + + def test_parsed_platform_with_www + social_link = create(:social_link, :url => "http://www.github.com/test") + + assert_equal "github", social_link.parsed[:platform] + assert_equal "test", social_link.parsed[:name] + end + + def test_parsed_platform_custom_name + social_link = create(:social_link, :url => "https://discord.com/users/0") + + assert_equal "discord", social_link.parsed[:platform] + assert_equal "Discord", social_link.parsed[:name] + end + + def test_parsed_platform_mastodon + social_link = create(:social_link, :url => "https://mastodon.social/@test") + + assert_equal "mastodon", social_link.parsed[:platform] + assert_equal "@test@mastodon.social", social_link.parsed[:name] + end + + def test_parsed_platform_mastodon_parsed + social_link = create(:social_link, :url => "@test@mapstodon.space") + + assert_equal "https://mapstodon.space/@test", social_link.parsed[:url] + assert_equal "mastodon", social_link.parsed[:platform] + assert_equal "@test@mapstodon.space", social_link.parsed[:name] + end + + def test_parsed_platform_other + url = "https://test.com/test" + expected = "test.com/test" + social_link = create(:social_link, :url => url) + + assert_nil social_link.parsed[:platform] + assert_equal social_link.parsed[:name], expected + end +end -- 2.39.5