Merge branch 'lobsters:master' into master

This commit is contained in:
Ben Harris 2023-05-15 12:09:26 -04:00 committed by GitHub
commit 87e8637524
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 373 additions and 285 deletions

View File

@ -1,6 +1,6 @@
source "https://rubygems.org"
gem "rails", "~> 7.0.3.1"
gem "rails", "~> 7.0.4.3"
gem "mysql2"

View File

@ -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

View File

@ -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' })
});
});

View File

@ -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 {

View File

@ -286,7 +286,7 @@ class CommentsController < ApplicationController
}
@title = "Upvoted Comments"
@saved_subnav = true
@above = 'saved/subnav'
@page = params[:page].to_i
if @page == 0

View File

@ -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

View File

@ -101,6 +101,7 @@ class SettingsController < ApplicationController
module_size: 5,
shape_rendering: "crispEdges")
@qr_secret = totp.secret
@qr_svg = "<a href=\"#{totp_url}\">#{qr}</a>"
end

View File

@ -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/))

View File

@ -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

View File

@ -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

View File

@ -134,7 +134,7 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
</span>
<% end %>
</div>
<div class="comment_text">
<div role="heading" aria-level="3" class="comment_text">
<% if comment.is_gone? %>
<p>
<span class="na">

View File

@ -1,4 +1,4 @@
<% render partial: 'upvoted/subnav' if @upvoted %>
<%= render partial: @above if @above %>
<ol class="comments comments1">
<% @comments.each do |comment| %>

View File

@ -5,7 +5,7 @@
<% end %>
<div class="box wide">
<%= 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 %>
<div class="boxline">
@ -19,7 +19,7 @@
<div class="boxline">
<%= 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" %>
</div>
<div class="boxline">
@ -68,81 +68,6 @@
<br>
<h2>External Accounts</h2>
<div class="boxline">
<%= f.label :gravatar,
raw("<a href=\"https://gravatar.com/\">Gravatar</a>:"),
:class => "required" %>
<span>
Your avatar will be cached from the Gravatar icon for your e-mail address above.
<%= link_post "Expire cache", "/avatars/expire" %>
</span>
</div>
<% if Pushover.enabled? %>
<div class="boxline">
<%= f.label :pushover_user_key,
raw("<a href=\"https://pushover.net/\">Pushover</a>:"),
:class => "required" %>
<span>
<%= link_post((@edit_user.pushover_user_key.present? ?
"Manage Pushover Subscription" : "Subscribe With Pushover"),
"/settings/pushover_auth", class_name: "pushover_button") %>
<span class="hint indent">
For optional comment and message notifications below
</span>
</span>
</div>
<% end %>
<% if Github.enabled? %>
<div class="boxline">
<span>
<%= f.label :github_username,
raw("<a href=\"https://github.com/\">GitHub</a>:"),
:class => "required" %>
<% if @edit_user.github_username.present? %>
Linked to
<strong><a href="https://github.com/<%= h(@edit_user.github_username)
%>"><%= h(@edit_user.github_username) %></a></strong>
<%= link_post "Disconnect", "/settings/github_disconnect" %>
<% else %>
<a href="/settings/github_auth">Connect</a>
<% end %>
</span>
</div>
<% end %>
<% if Twitter.enabled? %>
<div class="boxline">
<%= f.label :twitter_username,
raw("<a href=\"https://twitter.com/\">Twitter</a>:"),
:class => "required" %>
<span>
<% if @edit_user.twitter_username.present? %>
Linked to
<strong><a href="https://twitter.com/<%= h(@edit_user.twitter_username)
%>">@<%= h(@edit_user.twitter_username) %></a></strong>
<%= link_post "Disconnect", "/settings/twitter_disconnect" %>
<% else %>
<a href="/settings/twitter_auth">Connect</a>
<% end %>
</span>
</div>
<% end %>
<% if Keybase.enabled? %>
<div class="boxline">
<%= f.label :kb_username, raw("<a href=\"https://keybase.io/\">Keybase</a>:"), :class => "required" %>
<span>
<%= render :partial => "keybase_proofs/proofs", locals: {user: @edit_user, for_self: true} %>
</span>
</div>
<% end %>
<br>
<h2>Security Settings</h2>
<div class="boxline">
@ -275,7 +200,85 @@
</div>
<br>
<%= f.submit "Save All Settings" %>
<br>
<%= f.submit "Save Account Settings" %>
<% end %>
<br>
<br>
<h2>External Accounts</h2>
<div class="boxline">
<%= label_tag :gravatar,
raw("<a href=\"https://gravatar.com/\">Gravatar</a>:"),
:class => "required" %>
<span>
Your avatar will be cached from the Gravatar icon for your e-mail address above.
<%= link_post "Expire cache", "/avatars/expire" %>
</span>
</div>
<% if Pushover.enabled? %>
<div class="boxline">
<%= label_tag :pushover_user_key,
raw("<a href=\"https://pushover.net/\">Pushover</a>:"),
:class => "required" %>
<span>
<%= link_post((@edit_user.pushover_user_key.present? ?
"Manage Pushover Subscription" : "Subscribe With Pushover"),
"/settings/pushover_auth", class_name: "pushover_button") %>
<span class="hint indent">
For optional comment and message notifications below
</span>
</span>
</div>
<% end %>
<% if Github.enabled? %>
<div class="boxline">
<span>
<%= label_tag :github_username,
raw("<a href=\"https://github.com/\">GitHub</a>:"),
:class => "required" %>
<% if @edit_user.github_username.present? %>
Linked to
<strong><a href="https://github.com/<%= h(@edit_user.github_username)
%>"><%= h(@edit_user.github_username) %></a></strong>
<%= link_post "Disconnect", "/settings/github_disconnect" %>
<% else %>
<a href="/settings/github_auth">Connect</a>
<% end %>
</span>
</div>
<% end %>
<% if Twitter.enabled? %>
<div class="boxline">
<%= label_tag :twitter_username,
raw("<a href=\"https://twitter.com/\">Twitter</a>:"),
:class => "required" %>
<span>
<% if @edit_user.twitter_username.present? %>
Linked to
<strong><a href="https://twitter.com/<%= h(@edit_user.twitter_username)
%>">@<%= h(@edit_user.twitter_username) %></a></strong>
<%= link_post "Disconnect", "/settings/twitter_disconnect" %>
<% else %>
<a href="/settings/twitter_auth">Connect</a>
<% end %>
</span>
</div>
<% end %>
<% if Keybase.enabled? %>
<div class="boxline">
<%= label_tag :kb_username, raw("<a href=\"https://keybase.io/\">Keybase</a>:"), :class => "required" %>
<span>
<%= render :partial => "keybase_proofs/proofs", locals: {user: @edit_user, for_self: true} %>
</span>
</div>
<% end %>
<br>
@ -293,7 +296,8 @@
<br>
<div class="deletion">
<%= 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| %>
<h2>Delete Account</h2>
<p>

