Skip to content

Commit

Permalink
Merge pull request #7244 from fjordllc/main
Browse files Browse the repository at this point in the history
Release 2024-01-31 14:27:39
  • Loading branch information
komagata authored Feb 1, 2024
2 parents 867d8b6 + 496a1c3 commit b11b5e9
Show file tree
Hide file tree
Showing 45 changed files with 798 additions and 168 deletions.
10 changes: 4 additions & 6 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ inherit_gem:
- config/rubocop.yml
- config/rails.yml

Metrics/BlockLength:
Exclude:
- test/**/*

Metrics/ClassLength:
Exclude:
- test/**/*
Expand All @@ -20,14 +16,16 @@ Metrics/ClassLength:
- app/controllers/users_controller.rb
- app/mailers/activity_mailer.rb
- app/models/event.rb
- app/models/notification.rb
- app/models/notification_facade.rb
- app/models/practice.rb
- app/models/user.rb
- app/models/product.rb
- app/models/report.rb
- app/notifiers/activity_notifier.rb

Metrics/ModuleLength:
Exclude:
- app/decorators/user_decorator.rb

AllCops:
Exclude:
- '**/templates/**/*'
Expand Down
6 changes: 0 additions & 6 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,3 @@ Style/HashSyntax:
- 'test/system/sign_up_test.rb'
- 'test/system/user/products_test.rb'
- 'test/test_helper.rb'

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantFreeze:
Exclude:
- 'app/models/link_checker/extractor.rb'
6 changes: 4 additions & 2 deletions app/controllers/api/products/self_assigned_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ def index
@target = 'self_assigned_all' unless target_allowlist.include?(@target)
@products = case @target
when 'self_assigned_all'
Product.self_assigned_product(current_user.id)
Product.unhibernated_user_products
.self_assigned_product(current_user.id)
.unchecked
.list
.order_for_self_assigned_list
.page(params[:page])
when 'self_assigned_no_replied'
Product.self_assigned_no_replied_products(current_user.id)
Product.unhibernated_user_products
.self_assigned_no_replied_products(current_user.id)
.unchecked
.list
.page(params[:page])
Expand Down
56 changes: 7 additions & 49 deletions app/decorators/user_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,59 +1,13 @@
# frozen_string_literal: true

module UserDecorator
include Role
include Retire

def twitter_url
"https://twitter.com/#{twitter_account}"
end

def roles
role_list = [
{ role: 'retired', value: retired? },
{ role: 'hibernationed', value: hibernated? },
{ role: 'admin', value: admin? },
{ role: 'mentor', value: mentor? },
{ role: 'adviser', value: adviser? },
{ role: 'graduate', value: graduated? },
{ role: 'trainee', value: trainee? }
]
roles = role_list.find_all { |v| v[:value] }
.map { |v| v[:role] }
roles << :student if roles.empty?

roles
end

def primary_role
roles.first
end

def staff_roles
staff_roles = [
{ role: '管理者', value: admin? },
{ role: 'メンター', value: mentor? },
{ role: 'アドバイザー', value: adviser? }
]
staff_roles.find_all { |v| v[:value] }
.map { |v| v[:role] }
.join('、')
end

def roles_to_s
return '' if roles.empty?

roles = [
{ role: '退会ユーザー', value: retired? },
{ role: '休会ユーザー', value: hibernated? },
{ role: '管理者', value: admin? },
{ role: 'メンター', value: mentor? },
{ role: 'アドバイザー', value: adviser? },
{ role: '卒業生', value: graduated? },
{ role: '研修生', value: trainee? }
]
roles.find_all { |v| v[:value] }
.map { |v| v[:role] }
.join('、')
end

def icon_title
["#{login_name} (#{name})", staff_roles].reject(&:blank?)
.join(': ')
Expand Down Expand Up @@ -120,4 +74,8 @@ def address
country_name
end
end

def hibernation_days
ActiveSupport::Duration.build(Time.zone.now - hibernated_at).in_days.floor if hibernated_at?
end
end
26 changes: 26 additions & 0 deletions app/decorators/user_decorator/retire.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module UserDecorator
module Retire
def retire_countdown
ActiveSupport::Duration.build(scheduled_retire_at - Time.current) if hibernated_at?
end

def retire_deadline
countdown =
if retire_countdown.in_hours < 1
"#{retire_countdown.in_minutes.floor}分"
elsif retire_countdown.in_hours < 24
"#{retire_countdown.in_hours.floor}時間"
else
"#{retire_countdown.in_days.floor}日"
end

"#{l scheduled_retire_at} (自動退会まであと#{countdown})"
end

def countdown_danger_tag
retire_countdown.in_days <= 7 ? 'is-danger' : ''
end
end
end
54 changes: 54 additions & 0 deletions app/decorators/user_decorator/role.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

module UserDecorator
module Role
def roles
role_list = [
{ role: 'retired', value: retired? },
{ role: 'hibernationed', value: hibernated? },
{ role: 'admin', value: admin? },
{ role: 'mentor', value: mentor? },
{ role: 'adviser', value: adviser? },
{ role: 'graduate', value: graduated? },
{ role: 'trainee', value: trainee? }
]
roles = role_list.find_all { |v| v[:value] }
.map { |v| v[:role] }
roles << :student if roles.empty?

roles
end

def primary_role
roles.first
end

def staff_roles
staff_roles = [
{ role: '管理者', value: admin? },
{ role: 'メンター', value: mentor? },
{ role: 'アドバイザー', value: adviser? }
]
staff_roles.find_all { |v| v[:value] }
.map { |v| v[:role] }
.join('、')
end

def roles_to_s
return '' if roles.empty?

roles = [
{ role: '退会ユーザー', value: retired? },
{ role: '休会ユーザー', value: hibernated? },
{ role: '管理者', value: admin? },
{ role: 'メンター', value: mentor? },
{ role: 'アドバイザー', value: adviser? },
{ role: '卒業生', value: graduated? },
{ role: '研修生', value: trainee? }
]
roles.find_all { |v| v[:value] }
.map { |v| v[:role] }
.join('、')
end
end
end
4 changes: 2 additions & 2 deletions app/helpers/search_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def searchable_url(searchable)
document = searchable.commentable_type.constantize.find(searchable.commentable_id)
"#{polymorphic_url(document)}#comment_#{searchable.id}"
elsif searchable.instance_of?(Answer) || searchable.instance_of?(CorrectAnswer)
document = searchable.question
polymorphic_url(document).to_s
document = Question.find(searchable.question.id)
"#{polymorphic_url(document)}#answer_#{searchable.id}"
else
polymorphic_url(searchable)
end
Expand Down
173 changes: 173 additions & 0 deletions app/javascript/components/Searchable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React from 'react'
import dayjs from 'dayjs'
import ja from 'dayjs/locale/ja'
dayjs.locale(ja)

export default function Searchable({ searchable, word }) {
const roleClass = `is-${searchable.primary_role}`
const searchableClass = searchable.wip
? `is-wip is-${searchable.model_name}`
: `is-${searchable.model_name}`

const userUrl = `/users/${searchable.user_id}`
const documentAuthorUserUrl = `/users/${searchable.document_author_id}`
const talkUrl = `/talks/${searchable.talk_id}`

const updatedAt = dayjs(searchable.updated_at).format(
'YYYY年MM月DD日(dd) HH:mm'
)
const canDisplayTalk = searchable.model_name === 'user' && searchable.talk_id
const currentUser = window.currentUser

const labelContent =
searchable.model_name === 'regular_event' ? (
<span className="card-list-item__label-inner is-sm">
定期
<br />
イベント
</span>
) : searchable.model_name === 'event' ? (
<span className="card-list-item__label-inner is-sm">
特別
<br />
イベント
</span>
) : searchable.model_name === 'practice' ? (
<span className="card-list-item__label-inner">
プラク
<br />
ティス
</span>
) : (
<span className="card-list-item__label">
{searchable.model_name_with_i18n}
</span>
)

const badgeContent = searchable.wip ? (
<div className="a-list-item-badge is-wip">
<span>WIP</span>
</div>
) : searchable.is_comment_or_answer ? (
<div className="a-list-item-badge is-searchable">
<span>コメント</span>
</div>
) : searchable.is_user ? (
<div className="a-list-item-badge is-searchable">
<span>ユーザー</span>
</div>
) : (
''
)

const summary = () => {
const wordsPattern = word
.trim()
.replaceAll(/[.*+?^=!:${}()|[\]/\\]/g, '\\$&')
.replaceAll(/\s+/g, '|')
const pattern = new RegExp(wordsPattern, 'gi')
if (word) {
return searchable.summary.replaceAll(
pattern,
`<strong class='matched_word'>$&</strong>`
)
} else {
return searchable.summary
}
}

return (
<div className={`card-list-item ${searchableClass}`}>
<div className="card-list-item__inner">
{searchable.is_user && (
<div className="card-list-item__user">
<a className="card-list-item__user-link" href={searchable.url}>
<span className={`a-user-role ${roleClass}`}>
<img
className="card-list-item__user-icon a-user-icon"
src={searchable.avatar_url}
title={searchable.title}
alt={searchable.title}></img>
</span>
</a>
</div>
)}
{!searchable.is_user && (
<div className="card-list-item__label">{labelContent}</div>
)}
<div className="card-list-item__rows">
<div className="card-list-item__row">
<div className="card-list-item-title">
{badgeContent}
<div className="card-list-item-title__title">
<a
className="card-list-item-title__link a-text-link"
href={searchable.url}>
{searchable.title}
</a>
</div>
</div>
</div>
<div className="card-list-item__row">
<div className="card-list-item__summary">
<p dangerouslySetInnerHTML={{ __html: summary() }}></p>
</div>
</div>
<div className="card-list-item__row">
<div className="card-list-item-meta">
<div className="card-list-item-meta__items">
{!['practice', 'page', 'user'].includes(
searchable.model_name
) && (
<div className="card-list-item-meta__item">
<div className="card-list-item-meta__user">
<a
className="card-list-item-meta__icon-link"
href={userUrl}>
<span className={`a-user-role ${roleClass}`}>
<img
className="card-list-item-meta__icon a-user-icon"
src={searchable.avatar_url}
title={searchable.icon_title}
alt={searchable.icon_title}></img>
</span>
</a>
<a className="a-user-name" href={userUrl}>
{searchable.login_name}
</a>
</div>
</div>
)}
<div className="card-list-item-meta__item">
<div
className={`time a-meta datetime=${searchable.updated_at} pubdate="pubdate"`}>
{updatedAt}
</div>
</div>
{searchable.is_comment_or_answer && (
<div className="card-list-item-meta__item">
<div className="a-meta">
{'('}
<a className="a-user-name" href={documentAuthorUserUrl}>
{searchable.document_author_login_name}
</a>{' '}
{searchable.model_name_with_i18n}
{')'}
</div>
</div>
)}
{currentUser.roles.includes('admin') && canDisplayTalk && (
<div className="card-list-item-meta__item">
<a className="a-text-link" href={talkUrl}>
相談部屋
</a>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
Loading

0 comments on commit b11b5e9

Please sign in to comment.