Merge remote-tracking branch 'origin/main'

This commit is contained in:
Kay Faraday 2023-02-03 06:04:08 +00:00
commit 8b3d53b263
66 changed files with 1007 additions and 456 deletions

View File

@ -0,0 +1,54 @@
name: Bug Report
description: If something isn't working as expected
labels: [bug]
body:
- type: markdown
attributes:
value: |
Before opening an issue, please make sure to check if the [issue tracker](https://github.com/Retrospring/retrospring/issues?q=is%3Aissue+label%3Abug) in case it has been previously reported or fixed.
- type: textarea
attributes:
label: Steps to reproduce the problem
description: What were you trying to do?
value: |
1.
2.
3.
...
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
description: What should have happened?
validations:
required: true
- type: textarea
attributes:
label: Actual behaviour
description: What happened?
validations:
required: true
- type: textarea
attributes:
label: Detailed description
validations:
required: false
- type: textarea
attributes:
label: Specifications
description: |
If you host Retrospring, what version/commit hash of Retrospring did this issue occur in?
If a front-end issue, what browser and operating systems were you using?
placeholder: |
Retrospring 1970.0101.0
Ruby 3.1.3
Node.js 18.13.0
Google Chrome 112
Firefox 109.0
etc...
validations:
required: true

View File

@ -0,0 +1,20 @@
name: Feature Request
description: If you have a suggestion
labels: [suggestion]
body:
- type: markdown
attributes:
value: |
Please use a concise and distinct title for the issue.
- type: textarea
attributes:
label: Pitch
description: Describe your idea for a feature. Please check our [issue tracker](https://github.com/Retrospring/retrospring/issues?q=is%3Aissue+label%3Asuggestion) to make sure it has not already been suggested/implemented/turned down before.
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: Why do you think this feature is needed? Who would benefit from it?
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Discussions
url: https://github.com/retrospring/retrospring/discussions
about: Please ask and answer questions here.

View File

@ -50,9 +50,9 @@ gem "sentry-rails"
gem "sentry-ruby" gem "sentry-ruby"
gem "sentry-sidekiq" gem "sentry-sidekiq"
gem "sidekiq", "< 6" # remove version constraint once we have redis 5 gem "sidekiq", "< 7" # remove version constraint once are ready to upgrade https://github.com/mperham/sidekiq/blob/main/docs/7.0-Upgrade.md
gem "questiongenerator", "~> 1.0" gem "questiongenerator", "~> 1.1"
gem "httparty" gem "httparty"
gem "redcarpet" gem "redcarpet"
@ -97,7 +97,7 @@ group :development, :test do
gem "rspec-mocks" gem "rspec-mocks"
gem "rspec-rails", "~> 6.0" gem "rspec-rails", "~> 6.0"
gem "rspec-sidekiq", "~> 3.0", require: false gem "rspec-sidekiq", "~> 3.0", require: false
gem "rubocop", "~> 1.43" gem "rubocop", "~> 1.44"
gem "rubocop-rails", "~> 2.17" gem "rubocop-rails", "~> 2.17"
gem "shoulda-matchers", "~> 5.3" gem "shoulda-matchers", "~> 5.3"
gem "simplecov", require: false gem "simplecov", require: false

View File

@ -9,40 +9,40 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (6.1.7.1) actioncable (6.1.7.2)
actionpack (= 6.1.7.1) actionpack (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.1.7.1) actionmailbox (6.1.7.2)
actionpack (= 6.1.7.1) actionpack (= 6.1.7.2)
activejob (= 6.1.7.1) activejob (= 6.1.7.2)
activerecord (= 6.1.7.1) activerecord (= 6.1.7.2)
activestorage (= 6.1.7.1) activestorage (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.1.7.1) actionmailer (6.1.7.2)
actionpack (= 6.1.7.1) actionpack (= 6.1.7.2)
actionview (= 6.1.7.1) actionview (= 6.1.7.2)
activejob (= 6.1.7.1) activejob (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.1.7.1) actionpack (6.1.7.2)
actionview (= 6.1.7.1) actionview (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
rack (~> 2.0, >= 2.0.9) rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.7.1) actiontext (6.1.7.2)
actionpack (= 6.1.7.1) actionpack (= 6.1.7.2)
activerecord (= 6.1.7.1) activerecord (= 6.1.7.2)
activestorage (= 6.1.7.1) activestorage (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.1.7.1) actionview (6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -50,26 +50,26 @@ GEM
active_model_otp (2.3.1) active_model_otp (2.3.1)
activemodel activemodel
rotp (~> 6.2.0) rotp (~> 6.2.0)
activejob (6.1.7.1) activejob (6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.1.7.1) activemodel (6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
activemodel-serializers-xml (1.0.2) activemodel-serializers-xml (1.0.2)
activemodel (> 5.x) activemodel (> 5.x)
activesupport (> 5.x) activesupport (> 5.x)
builder (~> 3.1) builder (~> 3.1)
activerecord (6.1.7.1) activerecord (6.1.7.2)
activemodel (= 6.1.7.1) activemodel (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
activestorage (6.1.7.1) activestorage (6.1.7.2)
actionpack (= 6.1.7.1) actionpack (= 6.1.7.2)
activejob (= 6.1.7.1) activejob (= 6.1.7.2)
activerecord (= 6.1.7.1) activerecord (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (6.1.7.1) activesupport (6.1.7.2)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -85,7 +85,7 @@ GEM
rack (>= 0.9.0) rack (>= 0.9.0)
binding_of_caller (1.0.0) binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootsnap (1.15.0) bootsnap (1.16.0)
msgpack (~> 1.2) msgpack (~> 1.2)
bootstrap_form (5.1.0) bootstrap_form (5.1.0)
actionpack (>= 5.2) actionpack (>= 5.2)
@ -105,8 +105,8 @@ GEM
chunky_png (1.4.0) chunky_png (1.4.0)
coderay (1.1.3) coderay (1.1.3)
colorize (0.8.1) colorize (0.8.1)
concurrent-ruby (1.1.10) concurrent-ruby (1.2.0)
connection_pool (2.2.5) connection_pool (2.3.0)
crass (1.0.6) crass (1.0.6)
cssbundling-rails (1.1.2) cssbundling-rails (1.1.2)
railties (>= 6.0.0) railties (>= 6.0.0)
@ -150,7 +150,7 @@ GEM
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
equalizer (0.0.11) equalizer (0.0.11)
erubi (1.12.0) erubi (1.12.0)
excon (0.93.1) excon (0.98.0)
factory_bot (6.2.0) factory_bot (6.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
@ -165,7 +165,7 @@ GEM
ffi-compiler (1.0.1) ffi-compiler (1.0.1)
ffi (>= 1.0.0) ffi (>= 1.0.0)
rake rake
fog-aws (3.15.0) fog-aws (3.16.0)
fog-core (~> 2.1) fog-core (~> 2.1)
fog-json (~> 1.1) fog-json (~> 1.1)
fog-xml (~> 0.1) fog-xml (~> 0.1)
@ -184,13 +184,13 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0) nokogiri (>= 1.5.11, < 2.0.0)
formatador (1.1.0) formatador (1.1.0)
glob (0.3.1) glob (0.3.1)
globalid (1.0.1) globalid (1.1.0)
activesupport (>= 5.0) activesupport (>= 5.0)
haml (6.1.1) haml (6.1.1)
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
tilt tilt
haml_lint (0.43.0) haml_lint (0.45.0)
haml (>= 4.0, < 6.2) haml (>= 4.0, < 6.2)
parallel (~> 1.10) parallel (~> 1.10)
rainbow rainbow
@ -321,27 +321,27 @@ GEM
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.3.0) pundit (2.3.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
questiongenerator (1.0.0) questiongenerator (1.1.0)
racc (1.6.2) racc (1.6.2)
rack (2.2.6.2) rack (2.2.6.2)
rack-protection (2.2.2) rack-protection (3.0.5)
rack rack
rack-test (2.0.2) rack-test (2.0.2)
rack (>= 1.3) rack (>= 1.3)
rails (6.1.7.1) rails (6.1.7.2)
actioncable (= 6.1.7.1) actioncable (= 6.1.7.2)
actionmailbox (= 6.1.7.1) actionmailbox (= 6.1.7.2)
actionmailer (= 6.1.7.1) actionmailer (= 6.1.7.2)
actionpack (= 6.1.7.1) actionpack (= 6.1.7.2)
actiontext (= 6.1.7.1) actiontext (= 6.1.7.2)
actionview (= 6.1.7.1) actionview (= 6.1.7.2)
activejob (= 6.1.7.1) activejob (= 6.1.7.2)
activemodel (= 6.1.7.1) activemodel (= 6.1.7.2)
activerecord (= 6.1.7.1) activerecord (= 6.1.7.2)
activestorage (= 6.1.7.1) activestorage (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 6.1.7.1) railties (= 6.1.7.2)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
@ -361,17 +361,17 @@ GEM
nested_form (~> 0.3) nested_form (~> 0.3)
rails (>= 6.0, < 8) rails (>= 6.0, < 8)
turbo-rails (~> 1.0) turbo-rails (~> 1.0)
railties (6.1.7.1) railties (6.1.7.2)
actionpack (= 6.1.7.1) actionpack (= 6.1.7.2)
activesupport (= 6.1.7.1) activesupport (= 6.1.7.2)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.0.6) rake (13.0.6)
redcarpet (3.5.1) redcarpet (3.6.0)
redis (4.5.1) redis (4.8.0)
regexp_parser (2.6.1) regexp_parser (2.6.2)
request_store (1.5.1) request_store (1.5.1)
rack (>= 1.4) rack (>= 1.4)
responders (3.0.1) responders (3.0.1)
@ -417,7 +417,7 @@ GEM
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0) sidekiq (>= 2.4.0)
rspec-support (3.12.0) rspec-support (3.12.0)
rubocop (1.43.0) rubocop (1.44.1)
json (~> 2.3) json (~> 2.3)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.2.0.0) parser (>= 3.2.0.0)
@ -437,7 +437,7 @@ GEM
ruby-vips (2.1.4) ruby-vips (2.1.4)
ffi (~> 1.12) ffi (~> 1.12)
rubyzip (2.3.2) rubyzip (2.3.2)
sanitize (6.0.0) sanitize (6.0.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
sassc (2.4.0) sassc (2.4.0)
@ -458,11 +458,10 @@ GEM
sidekiq (>= 3.0) sidekiq (>= 3.0)
shoulda-matchers (5.3.0) shoulda-matchers (5.3.0)
activesupport (>= 5.2.0) activesupport (>= 5.2.0)
sidekiq (5.2.10) sidekiq (6.5.8)
connection_pool (~> 2.2, >= 2.2.2) connection_pool (>= 2.2.5, < 3)
rack (~> 2.0) rack (~> 2.0)
rack-protection (>= 1.5.0) redis (>= 4.5.0, < 5)
redis (~> 4.5, < 4.6.0)
simple_oauth (0.3.1) simple_oauth (0.3.1)
simplecov (0.22.0) simplecov (0.22.0)
docile (~> 1.1) docile (~> 1.1)
@ -485,7 +484,7 @@ GEM
activesupport (>= 5.2) activesupport (>= 5.2)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sysexits (1.2.0) sysexits (1.2.0)
temple (0.9.1) temple (0.10.0)
thor (1.2.1) thor (1.2.1)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.11) tilt (2.0.11)
@ -511,7 +510,7 @@ GEM
twitter-text (3.1.0) twitter-text (3.1.0)
idn-ruby idn-ruby
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (2.0.5) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
@ -579,7 +578,7 @@ DEPENDENCIES
pghero pghero
puma puma
pundit (~> 2.3) pundit (~> 2.3)
questiongenerator (~> 1.0) questiongenerator (~> 1.1)
rails (~> 6.1) rails (~> 6.1)
rails-controller-testing rails-controller-testing
rails-i18n (~> 7.0) rails-i18n (~> 7.0)
@ -594,7 +593,7 @@ DEPENDENCIES
rspec-mocks rspec-mocks
rspec-rails (~> 6.0) rspec-rails (~> 6.0)
rspec-sidekiq (~> 3.0) rspec-sidekiq (~> 3.0)
rubocop (~> 1.43) rubocop (~> 1.44)
rubocop-rails (~> 2.17) rubocop-rails (~> 2.17)
ruby-progressbar ruby-progressbar
rubyzip (~> 2.3) rubyzip (~> 2.3)
@ -604,7 +603,7 @@ DEPENDENCIES
sentry-ruby sentry-ruby
sentry-sidekiq sentry-sidekiq
shoulda-matchers (~> 5.3) shoulda-matchers (~> 5.3)
sidekiq (< 6) sidekiq (< 7)
simplecov simplecov
simplecov-cobertura simplecov-cobertura
simplecov-json simplecov-json

View File

@ -51,6 +51,15 @@ namespace :justask do # rubocop:disable Metrics/BlockLength
user.remove_role :moderator user.remove_role :moderator
puts "#{user.screen_name} is no longer a moderator." puts "#{user.screen_name} is no longer a moderator."
end end
desc "Removes users whose accounts haven't been verified for over 3 months."
task remove_stale: :environment do
puts "Removing stale users…"
removed = User.where(confirmed_at: nil)
.where("confirmation_sent_at < ?", DateTime.now.utc - 3.months)
.destroy_all.count
puts "Removed #{removed} users"
end
end end
namespace :db do namespace :db do

13
SECURITY.md Normal file
View File

@ -0,0 +1,13 @@
# Security Policy
If you believe you've found a security vulnerability in Retrospring (a bug that allows something to happen that shouldn't be possible), you can reach us at <security@retrospring.net>.
You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Retrospring's users to increased risk.
## Scope
A "vulnerability in Retrospring" is a vulnerability in the code distributed through our main source code repository on GitHub. Vulnerabilities that are specific to a given installation (e.g. misconfiguration) should be reported to the owner of that installation and not us.
## Supported Versions
As long as Retrospring is in rapid development pace the currently supported version for security issues is always the [latest tagged release](https://github.com/Retrospring/retrospring/releases/latest).

View File

@ -125,6 +125,8 @@ $spacers: map-merge($rs-spacers, $spacers);
--muted-text: 108, 117, 125; --muted-text: 108, 117, 125;
--input-text: 0, 0, 0; --input-text: 0, 0, 0;
--input-placeholder: 108, 117, 125; --input-placeholder: 108, 117, 125;
--raised-text: 0, 0, 0;
--raised-accent-text: 0, 0, 0;
--turbolinks-progress-color: #a58adc; // --primary lightened by 25% --turbolinks-progress-color: #a58adc; // --primary lightened by 25%
} }

View File

@ -42,7 +42,7 @@
& .dropdown-item:hover, & .dropdown-item:hover,
& .dropdown-item:active { & .dropdown-item:active {
background: transparent; background: transparent;
color: RGB(var(--body-text)); color: RGB(var(--raised-text));
} }
} }