View File

@ -10,6 +10,13 @@
</p>
<%= raw @qr_svg %>
<p>
Or to add to a device manually enter the secret:
<details>
<summary>Reveal Secret</summary>
<%= raw @qr_secret %>
</details>
</p>
<p>
Once you have finished registering with your TOTP application, proceed to

View File

@ -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&nbsp;Title"), :id => "story_fetch_title",
:type => "button" %>
<% elsif !f.object.new_record? && !f.object.url.blank? %>
<%= f.label :url, "URL:", :class => "required" %>

View File

@ -24,7 +24,7 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
<div class="score"><%= story.show_score_to_user?(@user) ? story.score : '~' %></div>
</div>
<div class="details">
<span class="link h-cite u-repost-of">
<span role="heading" aria-level="1" class="link h-cite u-repost-of">
<% if story.can_be_seen_by_user?(@user) %>
<a class="u-url" href="<%= story.url_or_comments_path %>" rel="ugc <%= story.send_referrer? ? '' : 'noreferrer' %>"><%= story.title %></a>
<% end %>
@ -173,7 +173,7 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
<% if !story.is_gone? || @user.try(:is_moderator?) %>
<span class="comments_label">
<span> | </span>
<a href="<%= story.comments_path %>">
<a role="heading" aria-level="2" href="<%= story.comments_path %>">
<% if story.comments_count == 0 %>
no comments</a>
<% else %>

View File

@ -1,64 +1,66 @@
<div class="box" id="story_box">
<%= form_with model: @story, id: 'edit_story' do |f| %>
<%= render :partial => "stories/form", :locals => { :story => @story,
:f => f } %>
<div id="story_holder">
<%= form_with model: @story, id: 'edit_story' do |f| %>
<%= render :partial => "stories/form", :locals => { :story => @story,
:f => f } %>
<% if @user.is_moderator? %>
<br />
<div class="box">
<div class="boxline">
<%= 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" %>
</div>
<div class="boxline">
<%= 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" %>
</div>
<% if @story.user_id != @user.id %>
<% if @user.is_moderator? %>
<br />
<div class="box">
<div class="boxline">
<%= 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" %>
</div>
<% end %>
<div class="boxline">
<%= 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" %>
</div>
<% if @story.user_id != @user.id %>
<div class="boxline">
<%= f.label :moderation_reason, "Mod Reason:", :class => "required" %>
<%= f.text_field :moderation_reason %>
</div>
<% end %>
</div>
<% end %>
<p></p>
<div class="box">
<div class="boxline actions markdown_help_toggler">
<div class="markdown_help_label">
Markdown formatting available
</div>
<%= f.submit "Save" %>
<% if @story.is_gone? && @story.is_undeletable_by_user?(@user) %>
&nbsp; | &nbsp;
<%= f.submit "Undelete", formaction: story_undelete_path(@story.short_id),
:data => { :confirm => "Undelete this story?" } %>
<% elsif !@story.is_gone? %>
&nbsp; | &nbsp;
<% 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 %>
<div style="clear: both;"></div>
<%= render :partial => "global/markdownhelp",
:locals => { allow_images: @story.can_have_images? } %>
</div>
</div>
<% end %>
<p></p>
<div class="box">
<div class="boxline actions markdown_help_toggler">
<div class="markdown_help_label">
Markdown formatting available
</div>
<%= f.submit "Save" %>
<% if @story.is_gone? && @story.is_undeletable_by_user?(@user) %>
&nbsp; | &nbsp;
<%= f.submit "Undelete", formaction: story_undelete_path(@story.short_id),
:data => { :confirm => "Undelete this story?" } %>
<% elsif !@story.is_gone? %>
&nbsp; | &nbsp;
<% 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 %>
<div style="clear: both;"></div>
<%= render :partial => "global/markdownhelp",
:locals => { allow_images: @story.can_have_images? } %>
</div>
</div>
<% end %>
</div>
</div>

View File

@ -1,16 +1,18 @@
<div class="box" id="story_box">
<%= 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 } %>
<div id="story_holder">
<%= 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 } %>
<p></p>
<p></p>
<div class="box">
<div class="boxline actions">
<%= f.submit "Suggest Changes" %>
&nbsp;or <a href="<%= story_path(@story.short_id) %>">cancel</a>
<div class="box">
<div class="boxline actions">
<%= f.submit "Suggest Changes" %>
&nbsp;or <a href="<%= story_path(@story.short_id) %>">cancel</a>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
</div>

View File

@ -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

View File

@ -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