diff --git a/Gemfile b/Gemfile index 55170961..c203692d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -gem "rails", "~> 7.0.3.1" +gem "rails", "~> 7.0.4.3" gem "mysql2" diff --git a/Gemfile.lock b/Gemfile.lock index abb43275..6afa8f28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,83 +2,83 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (1.1.0) - actioncable (7.0.3.1) - actionpack (= 7.0.3.1) - activesupport (= 7.0.3.1) + actioncable (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.3.1) - actionpack (= 7.0.3.1) - activejob (= 7.0.3.1) - activerecord (= 7.0.3.1) - activestorage (= 7.0.3.1) - activesupport (= 7.0.3.1) + actionmailbox (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.3.1) - actionpack (= 7.0.3.1) - actionview (= 7.0.3.1) - activejob (= 7.0.3.1) - activesupport (= 7.0.3.1) + actionmailer (7.0.4.3) + actionpack (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.3.1) - actionview (= 7.0.3.1) - activesupport (= 7.0.3.1) + actionpack (7.0.4.3) + actionview (= 7.0.4.3) + activesupport (= 7.0.4.3) rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) actionpack-page_caching (1.2.4) actionpack (>= 4.0.0) - actiontext (7.0.3.1) - actionpack (= 7.0.3.1) - activerecord (= 7.0.3.1) - activestorage (= 7.0.3.1) - activesupport (= 7.0.3.1) + actiontext (7.0.4.3) + actionpack (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.3.1) - activesupport (= 7.0.3.1) + actionview (7.0.4.3) + activesupport (= 7.0.4.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.3.1) - activesupport (= 7.0.3.1) + activejob (7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.3.6) - activemodel (7.0.3.1) - activesupport (= 7.0.3.1) - activerecord (7.0.3.1) - activemodel (= 7.0.3.1) - activesupport (= 7.0.3.1) - activerecord-typedstore (1.4.0) - activerecord (>= 5.2) - activestorage (7.0.3.1) - actionpack (= 7.0.3.1) - activejob (= 7.0.3.1) - activerecord (= 7.0.3.1) - activesupport (= 7.0.3.1) + activemodel (7.0.4.3) + activesupport (= 7.0.4.3) + activerecord (7.0.4.3) + activemodel (= 7.0.4.3) + activesupport (= 7.0.4.3) + activerecord-typedstore (1.5.1) + activerecord (>= 6.1) + activestorage (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activesupport (= 7.0.4.3) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.3.1) + activesupport (7.0.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.3) + public_suffix (>= 2.0.2, < 6.0) afm (0.2.2) ast (2.4.2) bcrypt (3.1.18) builder (3.2.4) byebug (11.1.3) - capybara (3.37.1) + capybara (3.39.0) addressable matrix mini_mime (>= 0.1.3) @@ -88,21 +88,21 @@ GEM regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) chunky_png (1.4.0) - commonmarker (0.23.6) - concurrent-ruby (1.1.10) + commonmarker (0.23.8) + concurrent-ruby (1.2.2) crack (0.4.5) rexml crass (1.0.6) - database_cleaner (2.0.1) - database_cleaner-active_record (~> 2.0.0) - database_cleaner-active_record (2.0.1) + database_cleaner (2.0.2) + database_cleaner-active_record (>= 2, < 3) + database_cleaner-active_record (2.1.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) + date (3.3.3) diff-lcs (1.5.0) - digest (3.1.0) docile (1.4.0) - erubi (1.10.0) + erubi (1.12.0) exception_notification (4.5.0) actionmailer (>= 5.2, < 8) activesupport (>= 5.2, < 8) @@ -112,128 +112,132 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (2.21.0) + faker (3.1.1) i18n (>= 1.8.11, < 2) ffi (1.15.5) flamegraph (0.9.5) - globalid (1.0.0) + globalid (1.1.0) activesupport (>= 5.0) hashdiff (1.0.1) hashery (2.1.2) + hashie (5.0.0) htmlentities (4.3.4) - i18n (1.11.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) jaro_winkler (1.5.4) - jquery-rails (4.4.0) + jquery-rails (4.5.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.2) - listen (3.7.1) + json (2.6.3) + listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.18.0) + loofah (2.20.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.1) + mail (2.8.1) mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp marcel (1.0.2) matrix (0.4.2) - memory_profiler (1.0.0) + memory_profiler (1.0.1) method_source (1.0.0) mini_mime (1.1.2) - mini_portile2 (2.8.0) - minitest (5.16.2) - mysql2 (0.5.4) - net-imap (0.2.3) - digest + mini_portile2 (2.8.1) + minitest (5.18.0) + mysql2 (0.5.5) + net-imap (0.3.4) + date net-protocol - strscan - net-pop (0.1.1) - digest + net-pop (0.1.2) net-protocol + net-protocol (0.2.1) timeout - net-protocol (0.1.3) - timeout - net-smtp (0.3.1) - digest + net-smtp (0.3.3) net-protocol - timeout - nio4r (2.5.8) - nokogiri (1.13.9) + nio4r (2.5.9) + nokogiri (1.14.2) mini_portile2 (~> 2.8.0) racc (~> 1.4) - nokogiri (1.13.9-x86_64-linux) + nokogiri (1.14.2-x86_64-linux) racc (~> 1.4) - oauth (0.5.10) + oauth (1.1.0) + oauth-tty (~> 1.0, >= 1.0.1) + snaky_hash (~> 2.0) + version_gem (~> 1.1) + oauth-tty (1.0.5) + version_gem (~> 1.1, >= 1.1.1) parallel (1.22.1) - parser (3.1.2.0) + parser (3.2.2.0) ast (~> 2.4.1) - pdf-reader (2.10.0) + pdf-reader (2.11.0) Ascii85 (~> 1.0) afm (~> 0.2.1) hashery (~> 2.0) ruby-rc4 ttfunk - public_suffix (4.0.7) - puma (5.6.4) + public_suffix (5.0.1) + puma (6.2.1) nio4r (~> 2.0) - racc (1.6.0) - rack (2.2.4) + racc (1.6.2) + rack (2.2.6.4) rack-attack (6.6.1) rack (>= 1.0, < 3) rack-mini-profiler (3.0.0) rack (>= 1.2.0) - rack-test (2.0.2) + rack-test (2.1.0) rack (>= 1.3) - rails (7.0.3.1) - actioncable (= 7.0.3.1) - actionmailbox (= 7.0.3.1) - actionmailer (= 7.0.3.1) - actionpack (= 7.0.3.1) - actiontext (= 7.0.3.1) - actionview (= 7.0.3.1) - activejob (= 7.0.3.1) - activemodel (= 7.0.3.1) - activerecord (= 7.0.3.1) - activestorage (= 7.0.3.1) - activesupport (= 7.0.3.1) + rails (7.0.4.3) + actioncable (= 7.0.4.3) + actionmailbox (= 7.0.4.3) + actionmailer (= 7.0.4.3) + actionpack (= 7.0.4.3) + actiontext (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activemodel (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) bundler (>= 1.15.0) - railties (= 7.0.3.1) + railties (= 7.0.4.3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.4.3) - loofah (~> 2.3) - railties (7.0.3.1) - actionpack (= 7.0.3.1) - activesupport (= 7.0.3.1) + rails-html-sanitizer (1.5.0) + loofah (~> 2.19, >= 2.19.1) + railties (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) method_source rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.0.6) - rb-fsevent (0.11.1) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) rb-readline (0.5.5) - regexp_parser (2.4.0) + regexp_parser (2.7.0) rexml (3.2.5) - rotp (6.2.0) - rqrcode (2.1.1) + rotp (6.2.2) + rqrcode (2.1.2) chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.0) + rspec-core (3.12.1) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-rails (6.0.0.rc1) + rspec-support (~> 3.12.0) + rspec-rails (6.0.1) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -241,7 +245,7 @@ GEM rspec-expectations (~> 3.11) rspec-mocks (~> 3.11) rspec-support (~> 3.11) - rspec-support (3.11.0) + rspec-support (3.12.0) rubocop (0.81.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) @@ -256,24 +260,27 @@ GEM rubocop (>= 0.72.0) rubocop-rspec (1.41.0) rubocop (>= 0.68.1) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) ruumba (0.1.17) rubocop - scenic (1.6.0) + scenic (1.7.0) activerecord (>= 4.0.0) railties (>= 4.0.0) scenic-mysql_adapter (1.0.1) mysql2 scenic (>= 1.4.0) - simplecov (0.21.2) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - sitemap_generator (6.2.1) + sitemap_generator (6.3.0) builder (~> 3.0) + snaky_hash (2.0.1) + hashie + version_gem (~> 1.1, >= 1.1.1) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -281,24 +288,24 @@ GEM actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) - stackprof (0.2.19) - strscan (3.0.3) + stackprof (0.2.24) svg-graph (2.2.1) thor (1.2.1) - timeout (0.3.0) + timeout (0.3.2) transaction_isolation (1.0.5) activerecord (>= 3.0.11) transaction_retry (1.0.3) activerecord (>= 3.0.11) transaction_isolation (>= 1.0.2) ttfunk (1.7.0) - tzinfo (2.0.4) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) unicode-display_width (1.8.0) vcr (6.1.0) - webmock (3.14.0) + version_gem (1.1.2) + webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -307,7 +314,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.0) + zeitwerk (2.6.7) PLATFORMS ruby @@ -338,7 +345,7 @@ DEPENDENCIES puma (>= 5.6.2) rack-attack rack-mini-profiler - rails (~> 7.0.3.1) + rails (~> 7.0.4.3) rb-readline rotp rqrcode diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index 40c1b98a..298d1c45 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -637,6 +637,7 @@ onPageLoad(() => { }); on('click', '.comment #flag_dropdown a', (event) => { + event.preventDefault(); if (event.target.getAttribute('data') != '') { Lobster.voteComment(parentSelector(event.target, '.comment'), -1, event.target.getAttribute('data')); } @@ -727,4 +728,12 @@ onPageLoad(() => { response.text().then(text => replace(comment, text)); }); }); + + on('click', '.comment_unread', (event) => { + const nodes = document.getElementsByClassName('comment_unread') + const foundIndex = Array.from(nodes).findIndex(node => node === event.target) + const targetIndex = (foundIndex + 1) % nodes.length; + const targetY = nodes[targetIndex].getBoundingClientRect().top + window.scrollY + window.scrollTo({ top: targetY, behavior: 'smooth' }) + }); }); diff --git a/app/assets/stylesheets/application.css.erb b/app/assets/stylesheets/application.css.erb index 3aa9522c..c4124fd3 100644 --- a/app/assets/stylesheets/application.css.erb +++ b/app/assets/stylesheets/application.css.erb @@ -59,6 +59,7 @@ light = <<-LIGHT --color-tag-bg: #fffcd7; --color-tag-border: #d5d458; --color-tag-media-bg: #ddebf9; + --color-tag-bg-img: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); --color-tag-media-border: #b2ccf0; --color-tag-meta-bg: #e0e0e0; --color-tag-meta-border: #c8c8c8; @@ -135,6 +136,7 @@ dark = <<-DARK --color-table-row-border: #262626; --color-tag-bg: #3b320d; + --color-tag-bg-img: none; --color-tag-border: #665501; --color-tag-media-bg: #15293d; --color-tag-media-border: #214669; @@ -1177,6 +1179,7 @@ div.comment_form_container textarea { span.comment_unread { color: var(--color-fg-accent); font-weight: 600; + cursor: pointer; } /* trees */ @@ -1482,11 +1485,11 @@ div.flash-success h2 { } #story_holder .ts-control .data-ts-item { /* item already selected by user*/ - background-color: #e4e4e4; - background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); - border: 1px solid #aaaaaa; + background-color: var(--color-tag-bg); + background-image: var(--color-tag-bg-img); + border: 1px solid var(--color-tagborder); border-radius: 3px; - color: #333; + color: var(--color-fgcontrast-10); line-height: 13px; margin: 3px 5px 3px 0; padding: 3px 0.5rem 3px 1rem !important; @@ -1808,7 +1811,7 @@ input[type="submit"].link_post.pushover_button { height: 7px; left: 4px; position: absolute; - width: 5px; + width: 5.4px; z-index: 10; } ol.stories.list li.story .mobile_comments span:after { diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index f37ef954..d63ca85a 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -286,7 +286,7 @@ class CommentsController < ApplicationController } @title = "Upvoted Comments" - @saved_subnav = true + @above = 'saved/subnav' @page = params[:page].to_i if @page == 0 diff --git a/app/controllers/hats_controller.rb b/app/controllers/hats_controller.rb index ed31d41f..0ddc7143 100644 --- a/app/controllers/hats_controller.rb +++ b/app/controllers/hats_controller.rb @@ -14,7 +14,7 @@ class HatsController < ApplicationController @hat_groups = {} - Hat.all.includes(:user).each do |h| + Hat.active.includes(:user).each do |h| @hat_groups[h.hat] ||= [] @hat_groups[h.hat].push h end diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 622f36b3..8d1f5b60 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -101,6 +101,7 @@ class SettingsController < ApplicationController module_size: 5, shape_rendering: "crispEdges") + @qr_secret = totp.secret @qr_svg = "#{qr}" end diff --git a/app/helpers/interval_helper.rb b/app/helpers/interval_helper.rb index eefe2971..76b6f535 100644 --- a/app/helpers/interval_helper.rb +++ b/app/helpers/interval_helper.rb @@ -1,5 +1,9 @@ module IntervalHelper - TIME_INTERVALS = { "d" => "Day", "w" => "Week", "m" => "Month", "y" => "Year" }.freeze + TIME_INTERVALS = { "h" => "Hour", + "d" => "Day", + "w" => "Week", + "m" => "Month", + "y" => "Year", }.freeze def time_interval(param) if (m = param.to_s.match(/\A(\d+)([#{TIME_INTERVALS.keys.join}])\z/)) diff --git a/app/models/hat.rb b/app/models/hat.rb index 8fe40f15..847ee353 100644 --- a/app/models/hat.rb +++ b/app/models/hat.rb @@ -1,12 +1,14 @@ class Hat < ApplicationRecord belongs_to :user - belongs_to :granted_by_user, :class_name => "User", :inverse_of => false + belongs_to :granted_by_user, class_name: "User", inverse_of: false after_create :log_moderation validates :user, :granted_by_user, :hat, presence: true validates :hat, :link, length: { maximum: 255 } + scope :active, -> { joins(:user).where(doffed_at: nil).merge(User.active) } + def doff_by_user_with_reason(user, reason) m = Moderation.new m.user_id = self.user_id diff --git a/app/models/story.rb b/app/models/story.rb index b12b7b65..7d4f3f15 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -237,6 +237,28 @@ class Story < ApplicationRecord urls = urls2.uniq end + # www.youtube.com + # m.youtube.com + # youtube.com redirects to www.youtube.com + # youtu.be redirects to www.youtube.com + # www.m.youtube.com doesn't work + # www.youtu.be doesn't exist + # m.youtu.be doesn't exist + if /^https?:\/\/((?:www\d*|m)\.)?(youtube\.com|youtu\.be)/i.match(url) + urls.each do |u| + id = /^https?:\/\/(?:(?:m|www)\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([A-z0-9\-_]+)/i + .match(u)[1] + + urls2.push "https://www.youtube.com/watch?v=#{id}" + # In theory, youtube redirects https://youtube.com to https://www.youtube.com + # let's check it just in case + urls2.push "https://youtube.com/watch?v=#{id}" + urls2.push "https://youtu.be/#{id}" + urls2.push "https://m.youtube.com/watch?v=#{id}" + end + urls = urls2.uniq + end + # https urls.each do |u| urls2.push u.gsub(/^http:\/\//i, "https://") @@ -999,6 +1021,9 @@ class Story < ApplicationRecord @fetched_attributes[:title] = title + # strip off common GitHub site + repo owner + @fetched_attributes[:title].sub!(/GitHub - [a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\//i, '') + # attempt to get the canonical url if it can be parsed, # if it is not the domain root path, and if it # responds to GET with a 200-level code diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb index 04c7628b..9b5c1a22 100644 --- a/app/views/comments/_comment.html.erb +++ b/app/views/comments/_comment.html.erb @@ -134,7 +134,7 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ? <% end %> -
+
<% if comment.is_gone? %>

