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-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 "redcarpet"
@ -97,7 +97,7 @@ group :development, :test do
gem "rspec-mocks"
gem "rspec-rails", "~> 6.0"
gem "rspec-sidekiq", "~> 3.0", require: false
gem "rubocop", "~> 1.43"
gem "rubocop", "~> 1.44"
gem "rubocop-rails", "~> 2.17"
gem "shoulda-matchers", "~> 5.3"
gem "simplecov", require: false

View File

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

View File

@ -51,6 +51,15 @@ namespace :justask do # rubocop:disable Metrics/BlockLength
user.remove_role :moderator
puts "#{user.screen_name} is no longer a moderator."
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
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;
--input-text: 0, 0, 0;
--input-placeholder: 108, 117, 125;
--raised-text: 0, 0, 0;
--raised-accent-text: 0, 0, 0;
--turbolinks-progress-color: #a58adc; // --primary lightened by 25%
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,9 @@
class InboxController < ApplicationController
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_inbox_entries
@ -11,15 +13,17 @@ class InboxController < ApplicationController
# rubocop disabled because of a false positive
flash[:info] = t(".author.info", author: @author) # rubocop:disable Rails/ActionControllerFlashBeforeRender
redirect_to inbox_path(last_id: params[:last_id])
return
end
@delete_id = find_delete_id
@disabled = true if @inbox.empty?
services = current_user.services.to_a
respond_to do |format|
format.html
format.html { render "show", locals: { services: } }
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
@inbox.update_all(new: false) # rubocop:disable Rails/SkipsModelValidations
@ -34,10 +38,11 @@ class InboxController < ApplicationController
user: current_user)
inbox = Inbox.create!(user: current_user, question_id: question.id, new: true)
services = current_user.services
respond_to do |format|
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)
end
@ -77,4 +82,9 @@ class InboxController < ApplicationController
.joins(:question)
.where(questions: { user: @author_user, author_is_anonymous: false })
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

View File

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

View File

@ -3,6 +3,8 @@
class NotificationsController < ApplicationController
before_action :authenticate_user!
after_action :mark_notifications_as_read, only: %i[index]
TYPE_MAPPINGS = {
"answer" => Notification::QuestionAnswered.name,
"comment" => Notification::Commented.name,
@ -14,8 +16,8 @@ class NotificationsController < ApplicationController
def index
@type = TYPE_MAPPINGS[params[:type]] || params[:type]
@notifications = cursored_notifications_for(type: @type, last_id: params[:last_id])
@notifications_last_id = @notifications.map(&:id).min
@more_data_available = !cursored_notifications_for(type: @type, last_id: @notifications_last_id, size: 1).count.zero?
paginate_notifications
@counters = count_unread_by_type
respond_to do |format|
format.html
@ -25,6 +27,22 @@ class NotificationsController < ApplicationController
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)
cursor_params = { last_id: last_id, size: size }.compact

View File

@ -8,22 +8,8 @@ class Settings::ThemeController < ApplicationController
def edit; end
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?
current_user.theme = Theme.new update_attributes
current_user.theme = Theme.new theme_attributes
current_user.theme.user_id = current_user.id
if current_user.theme.save
@ -31,7 +17,7 @@ class Settings::ThemeController < ApplicationController
else
flash[:error] = t(".error", errors: current_user.theme.errors.messages.flatten.join(" "))
end
elsif current_user.theme.update(update_attributes)
elsif current_user.theme.update(theme_attributes)
flash[:success] = t(".success")
else
flash[:error] = t(".error", errors: current_user.theme.errors.messages.flatten.join(" "))
@ -43,4 +29,23 @@ class Settings::ThemeController < ApplicationController
current_user.theme.destroy!
redirect_to edit_settings_theme_path
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

View File

@ -3,20 +3,13 @@
class UserController < ApplicationController
before_action :set_user
before_action :hidden_social_graph_redirect, only: %i[followers followings]
after_action :mark_notification_as_read, only: %i[show]
def show
@answers = @user.cursored_answers(last_id: params[:last_id])
@answers_last_id = @answers.map(&:id).min
@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|
format.html
format.turbo_stream
@ -24,36 +17,40 @@ class UserController < ApplicationController
end
def followers
@title = "Followers"
@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?
paginate_relationships(:cursored_follower_relationships)
@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|
format.html { render "show_follow" }
format.turbo_stream { render "show_follow" }
format.html { render "show_follow", locals: }
format.turbo_stream { render "show_follow", locals: }
end
end
def followings
@title = "Following"
@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?
paginate_relationships(:cursored_following_relationships)
@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|
format.html { render "show_follow" }
format.turbo_stream { render "show_follow" }
format.html { render "show_follow", locals: }
format.turbo_stream { render "show_follow", locals: }
end
end
def questions
@title = "Questions"
@questions = @user.cursored_questions(author_is_anonymous: false, direct: direct_param, last_id: params[:last_id])
@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?
@ -66,10 +63,36 @@ class UserController < ApplicationController
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
@user = User.where("LOWER(screen_name) = ?", params[:username].downcase).includes(:profile).first!
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
return if belongs_to_current_user? || !@user.privacy_hide_social_graph