View File

@ -4,6 +4,7 @@
margin-bottom: map.get($spacers, 3); margin-bottom: map.get($spacers, 3);
box-shadow: $box-shadow-sm; box-shadow: $box-shadow-sm;
background-color: var(--raised-bg); background-color: var(--raised-bg);
color: RGB(var(--raised-text));
p:last-child { p:last-child {
margin-bottom: 0; margin-bottom: 0;
@ -17,4 +18,5 @@
.card-header, .card-header,
.card-footer { .card-footer {
background-color: var(--raised-accent); background-color: var(--raised-accent);
color: RGB(var(--raised-accent-text));
} }

View File

@ -1,12 +1,12 @@
.dropdown-menu { .dropdown-menu {
color: RGB(var(--body-text)); color: RGB(var(--raised-text));
background-color: var(--raised-bg); background-color: var(--raised-bg);
box-shadow: $box-shadow-lg; box-shadow: $box-shadow-lg;
border: none; border: none;
} }
.dropdown-item { .dropdown-item {
color: RGB(var(--body-text)); color: RGB(var(--raised-text));
&.active, &.active,
&:active, &:active,
@ -16,6 +16,7 @@
} }
&:hover { &:hover {
color: RGB(var(--raised-accent-text));
background-color: var(--raised-accent); background-color: var(--raised-accent);
} }
} }

View File

@ -13,9 +13,10 @@
} }
.list-group-item-action { .list-group-item-action {
color: RGB(var(--body-text)); color: RGB(var(--raised-text));
&:hover, &:focus { &:hover, &:focus {
color: RGB(var(--raised-accent-text));
background-color: var(--raised-accent); background-color: var(--raised-accent);
} }

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class AjaxController < ApplicationController class AjaxController < ApplicationController
skip_before_action :find_active_announcements
before_action :build_response before_action :build_response
after_action :return_response after_action :return_response
@ -92,10 +93,6 @@ class AjaxController < ApplicationController
return_response return_response
end end
def find_active_announcements
# We do not need announcements here
end
private private
def build_response def build_response

View File

@ -3,7 +3,9 @@
class InboxController < ApplicationController class InboxController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def show after_action :mark_inbox_entries_as_read, only: %i[show]
def show # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
find_author find_author
find_inbox_entries find_inbox_entries
@ -11,15 +13,17 @@ class InboxController < ApplicationController
# rubocop disabled because of a false positive # rubocop disabled because of a false positive
flash[:info] = t(".author.info", author: @author) # rubocop:disable Rails/ActionControllerFlashBeforeRender flash[:info] = t(".author.info", author: @author) # rubocop:disable Rails/ActionControllerFlashBeforeRender
redirect_to inbox_path(last_id: params[:last_id]) redirect_to inbox_path(last_id: params[:last_id])
return
end end
@delete_id = find_delete_id @delete_id = find_delete_id
@disabled = true if @inbox.empty? @disabled = true if @inbox.empty?
services = current_user.services.to_a
respond_to do |format| respond_to do |format|
format.html format.html { render "show", locals: { services: } }
format.turbo_stream do format.turbo_stream do
render "show", layout: false, status: :see_other render "show", locals: { services: }, layout: false, status: :see_other
# rubocop disabled as just flipping a flag doesn't need to have validations to be run # rubocop disabled as just flipping a flag doesn't need to have validations to be run
@inbox.update_all(new: false) # rubocop:disable Rails/SkipsModelValidations @inbox.update_all(new: false) # rubocop:disable Rails/SkipsModelValidations
@ -34,10 +38,11 @@ class InboxController < ApplicationController
user: current_user) user: current_user)
inbox = Inbox.create!(user: current_user, question_id: question.id, new: true) inbox = Inbox.create!(user: current_user, question_id: question.id, new: true)
services = current_user.services
respond_to do |format| respond_to do |format|
format.turbo_stream do format.turbo_stream do
render turbo_stream: turbo_stream.prepend("entries", partial: "inbox/entry", locals: { i: inbox }) render turbo_stream: turbo_stream.prepend("entries", partial: "inbox/entry", locals: { i: inbox, services: })
inbox.update(new: false) inbox.update(new: false)
end end
@ -77,4 +82,9 @@ class InboxController < ApplicationController
.joins(:question) .joins(:question)
.where(questions: { user: @author_user, author_is_anonymous: false }) .where(questions: { user: @author_user, author_is_anonymous: false })
end end
def mark_inbox_entries_as_read
# using .dup to not modify @inbox -- useful in tests
@inbox&.dup&.update_all(new: false) # rubocop:disable Rails/SkipsModelValidations
end
end end

View File

