Merge pull request #935 from Retrospring/feature/long-questions
Add option to allow long questions
This commit is contained in:
commit
4692753485
|
@ -6,7 +6,7 @@ class Settings::ProfileController < ApplicationController
|
|||
def edit; end
|
||||
|
||||
def update
|
||||
profile_attributes = params.require(:profile).permit(:display_name, :motivation_header, :website, :location, :description, :anon_display_name)
|
||||
profile_attributes = params.require(:profile).permit(:display_name, :motivation_header, :website, :location, :description, :anon_display_name, :allow_long_questions)
|
||||
|
||||
if current_user.profile.update(profile_attributes)
|
||||
flash[:success] = t(".success")
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['input', 'warning'];
|
||||
|
||||
declare readonly inputTarget: HTMLInputElement;
|
||||
declare readonly warningTarget: HTMLElement;
|
||||
|
||||
static values = {
|
||||
warn: Number
|
||||
};
|
||||
|
||||
declare readonly warnValue: number;
|
||||
|
||||
connect(): void {
|
||||
this.inputTarget.addEventListener('input', this.update.bind(this));
|
||||
}
|
||||
|
||||
update(): void {
|
||||
if (this.inputTarget.value.length > this.warnValue) {
|
||||
if (this.warningTarget.classList.contains('d-none')) {
|
||||
this.warningTarget.classList.remove('d-none');
|
||||
}
|
||||
} else {
|
||||
if (!this.warningTarget.classList.contains('d-none')) {
|
||||
this.warningTarget.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,10 +24,6 @@ export function questionboxAllHandler(event: Event): void {
|
|||
document.querySelector<HTMLInputElement>('textarea[name=qb-all-question]').value = '';
|
||||
const modal = Modal.getInstance(document.querySelector('#modal-ask-followers'));
|
||||
modal.hide();
|
||||
|
||||
// FIXME: also solve this using a Stimulus controller
|
||||
const characterCount = document.querySelector<HTMLElement>('#modal-ask-followers [data-character-count-max-value]').dataset.characterCountMaxValue;
|
||||
document.querySelector<HTMLElement>('#modal-ask-followers [data-character-count-target="counter"]').innerHTML = characterCount;
|
||||
}
|
||||
|
||||
showNotification(data.message, data.success);
|
||||
|
|
|
@ -30,8 +30,10 @@ export function questionboxUserHandler(event: Event): void {
|
|||
document.querySelector<HTMLInputElement>('textarea[name=qb-question]').value = '';
|
||||
|
||||
// FIXME: also solve this using a Stimulus controller
|
||||
const characterCount = document.querySelector<HTMLElement>('#question-box[data-character-count-max-value]').dataset.characterCountMaxValue;
|
||||
document.querySelector<HTMLElement>('#question-box [data-character-count-target="counter"]').innerHTML = characterCount;
|
||||
const questionBox = document.getElementById('question-box');
|
||||
if ('characterCountMaxValue' in questionBox.dataset) {
|
||||
questionBox.querySelector<HTMLElement>('[data-character-count-target="counter"]').innerHTML = questionBox.dataset.characterCountMaxValue;
|
||||
}
|
||||
|
||||
if (promote) {
|
||||
const questionbox = document.querySelector('#question-box');
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Application } from "@hotwired/stimulus";
|
|||
import AnnouncementController from "retrospring/controllers/announcement_controller";
|
||||
import AutofocusController from "retrospring/controllers/autofocus_controller";
|
||||
import CharacterCountController from "retrospring/controllers/character_count_controller";
|
||||
import CharacterCountWarningController from "retrospring/controllers/character_count_warning_controller";
|
||||
import FormatPopupController from "retrospring/controllers/format_popup_controller";
|
||||
|
||||
/**
|
||||
|
@ -16,5 +17,6 @@ export default function (): void {
|
|||
window['Stimulus'].register('announcement', AnnouncementController);
|
||||
window['Stimulus'].register('autofocus', AutofocusController);
|
||||
window['Stimulus'].register('character-count', CharacterCountController);
|
||||
window['Stimulus'].register('character-count-warning', CharacterCountWarningController);
|
||||
window['Stimulus'].register('format-popup', FormatPopupController);
|
||||
}
|
||||
|
|
|
@ -26,4 +26,6 @@ class Profile < ApplicationRecord
|
|||
def safe_name
|
||||
display_name.presence || user.screen_name
|
||||
end
|
||||
|
||||
def question_length_limit = allow_long_questions ? nil : Question::SHORT_QUESTION_MAX_LENGTH
|
||||
end
|
||||
|
|
|
@ -5,7 +5,9 @@ class Question < ApplicationRecord
|
|||
has_many :answers, dependent: :destroy
|
||||
has_many :inboxes, dependent: :destroy
|
||||
|
||||
validates :content, length: { minimum: 1, maximum: 512 }
|
||||
validates :content, length: { minimum: 1 }
|
||||
|
||||
SHORT_QUESTION_MAX_LENGTH = 512
|
||||
|
||||
before_destroy do
|
||||
rep = Report.where(target_id: self.id, type: 'Reports::Question')
|
||||
|
@ -30,4 +32,6 @@ class Question < ApplicationRecord
|
|||
def generated? = %w[justask retrospring_exporter].include?(author_identifier)
|
||||
|
||||
def anonymous? = author_is_anonymous && author_identifier.present?
|
||||
|
||||
def long? = content.length > SHORT_QUESTION_MAX_LENGTH
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
%strong= t(".status.require_user_html", sign_in: link_to(t("voc.login"), new_user_session_path), sign_up: link_to(t("voc.register"), new_user_registration_path))
|
||||
- else
|
||||
- if user_signed_in? || user.privacy_allow_anonymous_questions?
|
||||
#question-box{ data: { controller: "character-count", "character-count-max-value": 512 }}
|
||||
#question-box{ data: user.profile.allow_long_questions ? {} : { controller: "character-count", "character-count-max-value": user.profile.question_length_limit }}
|
||||
%textarea.form-control{ name: "qb-question", placeholder: t(".placeholder"), data: { "character-count-target": "input" } }
|
||||
.row{ style: "padding-top: 5px;" }
|
||||
.col-6
|
||||
|
@ -36,7 +36,7 @@
|
|||
%input{ name: "qb-anonymous", type: :hidden, value: false }/
|
||||
.col-6
|
||||
%p.pull-right
|
||||
%span.text-muted.me-1{ data: { "character-count-target": "counter" } } 512
|
||||
%span.text-muted.me-1{ class: user.profile.allow_long_questions ? "d-none" : "", data: { "character-count-target": "counter" } }= Question::SHORT_QUESTION_MAX_LENGTH
|
||||
%input{ name: "qb-to", type: "hidden", value: user.id }/
|
||||
%button.btn.btn-primary{ name: "qb-ask",
|
||||
type: :button,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
.modal.fade#modal-ask-followers{ aria: { hidden: true, labelledby: "modal-ask-followers-label" }, role: :dialog, tabindex: -1 }
|
||||
.modal-dialog
|
||||
.modal-content{ data: { controller: "character-count", "character-count-max-value": 512 }}
|
||||
.modal-content{ data: { controller: "character-count-warning", "character-count-warning-warn-value": Question::SHORT_QUESTION_MAX_LENGTH }}
|
||||
.modal-header
|
||||
%h5.modal-title#modal-ask-followers-label= t(".title")
|
||||
%button.btn-close{ data: { bs_dismiss: :modal }, type: :button }
|
||||
%span.visually-hidden= t("voc.close")
|
||||
.modal-body
|
||||
.form-group.has-feedback
|
||||
%textarea.form-control{ name: "qb-all-question", placeholder: t(".placeholder"), data: { "character-count-target": "input" } }
|
||||
%p.text-end.text-muted.form-control-feedback{ data: { "character-count-target": "counter" } } 512
|
||||
%textarea.form-control{ name: "qb-all-question", placeholder: t(".placeholder"), data: { "character-count-warning-target": "input" } }
|
||||
.alert.alert-warning.mt-3.d-none{ data: { "character-count-warning-target": "warning" } }= t('.long_question_warning')
|
||||
.modal-footer
|
||||
%button.btn.btn-default{ type: :button, data: { bs_dismiss: :modal } }= t("voc.cancel")
|
||||
%button.btn.btn-primary{ name: "qb-all-ask", type: :button, data: { "character-count-target": "action", loading_text: t(".loading") } }= t(".action")
|
||||
%button.btn.btn-primary{ name: "qb-all-ask", type: :button, data: { loading_text: t(".loading") } }= t(".action")
|
||||
|
|
|
@ -49,6 +49,8 @@
|
|||
|
||||
= f.text_area :description
|
||||
|
||||
= f.check_box :allow_long_questions
|
||||
|
||||
= f.primary
|
||||
|
||||
- provide(:title, generate_title(t(".title")))
|
||||
|
|
|
@ -30,6 +30,7 @@ class QuestionWorker
|
|||
return true if follower.banned?
|
||||
return true if muted?(follower, question)
|
||||
return true if user.muting?(question.user)
|
||||
return true if question.long? && !follower.profile.allow_long_questions
|
||||
|
||||
false
|
||||
end
|
||||
|
|
|
@ -254,6 +254,7 @@ en:
|
|||
placeholder: "Type your question here…"
|
||||
action: "Ask"
|
||||
loading: "Asking…"
|
||||
long_question_warning: "This question will only be sent to those who allow long questions in their profile settings."
|
||||
comment_smiles:
|
||||
title: "People who smiled this comment"
|
||||
none: "No one has smiled this comment yet."
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAllowLongQuestionsToProfiles < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :profiles, :allow_long_questions, :boolean, default: false
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2022_12_27_065923) do
|
||||
ActiveRecord::Schema.define(version: 2023_01_08_114333) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -134,6 +134,7 @@ ActiveRecord::Schema.define(version: 2022_12_27_065923) do
|
|||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "anon_display_name"
|
||||
t.boolean "allow_long_questions", default: false
|
||||
t.index ["user_id"], name: "index_profiles_on_user_id"
|
||||
end
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ module Errors
|
|||
class InvalidBanDuration < BadRequest
|
||||
end
|
||||
|
||||
class QuestionTooLong < BadRequest
|
||||
end
|
||||
|
||||
class Forbidden < Base
|
||||
def status
|
||||
403
|
||||
|
|
|
@ -63,6 +63,7 @@ module UseCase
|
|||
|
||||
def check_user
|
||||
raise Errors::NotAuthorized if target_user.privacy_require_user && !source_user_id
|
||||
raise Errors::QuestionTooLong if content.length > ::Question::SHORT_QUESTION_MAX_LENGTH && !target_user.profile.allow_long_questions
|
||||
end
|
||||
|
||||
def create_question
|
||||
|
|
|
@ -27,11 +27,12 @@ describe UseCase::DataExport::User, :data_export do
|
|||
sign_in_count: 10,
|
||||
smiled_count: 28,
|
||||
profile: {
|
||||
display_name: "Fizzy Raccoon",
|
||||
description: "A small raccoon",
|
||||
location: "Binland",
|
||||
motivation_header: "",
|
||||
website: "https://retrospring.net"
|
||||
display_name: "Fizzy Raccoon",
|
||||
description: "A small raccoon",
|
||||
location: "Binland",
|
||||
motivation_header: "",
|
||||
website: "https://retrospring.net",
|
||||
allow_long_questions: true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -87,14 +88,15 @@ describe UseCase::DataExport::User, :data_export do
|
|||
privacy_noindex: false
|
||||
},
|
||||
profile: {
|
||||
display_name: "Fizzy Raccoon",
|
||||
description: "A small raccoon",
|
||||
location: "Binland",
|
||||
website: "https://retrospring.net",
|
||||
motivation_header: "",
|
||||
created_at: user.profile.created_at.as_json,
|
||||
updated_at: user.profile.updated_at.as_json,
|
||||
anon_display_name: nil
|
||||
display_name: "Fizzy Raccoon",
|
||||
description: "A small raccoon",
|
||||
location: "Binland",
|
||||
website: "https://retrospring.net",
|
||||
motivation_header: "",
|
||||
created_at: user.profile.created_at.as_json,
|
||||
updated_at: user.profile.updated_at.as_json,
|
||||
anon_display_name: nil,
|
||||
allow_long_questions: true
|
||||
},
|
||||
roles: {
|
||||
administrator: false,
|
||||
|
|
|
@ -57,11 +57,22 @@ describe UseCase::Question::Create do
|
|||
end
|
||||
end
|
||||
|
||||
context "content is too long" do
|
||||
context "content is over 512 characters long" do
|
||||
let(:content) { "a" * 513 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
|
||||
context "recipient does not allow long questions" do
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error(Errors::QuestionTooLong)
|
||||
end
|
||||
end
|
||||
|
||||
context "recipient allows long questions" do
|
||||
before do
|
||||
target_user.profile.allow_long_questions = true
|
||||
target_user.profile.save
|
||||
end
|
||||
|
||||
it_behaves_like "creates the question"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,11 +16,6 @@ RSpec.describe Question, :type => :model do
|
|||
expect(@question.content).to match 'Is this a question?'
|
||||
end
|
||||
|
||||
it 'does not save questions longer than 512 characters' do
|
||||
@question.content = 'X' * 513
|
||||
expect{@question.save!}.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
|
||||
it 'has many answers' do
|
||||
5.times { |i| Answer.create(content: "This is an answer. #{i}", user: FactoryBot.create(:user), question: @question) }
|
||||
expect(@question.answer_count).to match 5
|
||||
|
|
|
@ -6,7 +6,8 @@ describe QuestionWorker do
|
|||
describe "#perform" do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:user_id) { user.id }
|
||||
let(:question) { FactoryBot.create(:question, user:) }
|
||||
let(:content) { Faker::Lorem.sentence }
|
||||
let(:question) { FactoryBot.create(:question, content:, user:) }
|
||||
let(:question_id) { question.id }
|
||||
|
||||
before do
|
||||
|
@ -92,5 +93,20 @@ describe QuestionWorker do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "long question" do
|
||||
let(:content) { "x" * 1000 }
|
||||
|
||||
it "sends to recipients who allow long questions" do
|
||||
user.followers.first.profile.update(allow_long_questions: true)
|
||||
|
||||
expect { subject }
|
||||
.to(
|
||||
change { Inbox.where(user_id: user.followers.ids, question_id:, new: true).count }
|
||||
.from(0)
|
||||
.to(1)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue