Merge pull request #1403 from Retrospring/feature/avatar-component
Install `view_component` and add initial `AvatarComponent`
This commit is contained in:
commit
2432010256
|
@ -27,6 +27,10 @@ Lint/NestedMethodDefinition:
|
|||
Exclude:
|
||||
- api/sinatra/**/*
|
||||
|
||||
Lint/MissingSuper:
|
||||
Exclude:
|
||||
- app/components/**/*
|
||||
|
||||
|
||||
### Metrics
|
||||
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -66,6 +66,8 @@ gem "fake_email_validator"
|
|||
# TLD validation
|
||||
gem "tldv", "~> 0.1.0"
|
||||
|
||||
gem "view_component"
|
||||
|
||||
gem "jwt", "~> 2.7"
|
||||
|
||||
group :development do
|
||||
|
|
|
@ -485,6 +485,10 @@ GEM
|
|||
unf_ext (0.0.8)
|
||||
unicode-display_width (2.5.0)
|
||||
uniform_notifier (1.16.0)
|
||||
view_component (3.6.0)
|
||||
activesupport (>= 5.2.0, < 8.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
method_source (~> 1.0)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webpush (1.1.0)
|
||||
|
@ -580,6 +584,7 @@ DEPENDENCIES
|
|||
tldv (~> 0.1.0)
|
||||
turbo-rails
|
||||
twitter-text
|
||||
view_component
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.21
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
%img{ class: avatar_classes,
|
||||
alt: alt_text,
|
||||
src: avatar_image,
|
||||
loading: :lazy }
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AvatarComponent < ViewComponent::Base
|
||||
ALLOWED_SIZES = %w[xs sm md lg xl xxl].freeze
|
||||
|
||||
def initialize(user:, size:, classes: [])
|
||||
@user = user
|
||||
@size = size if ALLOWED_SIZES.include? size
|
||||
@classes = classes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def size_to_version(size)
|
||||
case size
|
||||
when "xs", "sm"
|
||||
:small
|
||||
when "md", "lg"
|
||||
:medium
|
||||
when "xl", "xxl"
|
||||
:large
|
||||
end
|
||||
end
|
||||
|
||||
def alt_text = "@#{@user.screen_name}"
|
||||
|
||||
def avatar_classes = @classes.unshift("avatar-#{@size}")
|
||||
|
||||
def avatar_image = @user.profile_picture.url(size_to_version(@size))
|
||||
end
|
|
@ -8,7 +8,7 @@
|
|||
.d-flex
|
||||
.flex-shrink-0
|
||||
%a{ href: user_path(comment.user) }
|
||||
%img.comment__user-avatar.avatar-sm{ src: comment.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: comment.user, size: "sm", classes: ["comment__user-avatar"])
|
||||
.flex-grow-1
|
||||
%h6.comment__user
|
||||
= user_screen_name comment.user
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- unless a.question.author_is_anonymous
|
||||
.flex-shrink-0
|
||||
%a{ href: user_path(a.question.user) }
|
||||
%img.answerbox__question-user-avatar.avatar-md{ src: a.question.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: a.question.user, size: "md", classes: ["answerbox__question-user-avatar"])
|
||||
.flex-grow-1
|
||||
%h6.text-muted.answerbox__question-user
|
||||
- if a.question.author_is_anonymous
|
||||
|
|
|
@ -9,4 +9,4 @@
|
|||
%a{ href: user_path(smile.user),
|
||||
title: user_screen_name(smile.user, url: false),
|
||||
data: { bs_toggle: :tooltip, bs_placement: :top, smile_id: smile.id } }
|
||||
%img.avatar-xs{ src: smile.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: smile.user, size: "xs")
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
.d-flex
|
||||
.flex-shrink-0
|
||||
%a{ href: user_path(a.user) }
|
||||
%img.answerbox__answer-user-avatar.avatar-sm{ src: a.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: a.user, size: "sm", classes: ["answerbox__answer-user-avatar"])
|
||||
.flex-grow-1
|
||||
%h6.answerbox__answer-user
|
||||
= raw t(".answered", hide: hidespan(t(".hide"), "d-none d-sm-inline"), user: user_screen_name(a.user))
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
.d-flex
|
||||
.flex-shrink-0
|
||||
%a{ href: user_path(u) }
|
||||
%img.avatar-md.me-2{ src: u.profile_picture.url(:medium) }
|
||||
= render AvatarComponent.new(user: u, size: "md", classes: ["me-2"])
|
||||
.flex-grow-1
|
||||
%h6.answerbox__question-user
|
||||
- if u.profile.display_name.blank?
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
- unless i.question.author_is_anonymous
|
||||
.flex-shrink-0
|
||||
%a.pull-left{ href: user_path(i.question.user) }
|
||||
%img.answerbox__question-user-avatar.avatar-md{ src: i.question.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: i.question.user, size: "md", classes: ["answerbox__question-user-avatar"])
|
||||
.flex-grow-1
|
||||
%h6.text-muted.answerbox__question-user
|
||||
- if i.question.author_is_anonymous
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.card.moderationbox{ data: { id: report.id } }
|
||||
.card-header
|
||||
%img.avatar-sm{ src: report.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: report.user, size: "sm")
|
||||
= t(".reported_html",
|
||||
user: user_screen_name(report.user),
|
||||
content: report.type.sub("Reports::", ""),
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
.d-flex
|
||||
.flex-shrink-0
|
||||
%a{ href: user_path(user) }
|
||||
%img.answerbox__question-user-avatar.avatar-md{ src: user.profile_picture.url(:medium) }
|
||||
= render AvatarComponent.new(user:, size: "md", classes: ["answerbox__question-user-avatar"])
|
||||
.flex-grow-1
|
||||
= t(".title_html", screen_name: user.screen_name, user_id: user.id)
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
%i.fa.fa-pencil-square-o
|
||||
%li.nav-item.dropdown.profile--image-dropdown
|
||||
%a.nav-link.dropdown-toggle.p-sm-0{ href: "#", data: { bs_toggle: :dropdown } }
|
||||
%img.avatar-md.d-none.d-sm-inline{ src: current_user.profile_picture.url(:small) }
|
||||
= render AvatarComponent.new(user: current_user, size: "md", classes: ["d-none", "d-sm-inline"])
|
||||
%span.d-inline.d-sm-none
|
||||
= current_user.screen_name
|
||||
%b.caret
|
||||
|
|
|
@ -12,5 +12,5 @@
|
|||
badge: notification_count, badge_color: "primary", badge_attr: { id: "notification-mobile-count" }, icon_only: true
|
||||
%li.nav-item.profile--image-dropdown
|
||||
%a.nav-link{ href: '#', data: { bs_toggle: 'dropdown', bs_target: '#rs-mobile-nav-profile' }, aria: { controls: 'rs-mobile-nav-profile', expanded: 'false' } }
|
||||
%img.avatar-md.d-inline{ src: current_user.profile_picture.url(:small) }
|
||||
= render AvatarComponent.new(user: current_user, size: "md", classes: ["d-inline"])
|
||||
= render 'navigation/dropdown/profile', size: "mobile"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%i.fa.fa-2x.fa-fw.fa-exclamation
|
||||
.flex-grow-1
|
||||
.notification__heading
|
||||
%img.avatar-xs{ src: notification.target.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: notification.target.user, size: "xs")
|
||||
= t(".heading_html",
|
||||
user: user_screen_name(notification.target.user),
|
||||
question: link_to(t(".link_text"), answer_path(username: notification.target.user.screen_name, id: notification.target.id), target: "_top"),
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%i.fa.fa-2x.fa-fw.fa-comments
|
||||
.flex-grow-1
|
||||
.notification__heading
|
||||
%img.avatar-xs{ src: notification.target.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: notification.target.user, size: "xs")
|
||||
- if notification.target.answer.user == current_user
|
||||
= t(".heading_html",
|
||||
user: user_screen_name(notification.target.user),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.d-flex.notification
|
||||
.flex-shrink-0.notification__icon
|
||||
%img.avatar-sm{ src: notification.target.source.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: notification.target.source, size: "sm")
|
||||
.flex-grow-1
|
||||
%h6.notification__user
|
||||
= user_screen_name notification.target.source
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%i.fa.fa-2x.fa-fw.fa-smile-o
|
||||
.flex-grow-1
|
||||
.notification__heading
|
||||
%img.avatar-xs{ src: notification.target.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: notification.target.user, size: "xs")
|
||||
- if notification.target.parent_type == "Answer"
|
||||
= t(".heading_html",
|
||||
user: user_screen_name(notification.target.user),
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
- unless question.author_is_anonymous
|
||||
.flex-shrink-0
|
||||
%a{ href: unless hidden then user_path(question.user) end }
|
||||
%img.answerbox__question-user-avatar.avatar-md{ src: question.user.profile_picture.url(:small) }
|
||||
= render AvatarComponent.new(user: question.user, size: "md", classes: ["answerbox__question-user-avatar"])
|
||||
.flex-grow-1
|
||||
%h6.text-muted.answerbox__question-user
|
||||
- identifier = question.author_is_anonymous ? question.author_identifier : nil
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
- @blocks.each do |block|
|
||||
%li.list-group-item
|
||||
.d-flex
|
||||
%img.avatar-md.d-none.d-sm-inline.me-2{ src: block.target.profile_picture.url(:small) }
|
||||
= render AvatarComponent.new(user: block.target, size: "md", classes: ["d-none", "d-sm-inline", "me-2"])
|
||||
%div
|
||||
%p.mb-0= user_screen_name(block.target)
|
||||
%p.text-muted.mb-0= t(".blocked", time: time_ago_in_words(block.created_at))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.d-flex.mb-2
|
||||
%img.avatar-md.me-2{ src: user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user:, size: "md", classes: ["me-2"])
|
||||
%p.align-self-center.m-0= user_screen_name(user, context_user: current_user)
|
||||
.ms-auto.d-inline-flex
|
||||
%button.btn.btn-default.align-self-center{ data: { action: :unmute, target: user.screen_name } }
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
%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) }
|
||||
= render AvatarComponent.new(user: current_user, size: "lg", classes: ["me-3"])
|
||||
.flex-grow-1
|
||||
= f.file_field :profile_picture, accept: APP_CONFIG[:accepted_image_formats].join(","), data: { cropper_target: "input", action: "cropper#change" }
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
- if type == "discover"
|
||||
.flex-shrink-0
|
||||
%a{ href: user_screen_name(q.user, link_only: true) }
|
||||
%img.avatar-md.me-2{ src: q.user&.profile_picture&.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: q.user, size: "md", classes: ["me-2"])
|
||||
.flex-grow-1
|
||||
%h6.text-muted.answerbox__question-user
|
||||
- if type.nil? && q.direct
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
%p.px-4.pb-2
|
||||
- list.members.each do |member|
|
||||
%a{ href: user_path(member.user), title: member.user.screen_name, data: { bs_toggle: :tooltip, bs_placement: :top } }
|
||||
%img.avatar-xs{ src: member.user.profile_picture.url(:small), loading: :lazy }
|
||||
= render AvatarComponent.new(user: member.user, size: "xs")
|
||||
- if !list && lists.empty?
|
||||
.p-3= t(".lists.notice_html")
|
||||
- lists.each do |list|
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe AvatarComponent, type: :component do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
|
||||
it "renders an avatar" do
|
||||
expect(
|
||||
render_inline(described_class.new(user:, size: "sm")).to_html,
|
||||
).to include(
|
||||
"no_avatar.png",
|
||||
)
|
||||
end
|
||||
|
||||
it "gets the medium version of a profile picture if requested" do
|
||||
expect(
|
||||
render_inline(described_class.new(user:, size: "md")).to_html,
|
||||
).to include(
|
||||
"medium/",
|
||||
)
|
||||
end
|
||||
|
||||
it "gets the large version of a profile picture if requested" do
|
||||
expect(
|
||||
render_inline(described_class.new(user:, size: "xl")).to_html,
|
||||
).to include(
|
||||
"large/",
|
||||
)
|
||||
end
|
||||
|
||||
it "includes additionally passed classes" do
|
||||
expect(
|
||||
render_inline(described_class.new(user:, size: "md", classes: %w[first-class second-class])).to_html,
|
||||
).to include(
|
||||
'class="avatar-md first-class second-class"',
|
||||
)
|
||||
end
|
||||
end
|
|
@ -11,6 +11,8 @@ require "rspec/rails"
|
|||
require "rspec/its"
|
||||
require "devise"
|
||||
require "rspec-sidekiq"
|
||||
require "view_component/test_helpers"
|
||||
require "view_component/system_test_helpers"
|
||||
|
||||
# Requires supporting ruby files with custom matchers and macros, etc, in
|
||||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
||||
|
@ -68,6 +70,8 @@ RSpec.configure do |config|
|
|||
config.include Devise::Test::ControllerHelpers, type: :controller
|
||||
config.include Devise::Test::ControllerHelpers, type: :helper
|
||||
config.include Devise::Test::ControllerHelpers, type: :view
|
||||
config.include ViewComponent::TestHelpers, type: :component
|
||||
config.include ViewComponent::SystemTestHelpers, type: :component
|
||||
end
|
||||
|
||||
Shoulda::Matchers.configure do |config|
|
||||
|
|
Loading…
Reference in New Issue