@ -3,7 +3,13 @@
class ManifestsController < ApplicationController class ManifestsController < ApplicationController
include ThemeHelper include ThemeHelper
skip_before_action :banned?
skip_before_action :find_active_announcements
def show def show
expires_in 1.day
return if fresh_when current_user&.theme
render json: { render json: {
name: APP_CONFIG["site_name"], name: APP_CONFIG["site_name"],
description: t("about.index.subtitle"), description: t("about.index.subtitle"),
@ -12,17 +18,19 @@ class ManifestsController < ApplicationController
display: "standalone", display: "standalone",
categories: %w[social], categories: %w[social],
lang: I18n.locale, lang: I18n.locale,
shortcuts: [ shortcuts:,
webapp_shortcut(inbox_url, t("navigation.inbox"), "inbox")
],
icons: webapp_icons, icons: webapp_icons,
theme_color: theme_color, theme_color:,
background_color: mobile_theme_color background_color: mobile_theme_color
} }
end end
private private
def shortcuts = [
webapp_shortcut(inbox_url, t("navigation.inbox"), "inbox")
]
def webapp_shortcut(url, name, icon_name) def webapp_shortcut(url, name, icon_name)
{ {
name: name, name: name,

View File

@ -3,6 +3,8 @@
class NotificationsController < ApplicationController class NotificationsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
after_action :mark_notifications_as_read, only: %i[index]
TYPE_MAPPINGS = { TYPE_MAPPINGS = {
"answer" => Notification::QuestionAnswered.name, "answer" => Notification::QuestionAnswered.name,
"comment" => Notification::Commented.name, "comment" => Notification::Commented.name,
@ -14,8 +16,8 @@ class NotificationsController < ApplicationController
def index def index
@type = TYPE_MAPPINGS[params[:type]] || params[:type] @type = TYPE_MAPPINGS[params[:type]] || params[:type]
@notifications = cursored_notifications_for(type: @type, last_id: params[:last_id]) @notifications = cursored_notifications_for(type: @type, last_id: params[:last_id])
@notifications_last_id = @notifications.map(&:id).min paginate_notifications
@more_data_available = !cursored_notifications_for(type: @type, last_id: @notifications_last_id, size: 1).count.zero? @counters = count_unread_by_type
respond_to do |format| respond_to do |format|
format.html format.html
@ -25,6 +27,22 @@ class NotificationsController < ApplicationController
private private
def paginate_notifications
@notifications_last_id = @notifications.map(&:id).min
@more_data_available = !cursored_notifications_for(type: @type, last_id: @notifications_last_id, size: 1).count.zero?
end
def count_unread_by_type
Notification.where(recipient: current_user, new: true)
.group(:target_type)
.count(:target_type)
end
def mark_notifications_as_read
# using .dup to not modify @notifications -- useful in tests
@notifications&.dup&.update_all(new: false) # rubocop:disable Rails/SkipsModelValidations
end
def cursored_notifications_for(type:, last_id:, size: nil) def cursored_notifications_for(type:, last_id:, size: nil)
cursor_params = { last_id: last_id, size: size }.compact cursor_params = { last_id: last_id, size: size }.compact

View File

@ -8,22 +8,8 @@ class Settings::ThemeController < ApplicationController
def edit; end def edit; end
def update def update
update_attributes = params.require(:theme).permit(%i[
primary_color primary_text
danger_color danger_text
success_color success_text
warning_color warning_text
info_color info_text
dark_color dark_text
light_color light_text
raised_background raised_accent
background_color body_text
muted_text input_color
input_text input_placeholder
])
if current_user.theme.nil? if current_user.theme.nil?
current_user.theme = Theme.new update_attributes current_user.theme = Theme.new theme_attributes
current_user.theme.user_id = current_user.id current_user.theme.user_id = current_user.id
if current_user.theme.save if current_user.theme.save
@ -31,7 +17,7 @@ class Settings::ThemeController < ApplicationController
else else
flash[:error] = t(".error", errors: current_user.theme.errors.messages.flatten.join(" ")) flash[:error] = t(".error", errors: current_user.theme.errors.messages.flatten.join(" "))
end end
elsif current_user.theme.update(update_attributes) elsif current_user.theme.update(theme_attributes)
flash[:success] = t(".success") flash[:success] = t(".success")
else else
flash[:error] = t(".error", errors: current_user.theme.errors.messages.flatten.join(" ")) flash[:error] = t(".error", errors: current_user.theme.errors.messages.flatten.join(" "))
@ -43,4 +29,23 @@ class Settings::ThemeController < ApplicationController
current_user.theme.destroy! current_user.theme.destroy!
redirect_to edit_settings_theme_path redirect_to edit_settings_theme_path
end end
private
def theme_attributes
params.require(:theme).permit(%i[
primary_color primary_text
danger_color danger_text
success_color success_text
warning_color warning_text
info_color info_text
dark_color dark_text
light_color light_text
raised_background raised_accent
raised_text raised_accent_text
background_color body_text
muted_text input_color
input_text input_placeholder
])
end
end end

View File

@ -3,20 +3,13 @@
class UserController < ApplicationController class UserController < ApplicationController
before_action :set_user before_action :set_user
before_action :hidden_social_graph_redirect, only: %i[followers followings] before_action :hidden_social_graph_redirect, only: %i[followers followings]
after_action :mark_notification_as_read, only: %i[show]
def show def show
@answers = @user.cursored_answers(last_id: params[:last_id]) @answers = @user.cursored_answers(last_id: params[:last_id])
@answers_last_id = @answers.map(&:id).min @answers_last_id = @answers.map(&:id).min
@more_data_available = !@user.cursored_answers(last_id: @answers_last_id, size: 1).count.zero? @more_data_available = !@user.cursored_answers(last_id: @answers_last_id, size: 1).count.zero?
if user_signed_in?
notif = Notification.where(target_type: "Relationship", target_id: @user.active_follow_relationships.where(target_id: current_user.id).pluck(:id), recipient_id: current_user.id, new: true).first
unless notif.nil?
notif.new = false
notif.save
end
end
respond_to do |format| respond_to do |format|
format.html format.html
format.turbo_stream format.turbo_stream
@ -24,36 +17,40 @@ class UserController < ApplicationController
end end
def followers def followers
@title = "Followers" paginate_relationships(:cursored_follower_relationships)
@relationships = @user.cursored_follower_relationships(last_id: params[:last_id])
@relationships_last_id = @relationships.map(&:id).min
@more_data_available = !@user.cursored_follower_relationships(last_id: @relationships_last_id, size: 1).count.zero?
@users = @relationships.map(&:source) @users = @relationships.map(&:source)
@type = :follower own_relationships = find_own_relationships
locals = {
type: :follower,
own_followings: own_relationships[Relationships::Follow],
own_blocks: own_relationships[Relationships::Block],
own_mutes: own_relationships[Relationships::Mute]
}
respond_to do |format| respond_to do |format|
format.html { render "show_follow" } format.html { render "show_follow", locals: }
format.turbo_stream { render "show_follow" } format.turbo_stream { render "show_follow", locals: }
end end
end end
def followings def followings
@title = "Following" paginate_relationships(:cursored_following_relationships)
@relationships = @user.cursored_following_relationships(last_id: params[:last_id])
@relationships_last_id = @relationships.map(&:id).min
@more_data_available = !@user.cursored_following_relationships(last_id: @relationships_last_id, size: 1).count.zero?
@users = @relationships.map(&:target) @users = @relationships.map(&:target)
@type = :friend own_relationships = find_own_relationships
locals = {
type: :friend,
own_followings: own_relationships[Relationships::Follow],
own_blocks: own_relationships[Relationships::Block],
own_mutes: own_relationships[Relationships::Mute]
}
respond_to do |format| respond_to do |format|
format.html { render "show_follow" } format.html { render "show_follow", locals: }
format.turbo_stream { render "show_follow" } format.turbo_stream { render "show_follow", locals: }
end end
end end
def questions def questions
@title = "Questions"
@questions = @user.cursored_questions(author_is_anonymous: false, direct: direct_param, last_id: params[:last_id]) @questions = @user.cursored_questions(author_is_anonymous: false, direct: direct_param, last_id: params[:last_id])
@questions_last_id = @questions.map(&:id).min @questions_last_id = @questions.map(&:id).min
@more_data_available = !@user.cursored_questions(author_is_anonymous: false, direct: direct_param, last_id: @questions_last_id, size: 1).count.zero? @more_data_available = !@user.cursored_questions(author_is_anonymous: false, direct: direct_param, last_id: @questions_last_id, size: 1).count.zero?
@ -66,10 +63,36 @@ class UserController < ApplicationController
private private
def mark_notification_as_read
return unless user_signed_in?
Notification
.where(
target_type: "Relationship",
target_id: @user.active_follow_relationships.where(target_id: current_user.id).pluck(:id),
recipient_id: current_user.id,
new: true
).update(new: false)
end
def set_user def set_user
@user = User.where("LOWER(screen_name) = ?", params[:username].downcase).includes(:profile).first! @user = User.where("LOWER(screen_name) = ?", params[:username].downcase).includes(:profile).first!
end end
def find_own_relationships
return {} unless user_signed_in?
Relationship.where(source: current_user, target_id: @users.map(&:id))
&.select(:target_id, :type)
&.group_by(&:type)
end
def paginate_relationships(method)
@relationships = @user.public_send(method, last_id: params[:last_id])
@relationships_last_id = @relationships.map(&:id).min
@more_data_available = !@user.public_send(method, last_id: @relationships_last_id, size: 1).count.zero?
end
def hidden_social_graph_redirect def hidden_social_graph_redirect
return if belongs_to_current_user? || !@user.privacy_hide_social_graph return if belongs_to_current_user? || !@user.privacy_hide_social_graph

View File

@ -2,28 +2,30 @@
module ThemeHelper module ThemeHelper
ATTRIBUTE_MAP = { ATTRIBUTE_MAP = {
"primary_color" => %w[primary primary-rgb], "primary_color" => %w[primary primary-rgb],
"primary_text" => "primary-text", "primary_text" => "primary-text",
"danger_color" => "danger", "danger_color" => "danger",
"danger_text" => "danger-text", "danger_text" => "danger-text",
"warning_color" => "warning", "warning_color" => "warning",
"warning_text" => "warning-text", "warning_text" => "warning-text",
"info_color" => "info", "info_color" => "info",
"info_text" => "info-text", "info_text" => "info-text",
"success_color" => "success", "success_color" => "success",
"success_text" => "success-text", "success_text" => "success-text",
"dark_color" => "dark", "dark_color" => "dark",
"dark_text" => "dark-text", "dark_text" => "dark-text",
"light_color" => "light", "light_color" => "light",
"light_text" => "light-text", "light_text" => "light-text",
"raised_background" => %w[raised-bg raised-bg-rgb], "raised_background" => %w[raised-bg raised-bg-rgb],
"raised_accent" => %w[raised-accent raised-accent-rgb], "raised_text" => "raised-text",
"background_color" => "background", "raised_accent" => %w[raised-accent raised-accent-rgb],
"body_text" => "body-text", "raised_accent_text" => "raised-accent-text",
"input_color" => "input-bg", "background_color" => "background",
"input_text" => "input-text", "body_text" => "body-text",
"input_placeholder" => "input-placeholder", "input_color" => "input-bg",
"muted_text" => "muted-text" "input_text" => "input-text",
"input_placeholder" => "input-placeholder",
"muted_text" => "muted-text"
}.freeze }.freeze
def render_theme def render_theme

View File

@ -0,0 +1,49 @@
import { Controller } from '@hotwired/stimulus';
import Croppr from 'croppr';
export default class extends Controller {
static targets = ['input', 'controls', 'cropper', 'x', 'y', 'w', 'h'];
declare readonly inputTarget: HTMLInputElement;
declare readonly controlsTarget: HTMLElement;
declare readonly cropperTarget: HTMLImageElement;
declare readonly xTarget: HTMLInputElement;
declare readonly yTarget: HTMLInputElement;
declare readonly wTarget: HTMLInputElement;
declare readonly hTarget: HTMLInputElement;
static values = {
aspectRatio: String
};
declare readonly aspectRatioValue: string;
readImage(file: File, callback: (string) => void): void {
callback((window.URL || window.webkitURL).createObjectURL(file));
}
updateValues(data: Record<string, string>): void {
this.xTarget.value = data.x;
this.yTarget.value = data.y;
this.wTarget.value = data.width;
this.hTarget.value = data.height;
}
change(): void {
this.controlsTarget.classList.toggle('d-none');
if (this.inputTarget.files && this.inputTarget.files[0]) {
this.readImage(this.inputTarget.files[0], (src) => {
this.cropperTarget.src = src;
new Croppr(this.cropperTarget, {
aspectRatio: parseFloat(this.aspectRatioValue),
startSize: [100, 100, '%'],
onCropStart: this.updateValues.bind(this),
onCropMove: this.updateValues.bind(this),
onCropEnd: this.updateValues.bind(this)
});
});
}
}
}

View File

@ -1,6 +0,0 @@
export function authorSearchHandler(event: Event): void {
event.preventDefault();
const author = document.querySelector<HTMLInputElement>('#author')?.value;
window.location.href = `/inbox/${encodeURIComponent(author)}`;
}

View File

@ -62,6 +62,7 @@ export function deleteAllQuestionsHandler(event: Event): void {
export function deleteAllAuthorQuestionsHandler(event: Event): void { export function deleteAllAuthorQuestionsHandler(event: Event): void {
const button = event.target as Element; const button = event.target as Element;
const count = button.getAttribute('data-ib-count'); const count = button.getAttribute('data-ib-count');
const urlSearchParams = new URLSearchParams(window.location.search);
swal({ swal({
title: I18n.translate('frontend.inbox.confirm_all.title', { count: count }), title: I18n.translate('frontend.inbox.confirm_all.title', { count: count }),
@ -75,7 +76,7 @@ export function deleteAllAuthorQuestionsHandler(event: Event): void {
}, (returnValue) => { }, (returnValue) => {
if (returnValue === null) return false; if (returnValue === null) return false;
post(`/ajax/delete_all_inbox/${location.pathname.split('/')[2]}`) post(`/ajax/delete_all_inbox/${urlSearchParams.get('author')}`)
.then(async response => { .then(async response => {
const data = await response.json; const data = await response.json;

View File

@ -6,8 +6,7 @@ import { deleteAllAuthorQuestionsHandler, deleteAllQuestionsHandler } from './de
export default (): void => { export default (): void => {
registerEvents([ registerEvents([
{ type: 'click', target: '#ib-delete-all', handler: deleteAllQuestionsHandler, global: true }, { type: 'click', target: '#ib-delete-all', handler: deleteAllQuestionsHandler, global: true },
{ type: 'click', target: '#ib-delete-all-author', handler: deleteAllAuthorQuestionsHandler, global: true }, { type: 'click', target: '#ib-delete-all-author', handler: deleteAllAuthorQuestionsHandler, global: true }
{ type: 'submit', target: '#author-form', handler: authorSearchHandler, global: true }
]); ]);
registerInboxEntryEvents(); registerInboxEntryEvents();

View File

@ -1,61 +0,0 @@
import Croppr from 'croppr';
const readImage = (file, callback) => callback((window.URL || window.webkitURL).createObjectURL(file));
export function profilePictureChangeHandler(event: Event): void {
const input = event.target as HTMLInputElement;
const cropControls = document.querySelector('#profile-picture-crop-controls');
cropControls.classList.toggle('d-none');
if (input.files && input.files[0]) {
readImage(input.files[0], (src) => {
const updateValues = (data) => {
document.querySelector<HTMLInputElement>('#profile_picture_x').value = data.x;
document.querySelector<HTMLInputElement>('#profile_picture_y').value = data.y;
document.querySelector<HTMLInputElement>('#profile_picture_w').value = data.width;
document.querySelector<HTMLInputElement>('#profile_picture_h').value = data.height;
}
const cropper = document.querySelector<HTMLImageElement>('#profile-picture-cropper');
cropper.src = src;
new Croppr(cropper, {
aspectRatio: 1,
startSize: [100, 100, '%'],
onCropStart: updateValues,
onCropMove: updateValues,
onCropEnd: updateValues
});
});
}
}
export function profileHeaderChangeHandler(event: Event): void {
const input = event.target as HTMLInputElement;
const cropControls = document.querySelector('#profile-header-crop-controls');
cropControls.classList.toggle('d-none');
if (input.files && input.files[0]) {
readImage(input.files[0], (src) => {
const updateValues = (data) => {
document.querySelector<HTMLInputElement>('#profile_header_x').value = data.x;
document.querySelector<HTMLInputElement>('#profile_header_y').value = data.y;
document.querySelector<HTMLInputElement>('#profile_header_w').value = data.width;
document.querySelector<HTMLInputElement>('#profile_header_h').value = data.height;
}
const cropper = document.querySelector<HTMLImageElement>('#profile-header-cropper');
cropper.src = src;
new Croppr(cropper, {
aspectRatio: 7/30,
startSize: [100, 100, '%'],
onCropStart: updateValues,
onCropMove: updateValues,
onCropEnd: updateValues
});
});
}
}

View File

@ -1,11 +1,8 @@
import registerEvents from "utilities/registerEvents"; import registerEvents from "utilities/registerEvents";
import { profileHeaderChangeHandler, profilePictureChangeHandler } from "./crop";
import { userSubmitHandler } from "./password"; import { userSubmitHandler } from "./password";
export default (): void => { export default (): void => {
registerEvents([ registerEvents([
{ type: 'submit', target: document.querySelector('#edit_user'), handler: userSubmitHandler }, { type: 'submit', target: document.querySelector('#edit_user'), handler: userSubmitHandler }
{ type: 'change', target: document.querySelector('#user_profile_picture[type=file]'), handler: profilePictureChangeHandler },
{ type: 'change', target: document.querySelector('#user_profile_header[type=file]'), handler: profileHeaderChangeHandler }
]); ]);
} }

View File

@ -7,6 +7,7 @@ import FormatPopupController from "retrospring/controllers/format_popup_controll
import CollapseController from "retrospring/controllers/collapse_controller"; import CollapseController from "retrospring/controllers/collapse_controller";
import ThemeController from "retrospring/controllers/theme_controller"; import ThemeController from "retrospring/controllers/theme_controller";
import CapabilitiesController from "retrospring/controllers/capabilities_controller"; import CapabilitiesController from "retrospring/controllers/capabilities_controller";
import CropperController from "retrospring/controllers/cropper_controller";
/** /**
* This module sets up Stimulus and our controllers * This module sets up Stimulus and our controllers
@ -23,6 +24,7 @@ export default function (): void {
window['Stimulus'].register('character-count', CharacterCountController); window['Stimulus'].register('character-count', CharacterCountController);
window['Stimulus'].register('character-count-warning', CharacterCountWarningController); window['Stimulus'].register('character-count-warning', CharacterCountWarningController);
window['Stimulus'].register('collapse', CollapseController); window['Stimulus'].register('collapse', CollapseController);
window['Stimulus'].register('cropper', CropperController);
window['Stimulus'].register('format-popup', FormatPopupController); window['Stimulus'].register('format-popup', FormatPopupController);
window['Stimulus'].register('theme', ThemeController); window['Stimulus'].register('theme', ThemeController);
} }

View File

@ -14,7 +14,9 @@ export const THEME_MAPPING = {
'light_color': 'light', 'light_color': 'light',
'light_text': 'light-text', 'light_text': 'light-text',
'raised_background': 'raised-bg', 'raised_background': 'raised-bg',
'raised_text': 'raised-text',
'raised_accent': 'raised-accent', 'raised_accent': 'raised-accent',
'raised_accent_text': 'raised-accent-text',
'background_color': 'background', 'background_color': 'background',
'body_text': 'body-text', 'body_text': 'body-text',
'input_color': 'input-bg', 'input_color': 'input-bg',

View File

@ -11,11 +11,11 @@ class Notification < ApplicationRecord
define_cursor_paginator :cursored_for_type, :for_type define_cursor_paginator :cursored_for_type, :for_type
def for(recipient, **kwargs) def for(recipient, **kwargs)
where(kwargs.merge!(recipient:)).order(:created_at).reverse_order where(kwargs.merge!(recipient:)).includes(:target).order(:created_at).reverse_order
end end
def for_type(recipient, type, **kwargs) def for_type(recipient, type, **kwargs)
where(kwargs.merge!(recipient:)).where(type:).order(:created_at).reverse_order where(kwargs.merge!(recipient:)).includes(:target).where(type:).order(:created_at).reverse_order
end end
def notify(recipient, target) def notify(recipient, target)

View File

@ -5,10 +5,11 @@ module User::AnswerMethods
define_cursor_paginator :cursored_answers, :ordered_answers define_cursor_paginator :cursored_answers, :ordered_answers
# @return [ActiveRecord::Relation<Answer>] List of a user's answers
def ordered_answers def ordered_answers
answers answers
.order(:created_at) .order(:created_at)
.reverse_order .reverse_order
.includes(comments: [:user, :smiles], question: [:user], smiles: [:user]) .includes(comments: %i[user smiles], question: { user: :profile }, smiles: [:user])
end end
end end

View File

@ -5,9 +5,10 @@ module User::InboxMethods
define_cursor_paginator :cursored_inbox, :ordered_inbox define_cursor_paginator :cursored_inbox, :ordered_inbox
# @return [ActiveRecord::Relation<Inbox>] the user's inbox entries
def ordered_inbox def ordered_inbox
inboxes inboxes
.includes(:question, :user) .includes(:question, user: :profile)
.order(:created_at) .order(:created_at)
.reverse_order .reverse_order
end end

View File

@ -5,6 +5,7 @@ module User::QuestionMethods
define_cursor_paginator :cursored_questions, :ordered_questions define_cursor_paginator :cursored_questions, :ordered_questions
# @return [ActiveRecord::Relation<Question>] List of questions sent by the user
def ordered_questions(author_is_anonymous: nil, direct: nil) def ordered_questions(author_is_anonymous: nil, direct: nil)
questions questions
.where({ author_is_anonymous:, direct: }.compact) .where({ author_is_anonymous:, direct: }.compact)

View File

@ -6,10 +6,12 @@ module User::RelationshipMethods
define_cursor_paginator :cursored_following_relationships, :ordered_following_relationships define_cursor_paginator :cursored_following_relationships, :ordered_following_relationships
define_cursor_paginator :cursored_follower_relationships, :ordered_follower_relationships define_cursor_paginator :cursored_follower_relationships, :ordered_follower_relationships
# @return [ActiveRecord::Relation<Relationships::Follow>] List of the user's following relationships
def ordered_following_relationships def ordered_following_relationships
active_follow_relationships.reverse_order.includes(target: [:profile]) active_follow_relationships.reverse_order.includes(target: [:profile])
end end
# @return [ActiveRecord::Relation<Relationships::Follow>] List of the user's follower relationships
def ordered_follower_relationships def ordered_follower_relationships
passive_follow_relationships.reverse_order.includes(source: [:profile]) passive_follow_relationships.reverse_order.includes(source: [:profile])
end end

View File

@ -5,8 +5,12 @@ module User::TimelineMethods
define_cursor_paginator :cursored_timeline, :timeline define_cursor_paginator :cursored_timeline, :timeline
# @return [Array] the users' timeline # @return [ActiveRecord::Relation<Answer>] the user's timeline
def timeline def timeline
Answer.where("user_id in (?) OR user_id = ?", following_ids, id).order(:created_at).reverse_order.includes(comments: %i[user smiles], question: [:user], user: [:profile], smiles: [:user]) Answer
.where("user_id in (?) OR user_id = ?", following_ids, id)
.order(:created_at)
.reverse_order
.includes(comments: %i[user smiles], question: { user: :profile }, user: [:profile], smiles: [:user])
end end
end end

View File

@ -7,6 +7,7 @@ class TypoedEmailValidator < ActiveModel::EachValidator
INVALID_ENDINGS = [ INVALID_ENDINGS = [
# with @: # with @:
*%w[ *%w[
aoo.com
fmail.com fmail.com
gail.com gail.com
gamil.com gamil.com
@ -19,6 +20,7 @@ class TypoedEmailValidator < ActiveModel::EachValidator
gmaile.com gmaile.com
gmaill.com gmaill.com
gmali.com gmali.com
gmaul.com
gnail.com gnail.com
hotamil.com hotamil.com
hotmai.com hotmai.com

View File

@ -40,9 +40,9 @@
= render "shared/format_link" = render "shared/format_link"
.card-footer.d-none{ id: "ib-options-#{i.id}" } .card-footer.d-none{ id: "ib-options-#{i.id}" }
%h4= t(".sharing.heading") %h4= t(".sharing.heading")
- if current_user.services.count.positive? - if services.count.positive?
.row .row
- current_user.services.each do |service| - services.each do |service|
.col-md-3.col-sm-4.col-xs-6 .col-md-3.col-sm-4.col-xs-6
%label %label
%input{ type: "checkbox", name: "ib-share", checked: :checked, data: { ib_id: i.id, service: service.provider } } %input{ type: "checkbox", name: "ib-share", checked: :checked, data: { ib_id: i.id, service: service.provider } }

View File

@ -1,6 +1,6 @@
#entries #entries
- @inbox.each do |i| - @inbox.each do |i|
= render "inbox/entry", i: = render "inbox/entry", services:, i:
- if @inbox.empty? - if @inbox.empty?
%p.empty= t(".empty") %p.empty= t(".empty")

View File

@ -1,6 +1,6 @@
= turbo_stream.append "entries" do = turbo_stream.append "entries" do
- @inbox.each do |i| - @inbox.each do |i|
= render "inbox/entry", i: = render "inbox/entry", services:, i:
= turbo_stream.update "paginator" do = turbo_stream.update "paginator" do
- if @more_data_available - if @more_data_available

View File

@ -9,6 +9,5 @@
= render 'shared/links' = render 'shared/links'
:ruby :ruby
@inbox.update_all(new: false)
provide(:title, generate_title('Inbox')) provide(:title, generate_title('Inbox'))
parent_layout 'base' parent_layout 'base'

View File

@ -8,5 +8,4 @@
.d-block.d-sm-none= render "shared/links" .d-block.d-sm-none= render "shared/links"
:ruby :ruby
Notification.for(current_user).update_all(new: false)
parent_layout 'base' parent_layout 'base'

View File

@ -2,36 +2,38 @@
.card-body .card-body
= bootstrap_form_for(current_user, url: settings_profile_picture_path, html: { multipart: true }, method: :patch, data: { turbo: false }) do |f| = bootstrap_form_for(current_user, url: settings_profile_picture_path, html: { multipart: true }, method: :patch, data: { turbo: false }) do |f|
.d-flex#profile-picture-media %div{ data: { controller: "cropper", cropper_aspect_ratio_value: "1" } }
.flex-shrink-0 .d-flex
%img.avatar-lg.me-3{ src: current_user.profile_picture.url(:medium) } .flex-shrink-0
.flex-grow-1 %img.avatar-lg.me-3{ src: current_user.profile_picture.url(:medium) }
= f.file_field :profile_picture, accept: APP_CONFIG[:accepted_image_formats].join(",") .flex-grow-1
= f.file_field :profile_picture, accept: APP_CONFIG[:accepted_image_formats].join(","), data: { cropper_target: "input", action: "cropper#change" }
.row.d-none#profile-picture-crop-controls .row.d-none{ data: { cropper_target: "controls" } }
.col-sm-10.col-md-8 .col-sm-10.col-md-8
%strong= t(".adjust.profile_picture") %strong= t(".adjust.profile_picture")
%img#profile-picture-cropper{ src: current_user.profile_picture.url(:medium) } %img{ src: current_user.profile_picture.url(:medium), data: { cropper_target: "cropper" } }
.row.mb-2#profile-header-media - %i[profile_picture_x profile_picture_y profile_picture_w profile_picture_h].each do |attrib|
.col-xs-12.col-md-6 = f.hidden_field attrib, id: attrib, data: { cropper_target: attrib.to_s.split("_").last }
%img.mw-100.me-3{ src: current_user.profile_header.url(:mobile) }
.col-xs-12.col-md-6.mt-3.mt-sm-0.ps-3.pe-3
= f.file_field :profile_header, accept: APP_CONFIG[:accepted_image_formats].join(",")
.row.d-none#profile-header-crop-controls %div{ data: { controller: "cropper", cropper_aspect_ratio_value: "0.23" } }
.col-sm-10.col-md-8 .row.mb-2
%strong= t(".adjust.profile_header") .col-xs-12.col-md-6
%img#profile-header-cropper{ src: current_user.profile_header.url(:web) } %img.mw-100.me-3{ src: current_user.profile_header.url(:mobile) }
.col-xs-12.col-md-6.mt-3.mt-sm-0.ps-3.pe-3
= f.file_field :profile_header, accept: APP_CONFIG[:accepted_image_formats].join(","), data: { cropper_target: "input", action: "cropper#change" }
.row.d-none{ data: { cropper_target: "controls" } }
.col-sm-10.col-md-8
%strong= t(".adjust.profile_header")
%img{ src: current_user.profile_header.url(:web), data: { cropper_target: "cropper" } }
- %i[profile_header_x profile_header_y profile_header_w profile_header_h].each do |attrib|
= f.hidden_field attrib, id: attrib, data: { cropper_target: attrib.to_s.split("_").last }
= f.check_box :show_foreign_themes = f.check_box :show_foreign_themes
- %i[profile_picture_x profile_picture_y profile_picture_w profile_picture_h].each do |attrib|
= f.hidden_field attrib, id: attrib
- %i[profile_header_x profile_header_y profile_header_w profile_header_h].each do |attrib|
= f.hidden_field attrib, id: attrib
= f.primary t(".submit_picture") = f.primary t(".submit_picture")
.card .card
.card-body .card-body

View File

@ -21,6 +21,23 @@
= f.text_field :background_color, class: "color", data: { default: 0xF0EDF4, theme_target: "color", action: "theme#updatePreview" } = f.text_field :background_color, class: "color", data: { default: 0xF0EDF4, theme_target: "color", action: "theme#updatePreview" }
.col-sm-6 .col-sm-6
= f.text_field :body_text, class: "color", data: { default: 0x000000, theme_target: "color", action: "theme#updatePreview" } = f.text_field :body_text, class: "color", data: { default: 0x000000, theme_target: "color", action: "theme#updatePreview" }
.card
.card-body
%h2= t(".raised.heading")
%p= t(".raised.body")
.row
.col-sm-6
= f.text_field :raised_background, class: "color", data: { default: 0xFFFFFF, theme_target: "color", action: "theme#updatePreview" }
.col-sm-6
= f.text_field :raised_text, class: "color", data: { default: 0x000000, theme_target: "color", action: "theme#updatePreview" }
.row
.col-sm-6
= f.text_field :raised_accent, class: "color", data: { default: 0xF7F7F7, theme_target: "color", action: "theme#updatePreview" }
.col-sm-6
= f.text_field :raised_accent_text, class: "color", data: { default: 0x000000, theme_target: "color", action: "theme#updatePreview" }
.card-footer
%p= t(".raised.accent.example")
.card .card
.card-body .card-body
%h2= t(".colors.heading") %h2= t(".colors.heading")
@ -93,23 +110,11 @@
.row .row
.col-sm-6 .col-sm-6
= f.text_field :input_placeholder, class: "color", data: { default: 0x6C757D, theme_target: "color" } = f.text_field :input_placeholder, class: "color", data: { default: 0x6C757D, theme_target: "color", action: "theme#updatePreview" }
.col-sm-6 .col-sm-6
.form-group .form-group
%label.form-label Example Input %label.form-label Example Input
%input.form-control{ placeholder: "A test placeholder" } %input.form-control{ placeholder: "A test placeholder" }
.card
.card-body
%h2= t(".raised.heading")
%p= t(".raised.body")
.row
.col-sm-6
= f.text_field :raised_background, class: "color", data: { default: 0xFFFFFF, theme_target: "color" }
.col-sm-6
= f.text_field :raised_accent, class: "color", data: { default: 0xF7F7F7, theme_target: "color" }
.card-footer
%p= t(".raised.accent.example")
.card .card
.card-body .card-body
.pull-left .pull-left

View File

@ -9,4 +9,4 @@
= user.profile.display_name = user.profile.display_name
.profile__screen-name .profile__screen-name
= user.screen_name = user.screen_name
= render 'user/actions', user: user, type: type = render "user/actions", user:, type:, own_followings:, own_blocks:, own_mutes:

View File

@ -9,18 +9,18 @@
.list-group .list-group
= list_group_item t('.answer'), = list_group_item t('.answer'),
notifications_path('answer'), notifications_path('answer'),
badge: Notification.for(current_user).where(target_type: 'Answer', new: true).count badge: @counters['Answer']
= list_group_item t('.smile'), = list_group_item t('.smile'),
notifications_path('smile'), notifications_path('smile'),
badge: Notification.for(current_user).where(target_type: 'Smile', new: true).count badge: @counters['Smile']
= list_group_item t('.comment'), = list_group_item t('.comment'),
notifications_path('comment'), notifications_path('comment'),
badge: Notification.for(current_user).where(target_type: 'Comment', new: true).count badge: @counters['Comment']
= list_group_item t('.commentsmile'), = list_group_item t('.commentsmile'),
notifications_path('commentsmile'), notifications_path('commentsmile'),
badge: Notification.for(current_user).where(target_type: 'CommentSmile', new: true).count badge: @counters['CommentSmile']
= list_group_item t('.relationship'), = list_group_item t('.relationship'),
notifications_path('relationship'), notifications_path('relationship'),
badge: Notification.for(current_user).where(target_type: 'Relationship', new: true).count badge: @counters['Relationship']
.d-none.d-sm-block= render 'shared/links' .d-none.d-sm-block= render 'shared/links'

View File

@ -1,11 +1,14 @@
.profile__actions .profile__actions
- type ||= :nil - type ||= :nil
- own_followings ||= nil
- own_blocks ||= nil
- own_mutes ||= nil
- if user_signed_in? && user == current_user - if user_signed_in? && user == current_user
.d-grid .d-grid
%a.btn.btn-dark{ href: settings_profile_path } Edit profile %a.btn.btn-dark{ href: settings_profile_path } Edit profile
- elsif user_signed_in? - elsif user_signed_in?
.d-grid.gap-2 .d-grid.gap-2
- if current_user.following? user - if own_followings&.include?(user.id) || current_user.following?(user)
%button.btn.btn-primary{ type: :button, name: 'user-action', data: { action: :unfollow, type: type, target: user.screen_name } } %button.btn.btn-primary{ type: :button, name: 'user-action', data: { action: :unfollow, type: type, target: user.screen_name } }
= t("voc.unfollow") = t("voc.unfollow")
- else - else
@ -19,7 +22,7 @@
%a.dropdown-item.d-block.d-sm-none{ href: '#', data: { bs_target: '#modal-list-memberships', bs_toggle: :modal } } %a.dropdown-item.d-block.d-sm-none{ href: '#', data: { bs_target: '#modal-list-memberships', bs_toggle: :modal } }
%i.fa.fa-list.fa-fw %i.fa.fa-list.fa-fw
= t(".list") = t(".list")
- if current_user.blocking?(user) - if own_blocks&.include?(user.id) || current_user.blocking?(user)
%a.dropdown-item{ href: '#', data: { action: :unblock, target: user.screen_name } } %a.dropdown-item{ href: '#', data: { action: :unblock, target: user.screen_name } }
%i.fa.fa-minus-circle.fa-fw %i.fa.fa-minus-circle.fa-fw
%span.pe-none= t("voc.unblock") %span.pe-none= t("voc.unblock")
@ -27,7 +30,7 @@
%a.dropdown-item{ href: '#', data: { action: :block, target: user.screen_name } } %a.dropdown-item{ href: '#', data: { action: :block, target: user.screen_name } }
%i.fa.fa-minus-circle.fa-fw %i.fa.fa-minus-circle.fa-fw
%span.pe-none= t("voc.block") %span.pe-none= t("voc.block")
- if current_user.muting?(user) - if own_mutes&.include?(user.id) || current_user.muting?(user)
%a.dropdown-item{ href: '#', data: { action: :unmute, target: user.screen_name } } %a.dropdown-item{ href: '#', data: { action: :unmute, target: user.screen_name } }
%i.fa.fa-volume-off.fa-fw %i.fa.fa-volume-off.fa-fw
%span.pe-none= t("voc.unmute") %span.pe-none= t("voc.unmute")

View File

@ -1,15 +1,15 @@
.row.row-cols-1.row-cols-sm-2.row-cols-md-3#users .row.row-cols-1.row-cols-sm-2.row-cols-md-3#users
- @users.each do |user| - @users.each do |user|
.col.pb-3 .col.pb-3
= render 'shared/userbox', user: user, type: @type = render "shared/userbox", user:, type:, own_followings:, own_blocks:, own_mutes:
- if @more_data_available - if @more_data_available
.d-flex.justify-content-center.justify-content-sm-start#paginator .d-flex.justify-content-center.justify-content-sm-start#paginator
= button_to t("voc.load"), @type == :follower ? show_user_followers_path(@user) : show_user_followings_path(@user), = button_to t("voc.load"), type == :follower ? show_user_followers_path(@user) : show_user_followings_path(@user),
class: "btn btn-light", class: "btn btn-light",
method: :get, method: :get,
params: { last_id: @relationships_last_id }, params: { last_id: @relationships_last_id },
form: { data: { turbo_stream: true } } form: { data: { turbo_stream: true } }
- provide(:title, user_title(@user, 'friends and followers')) - provide(:title, t(".title.#{type}", user: @user.profile.safe_name))
- parent_layout 'user/profile' - parent_layout "user/profile"

View File

@ -1,11 +1,11 @@
= turbo_stream.append "users" do = turbo_stream.append "users" do
- @users.each do |user| - @users.each do |user|
.col.pb-3 .col.pb-3
= render 'shared/userbox', user: user, type: @type = render "shared/userbox", user:, type:, own_followings:, own_blocks:, own_mutes:
= turbo_stream.update "paginator" do = turbo_stream.update "paginator" do
- if @more_data_available - if @more_data_available
= button_to t("voc.load"), @type == :follower ? show_user_followers_path(@user) : show_user_followings_path(@user), = button_to t("voc.load"), type == :follower ? show_user_followers_path(@user) : show_user_followings_path(@user),
class: "btn btn-light", class: "btn btn-light",
method: :get, method: :get,
params: { last_id: @relationships_last_id }, params: { last_id: @relationships_last_id },

View File

@ -1 +1,6 @@
QuestionGenerator.compile # frozen_string_literal: true
Rails.application.config.to_prepare do
QuestionGenerator.question_base_path = File.expand_path("../questions", __dir__)
QuestionGenerator.compile
end

View File

@ -8,4 +8,20 @@ Sentry.init do |config|
# of transactions for performance monitoring. # of transactions for performance monitoring.
# We recommend adjusting this value in production # We recommend adjusting this value in production
config.traces_sample_rate = 0.25 config.traces_sample_rate = 0.25
exception_fingerprints = {
Excon::Error::ServiceUnavailable => 'external-service',
Twitter::Error::InternalServerError => 'external-service',
}
config.before_send = lambda do |event, hint|
# These are used for user-facing errors, not when something goes wrong
next if hint[:exception].is_a?(Errors::Base)
exception_class = hint[:exception].class
if exception_fingerprints.key?(exception_class)
event.fingerprint = [exception_fingerprints[exception_class]]
end
event
end
end end

View File

@ -49,7 +49,9 @@ en:
primary_color: "Primary colour" primary_color: "Primary colour"
primary_text: "Primary text colour" primary_text: "Primary text colour"
raised_accent: "Raised accent colour" raised_accent: "Raised accent colour"
raised_accent_text: "Raised accent text colour"
raised_background: "Raised background colour" raised_background: "Raised background colour"
raised_text: "Raised text colour"
success_color: "Success colour" success_color: "Success colour"
success_text: "Success text colour" success_text: "Success text colour"
warning_color: "Warning colour" warning_color: "Warning colour"

View File

@ -651,6 +651,10 @@ en:
index: index:
title: "Questions from %{author_identifier}" title: "Questions from %{author_identifier}"
user: user:
show_follow:
title:
follower: "%{user}'s followers"
friend: "%{user}'s followings"
actions: actions:
view_inbox: "View inbox" view_inbox: "View inbox"
privilege: "Check %{user}'s privileges" privilege: "Check %{user}'s privileges"

311
config/questions/en.yml Normal file
View File

@ -0,0 +1,311 @@
Do:
you:
- recycle
- have:
- a:
- nickname
- any:
- siblings
- pets
- like:
- social networks
- muffins
- video games
- the:
- city:
- or:
- country
- where you:
- live
- to:
- travel
- sing out loud when no one else is around
- want:
- old times back
- to know more:
about:
- your:
- future
- relatives
- history
- new inventions
- more:
- money
- friends
- things than you need
- think that:
- late is better than never
- prefer:
to:
- take baths or showers
your:
friends:
like:
- you:
- the way you are
- social networks
- muffins
- video games
know:
[much, too much]:
about:
- your:
- life
- hobbies
- family
- friends
- you
What:
- was:
the:
best:
- day of your life like
last:
song:
- you listened on repeat
thing:
you:
have:
- bought
- done
- eaten
your:
favourite:
song:
- as a 5 year old
- from a few weeks ago
- would:
you:
do:
if:
you:
had:
- one million dollars
- the ability to fly
were:
- a dragon
woke up:
with:
- someone else next to you
- some drawings in your face
- do:
you:
do:
- for fun
- 'on':
- the:
- weekend
like:
more,:
- dogs or cats
- shower or bath
- tea or coffee
think:
about:
- the:
- Internet
- Bad Dragon
- dragons
- '"fact" accounts'
- society
- cats
- surveillance
- coyotes
- raccoons
- foxes
- dogs
- lizards
- [activity, activities]:
do:
you:
- enjoy the most
- kind:
of:
animals:
- do you have
- habit:
do:
you:
really:
find:
cute:
- in a person
- is:
your:
- first memory
- dream job
- favourite:
- season
- video game
- board game
- sports team
- activity
- beverage
- Internet browser
- piece of music
- console
- snack
- food
- animal
- programming language:
- ''
- and why is it:
- PHP
- Rust
- C++
- Lisp
- Pascal
- Swift
- Ruby
- website:
- ''
- and why is it:
- Reddit
- Twitter
- Facebook
- YouTube
the:
[best, worst]:
thing:
- about:
the:
- Internet
your:
- favourite:
- series
- movie
- book
- country
- hometown
- one can do:
- if:
- "they're bored"
- ever
fast:
food:
- chain
one:
- thing you would like to become better at
- was:
the:
last:
thing:
you:
- did
- ate
- looked for
[best, worst]:
thing:
- "you've eaten so far"
- languages do you know
- apps do you use daily
- 'is heavier: a kilogram of steel, or a kilogram of feathers'
Can:
you:
- swim
- speak:
- different:
- languages
- play:
- any:
- sports
- the:
- piano
- guitar
- trumpet
- saxophone
- baseball
- ski
- cook
- dance
- yodel
- draw
Are:
you:
- religious
Have:
you:
ever:
- been:
- to:
- Austria
- Germany
- Japan
- Switzerland
- France
- Sweden
- Norway
- Finland
- Australia
- Italy
- Russia
- China
- the:
- USA
- United Kingdom
- caught:
- "doing things you shouldn't do"
- cheating
- mistaken for:
- someone else
- listened to:
- classical music
- music:
- "from the 80's"
- dubstep
- nightcore
- metal
- had:
- an:
- accident
- written:
- a love letter
- code
- in Japanese
How:
has:
your:
day:
- been
old:
are:
- you
many:
- open tabs do you currently have
- followers is too many
Where:
do:
you:
- work
- live
Which:
food:
do:
you:
- love
- hate
Who:
is:
the:
- most famous person you have met
your:
- favourite:
- Retrospring user
- actor
- artist
- comedian
- musician
Why:
do:
- they call it oven when you of in the cold food of out hot eat the food
Would:
you:
rather:
live:
- in:
- a city or in the countryside
- a flat or in a house
lose:
- an arm or a leg
be:
- the best player on a horrible team or the worst player on a great team

View File

@ -157,14 +157,14 @@ Rails.application.routes.draw do
get "/@:username/q/:id", to: "question#show", as: :question get "/@:username/q/:id", to: "question#show", as: :question
get "/@:username/followers", to: "user#followers", as: :show_user_followers get "/@:username/followers", to: "user#followers", as: :show_user_followers
get "/@:username/followings", to: "user#followings", as: :show_user_followings get "/@:username/followings", to: "user#followings", as: :show_user_followings
get "/@:username/friends", to: redirect("/@%{username}/followings/p/%{page}") get "/@:username/friends", to: redirect("/@%{username}/followings")
get "/@:username/questions", to: "user#questions", as: :show_user_questions get "/@:username/questions", to: "user#questions", as: :show_user_questions
get "/:username", to: "user#show", as: :user_alt get "/:username", to: "user#show", as: :user_alt
get "/:username/a/:id", to: "answer#show", as: :answer_alt get "/:username/a/:id", to: "answer#show", as: :answer_alt
get "/:username/q/:id", to: "question#show", as: :question_alt get "/:username/q/:id", to: "question#show", as: :question_alt
get "/:username/followers", to: "user#followers", as: :show_user_followers_alt get "/:username/followers", to: "user#followers", as: :show_user_followers_alt
get "/:username/followings", to: "user#followings", as: :show_user_followings_alt get "/:username/followings", to: "user#followings", as: :show_user_followings_alt
get "/:username/friends", to: redirect("/%{username}/followings/p/%{page}") get "/:username/friends", to: redirect("/%{username}/followings")
get "/:username/questions", to: "user#questions", as: :show_user_questions_alt get "/:username/questions", to: "user#questions", as: :show_user_questions_alt
get "/feedback/consent", to: "feedback#consent", as: "feedback_consent" get "/feedback/consent", to: "feedback#consent", as: "feedback_consent"

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddRaisedAndAccentTextToThemes < ActiveRecord::Migration[6.1]
def up
add_column :themes, :raised_text, :integer, default: 0x000000, null: false
add_column :themes, :raised_accent_text, :integer, default: 0x000000, null: false
end
def down
remove_column :themes, :raised_text
remove_column :themes, :raised_accent_text
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class UseBodyTextForRaisedTextsInThemes < ActiveRecord::Migration[6.1]
def up
execute <<~SQUIRREL
UPDATE themes
SET raised_text = body_text,
raised_accent_text = body_text
WHERE body_text != 0;
SQUIRREL
end
def down; end
end

View File

@ -181,7 +181,7 @@ ActiveRecord::Schema.define(version: 2023_02_03_054229) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id" t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
t.index ["resource_type", "resource_id"], name: "index_roles_on_resource" t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
end end
create_table "rpush_apps", force: :cascade do |t| create_table "rpush_apps", force: :cascade do |t|
@ -301,6 +301,8 @@ ActiveRecord::Schema.define(version: 2023_02_03_054229) do
t.integer "light_color", default: 16316922 t.integer "light_color", default: 16316922
t.integer "light_text", default: 0 t.integer "light_text", default: 0
t.integer "input_placeholder", default: 7107965, null: false t.integer "input_placeholder", default: 7107965, null: false
t.integer "raised_text", default: 0, null: false
t.integer "raised_accent_text", default: 0, null: false
t.index ["user_id", "created_at"], name: "index_themes_on_user_id_and_created_at" t.index ["user_id", "created_at"], name: "index_themes_on_user_id_and_created_at"
end end

View File

@ -19,7 +19,7 @@ services:
- 1234:1234 - 1234:1234
redis: redis:
image: redis:3.2.11-alpine image: redis:6.2.10-alpine
ports: ports:
- 6379:6379 - 6379:6379

View File

@ -17,9 +17,9 @@ module Retrospring
def month = 1 def month = 1
def day = 23 def day = 31
def patch = 0 def patch = 1
def suffix = "" def suffix = ""

View File

@ -28,7 +28,7 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.11.0", "@typescript-eslint/eslint-plugin": "^4.11.0",
"@typescript-eslint/parser": "^4.11.0", "@typescript-eslint/parser": "^4.11.0",
"esbuild": "^0.17.4", "esbuild": "^0.17.5",
"eslint": "^7.16.0", "eslint": "^7.16.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"stylelint": "^14.16.1", "stylelint": "^14.16.1",

View File

@ -74,7 +74,7 @@
<div class="dialog"> <div class="dialog">
<div> <div>
<h1>This page takes way too long to load!</h1> <h1>This page takes way too long to load!</h1>
<img src="images/errors/original/angry_unicorn.png" alt="Unicorn"> <img src="/images/errors/original/angry_unicorn.png" alt="Unicorn">
</div> </div>
<p>Sorry about that. Please try refreshing and contact us if the problem persists.</p> <p>Sorry about that. Please try refreshing and contact us if the problem persists.</p>
</div> </div>

View File

@ -47,6 +47,9 @@ self.addEventListener('install', function (event) {
}); });
self.addEventListener('fetch', function (event) { self.addEventListener('fetch', function (event) {
const url = new URL(event.request.url);
if (event.request.method !== 'GET' || !OFFLINE_CACHE_PATHS.includes(url.pathname)) return;
event.respondWith( event.respondWith(
(async () => { (async () => {
try { try {

View File

@ -58,6 +58,10 @@ describe InboxController, type: :controller do
end end
end end
it "updates the inbox entry status" do
expect { subject }.to change { inbox_entry.reload.new? }.from(true).to(false)
end
context "when requested the turbo stream format" do context "when requested the turbo stream format" do
subject { get :show, format: :turbo_stream } subject { get :show, format: :turbo_stream }
@ -175,9 +179,7 @@ describe InboxController, type: :controller do
inbox: [], inbox: [],
inbox_last_id: nil, inbox_last_id: nil,
more_data_available: false, more_data_available: false,
inbox_count: 0, inbox_count: 0
delete_id: "ib-delete-all",
disabled: true
} }
end end
end end
@ -209,9 +211,7 @@ describe InboxController, type: :controller do
inbox: [], inbox: [],
inbox_last_id: nil, inbox_last_id: nil,
more_data_available: false, more_data_available: false,
inbox_count: 0, inbox_count: 0
delete_id: "ib-delete-all",
disabled: true
} }
end end
end end

View File

@ -33,5 +33,9 @@ describe NotificationsController do
expect(response).to render_template(:index) expect(response).to render_template(:index)
expect(controller.instance_variable_get(:@notifications)).to have_attributes(size: 2) expect(controller.instance_variable_get(:@notifications)).to have_attributes(size: 2)
end end
it "marks notifications as read" do
expect { subject }.to change { Notification.for(user).where(new: true).count }.from(2).to(0)
end
end end
end end

View File

@ -15,12 +15,14 @@ FactoryBot.define do
dark_color { 6_710_886 } dark_color { 6_710_886 }
dark_text { 15_658_734 } dark_text { 15_658_734 }
raised_background { 16_777_215 } raised_background { 16_777_215 }
raised_text { 3_355_443 }
background_color { 13_026_795 } background_color { 13_026_795 }
body_text { 3_355_443 } body_text { 3_355_443 }
muted_text { 3_355_443 } muted_text { 3_355_443 }
input_color { 15_789_556 } input_color { 15_789_556 }
input_text { 6_710_886 } input_text { 6_710_886 }
raised_accent { 16_250_871 } raised_accent { 16_250_871 }
raised_accent_text { 3_355_443 }
light_color { 16_316_922 } light_color { 16_316_922 }
light_text { 0 } light_text { 0 }
end end

View File

@ -22,32 +22,34 @@ describe UseCase::DataExport::Theme, :data_export do
expect(json_file("theme.json")).to eq( expect(json_file("theme.json")).to eq(
{ {
theme: { theme: {
id: theme.id, id: theme.id,
user_id: user.id, user_id: user.id,
primary_color: 9342168, primary_color: 9342168,
primary_text: 16777215, primary_text: 16777215,
danger_color: 14257035, danger_color: 14257035,
danger_text: 16777215, danger_text: 16777215,
success_color: 12573067, success_color: 12573067,
success_text: 16777215, success_text: 16777215,
warning_color: 14261899, warning_color: 14261899,
warning_text: 16777215, warning_text: 16777215,
info_color: 9165273, info_color: 9165273,
info_text: 16777215, info_text: 16777215,
dark_color: 6710886, dark_color: 6710886,
dark_text: 15658734, dark_text: 15658734,
raised_background: 16777215, raised_background: 16777215,
background_color: 13026795, raised_text: 3355443,
body_text: 3355443, background_color: 13026795,
muted_text: 3355443, body_text: 3355443,
created_at: "2022-12-10T13:37:42.000Z", muted_text: 3355443,
updated_at: "2022-12-10T13:37:42.000Z", created_at: "2022-12-10T13:37:42.000Z",
input_color: 15789556, updated_at: "2022-12-10T13:37:42.000Z",
input_text: 6710886, input_color: 15789556,
raised_accent: 16250871, input_text: 6710886,
light_color: 16316922, raised_accent: 16250871,
light_text: 0, raised_accent_text: 3355443,
input_placeholder: 7107965 light_color: 16316922,
light_text: 0,
input_placeholder: 7107965
} }
} }
) )

View File

@ -81,6 +81,7 @@ RSpec.describe User, type: :model do
# nor .mail (.email is, however) # nor .mail (.email is, however)
include_examples "invalid email", "fritz.fantom@proton.mail" include_examples "invalid email", "fritz.fantom@proton.mail"
# common typos: # common typos:
include_examples "invalid email", "fritz.fantom@aoo.com"
include_examples "invalid email", "fritz.fantom@fmail.com" include_examples "invalid email", "fritz.fantom@fmail.com"
include_examples "invalid email", "fritz.fantom@gamil.com" include_examples "invalid email", "fritz.fantom@gamil.com"
include_examples "invalid email", "fritz.fantom@gemail.com" include_examples "invalid email", "fritz.fantom@gemail.com"
@ -93,6 +94,7 @@ RSpec.describe User, type: :model do
include_examples "invalid email", "fritz.fantom@gmaile.com" include_examples "invalid email", "fritz.fantom@gmaile.com"
include_examples "invalid email", "fritz.fantom@gmaill.com" include_examples "invalid email", "fritz.fantom@gmaill.com"
include_examples "invalid email", "fritz.fantom@gmali.com" include_examples "invalid email", "fritz.fantom@gmali.com"
include_examples "invalid email", "fritz.fantom@gmaul.com"
include_examples "invalid email", "fritz.fantom@gnail.com" include_examples "invalid email", "fritz.fantom@gnail.com"
include_examples "invalid email", "fritz.fantom@hotamil.com" include_examples "invalid email", "fritz.fantom@hotamil.com"
include_examples "invalid email", "fritz.fantom@hotmai.com" include_examples "invalid email", "fritz.fantom@hotmai.com"

228
yarn.lock
View File

@ -49,115 +49,115 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
"@esbuild/android-arm64@0.17.4": "@esbuild/android-arm64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.4.tgz#0a900a7e448cc038ae5a751255257fc67163ed32" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.5.tgz#a145f43018e639bed94ed637369e2dcdd6bf9ea2"
integrity sha512-91VwDrl4EpxBCiG6h2LZZEkuNvVZYJkv2T9gyLG/mhGG1qrM7i5SwUcg/hlSPnL/4hDT0TFcF35/XMGSn0bemg== integrity sha512-KHWkDqYAMmKZjY4RAN1PR96q6UOtfkWlTS8uEwWxdLtkRt/0F/csUhXIrVfaSIFxnscIBMPynGfhsMwQDRIBQw==
"@esbuild/android-arm@0.17.4": "@esbuild/android-arm@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.4.tgz#fe32ce82eb6064d3dc13c0d8ca0e440bbc776c93" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.5.tgz#9fa2deff7fc5d180bb4ecff70beea3a95ac44251"
integrity sha512-R9GCe2xl2XDSc2XbQB63mFiFXHIVkOP+ltIxICKXqUPrFX97z6Z7vONCLQM1pSOLGqfLrGi3B7nbhxmFY/fomg== integrity sha512-crmPUzgCmF+qZXfl1YkiFoUta2XAfixR1tEnr/gXIixE+WL8Z0BGqfydP5oox0EUOgQMMRgtATtakyAcClQVqQ==
"@esbuild/android-x64@0.17.4": "@esbuild/android-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.4.tgz#6ae1056f6ecf1963c1d076cf5f0109b52d8049f6" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.5.tgz#145fc61f810400e65a56b275280d1422a102c2ef"
integrity sha512-mGSqhEPL7029XL7QHNPxPs15JVa02hvZvysUcyMP9UXdGFwncl2WU0bqx+Ysgzd+WAbv8rfNa73QveOxAnAM2w== integrity sha512-8fI/AnIdmWz/+1iza2WrCw8kwXK9wZp/yZY/iS8ioC+U37yJCeppi9EHY05ewJKN64ASoBIseufZROtcFnX5GA==
"@esbuild/darwin-arm64@0.17.4": "@esbuild/darwin-arm64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.4.tgz#5064d81ee5b8d646a5b7cc3e53c98cb983c4af55" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.5.tgz#61fb0546aa4bae0850817d6e0d008b1cb3f64b49"
integrity sha512-tTyJRM9dHvlMPt1KrBFVB5OW1kXOsRNvAPtbzoKazd5RhD5/wKlXk1qR2MpaZRYwf4WDMadt0Pv0GwxB41CVow== integrity sha512-EAvaoyIySV6Iif3NQCglUNpnMfHSUgC5ugt2efl3+QDntucJe5spn0udNZjTgNi6tKVqSceOw9tQ32liNZc1Xw==
"@esbuild/darwin-x64@0.17.4": "@esbuild/darwin-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.4.tgz#67f0213b3333248b32a97a7fc3fee880c2157674" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.5.tgz#54b770f0c49f524ae9ba24c85d6dea8b521f610d"
integrity sha512-phQuC2Imrb3TjOJwLN8EO50nb2FHe8Ew0OwgZDH1SV6asIPGudnwTQtighDF2EAYlXChLoMJwqjAp4vAaACq6w== integrity sha512-ha7QCJh1fuSwwCgoegfdaljowwWozwTDjBgjD3++WAy/qwee5uUi1gvOg2WENJC6EUyHBOkcd3YmLDYSZ2TPPA==
"@esbuild/freebsd-arm64@0.17.4": "@esbuild/freebsd-arm64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.4.tgz#8eaaa126d9ff24822c730f06a71ac2d1091dc1c2" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.5.tgz#be1dd18b7b9411f10bdc362ba8bff16386175367"
integrity sha512-oH6JUZkocgmjzzYaP5juERLpJQSwazdjZrTPgLRmAU2bzJ688x0vfMB/WTv4r58RiecdHvXOPC46VtsMy/mepg== integrity sha512-VbdXJkn2aI2pQ/wxNEjEcnEDwPpxt3CWWMFYmO7CcdFBoOsABRy2W8F3kjbF9F/pecEUDcI3b5i2w+By4VQFPg==
"@esbuild/freebsd-x64@0.17.4": "@esbuild/freebsd-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.4.tgz#314eff900a71abf64d4e5bea31e430d8ebd78d79" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.5.tgz#c9c1960fa3e1eada4e5d4be2a11a2f04ce14198f"
integrity sha512-U4iWGn/9TrAfpAdfd56eO0pRxIgb0a8Wj9jClrhT8hvZnOnS4dfMPW7o4fn15D/KqoiVYHRm43jjBaTt3g/2KA== integrity sha512-olgGYND1/XnnWxwhjtY3/ryjOG/M4WfcA6XH8dBTH1cxMeBemMODXSFhkw71Kf4TeZFFTN25YOomaNh0vq2iXg==
"@esbuild/linux-arm64@0.17.4": "@esbuild/linux-arm64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.4.tgz#5bed6bb8eb1d331644f8b31c87b8df57f204e84e" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.5.tgz#34d96d11c6899017ecae42fb97de8e0c3282902f"
integrity sha512-UkGfQvYlwOaeYJzZG4cLV0hCASzQZnKNktRXUo3/BMZvdau40AOz9GzmGA063n1piq6VrFFh43apRDQx8hMP2w== integrity sha512-8a0bqSwu3OlLCfu2FBbDNgQyBYdPJh1B9PvNX7jMaKGC9/KopgHs37t+pQqeMLzcyRqG6z55IGNQAMSlCpBuqg==
"@esbuild/linux-arm@0.17.4": "@esbuild/linux-arm@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.4.tgz#6eaa41f37e231d113da715a1d9cc820e5523aeb6" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.5.tgz#86332e6293fd713a54ab299a5e2ed7c60c9e1c07"
integrity sha512-S2s9xWTGMTa/fG5EyMGDeL0wrWVgOSQcNddJWgu6rG1NCSXJHs76ZP9AsxjB3f2nZow9fWOyApklIgiTGZKhiw== integrity sha512-YBdCyQwA3OQupi6W2/WO4FnI+NWFWe79cZEtlbqSESOHEg7a73htBIRiE6uHPQe7Yp5E4aALv+JxkRLGEUL7tw==
"@esbuild/linux-ia32@0.17.4": "@esbuild/linux-ia32@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.4.tgz#3fc352bb54e0959fda273cd2253b1c72ca41b8c2" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.5.tgz#7bd9185c844e7dfce6a01dfdec584e115602a8c4"
integrity sha512-3lqFi4VFo/Vwvn77FZXeLd0ctolIJH/uXkH3yNgEk89Eh6D3XXAC9/iTPEzeEpsNE5IqGIsFa5Z0iPeOh25IyA== integrity sha512-uCwm1r/+NdP7vndctgq3PoZrnmhmnecWAr114GWMRwg2QMFFX+kIWnp7IO220/JLgnXK/jP7VKAFBGmeOYBQYQ==
"@esbuild/linux-loong64@0.17.4": "@esbuild/linux-loong64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.4.tgz#86d54f690be53669cd2a38a5333ecf2608c11189" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.5.tgz#2907d4120c7b3642b96be6014f77e7624c378eea"
integrity sha512-HqpWZkVslDHIwdQ9D+gk7NuAulgQvRxF9no54ut/M55KEb3mi7sQS3GwpPJzSyzzP0UkjQVN7/tbk88/CaX4EQ== integrity sha512-3YxhSBl5Sb6TtBjJu+HP93poBruFzgXmf3PVfIe4xOXMj1XpxboYZyw3W8BhoX/uwxzZz4K1I99jTE/5cgDT1g==
"@esbuild/linux-mips64el@0.17.4": "@esbuild/linux-mips64el@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.4.tgz#3dbd897bd8f047fef35e69bd253b8f07ca7fe483" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.5.tgz#fc98be741e8080ecd13b404d5fca5302d3835bf4"
integrity sha512-d/nMCKKh/SVDbqR9ju+b78vOr0tNXtfBjcp5vfHONCCOAL9ad8gN9dC/u+UnH939pz7wO+0u/x9y1MaZcb/lKA== integrity sha512-Hy5Z0YVWyYHdtQ5mfmfp8LdhVwGbwVuq8mHzLqrG16BaMgEmit2xKO+iDakHs+OetEx0EN/2mUzDdfdktI+Nmg==
"@esbuild/linux-ppc64@0.17.4": "@esbuild/linux-ppc64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.4.tgz#defaff6db9a60f08936fc0c59e0eabfb1055968a" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.5.tgz#ea12e8f6b290a613ac4903c9e00835c69ced065c"
integrity sha512-lOD9p2dmjZcNiTU+sGe9Nn6G3aYw3k0HBJies1PU0j5IGfp6tdKOQ6mzfACRFCqXjnBuTqK7eTYpwx09O5LLfg== integrity sha512-5dbQvBLbU/Y3Q4ABc9gi23hww1mQcM7KZ9KBqabB7qhJswYMf8WrDDOSw3gdf3p+ffmijMd28mfVMvFucuECyg==
"@esbuild/linux-riscv64@0.17.4": "@esbuild/linux-riscv64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.4.tgz#270a09f6f4205a8a8c8ed3c7dbabdcebaafa8a84" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.5.tgz#ce47b15fd4227eeb0590826e41bdc430c5bfd06c"
integrity sha512-mTGnwWwVshAjGsd8rP+K6583cPDgxOunsqqldEYij7T5/ysluMHKqUIT4TJHfrDFadUwrghAL6QjER4FeqQXoA== integrity sha512-fp/KUB/ZPzEWGTEUgz9wIAKCqu7CjH1GqXUO2WJdik1UNBQ7Xzw7myIajpxztE4Csb9504ERiFMxZg5KZ6HlZQ==
"@esbuild/linux-s390x@0.17.4": "@esbuild/linux-s390x@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.4.tgz#197695bece68f514dcdcc286562b5d48c5dad5f9" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.5.tgz#962fa540d7498967270eb1d4b9ac6c4a4f339735"
integrity sha512-AQYuUGp50XM29/N/dehADxvc2bUqDcoqrVuijop1Wv72SyxT6dDB9wjUxuPZm2HwIM876UoNNBMVd+iX/UTKVQ== integrity sha512-kRV3yw19YDqHTp8SfHXfObUFXlaiiw4o2lvT1XjsPZ++22GqZwSsYWJLjMi1Sl7j9qDlDUduWDze/nQx0d6Lzw==
"@esbuild/linux-x64@0.17.4": "@esbuild/linux-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.4.tgz#db50cdfb071c0d367025c1c98563aab1318f800e" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.5.tgz#9fa52884c3d876593a522aa1d4df43b717907050"
integrity sha512-+AsFBwKgQuhV2shfGgA9YloxLDVjXgUEWZum7glR5lLmV94IThu/u2JZGxTgjYby6kyXEx8lKOqP5rTEVBR0Rw== integrity sha512-vnxuhh9e4pbtABNLbT2ANW4uwQ/zvcHRCm1JxaYkzSehugoFd5iXyC4ci1nhXU13mxEwCnrnTIiiSGwa/uAF1g==
"@esbuild/netbsd-x64@0.17.4": "@esbuild/netbsd-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.4.tgz#e4d5d8022f8eddbd7d9899d58265915444f46f3b" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.5.tgz#47bb187b86aad9622051cb80c27e439b7d9e3a9a"
integrity sha512-zD1TKYX9553OiLS/qkXPMlWoELYkH/VkzRYNKEU+GwFiqkq0SuxsKnsCg5UCdxN3cqd+1KZ8SS3R+WG/Hxy2jQ== integrity sha512-cigBpdiSx/vPy7doUyImsQQBnBjV5f1M99ZUlaJckDAJjgXWl6y9W17FIfJTy8TxosEF6MXq+fpLsitMGts2nA==
"@esbuild/openbsd-x64@0.17.4": "@esbuild/openbsd-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.4.tgz#9b770e1e7745824cbe155f5a742fc781855a7e68" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.5.tgz#abc55c35a1ed2bc3c5ede2ef50a3b2f87395009a"
integrity sha512-PY1NjEsLRhPEFFg1AV0/4Or/gR+q2dOb9s5rXcPuCjyHRzbt8vnHJl3vYj+641TgWZzTFmSUnZbzs1zwTzjeqw== integrity sha512-VdqRqPVIjjZfkf40LrqOaVuhw9EQiAZ/GNCSM2UplDkaIzYVsSnycxcFfAnHdWI8Gyt6dO15KHikbpxwx+xHbw==
"@esbuild/sunos-x64@0.17.4": "@esbuild/sunos-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.4.tgz#4c6d2290f8bf39ab9284f5a1b9a2210858e2d6e6" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.5.tgz#b83c080a2147662599a5d18b2ff47f07c93e03a0"
integrity sha512-B3Z7s8QZQW9tKGleMRXvVmwwLPAUoDCHs4WZ2ElVMWiortLJFowU1NjAhXOKjDgC7o9ByeVcwyOlJ+F2r6ZgmQ== integrity sha512-ItxPaJ3MBLtI4nK+mALLEoUs6amxsx+J1ibnfcYMkqaCqHST1AkF4aENpBehty3czqw64r/XqL+W9WqU6kc2Qw==
"@esbuild/win32-arm64@0.17.4": "@esbuild/win32-arm64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.4.tgz#424954b6d598f40e2c5a0d85e3af07147fb41909" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.5.tgz#2a4c41f427d9cf25b75f9d61493711a482106850"
integrity sha512-0HCu8R3mY/H5V7N6kdlsJkvrT591bO/oRZy8ztF1dhgNU5xD5tAh5bKByT1UjTGjp/VVBsl1PDQ3L18SfvtnBQ== integrity sha512-4u2Q6qsJTYNFdS9zHoAi80spzf78C16m2wla4eJPh4kSbRv+BpXIfl6TmBSWupD8e47B1NrTfrOlEuco7mYQtg==
"@esbuild/win32-ia32@0.17.4": "@esbuild/win32-ia32@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.4.tgz#2c94e9c3a82c779d3f07b3fb5c482a2e3fecedb1" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.5.tgz#7c14e3250725d0e2c21f89c98eb6abb520cba0e0"
integrity sha512-VUjhVDQycse1gLbe06pC/uaA0M+piQXJpdpNdhg8sPmeIZZqu5xPoGWVCmcsOO2gaM2cywuTYTHkXRozo3/Nkg== integrity sha512-KYlm+Xu9TXsfTWAcocLuISRtqxKp/Y9ZBVg6CEEj0O5J9mn7YvBKzAszo2j1ndyzUPk+op+Tie2PJeN+BnXGqQ==
"@esbuild/win32-x64@0.17.4": "@esbuild/win32-x64@0.17.5":
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.4.tgz#9b7760cdc77678bdbc5b582fae2cf3de449df048" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.5.tgz#a8f3d26d8afc5186eccda265ceb1820b8e8830be"
integrity sha512-0kLAjs+xN5OjhTt/aUA6t48SfENSCKgGPfExADYTOo/UCn0ivxos9/anUVeSfg+L+2O9xkFxvJXIJfG+Q4sYSg== integrity sha512-XgA9qWRqby7xdYXuF6KALsn37QGBMHsdhmnpjfZtYxKxbTOwfnDM6MYi2WuUku5poNaX2n9XGVr20zgT/2QwCw==
"@eslint/eslintrc@^0.4.3": "@eslint/eslintrc@^0.4.3":
version "0.4.3" version "0.4.3"
@ -835,33 +835,33 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1" is-date-object "^1.0.1"
is-symbol "^1.0.2" is-symbol "^1.0.2"
esbuild@^0.17.4: esbuild@^0.17.5:
version "0.17.4" version "0.17.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.4.tgz#af4f8f78604c67f8e6afbdee36a3f4211ecfc859" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.5.tgz#cd76d75700d49ac050ad9eedfbed777bd6a9d930"
integrity sha512-zBn9MeCwT7W5F1a3lXClD61ip6vQM+H8Msb0w8zMT4ZKBpDg+rFAraNyWCDelB/2L6M3g6AXHPnsyvjMFnxtFw== integrity sha512-Bu6WLCc9NMsNoMJUjGl3yBzTjVLXdysMltxQWiLAypP+/vQrf+3L1Xe8fCXzxaECus2cEJ9M7pk4yKatEwQMqQ==
optionalDependencies: optionalDependencies:
"@esbuild/android-arm" "0.17.4" "@esbuild/android-arm" "0.17.5"
"@esbuild/android-arm64" "0.17.4" "@esbuild/android-arm64" "0.17.5"
"@esbuild/android-x64" "0.17.4" "@esbuild/android-x64" "0.17.5"
"@esbuild/darwin-arm64" "0.17.4" "@esbuild/darwin-arm64" "0.17.5"
"@esbuild/darwin-x64" "0.17.4" "@esbuild/darwin-x64" "0.17.5"
"@esbuild/freebsd-arm64" "0.17.4" "@esbuild/freebsd-arm64" "0.17.5"
"@esbuild/freebsd-x64" "0.17.4" "@esbuild/freebsd-x64" "0.17.5"
"@esbuild/linux-arm" "0.17.4" "@esbuild/linux-arm" "0.17.5"
"@esbuild/linux-arm64" "0.17.4" "@esbuild/linux-arm64" "0.17.5"
"@esbuild/linux-ia32" "0.17.4" "@esbuild/linux-ia32" "0.17.5"
"@esbuild/linux-loong64" "0.17.4" "@esbuild/linux-loong64" "0.17.5"
"@esbuild/linux-mips64el" "0.17.4" "@esbuild/linux-mips64el" "0.17.5"
"@esbuild/linux-ppc64" "0.17.4" "@esbuild/linux-ppc64" "0.17.5"
"@esbuild/linux-riscv64" "0.17.4" "@esbuild/linux-riscv64" "0.17.5"
"@esbuild/linux-s390x" "0.17.4" "@esbuild/linux-s390x" "0.17.5"
"@esbuild/linux-x64" "0.17.4" "@esbuild/linux-x64" "0.17.5"
"@esbuild/netbsd-x64" "0.17.4" "@esbuild/netbsd-x64" "0.17.5"
"@esbuild/openbsd-x64" "0.17.4" "@esbuild/openbsd-x64" "0.17.5"
"@esbuild/sunos-x64" "0.17.4" "@esbuild/sunos-x64" "0.17.5"
"@esbuild/win32-arm64" "0.17.4" "@esbuild/win32-arm64" "0.17.5"
"@esbuild/win32-ia32" "0.17.4" "@esbuild/win32-ia32" "0.17.5"
"@esbuild/win32-x64" "0.17.4" "@esbuild/win32-x64" "0.17.5"
escape-string-regexp@^1.0.5: escape-string-regexp@^1.0.5:
version "1.0.5" version "1.0.5"