diff --git a/app/views/comments/index.html.erb b/app/views/comments/index.html.erb index fdc39798..e1e7414b 100644 --- a/app/views/comments/index.html.erb +++ b/app/views/comments/index.html.erb @@ -1,4 +1,4 @@ -<% render partial: 'upvoted/subnav' if @upvoted %> +<%= render partial: @above if @above %>

    <% @comments.each do |comment| %> diff --git a/app/views/settings/index.html.erb b/app/views/settings/index.html.erb index a8574b33..7a370ed4 100644 --- a/app/views/settings/index.html.erb +++ b/app/views/settings/index.html.erb @@ -5,7 +5,7 @@ <% end %>
    - <%= form_with model: @edit_user, url: settings_path, method: :post, id: 'edit_user' do |f| %> + <%= form_with model: @edit_user, url: settings_path, method: :post, id: 'edit_user', namespace: 'edit_user' do |f| %> <%= errors_for f.object %>
    @@ -19,7 +19,7 @@
    <%= f.label :current_password, "Current Password:", :class => "required" %> - <%= f.password_field :current_password, :name => "current_password", :size => 30 %> + <%= f.password_field :current_password, :name => "current_password", :size => 30, :autocomplete => "off" %>
    @@ -68,81 +68,6 @@
    -

    External Accounts

    - -
    - <%= f.label :gravatar, - raw("Gravatar:"), - :class => "required" %> - - Your avatar will be cached from the Gravatar icon for your e-mail address above. - <%= link_post "Expire cache", "/avatars/expire" %> - -
    - - <% if Pushover.enabled? %> -
    - <%= f.label :pushover_user_key, - raw("Pushover:"), - :class => "required" %> - - <%= link_post((@edit_user.pushover_user_key.present? ? - "Manage Pushover Subscription" : "Subscribe With Pushover"), - "/settings/pushover_auth", class_name: "pushover_button") %> - - For optional comment and message notifications below - - -
    - <% end %> - - <% if Github.enabled? %> -
    - - <%= f.label :github_username, - raw("GitHub:"), - :class => "required" %> - <% if @edit_user.github_username.present? %> - Linked to - <%= h(@edit_user.github_username) %> - <%= link_post "Disconnect", "/settings/github_disconnect" %> - <% else %> - Connect - <% end %> - -
    - <% end %> - - <% if Twitter.enabled? %> -
    - <%= f.label :twitter_username, - raw("Twitter:"), - :class => "required" %> - - <% if @edit_user.twitter_username.present? %> - Linked to - @<%= h(@edit_user.twitter_username) %> - <%= link_post "Disconnect", "/settings/twitter_disconnect" %> - <% else %> - Connect - <% end %> - -
    - <% end %> - - <% if Keybase.enabled? %> -
    - <%= f.label :kb_username, raw("Keybase:"), :class => "required" %> - - <%= render :partial => "keybase_proofs/proofs", locals: {user: @edit_user, for_self: true} %> - -
    - <% end %> - -
    -

    Security Settings

    @@ -275,7 +200,85 @@

    - <%= f.submit "Save All Settings" %> +
    + + <%= f.submit "Save Account Settings" %> + <% end %> + +
    +
    + +

    External Accounts

    + +
    + <%= label_tag :gravatar, + raw("Gravatar:"), + :class => "required" %> + + Your avatar will be cached from the Gravatar icon for your e-mail address above. + <%= link_post "Expire cache", "/avatars/expire" %> + +
    + + <% if Pushover.enabled? %> +
    + <%= label_tag :pushover_user_key, + raw("Pushover:"), + :class => "required" %> + + <%= link_post((@edit_user.pushover_user_key.present? ? + "Manage Pushover Subscription" : "Subscribe With Pushover"), + "/settings/pushover_auth", class_name: "pushover_button") %> + + For optional comment and message notifications below + + +
    + <% end %> + + <% if Github.enabled? %> +
    + + <%= label_tag :github_username, + raw("GitHub:"), + :class => "required" %> + <% if @edit_user.github_username.present? %> + Linked to + <%= h(@edit_user.github_username) %> + <%= link_post "Disconnect", "/settings/github_disconnect" %> + <% else %> + Connect + <% end %> + +
    + <% end %> + + <% if Twitter.enabled? %> +
    + <%= label_tag :twitter_username, + raw("Twitter:"), + :class => "required" %> + + <% if @edit_user.twitter_username.present? %> + Linked to + @<%= h(@edit_user.twitter_username) %> + <%= link_post "Disconnect", "/settings/twitter_disconnect" %> + <% else %> + Connect + <% end %> + +
    + <% end %> + + <% if Keybase.enabled? %> +
    + <%= label_tag :kb_username, raw("Keybase:"), :class => "required" %> + + <%= render :partial => "keybase_proofs/proofs", locals: {user: @edit_user, for_self: true} %> + +
    <% end %>
    @@ -293,7 +296,8 @@
    - <%= form_with model: @edit_user, url: delete_account_path, method: :post, id: 'delete_user' do |f| %> + <%= form_with model: @edit_user, url: delete_account_path, method: :post, id: 'delete_user', namespace: 'delete_user' do |f| %> +

    Delete Account

    diff --git a/app/views/settings/twofa_enroll.html.erb b/app/views/settings/twofa_enroll.html.erb index 23522856..fd04ed5a 100644 --- a/app/views/settings/twofa_enroll.html.erb +++ b/app/views/settings/twofa_enroll.html.erb @@ -10,6 +10,13 @@

    <%= raw @qr_svg %> +

    + Or to add to a device manually enter the secret: +

    + Reveal Secret + <%= raw @qr_secret %> +
    +

    Once you have finished registering with your TOTP application, proceed to diff --git a/app/views/stories/_form.html.erb b/app/views/stories/_form.html.erb index f1358196..7720d035 100644 --- a/app/views/stories/_form.html.erb +++ b/app/views/stories/_form.html.erb @@ -6,7 +6,7 @@ <% if f.object.url_is_editable_by_user?(@user) %> <%= f.label :url, "URL:", :class => "required" %> <%= f.text_field :url, :autocomplete => "off" %> - <%= button_tag "Fetch Title", :id => "story_fetch_title", + <%= button_tag raw("Fetch Title"), :id => "story_fetch_title", :type => "button" %> <% elsif !f.object.new_record? && !f.object.url.blank? %> <%= f.label :url, "URL:", :class => "required" %> diff --git a/app/views/stories/_listdetail.html.erb b/app/views/stories/_listdetail.html.erb index ff6fac9c..19a9830b 100644 --- a/app/views/stories/_listdetail.html.erb +++ b/app/views/stories/_listdetail.html.erb @@ -24,7 +24,7 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>

    <%= story.show_score_to_user?(@user) ? story.score : '~' %>
    - + <% if story.can_be_seen_by_user?(@user) %> <%= story.title %> <% end %> @@ -173,7 +173,7 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %> <% if !story.is_gone? || @user.try(:is_moderator?) %> | - + <% if story.comments_count == 0 %> no comments <% else %> diff --git a/app/views/stories/edit.html.erb b/app/views/stories/edit.html.erb index 834f5e10..66d1ba6f 100644 --- a/app/views/stories/edit.html.erb +++ b/app/views/stories/edit.html.erb @@ -1,64 +1,66 @@
    - <%= form_with model: @story, id: 'edit_story' do |f| %> - <%= render :partial => "stories/form", :locals => { :story => @story, - :f => f } %> +
    + <%= form_with model: @story, id: 'edit_story' do |f| %> + <%= render :partial => "stories/form", :locals => { :story => @story, + :f => f } %> - <% if @user.is_moderator? %> -
    -
    -
    - <%= f.label :merge_story_short_id, "Merge Into:", - :class => "required" %> - <%= f.text_field :merge_story_short_id, :autocomplete => "off", - :placeholder => "Short id of story into which this story " << - "be merged" %> -
    -
    - <%= f.label :unavailable_at, "Unavailable:", - :class => "required" %> - <%= f.check_box :is_unavailable %> - <%= f.label :unavailable_at, "Source URL is unavailable, " << - "enable display of cached text", :class => "normal" %> -
    - <% if @story.user_id != @user.id %> + <% if @user.is_moderator? %> +
    +
    - <%= f.label :moderation_reason, "Mod Reason:", :class => "required" %> - <%= f.text_field :moderation_reason %> + <%= f.label :merge_story_short_id, "Merge Into:", + :class => "required" %> + <%= f.text_field :merge_story_short_id, :autocomplete => "off", + :placeholder => "Short id of story into which this story " << + "be merged" %>
    - <% end %> +
    + <%= f.label :unavailable_at, "Unavailable:", + :class => "required" %> + <%= f.check_box :is_unavailable %> + <%= f.label :unavailable_at, "Source URL is unavailable, " << + "enable display of cached text", :class => "normal" %> +
    + <% if @story.user_id != @user.id %> +
    + <%= f.label :moderation_reason, "Mod Reason:", :class => "required" %> + <%= f.text_field :moderation_reason %> +
    + <% end %> +
    + <% end %> + +

    + +
    +
    +
    + Markdown formatting available +
    + + <%= f.submit "Save" %> + + <% if @story.is_gone? && @story.is_undeletable_by_user?(@user) %> +   |   + <%= f.submit "Undelete", formaction: story_undelete_path(@story.short_id), + :data => { :confirm => "Undelete this story?" } %> + <% elsif !@story.is_gone? %> +   |   + <% if @story.user_id != @user.id && @user.is_moderator? %> + <%= f.submit "Delete", formaction: story_destroy_path(@story.short_id), + :class => "deletion", :data => { :confirm => "Delete this story?" } %> + <% else %> + <%= f.submit "Delete", formaction: story_destroy_path(@story.short_id), + :class => "deletion", :data => { :confirm => "Delete this story?" } %> + <% end %> + <% end %> + +
    + + <%= render :partial => "global/markdownhelp", + :locals => { allow_images: @story.can_have_images? } %> +
    <% end %> - -

    - -
    -
    -
    - Markdown formatting available -
    - - <%= f.submit "Save" %> - - <% if @story.is_gone? && @story.is_undeletable_by_user?(@user) %> -   |   - <%= f.submit "Undelete", formaction: story_undelete_path(@story.short_id), - :data => { :confirm => "Undelete this story?" } %> - <% elsif !@story.is_gone? %> -   |   - <% if @story.user_id != @user.id && @user.is_moderator? %> - <%= f.submit "Delete", formaction: story_destroy_path(@story.short_id), - :class => "deletion", :data => { :confirm => "Delete this story?" } %> - <% else %> - <%= f.submit "Delete", formaction: story_destroy_path(@story.short_id), - :class => "deletion", :data => { :confirm => "Delete this story?" } %> - <% end %> - <% end %> - -
    - - <%= render :partial => "global/markdownhelp", - :locals => { allow_images: @story.can_have_images? } %> -
    -
    - <% end %> +
    diff --git a/app/views/stories/suggest.html.erb b/app/views/stories/suggest.html.erb index 33077582..7c192747 100644 --- a/app/views/stories/suggest.html.erb +++ b/app/views/stories/suggest.html.erb @@ -1,16 +1,18 @@
    - <%= form_with model: @story, url: story_suggest_path(@story.short_id), method: :post, html: { id: 'edit_story' } do |f| %> - <%= render :partial => "stories/form", :locals => { :story => @story, - :f => f, :suggesting => true } %> +
    + <%= form_with model: @story, url: story_suggest_path(@story.short_id), method: :post, html: { id: 'edit_story' } do |f| %> + <%= render :partial => "stories/form", :locals => { :story => @story, + :f => f, :suggesting => true } %> -

    +

    -
    -
    - <%= f.submit "Suggest Changes" %> -  or cancel +
    +
    + <%= f.submit "Suggest Changes" %> +  or cancel +
    -
    - <% end %> + <% end %> +
    diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index ceaf0ad4..24eeec0e 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -25,7 +25,7 @@ Rack::Attack.throttle("login", limit: 4, period: 60) do |request| end Rack::Attack.throttle("log4j probe", limit: 1, period: 1.week.to_i) do |request| - request.ip if request.user_agent.include? '${' + request.ip if request.user_agent.try(:include?, '${') end # explain the throttle diff --git a/spec/models/story_spec.rb b/spec/models/story_spec.rb index 8e8f2702..071c74f5 100644 --- a/spec/models/story_spec.rb +++ b/spec/models/story_spec.rb @@ -327,6 +327,28 @@ describe Story do expect(s1.similar_stories).to eq([s2]) expect(s2.similar_stories).to eq([s1]) end + + it "finds similar www.youtube and youtu.be URLs" do + s1 = create(:story, + url: 'https://www.youtube.com/watch?v=7Pq-S557XQU', + created_at: (Story::RECENT_DAYS + 1).days.ago) + + s2 = create(:story, url: 'https://youtu.be/7Pq-S557XQU') + + expect(s1.similar_stories).to eq([s2]) + expect(s2.similar_stories).to eq([s1]) + end + + it "finds similar www.youtube and m.youtube URLs" do + s1 = create(:story, + url: 'https://www.youtube.com/watch?v=7Pq-S557XQU', + created_at: (Story::RECENT_DAYS + 1).days.ago) + + s2 = create(:story, url: 'https://m.youtube.com/watch?v=7Pq-S557XQU') + + expect(s1.similar_stories).to eq([s2]) + expect(s2.similar_stories).to eq([s1]) + end end describe "#calculated_hotness" do