diff --git a/app/assets/stylesheets/components/_answerbox.scss b/app/assets/stylesheets/components/_answerbox.scss index e0e3936f3..ed550dc6e 100644 --- a/app/assets/stylesheets/components/_answerbox.scss +++ b/app/assets/stylesheets/components/_answerbox.scss @@ -1,8 +1,6 @@ @use "sass:map"; .answerbox { - &__question-text, - &__question-user, &__answer-user, &__answer-date { margin-bottom: 0; @@ -25,7 +23,6 @@ margin-bottom: map.get($spacers, 3); } - &__question-user-avatar, &__answer-user-avatar { margin-right: map.get($spacers, 2); border-radius: $avatar-border-radius; diff --git a/app/assets/stylesheets/components/_collapse.scss b/app/assets/stylesheets/components/_collapse.scss index 0f8afd606..a54dfca02 100644 --- a/app/assets/stylesheets/components/_collapse.scss +++ b/app/assets/stylesheets/components/_collapse.scss @@ -12,7 +12,7 @@ } } - &.answerbox__question-text { + &.question__text { max-height: 15rem; @include media-breakpoint-up('sm') { diff --git a/app/assets/stylesheets/components/_question.scss b/app/assets/stylesheets/components/_question.scss index 7d03e06bb..1a32ded72 100644 --- a/app/assets/stylesheets/components/_question.scss +++ b/app/assets/stylesheets/components/_question.scss @@ -1,18 +1,26 @@ +@use "sass:map"; + .question { - &--fixed { - position: absolute; + + &__avatar { + margin-right: map.get($spacers, 2); + border-radius: $avatar-border-radius; + } + + &__text, + &__user { + margin-bottom: 0; + overflow: hidden; + word-break: break-word; + } + + &--sticky { + border-radius: 0; width: 100%; - z-index: 999; @include media-breakpoint-up('sm') { - position: fixed; + position: sticky; + top: $navbar-height; } } - - &--hidden { - visibility: hidden; - position: relative; - box-shadow: none; - z-index: -1; - } } diff --git a/app/components/application_component.rb b/app/components/application_component.rb new file mode 100644 index 000000000..0be055fa4 --- /dev/null +++ b/app/components/application_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ApplicationComponent < ViewComponent::Base + include ApplicationHelper + delegate :current_user, to: :helpers + delegate :user_signed_in?, to: :helpers +end diff --git a/app/components/question_component.rb b/app/components/question_component.rb new file mode 100644 index 000000000..f14a1de36 --- /dev/null +++ b/app/components/question_component.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class QuestionComponent < ApplicationComponent + include ApplicationHelper + include BootstrapHelper + include UserHelper + + def initialize(question:, context_user: nil, collapse: true, hide_avatar: false, profile_question: false) + @question = question + @context_user = context_user + @collapse = collapse + @hide_avatar = hide_avatar + @profile_question = profile_question + end + + private + + def author_identifier = @question.author_is_anonymous ? @question.author_identifier : nil + + def follower_question? = !@question.author_is_anonymous && !@question.direct && @question.answer_count.positive? + + def hide_avatar? = @hide_avatar || @question.author_is_anonymous +end diff --git a/app/components/question_component/question_component.en.yml b/app/components/question_component/question_component.en.yml new file mode 100644 index 000000000..3978ea983 --- /dev/null +++ b/app/components/question_component/question_component.en.yml @@ -0,0 +1,8 @@ +en: + answers: + zero: "0 answers" + one: "1 answer" + other: "%{count} answers" + asked_html: "%{user} asked %{time} ago" + visible_to_you: "Only visible to you as it was asked directly" + visible_mod_mode: "You can see this because you are in moderation view" diff --git a/app/components/question_component/question_component.html.haml b/app/components/question_component/question_component.html.haml new file mode 100644 index 000000000..5716b279a --- /dev/null +++ b/app/components/question_component/question_component.html.haml @@ -0,0 +1,30 @@ +.d-flex + - unless hide_avatar? + .flex-shrink-0 + %a{ href: user_path(@question.user) } + = render AvatarComponent.new(user: @question.user, size: "md", classes: ["question__avatar"]) + .flex-grow-1 + %h6.text-muted.question__user + - if @question.author_is_anonymous + %i.fas.fa-user-secret{ title: t(".anon_hint") } + - if @profile_question && @question.direct + - if user_signed_in? && @question.user == current_user + %i.fa.fa-eye-slash{ data: { bs_toggle: "tooltip", bs_title: t(".visible_to_you") } } + - elsif moderation_view? + %i.fa.fa-eye-slash{ data: { bs_toggle: "tooltip", bs_title: t(".visible_mod_mode") } } + = t(".asked_html", user: user_screen_name(@question.user, context_user: @context_user, author_identifier: author_identifier), time: time_tooltip(@question)) + - if follower_question? + · + %a{ href: question_path(@question.user.screen_name, @question.id), data: { selection_hotkey: "a" } } + = t(".answers", count: @question.answer_count) + .question__body{ data: { controller: @question.long? ? "collapse" : nil } } + .question__text{ class: @question.long? && @collapse ? "collapsed" : "", data: { collapse_target: "content" } } + = question_markdown @question.content + - if @question.long? && @collapse + = render "shared/collapse", type: "question" + - if user_signed_in? + .flex-shrink-0.ms-auto + .btn-group + %button.btn.btn-link.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } } + %span.caret + = render "actions/question", question: @question diff --git a/app/javascript/retrospring/features/memes/index.ts b/app/javascript/retrospring/features/memes/index.ts index fe509ce72..9f74598fd 100644 --- a/app/javascript/retrospring/features/memes/index.ts +++ b/app/javascript/retrospring/features/memes/index.ts @@ -4,8 +4,8 @@ export default (): void => { cheet('up up down down left right left right b a', () => { document.body.classList.add('fa-spin'); - Array.from(document.querySelectorAll('.answerbox__question-text')).forEach((element: HTMLElement) => { + Array.from(document.querySelectorAll('.question__text')).forEach((element: HTMLElement) => { element.innerText = ':^)'; }); }); -} \ No newline at end of file +} diff --git a/app/views/answerbox/_header.html.haml b/app/views/answerbox/_header.html.haml deleted file mode 100644 index 37f5b4064..000000000 --- a/app/views/answerbox/_header.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -.card-header - .d-flex - - unless a.question.author_is_anonymous - .flex-shrink-0 - %a{ href: user_path(a.question.user) } - = 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 - %i.fas.fa-user-secret{ title: t(".anon_hint") } - = t(".asked_html", user: user_screen_name(a.question.user, context_user: a.user, author_identifier: a.question.author_is_anonymous ? a.question.author_identifier: nil), time: time_tooltip(a.question)) - - if !a.question.author_is_anonymous && !a.question.direct - · - %a{ href: question_path(a.question.user.screen_name, a.question.id), data: { selection_hotkey: "a" } } - = t(".answers", count: a.question.answer_count) - .answerbox__question-body{ data: { controller: a.question.long? ? "collapse" : nil } } - .answerbox__question-text{ class: a.question.long? && !display_all ? "collapsed" : "", data: { collapse_target: "content" } } - = question_markdown a.question.content - - if a.question.long? && !display_all - = render "shared/collapse", type: "question" - - if user_signed_in? - .flex-shrink-0.ms-auto - .btn-group - %button.btn.btn-link.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } } - %span.caret - = render "actions/question", question: a.question diff --git a/app/views/application/_answerbox.html.haml b/app/views/application/_answerbox.html.haml index 470101057..de1349f1f 100644 --- a/app/views/application/_answerbox.html.haml +++ b/app/views/application/_answerbox.html.haml @@ -1,7 +1,8 @@ - display_all ||= nil .card.answerbox{ data: { id: a.id, q_id: a.question.id, navigation_target: "traversable" } } - if @question.nil? - = render "answerbox/header", a: a, display_all: display_all + .card-header + = render QuestionComponent.new(question: a.question, context_user: a.user, collapse: !display_all) .card-body .answerbox__answer-body{ data: { controller: a.long? ? "collapse" : nil } } .answerbox__answer-text{ class: a.long? && !display_all ? "collapsed" : "", data: { collapse_target: "content" } } diff --git a/app/views/discover/_userbox.html.haml b/app/views/discover/_userbox.html.haml index 165ccc51a..8293104bc 100644 --- a/app/views/discover/_userbox.html.haml +++ b/app/views/discover/_userbox.html.haml @@ -5,7 +5,7 @@ %a{ href: user_path(u) } = render AvatarComponent.new(user: u, size: "md", classes: ["me-2"]) .flex-grow-1 - %h6.answerbox__question-user + %h6.question__user - if u.profile.display_name.blank? %a{ href: user_path(u) } = u.screen_name @@ -13,4 +13,4 @@ %a{ href: user_path(u) } = u.profile.display_name %span.text-muted= u.screen_name - %p.answerbox__question-text= t(".#{type}", value: value, count: value) + %p.question__text= t(".#{type}", value: value, count: value) diff --git a/app/views/inbox/_entry.html.haml b/app/views/inbox/_entry.html.haml index 873d37954..4c9bd37b8 100644 --- a/app/views/inbox/_entry.html.haml +++ b/app/views/inbox/_entry.html.haml @@ -1,30 +1,6 @@ .card.inbox-entry{ id: "inbox_#{i.id}", class: i.new? ? "inbox-entry--new" : "", data: { id: i.id } } .card-header - .d-flex - - unless i.question.author_is_anonymous - .flex-shrink-0 - %a.pull-left{ href: user_path(i.question.user) } - = 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 - %i.fas.fa-user-secret{ title: t('.anon_hint') } - = t(".asked_html", user: user_screen_name(i.question.user, context_user: i.user, author_identifier: i.question.author_is_anonymous ? i.question.author_identifier : nil), time: time_tooltip(i.question)) - - if !i.question.author_is_anonymous && i.question.answer_count.positive? - · - %a{ href: question_path(i.question.user.screen_name, i.question.id) } - = t(".answers", count: i.question.answer_count) - .answerbox__question-body{ data: { controller: i.question.long? ? "collapse" : nil } } - .answerbox__question-text{ class: i.question.long? ? "collapsed" : "", data: { collapse_target: "content" } } - = question_markdown i.question.content - - if i.question.long? - = render "shared/collapse", type: "question" - - if i.question.user_id != current_user.id || current_user.has_cached_role?(:administrator) - .flex-shrink-0.ms-auto - .btn-group - %button.btn.btn-default.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } } - %span.caret - = render "actions/question", question: i.question + = render QuestionComponent.new(question: i.question, context_user: i.user) - if current_user == i.user .card-body %textarea.form-control.mb-3{ name: "ib-answer", placeholder: t(".placeholder"), data: { id: i.id } } diff --git a/app/views/moderation/inbox/_header.html.haml b/app/views/moderation/inbox/_header.html.haml index cd57e8d8c..14d9f868d 100644 --- a/app/views/moderation/inbox/_header.html.haml +++ b/app/views/moderation/inbox/_header.html.haml @@ -4,6 +4,6 @@ .d-flex .flex-shrink-0 %a{ href: user_path(user) } - = render AvatarComponent.new(user:, size: "md", classes: ["answerbox__question-user-avatar"]) + = render AvatarComponent.new(user:, size: "md", classes: ["question__avatar"]) .flex-grow-1 = t(".title_html", screen_name: user.screen_name, user_id: user.id) diff --git a/app/views/question/_question.html.haml b/app/views/question/_question.html.haml deleted file mode 100644 index c25594f04..000000000 --- a/app/views/question/_question.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -.card.question--fixed{ class: hidden ? 'question--hidden' : '', tabindex: hidden ? -1 : '', aria: { hidden: hidden } } - .container - .card-body - .d-flex - - unless question.author_is_anonymous - .flex-shrink-0 - %a{ href: unless hidden then user_path(question.user) end } - = 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 - - if hidden - = user_screen_name question.user, author_identifier: identifier, url: false - - else - = t("answerbox.header.asked_html", user: user_screen_name(question.user, author_identifier: identifier), time: time_tooltip(question)) - .answerbox__question-body{ data: { controller: question.long? ? "collapse" : nil } } - .answerbox__question-text{ class: question.long? ? "collapsed" : "", data: { collapse_target: "content" } } - = question_markdown question.content - - if question.long? - = render "shared/collapse", type: "question" - - if user_signed_in? - .flex-shrink-0.ms-auto - .btn-group - %button.btn.btn-link.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } } - %span.caret - - unless hidden - = render "actions/question", question: question diff --git a/app/views/question/show.html.haml b/app/views/question/show.html.haml index e1d0b2381..966517909 100644 --- a/app/views/question/show.html.haml +++ b/app/views/question/show.html.haml @@ -1,6 +1,8 @@ - provide(:title, question_title(@question)) -= render "question", question: @question, hidden: false -= render "question", question: @question, hidden: true +.card.question--sticky + .container + .card-body + = render QuestionComponent.new(question: @question) .container.question-page #answers{ data: { controller: "navigation" } } %button.d-none{ data: { hotkey: "j", action: "navigation#down" } } diff --git a/app/views/shared/_question.html.haml b/app/views/shared/_question.html.haml index 6ebd7d933..022cc71ad 100644 --- a/app/views/shared/_question.html.haml +++ b/app/views/shared/_question.html.haml @@ -1,30 +1,4 @@ - type ||= nil .card.questionbox{ data: { id: q.id } } .card-body{ data: { controller: q.long? ? "collapse" : nil } } - .d-flex - - if type == "discover" - .flex-shrink-0 - %a{ href: user_screen_name(q.user, link_only: true) } - = 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 - - if user_signed_in? && q.user == current_user - %i.fa.fa-eye-slash{ data: { bs_toggle: "tooltip", bs_title: t(".visible_to_you") } } - - elsif moderation_view? - %i.fa.fa-eye-slash{ data: { bs_toggle: "tooltip", bs_title: t(".visible_mod_mode") } } - = t("answerbox.header.asked_html", user: user_screen_name(q.user), time: time_tooltip(q)) - - if q.answer_count > 1 - · - %a{ href: question_path(q.user.screen_name, q.id) } - = pluralize(q.answer_count, t("voc.answer")) - .answerbox__question-text{ class: q.long? ? "collapsed" : "", data: { collapse_target: "content" } } - = question_markdown q.content - - if q.long? - = render "shared/collapse", type: "question" - - if user_signed_in? - .flex-shrink-0.ms-auto - .btn-group - %button.btn.btn-link.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } } - %span.caret - = render "actions/question", question: q + = render QuestionComponent.new(question: q, hide_avatar: type == "discover" ? false : true, profile_question: true) diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 8613ff57b..1af7cb01f 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -584,9 +584,6 @@ en: anonymous_block: deleted_question: "Deleted question" blocked: "blocked %{time} ago" - question: - visible_to_you: "Only visible to you as it was asked directly" - visible_mod_mode: "You can see this because you are in moderation view" hotkeys: navigation: title: "Navigation" diff --git a/spec/views/inbox/_entry.html.haml_spec.rb b/spec/views/inbox/_entry.html.haml_spec.rb index a94c2c1c3..c8df132f8 100644 --- a/spec/views/inbox/_entry.html.haml_spec.rb +++ b/spec/views/inbox/_entry.html.haml_spec.rb @@ -8,7 +8,7 @@ let(:user) { FactoryBot.create(:user, sharing_enabled:, sharing_custom_url:) } let(:sharing_enabled) { true } let(:sharing_custom_url) { nil } - let(:question) { FactoryBot.create(:question, content: "owo what's this?", author_is_anonymous:, user: question_user, answer_count:) } + let(:question) { FactoryBot.create(:question, content: "owo what's this?", author_is_anonymous:, user: question_user, answer_count:, direct: false) } let(:author_is_anonymous) { true } let(:question_user) { nil } let(:answer_count) { 0 } @@ -45,7 +45,7 @@ let(:author_is_anonymous) { false } it "has an avatar" do - expect(rendered).to have_css(%(img.answerbox__question-user-avatar)) + expect(rendered).to have_css(%(img.question__avatar)) end it "does not have an icon indicating the author is anonymous" do