View File

@ -2,28 +2,30 @@
module ThemeHelper
ATTRIBUTE_MAP = {
"primary_color" => %w[primary primary-rgb],
"primary_text" => "primary-text",
"danger_color" => "danger",
"danger_text" => "danger-text",
"warning_color" => "warning",
"warning_text" => "warning-text",
"info_color" => "info",
"info_text" => "info-text",
"success_color" => "success",
"success_text" => "success-text",
"dark_color" => "dark",
"dark_text" => "dark-text",
"light_color" => "light",
"light_text" => "light-text",
"raised_background" => %w[raised-bg raised-bg-rgb],
"raised_accent" => %w[raised-accent raised-accent-rgb],
"background_color" => "background",
"body_text" => "body-text",
"input_color" => "input-bg",
"input_text" => "input-text",
"input_placeholder" => "input-placeholder",
"muted_text" => "muted-text"
"primary_color" => %w[primary primary-rgb],
"primary_text" => "primary-text",
"danger_color" => "danger",
"danger_text" => "danger-text",
"warning_color" => "warning",
"warning_text" => "warning-text",
"info_color" => "info",
"info_text" => "info-text",
"success_color" => "success",
"success_text" => "success-text",
"dark_color" => "dark",
"dark_text" => "dark-text",
"light_color" => "light",
"light_text" => "light-text",
"raised_background" => %w[raised-bg raised-bg-rgb],
"raised_text" => "raised-text",
"raised_accent" => %w[raised-accent raised-accent-rgb],
"raised_accent_text" => "raised-accent-text",
"background_color" => "background",
"body_text" => "body-text",
"input_color" => "input-bg",
"input_text" => "input-text",
"input_placeholder" => "input-placeholder",
"muted_text" => "muted-text"
}.freeze
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 {
const button = event.target as Element;
const count = button.getAttribute('data-ib-count');
const urlSearchParams = new URLSearchParams(window.location.search);
swal({
title: I18n.translate('frontend.inbox.confirm_all.title', { count: count }),
@ -75,7 +76,7 @@ export function deleteAllAuthorQuestionsHandler(event: Event): void {
}, (returnValue) => {
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 => {
const data = await response.json;

View File

@ -6,8 +6,7 @@ import { deleteAllAuthorQuestionsHandler, deleteAllQuestionsHandler } from './de
export default (): void => {
registerEvents([
{ type: 'click', target: '#ib-delete-all', handler: deleteAllQuestionsHandler, global: true },
{ type: 'click', target: '#ib-delete-all-author', handler: deleteAllAuthorQuestionsHandler, global: true },
{ type: 'submit', target: '#author-form', handler: authorSearchHandler, global: true }
{ type: 'click', target: '#ib-delete-all-author', handler: deleteAllAuthorQuestionsHandler, global: true }
]);
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 { profileHeaderChangeHandler, profilePictureChangeHandler } from "./crop";
import { userSubmitHandler } from "./password";
export default (): void => {
registerEvents([
{ 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 }
{ type: 'submit', target: document.querySelector('#edit_user'), handler: userSubmitHandler }
]);
}

View File

@ -7,6 +7,7 @@ import FormatPopupController from "retrospring/controllers/format_popup_controll
import CollapseController from "retrospring/controllers/collapse_controller";
import ThemeController from "retrospring/controllers/theme_controller";
import CapabilitiesController from "retrospring/controllers/capabilities_controller";
import CropperController from "retrospring/controllers/cropper_controller";
/**
* 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-warning', CharacterCountWarningController);
window['Stimulus'].register('collapse', CollapseController);
window['Stimulus'].register('cropper', CropperController);
window['Stimulus'].register('format-popup', FormatPopupController);
window['Stimulus'].register('theme', ThemeController);
}

View File

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

View File

@ -11,11 +11,11 @@ class Notification < ApplicationRecord
define_cursor_paginator :cursored_for_type, :for_type
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
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
def notify(recipient, target)

View File

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

View File

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

View File

@ -5,6 +5,7 @@ module User::QuestionMethods
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)
questions
.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_follower_relationships, :ordered_follower_relationships
# @return [ActiveRecord::Relation<Relationships::Follow>] List of the user's following relationships
def ordered_following_relationships
active_follow_relationships.reverse_order.includes(target: [:profile])
end
# @return [ActiveRecord::Relation<Relationships::Follow>] List of the user's follower relationships
def ordered_follower_relationships
passive_follow_relationships.reverse_order.includes(source: [:profile])
end

View File

@ -5,8 +5,12 @@ module User::TimelineMethods
define_cursor_paginator :cursored_timeline, :timeline
# @return [Array] the users' timeline
# @return [ActiveRecord::Relation<Answer>] the user's 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,36 +2,38 @@
.card-body
= 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
.flex-shrink-0
%img.avatar-lg.me-3{ src: current_user.profile_picture.url(:medium) }
.flex-grow-1
= f.file_field :profile_picture, accept: APP_CONFIG[:accepted_image_formats].join(",")
%div{ data: { controller: "cropper", cropper_aspect_ratio_value: "1" } }
.d-flex
.flex-shrink-0
%img.avatar-lg.me-3{ src: current_user.profile_picture.url(:medium) }
.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
.col-sm-10.col-md-8
%strong= t(".adjust.profile_picture")
%img#profile-picture-cropper{ src: current_user.profile_picture.url(:medium) }
.row.d-none{ data: { cropper_target: "controls" } }
.col-sm-10.col-md-8
%strong= t(".adjust.profile_picture")
%img{ src: current_user.profile_picture.url(:medium), data: { cropper_target: "cropper" } }
.row.mb-2#profile-header-media
.col-xs-12.col-md-6
%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(",")
- %i[profile_picture_x profile_picture_y profile_picture_w profile_picture_h].each do |attrib|
= f.hidden_field attrib, id: attrib, data: { cropper_target: attrib.to_s.split("_").last }
.row.d-none#profile-header-crop-controls
.col-sm-10.col-md-8
%strong= t(".adjust.profile_header")
%img#profile-header-cropper{ src: current_user.profile_header.url(:web) }
%div{ data: { controller: "cropper", cropper_aspect_ratio_value: "0.23" } }
.row.mb-2
.col-xs-12.col-md-6
%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
- %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")
.card
.card-body

View File

@ -21,6 +21,23 @@
= f.text_field :background_color, class: "color", data: { default: 0xF0EDF4, theme_target: "color", action: "theme#updatePreview" }
.col-sm-6
= 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-body
%h2= t(".colors.heading")
@ -93,23 +110,11 @@
.row
.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
.form-group
%label.form-label Example Input
%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-body
.pull-left

View File

@ -9,4 +9,4 @@
= user.profile.display_name
.profile__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_item t('.answer'),
notifications_path('answer'),
badge: Notification.for(current_user).where(target_type: 'Answer', new: true).count
badge: @counters['Answer']
= list_group_item t('.smile'),
notifications_path('smile'),
badge: Notification.for(current_user).where(target_type: 'Smile', new: true).count
badge: @counters['Smile']
= list_group_item t('.comment'),
notifications_path('comment'),
badge: Notification.for(current_user).where(target_type: 'Comment', new: true).count
badge: @counters['Comment']
= list_group_item t('.commentsmile'),
notifications_path('commentsmile'),
badge: Notification.for(current_user).where(target_type: 'CommentSmile', new: true).count
badge: @counters['CommentSmile']
= list_group_item t('.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'

View File

@ -1,11 +1,14 @@
.profile__actions
- type ||= :nil
- own_followings ||= nil
- own_blocks ||= nil
- own_mutes ||= nil
- if user_signed_in? && user == current_user
.d-grid
%a.btn.btn-dark{ href: settings_profile_path } Edit profile
- elsif user_signed_in?
.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 } }
= t("voc.unfollow")
- else
@ -19,7 +22,7 @@
%a.dropdown-item.d-block.d-sm-none{ href: '#', data: { bs_target: '#modal-list-memberships', bs_toggle: :modal } }
%i.fa.fa-list.fa-fw
= 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 } }
%i.fa.fa-minus-circle.fa-fw
%span.pe-none= t("voc.unblock")
@ -27,7 +30,7 @@
%a.dropdown-item{ href: '#', data: { action: :block, target: user.screen_name } }
%i.fa.fa-minus-circle.fa-fw
%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 } }
%i.fa.fa-volume-off.fa-fw
%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
- @users.each do |user|
.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
.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",
method: :get,
params: { last_id: @relationships_last_id },
form: { data: { turbo_stream: true } }
- provide(:title, user_title(@user, 'friends and followers'))
- parent_layout 'user/profile'
- provide(:title, t(".title.#{type}", user: @user.profile.safe_name))
- parent_layout "user/profile"

View File

@ -1,11 +1,11 @@
= turbo_stream.append "users" do
- @users.each do |user|
.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
- 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",
method: :get,
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.
# We recommend adjusting this value in production
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

View File

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

View File

@ -651,6 +651,10 @@ en:
index:
title: "Questions from %{author_identifier}"
user:
show_follow:
title:
follower: "%{user}'s followers"
friend: "%{user}'s followings"
actions:
view_inbox: "View inbox"
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/followers", to: "user#followers", as: :show_user_followers
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", to: "user#show", as: :user_alt
get "/:username/a/:id", to: "answer#show", as: :answer_alt
get "/:username/q/:id", to: "question#show", as: :question_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/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 "/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 "updated_at", null: false
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
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_text", default: 0
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"
end

View File

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

View File

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

View File

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

View File

@ -74,7 +74,7 @@
<div class="dialog">
<div>
<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>
<p>Sorry about that. Please try refreshing and contact us if the problem persists.</p>
</div>

View File

@ -47,6 +47,9 @@ self.addEventListener('install', 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(
(async () => {
try {

View File

@ -58,6 +58,10 @@ describe InboxController, type: :controller do
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
subject { get :show, format: :turbo_stream }
@ -175,9 +179,7 @@ describe InboxController, type: :controller do
inbox: [],
inbox_last_id: nil,
more_data_available: false,
inbox_count: 0,
delete_id: "ib-delete-all",
disabled: true
inbox_count: 0
}
end
end
@ -209,9 +211,7 @@ describe InboxController, type: :controller do
inbox: [],
inbox_last_id: nil,
more_data_available: false,
inbox_count: 0,
delete_id: "ib-delete-all",
disabled: true
inbox_count: 0
}
end
end

View File

@ -33,5 +33,9 @@ describe NotificationsController do
expect(response).to render_template(:index)
expect(controller.instance_variable_get(:@notifications)).to have_attributes(size: 2)
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

View File

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

View File

@ -22,32 +22,34 @@ describe UseCase::DataExport::Theme, :data_export do
expect(json_file("theme.json")).to eq(
{
theme: {
id: theme.id,
user_id: user.id,
primary_color: 9342168,
primary_text: 16777215,
danger_color: 14257035,
danger_text: 16777215,
success_color: 12573067,
success_text: 16777215,
warning_color: 14261899,
warning_text: 16777215,
info_color: 9165273,
info_text: 16777215,
dark_color: 6710886,
dark_text: 15658734,
raised_background: 16777215,
background_color: 13026795,
body_text: 3355443,
muted_text: 3355443,
created_at: "2022-12-10T13:37:42.000Z",
updated_at: "2022-12-10T13:37:42.000Z",
input_color: 15789556,
input_text: 6710886,
raised_accent: 16250871,
light_color: 16316922,
light_text: 0,
input_placeholder: 7107965
id: theme.id,
user_id: user.id,
primary_color: 9342168,
primary_text: 16777215,
danger_color: 14257035,
danger_text: 16777215,
success_color: 12573067,
success_text: 16777215,
warning_color: 14261899,
warning_text: 16777215,
info_color: 9165273,
info_text: 16777215,
dark_color: 6710886,
dark_text: 15658734,
raised_background: 16777215,
raised_text: 3355443,
background_color: 13026795,
body_text: 3355443,
muted_text: 3355443,
created_at: "2022-12-10T13:37:42.000Z",
updated_at: "2022-12-10T13:37:42.000Z",
input_color: 15789556,
input_text: 6710886,
raised_accent: 16250871,
raised_accent_text: 3355443,
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)
include_examples "invalid email", "fritz.fantom@proton.mail"
# common typos:
include_examples "invalid email", "fritz.fantom@aoo.com"
include_examples "invalid email", "fritz.fantom@fmail.com"
include_examples "invalid email", "fritz.fantom@gamil.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@gmaill.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@hotamil.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"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
"@esbuild/android-arm64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.4.tgz#0a900a7e448cc038ae5a751255257fc67163ed32"
integrity sha512-91VwDrl4EpxBCiG6h2LZZEkuNvVZYJkv2T9gyLG/mhGG1qrM7i5SwUcg/hlSPnL/4hDT0TFcF35/XMGSn0bemg==
"@esbuild/android-arm64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.5.tgz#a145f43018e639bed94ed637369e2dcdd6bf9ea2"
integrity sha512-KHWkDqYAMmKZjY4RAN1PR96q6UOtfkWlTS8uEwWxdLtkRt/0F/csUhXIrVfaSIFxnscIBMPynGfhsMwQDRIBQw==
"@esbuild/android-arm@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.4.tgz#fe32ce82eb6064d3dc13c0d8ca0e440bbc776c93"
integrity sha512-R9GCe2xl2XDSc2XbQB63mFiFXHIVkOP+ltIxICKXqUPrFX97z6Z7vONCLQM1pSOLGqfLrGi3B7nbhxmFY/fomg==
"@esbuild/android-arm@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.5.tgz#9fa2deff7fc5d180bb4ecff70beea3a95ac44251"
integrity sha512-crmPUzgCmF+qZXfl1YkiFoUta2XAfixR1tEnr/gXIixE+WL8Z0BGqfydP5oox0EUOgQMMRgtATtakyAcClQVqQ==
"@esbuild/android-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.4.tgz#6ae1056f6ecf1963c1d076cf5f0109b52d8049f6"
integrity sha512-mGSqhEPL7029XL7QHNPxPs15JVa02hvZvysUcyMP9UXdGFwncl2WU0bqx+Ysgzd+WAbv8rfNa73QveOxAnAM2w==
"@esbuild/android-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.5.tgz#145fc61f810400e65a56b275280d1422a102c2ef"
integrity sha512-8fI/AnIdmWz/+1iza2WrCw8kwXK9wZp/yZY/iS8ioC+U37yJCeppi9EHY05ewJKN64ASoBIseufZROtcFnX5GA==
"@esbuild/darwin-arm64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.4.tgz#5064d81ee5b8d646a5b7cc3e53c98cb983c4af55"
integrity sha512-tTyJRM9dHvlMPt1KrBFVB5OW1kXOsRNvAPtbzoKazd5RhD5/wKlXk1qR2MpaZRYwf4WDMadt0Pv0GwxB41CVow==
"@esbuild/darwin-arm64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.5.tgz#61fb0546aa4bae0850817d6e0d008b1cb3f64b49"
integrity sha512-EAvaoyIySV6Iif3NQCglUNpnMfHSUgC5ugt2efl3+QDntucJe5spn0udNZjTgNi6tKVqSceOw9tQ32liNZc1Xw==
"@esbuild/darwin-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.4.tgz#67f0213b3333248b32a97a7fc3fee880c2157674"
integrity sha512-phQuC2Imrb3TjOJwLN8EO50nb2FHe8Ew0OwgZDH1SV6asIPGudnwTQtighDF2EAYlXChLoMJwqjAp4vAaACq6w==
"@esbuild/darwin-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.5.tgz#54b770f0c49f524ae9ba24c85d6dea8b521f610d"
integrity sha512-ha7QCJh1fuSwwCgoegfdaljowwWozwTDjBgjD3++WAy/qwee5uUi1gvOg2WENJC6EUyHBOkcd3YmLDYSZ2TPPA==
"@esbuild/freebsd-arm64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.4.tgz#8eaaa126d9ff24822c730f06a71ac2d1091dc1c2"
integrity sha512-oH6JUZkocgmjzzYaP5juERLpJQSwazdjZrTPgLRmAU2bzJ688x0vfMB/WTv4r58RiecdHvXOPC46VtsMy/mepg==
"@esbuild/freebsd-arm64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.5.tgz#be1dd18b7b9411f10bdc362ba8bff16386175367"
integrity sha512-VbdXJkn2aI2pQ/wxNEjEcnEDwPpxt3CWWMFYmO7CcdFBoOsABRy2W8F3kjbF9F/pecEUDcI3b5i2w+By4VQFPg==
"@esbuild/freebsd-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.4.tgz#314eff900a71abf64d4e5bea31e430d8ebd78d79"
integrity sha512-U4iWGn/9TrAfpAdfd56eO0pRxIgb0a8Wj9jClrhT8hvZnOnS4dfMPW7o4fn15D/KqoiVYHRm43jjBaTt3g/2KA==
"@esbuild/freebsd-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.5.tgz#c9c1960fa3e1eada4e5d4be2a11a2f04ce14198f"
integrity sha512-olgGYND1/XnnWxwhjtY3/ryjOG/M4WfcA6XH8dBTH1cxMeBemMODXSFhkw71Kf4TeZFFTN25YOomaNh0vq2iXg==
"@esbuild/linux-arm64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.4.tgz#5bed6bb8eb1d331644f8b31c87b8df57f204e84e"
integrity sha512-UkGfQvYlwOaeYJzZG4cLV0hCASzQZnKNktRXUo3/BMZvdau40AOz9GzmGA063n1piq6VrFFh43apRDQx8hMP2w==
"@esbuild/linux-arm64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.5.tgz#34d96d11c6899017ecae42fb97de8e0c3282902f"
integrity sha512-8a0bqSwu3OlLCfu2FBbDNgQyBYdPJh1B9PvNX7jMaKGC9/KopgHs37t+pQqeMLzcyRqG6z55IGNQAMSlCpBuqg==
"@esbuild/linux-arm@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.4.tgz#6eaa41f37e231d113da715a1d9cc820e5523aeb6"
integrity sha512-S2s9xWTGMTa/fG5EyMGDeL0wrWVgOSQcNddJWgu6rG1NCSXJHs76ZP9AsxjB3f2nZow9fWOyApklIgiTGZKhiw==
"@esbuild/linux-arm@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.5.tgz#86332e6293fd713a54ab299a5e2ed7c60c9e1c07"
integrity sha512-YBdCyQwA3OQupi6W2/WO4FnI+NWFWe79cZEtlbqSESOHEg7a73htBIRiE6uHPQe7Yp5E4aALv+JxkRLGEUL7tw==
"@esbuild/linux-ia32@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.4.tgz#3fc352bb54e0959fda273cd2253b1c72ca41b8c2"
integrity sha512-3lqFi4VFo/Vwvn77FZXeLd0ctolIJH/uXkH3yNgEk89Eh6D3XXAC9/iTPEzeEpsNE5IqGIsFa5Z0iPeOh25IyA==
"@esbuild/linux-ia32@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.5.tgz#7bd9185c844e7dfce6a01dfdec584e115602a8c4"
integrity sha512-uCwm1r/+NdP7vndctgq3PoZrnmhmnecWAr114GWMRwg2QMFFX+kIWnp7IO220/JLgnXK/jP7VKAFBGmeOYBQYQ==
"@esbuild/linux-loong64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.4.tgz#86d54f690be53669cd2a38a5333ecf2608c11189"
integrity sha512-HqpWZkVslDHIwdQ9D+gk7NuAulgQvRxF9no54ut/M55KEb3mi7sQS3GwpPJzSyzzP0UkjQVN7/tbk88/CaX4EQ==
"@esbuild/linux-loong64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.5.tgz#2907d4120c7b3642b96be6014f77e7624c378eea"
integrity sha512-3YxhSBl5Sb6TtBjJu+HP93poBruFzgXmf3PVfIe4xOXMj1XpxboYZyw3W8BhoX/uwxzZz4K1I99jTE/5cgDT1g==
"@esbuild/linux-mips64el@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.4.tgz#3dbd897bd8f047fef35e69bd253b8f07ca7fe483"
integrity sha512-d/nMCKKh/SVDbqR9ju+b78vOr0tNXtfBjcp5vfHONCCOAL9ad8gN9dC/u+UnH939pz7wO+0u/x9y1MaZcb/lKA==
"@esbuild/linux-mips64el@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.5.tgz#fc98be741e8080ecd13b404d5fca5302d3835bf4"
integrity sha512-Hy5Z0YVWyYHdtQ5mfmfp8LdhVwGbwVuq8mHzLqrG16BaMgEmit2xKO+iDakHs+OetEx0EN/2mUzDdfdktI+Nmg==
"@esbuild/linux-ppc64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.4.tgz#defaff6db9a60f08936fc0c59e0eabfb1055968a"
integrity sha512-lOD9p2dmjZcNiTU+sGe9Nn6G3aYw3k0HBJies1PU0j5IGfp6tdKOQ6mzfACRFCqXjnBuTqK7eTYpwx09O5LLfg==
"@esbuild/linux-ppc64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.5.tgz#ea12e8f6b290a613ac4903c9e00835c69ced065c"
integrity sha512-5dbQvBLbU/Y3Q4ABc9gi23hww1mQcM7KZ9KBqabB7qhJswYMf8WrDDOSw3gdf3p+ffmijMd28mfVMvFucuECyg==
"@esbuild/linux-riscv64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.4.tgz#270a09f6f4205a8a8c8ed3c7dbabdcebaafa8a84"
integrity sha512-mTGnwWwVshAjGsd8rP+K6583cPDgxOunsqqldEYij7T5/ysluMHKqUIT4TJHfrDFadUwrghAL6QjER4FeqQXoA==
"@esbuild/linux-riscv64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.5.tgz#ce47b15fd4227eeb0590826e41bdc430c5bfd06c"
integrity sha512-fp/KUB/ZPzEWGTEUgz9wIAKCqu7CjH1GqXUO2WJdik1UNBQ7Xzw7myIajpxztE4Csb9504ERiFMxZg5KZ6HlZQ==
"@esbuild/linux-s390x@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.4.tgz#197695bece68f514dcdcc286562b5d48c5dad5f9"
integrity sha512-AQYuUGp50XM29/N/dehADxvc2bUqDcoqrVuijop1Wv72SyxT6dDB9wjUxuPZm2HwIM876UoNNBMVd+iX/UTKVQ==
"@esbuild/linux-s390x@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.5.tgz#962fa540d7498967270eb1d4b9ac6c4a4f339735"
integrity sha512-kRV3yw19YDqHTp8SfHXfObUFXlaiiw4o2lvT1XjsPZ++22GqZwSsYWJLjMi1Sl7j9qDlDUduWDze/nQx0d6Lzw==
"@esbuild/linux-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.4.tgz#db50cdfb071c0d367025c1c98563aab1318f800e"
integrity sha512-+AsFBwKgQuhV2shfGgA9YloxLDVjXgUEWZum7glR5lLmV94IThu/u2JZGxTgjYby6kyXEx8lKOqP5rTEVBR0Rw==
"@esbuild/linux-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.5.tgz#9fa52884c3d876593a522aa1d4df43b717907050"
integrity sha512-vnxuhh9e4pbtABNLbT2ANW4uwQ/zvcHRCm1JxaYkzSehugoFd5iXyC4ci1nhXU13mxEwCnrnTIiiSGwa/uAF1g==
"@esbuild/netbsd-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.4.tgz#e4d5d8022f8eddbd7d9899d58265915444f46f3b"
integrity sha512-zD1TKYX9553OiLS/qkXPMlWoELYkH/VkzRYNKEU+GwFiqkq0SuxsKnsCg5UCdxN3cqd+1KZ8SS3R+WG/Hxy2jQ==
"@esbuild/netbsd-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.5.tgz#47bb187b86aad9622051cb80c27e439b7d9e3a9a"
integrity sha512-cigBpdiSx/vPy7doUyImsQQBnBjV5f1M99ZUlaJckDAJjgXWl6y9W17FIfJTy8TxosEF6MXq+fpLsitMGts2nA==
"@esbuild/openbsd-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.4.tgz#9b770e1e7745824cbe155f5a742fc781855a7e68"
integrity sha512-PY1NjEsLRhPEFFg1AV0/4Or/gR+q2dOb9s5rXcPuCjyHRzbt8vnHJl3vYj+641TgWZzTFmSUnZbzs1zwTzjeqw==
"@esbuild/openbsd-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.5.tgz#abc55c35a1ed2bc3c5ede2ef50a3b2f87395009a"
integrity sha512-VdqRqPVIjjZfkf40LrqOaVuhw9EQiAZ/GNCSM2UplDkaIzYVsSnycxcFfAnHdWI8Gyt6dO15KHikbpxwx+xHbw==
"@esbuild/sunos-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.4.tgz#4c6d2290f8bf39ab9284f5a1b9a2210858e2d6e6"
integrity sha512-B3Z7s8QZQW9tKGleMRXvVmwwLPAUoDCHs4WZ2ElVMWiortLJFowU1NjAhXOKjDgC7o9ByeVcwyOlJ+F2r6ZgmQ==
"@esbuild/sunos-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.5.tgz#b83c080a2147662599a5d18b2ff47f07c93e03a0"
integrity sha512-ItxPaJ3MBLtI4nK+mALLEoUs6amxsx+J1ibnfcYMkqaCqHST1AkF4aENpBehty3czqw64r/XqL+W9WqU6kc2Qw==
"@esbuild/win32-arm64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.4.tgz#424954b6d598f40e2c5a0d85e3af07147fb41909"
integrity sha512-0HCu8R3mY/H5V7N6kdlsJkvrT591bO/oRZy8ztF1dhgNU5xD5tAh5bKByT1UjTGjp/VVBsl1PDQ3L18SfvtnBQ==
"@esbuild/win32-arm64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.5.tgz#2a4c41f427d9cf25b75f9d61493711a482106850"
integrity sha512-4u2Q6qsJTYNFdS9zHoAi80spzf78C16m2wla4eJPh4kSbRv+BpXIfl6TmBSWupD8e47B1NrTfrOlEuco7mYQtg==
"@esbuild/win32-ia32@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.4.tgz#2c94e9c3a82c779d3f07b3fb5c482a2e3fecedb1"
integrity sha512-VUjhVDQycse1gLbe06pC/uaA0M+piQXJpdpNdhg8sPmeIZZqu5xPoGWVCmcsOO2gaM2cywuTYTHkXRozo3/Nkg==
"@esbuild/win32-ia32@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.5.tgz#7c14e3250725d0e2c21f89c98eb6abb520cba0e0"
integrity sha512-KYlm+Xu9TXsfTWAcocLuISRtqxKp/Y9ZBVg6CEEj0O5J9mn7YvBKzAszo2j1ndyzUPk+op+Tie2PJeN+BnXGqQ==
"@esbuild/win32-x64@0.17.4":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.4.tgz#9b7760cdc77678bdbc5b582fae2cf3de449df048"
integrity sha512-0kLAjs+xN5OjhTt/aUA6t48SfENSCKgGPfExADYTOo/UCn0ivxos9/anUVeSfg+L+2O9xkFxvJXIJfG+Q4sYSg==
"@esbuild/win32-x64@0.17.5":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.5.tgz#a8f3d26d8afc5186eccda265ceb1820b8e8830be"
integrity sha512-XgA9qWRqby7xdYXuF6KALsn37QGBMHsdhmnpjfZtYxKxbTOwfnDM6MYi2WuUku5poNaX2n9XGVr20zgT/2QwCw==
"@eslint/eslintrc@^0.4.3":
version "0.4.3"
@ -835,33 +835,33 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
esbuild@^0.17.4:
version "0.17.4"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.4.tgz#af4f8f78604c67f8e6afbdee36a3f4211ecfc859"
integrity sha512-zBn9MeCwT7W5F1a3lXClD61ip6vQM+H8Msb0w8zMT4ZKBpDg+rFAraNyWCDelB/2L6M3g6AXHPnsyvjMFnxtFw==
esbuild@^0.17.5:
version "0.17.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.5.tgz#cd76d75700d49ac050ad9eedfbed777bd6a9d930"
integrity sha512-Bu6WLCc9NMsNoMJUjGl3yBzTjVLXdysMltxQWiLAypP+/vQrf+3L1Xe8fCXzxaECus2cEJ9M7pk4yKatEwQMqQ==
optionalDependencies:
"@esbuild/android-arm" "0.17.4"
"@esbuild/android-arm64" "0.17.4"
"@esbuild/android-x64" "0.17.4"
"@esbuild/darwin-arm64" "0.17.4"
"@esbuild/darwin-x64" "0.17.4"
"@esbuild/freebsd-arm64" "0.17.4"
"@esbuild/freebsd-x64" "0.17.4"
"@esbuild/linux-arm" "0.17.4"
"@esbuild/linux-arm64" "0.17.4"
"@esbuild/linux-ia32" "0.17.4"
"@esbuild/linux-loong64" "0.17.4"
"@esbuild/linux-mips64el" "0.17.4"
"@esbuild/linux-ppc64" "0.17.4"
"@esbuild/linux-riscv64" "0.17.4"
"@esbuild/linux-s390x" "0.17.4"
"@esbuild/linux-x64" "0.17.4"
"@esbuild/netbsd-x64" "0.17.4"
"@esbuild/openbsd-x64" "0.17.4"
"@esbuild/sunos-x64" "0.17.4"
"@esbuild/win32-arm64" "0.17.4"
"@esbuild/win32-ia32" "0.17.4"
"@esbuild/win32-x64" "0.17.4"
"@esbuild/android-arm" "0.17.5"
"@esbuild/android-arm64" "0.17.5"
"@esbuild/android-x64" "0.17.5"
"@esbuild/darwin-arm64" "0.17.5"
"@esbuild/darwin-x64" "0.17.5"
"@esbuild/freebsd-arm64" "0.17.5"
"@esbuild/freebsd-x64" "0.17.5"
"@esbuild/linux-arm" "0.17.5"
"@esbuild/linux-arm64" "0.17.5"
"@esbuild/linux-ia32" "0.17.5"
"@esbuild/linux-loong64" "0.17.5"
"@esbuild/linux-mips64el" "0.17.5"
"@esbuild/linux-ppc64" "0.17.5"
"@esbuild/linux-riscv64" "0.17.5"
"@esbuild/linux-s390x" "0.17.5"
"@esbuild/linux-x64" "0.17.5"
"@esbuild/netbsd-x64" "0.17.5"
"@esbuild/openbsd-x64" "0.17.5"
"@esbuild/sunos-x64" "0.17.5"
"@esbuild/win32-arm64" "0.17.5"
"@esbuild/win32-ia32" "0.17.5"
"@esbuild/win32-x64" "0.17.5"
escape-string-regexp@^1.0.5:
version "1.0.5"