Merge branch 'lobsters:master' into master
This commit is contained in:
commit
87e8637524
2
Gemfile
2
Gemfile
|
@ -1,6 +1,6 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "rails", "~> 7.0.3.1"
|
||||
gem "rails", "~> 7.0.4.3"
|
||||
|
||||
gem "mysql2"
|
||||
|
||||
|
|
253
Gemfile.lock
253
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
|
||||
|
|
|
@ -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' })
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -286,7 +286,7 @@ class CommentsController < ApplicationController
|
|||
}
|
||||
|
||||
@title = "Upvoted Comments"
|
||||
@saved_subnav = true
|
||||
@above = 'saved/subnav'
|
||||
|
||||
@page = params[:page].to_i
|
||||
if @page == 0
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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/))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<% render partial: 'upvoted/subnav' if @upvoted %>
|
||||
<%= render partial: @above if @above %>
|
||||
|
||||
<ol class="comments comments1">
|
||||
<% @comments.each do |comment| %>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" %>
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -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) %>
|
||||
|
|
||||
<%= 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 %>
|
||||
|
||||
<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) %>
|
||||
|
|
||||
<%= 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 %>
|
||||
|
||||
<div style="clear: both;"></div>
|
||||
|
||||
<%= render :partial => "global/markdownhelp",
|
||||
:locals => { allow_images: @story.can_have_images? } %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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" %>
|
||||
or <a href="<%= story_path(@story.short_id) %>">cancel</a>
|
||||
<div class="box">
|
||||
<div class="boxline actions">
|
||||
<%= f.submit "Suggest Changes" %>
|
||||
or <a href="<%= story_path(@story.short_id) %>">cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue