Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework of the character count requirements display #1189

Merged
merged 16 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 71 additions & 42 deletions app/assets/javascripts/character_count.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,89 @@
$(() => {
const setIcon = (el, icon) => {
/**
* @typedef {'fa-ellipsis-h'|'fa-times'|'fa-exclamation-circle'|'fa-check'} CounterIcon
* @typedef {'info'|'warning'|'error'|'default'} CounterState
* @typedef {'valid'|'invalid'} InputValidationState
* @typedef {'disabled'|'enabled'} SubmitButtonDisabledState
*/

/**
* Sets the icon to show before the counter, if any
* @param {CounterIcon} icon name of the icon to show
*/
const setCounterIcon = (el, icon) => {
const icons = ['fa-ellipsis-h', 'fa-check', 'fa-exclamation-circle', 'fa-times'];
el.removeClass(icons.join(' ')).addClass(icon);
};

$(document).on('keyup change paste', '[data-character-count]', ev => {
/**
* Sets the counter's state
* @param {CounterState} state the state to set
*/
const setCounterState = (el, state) => {
if (state === 'info') {
el.removeClass('has-color-yellow-700 has-color-red-500').addClass('has-color-primary');
}
else if (state === 'warning') {
el.removeClass('has-color-red-500 has-color-primary').addClass('has-color-yellow-700');
}
else if (state === 'error') {
el.removeClass('has-color-yellow-700 has-color-primary').addClass('has-color-red-500');
}
else {
el.removeClass('has-color-red-500 has-color-yellow-700 has-color-primary');
}
};

/**
* Sets the input's validation state
* @param {InputValidationState} state the state to set
*/
const setInputValidationState = (el, state) => {
const isInvalid = state === 'invalid';
el.toggleClass('failed-validation', isInvalid);
};

/**
* Sets the submit button's disabled state
* @param {SubmitButtonDisabledState} state the state to set
*/
const setSubmitButtonDisabledState = (el, state) => {
const isDisabled = state === 'disabled';
el.attr('disabled', isDisabled).toggleClass('is-muted', isDisabled);
};

$(document).on('keyup change paste', '[data-character-count]', (ev) => {
const $tgt = $(ev.target);
const $counter = $($tgt.attr('data-character-count'));
const $button = $counter.parents('form').find('input[type="submit"]');
const $count = $counter.find('.js-character-count__count');
const $icon = $counter.find('.js-character-count__icon');

const displayAt = parseFloat($counter.attr('data-display-at'));
const count = $tgt.val().length;
const max = parseInt($counter.attr('data-max'), 10);
const min = parseInt($counter.attr('data-min'), 10);
const count = $tgt.val().length;
const text = `${count} / ${max}`;
const threshold = parseFloat($counter.attr('data-threshold'));

if (displayAt) {
if (count >= displayAt * max) {
$counter.removeClass('hide');
}
else {
$counter.addClass('hide');
}
}
const gtnMax = count > max;
const ltnMin = count < min;
const gteThreshold = count >= threshold * max;

if (count > max) {
$counter.removeClass('has-color-yellow-700 has-color-primary').addClass('has-color-red-500');
setIcon($icon, 'fa-times');
if ($button) {
$button.attr('disabled', true).addClass('is-muted');
}
}
else if (count > 0.75 * max) {
$counter.removeClass('has-color-red-500 has-color-primary').addClass('has-color-yellow-700');
setIcon($icon, 'fa-exclamation-circle');
if ($button) {
$button.attr('disabled', false).removeClass('is-muted');
}
}
else if (min && count < min) {
$counter.removeClass('has-color-yellow-700 has-color-red-500').addClass('has-color-primary');
setIcon($icon, 'fa-ellipsis-h');
if ($button) {
$button.attr('disabled', true).addClass('is-muted');
}
$tgt.addClass('failed-validation');
}
else {
$counter.removeClass('has-color-red-500 has-color-yellow-700 has-color-primary');
setIcon($icon, 'fa-check');
if ($button) {
$button.attr('disabled', false).removeClass('is-muted');
}
$tgt.removeClass('failed-validation');
const text = `${count} / ${ltnMin ? min : max}`;

if (gtnMax || ltnMin) {
setCounterState($counter, 'error');
setCounterIcon($icon, 'fa-times');
setSubmitButtonDisabledState($button, 'disabled');
setInputValidationState($tgt, 'invalid');
} else if (gteThreshold) {
setCounterState($counter, 'warning');
setCounterIcon($icon, 'fa-exclamation-circle');
setSubmitButtonDisabledState($button, 'enabled');
} else {
setCounterState($counter, 'default');
setCounterIcon($icon, 'fa-check');
setSubmitButtonDisabledState($button, 'enabled');
setInputValidationState($tgt, 'valid');
}

$count.text(text);
Expand Down
13 changes: 2 additions & 11 deletions app/views/comments/_new_thread_modal.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,15 @@
<div class="form-caption">Start the thread with a comment.</div>
<%= text_area_tag :body, '', class: 'form-element js-comment-field', required: true,
data: { post: post.id, thread: '-1', character_count: ".js-character-count-#{post.id}" } %>
<span class="has-float-right has-font-size-caption js-character-count-<%= post.id %>"
data-max="1000" data-min="15">
<i class="fas fa-ellipsis-h js-character-count__icon"></i>
<span class="js-character-count__count">0 / 1000</span>
</span>
<%= render 'shared/char_count', type: post.id, min: 15, max: 1000 %>

<%= label_tag :title, 'Comment thread title (optional)', class: 'form-element' %>
<span class="form-caption">
You can give your comment thread a title. If you leave this blank, the beginning of the initial comment will
be shown.
</span>
<%= text_field_tag :title, '', class: 'form-element', data: { character_count: ".js-character-count-thread-title" } %>

<span class="has-float-right has-font-size-caption js-character-count-thread-title hide"
data-max="255" data-min="0" data-display-at="0.75">
<i class="fas fa-ellipsis-h js-character-count__icon"></i>
<span class="js-character-count__count">0 / 255</span>
</span>
<%= render 'shared/char_count', type: 'thread-title' %>

<%= submit_tag 'Create thread', class: 'button is-filled', id: "create_thread_button_#{post.id}", disabled: true %>
<% end %>
Expand Down
8 changes: 2 additions & 6 deletions app/views/comments/thread.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,8 @@
<%= text_area_tag :content, '', class: 'form-element js-comment-field',
data: { thread: @comment_thread.id, post: @comment_thread.post_id,
character_count: ".js-character-count-#{@post.id}" } %>
<span class="has-float-right has-font-size-caption js-character-count-<%= @post.id %>"
data-max="1000" data-min="15">
<i class="fas fa-ellipsis-h js-character-count__icon"></i>
<span class="js-character-count__count">0 / 1000</span>
</span>

<%= render 'shared/char_count', type: @post.id, min: 15, max: 1000 %>

<%= submit_tag 'Add reply', class: 'button is-muted is-filled', disabled:true %>
<% end %>
<% end %>
Expand Down
9 changes: 2 additions & 7 deletions app/views/posts/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,9 @@
<%= f.text_field :title, class: 'form-element post_title', data: { character_count: ".js-character-count-post-title" } %>
</div>
<div>
<span class="has-float-right has-font-size-caption js-character-count-post-title hide"
data-min="<%= min_title_length(category) %>"
data-max="<%= max_title_length(category) %>"
data-display-at="0.75">
<i class="fas fa-ellipsis-h js-character-count__icon"></i>
<span class="js-character-count__count">0 / <%= max_title_length(category) %></span>
</span>
<%= render 'shared/char_count', type: 'post-title', cur: post.body_markdown&.length, max: max_title_length(category), min: min_title_length(category) %>
</div>

<% end %>

<% if post_type.has_tags? && category.present? %>
Expand Down
8 changes: 1 addition & 7 deletions app/views/posts/_mdhint.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,5 @@
<div class="form-caption widget--footer js-post-field-footer">
We <a href="/help/formatting">support Markdown</a> for posts:
<strong>**bold**</strong>, <em>*italics*</em>, <code>`code`</code>, two newlines for paragraphs
<span class="has-float-right has-font-size-caption js-character-count-post-body hide"
data-min="<%= min_length %>"
data-max="<%= max_length %>"
data-display-at="0.75">
<i class="fas fa-ellipsis-h js-character-count__icon"></i>
<span class="js-character-count__count">0 / <%= max_length %></span>
</span>
<%= render 'shared/char_count', type: 'post-body', min: min_length, max: max_length %>
</div>
28 changes: 28 additions & 0 deletions app/views/shared/_char_count.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<%#
Oaphi marked this conversation as resolved.
Show resolved Hide resolved
Reusable helper view for character count requirements.

Variables:
cur : current number of characters (default 0)
max : maximum number of characters allowed (default 255)
min : minimum number of characters allowed (default 0)
threshold : fraction of max to show the count at (default 0.75)
type : character count type (e.g.: post-title)
%>

<%
# defaults & normalization
cur ||= defined?(cur) && !cur.nil? ? cur.to_i : 0
max ||= defined?(max) && !max.nil? ? max.to_i : 255
min ||= defined?(min) && !min.nil? ? min.to_i : 0
threshold ||= defined?(threshold) && !threshold.nil? ? threshold.to_f : 0.75
%>

<span class="has-float-right has-font-size-caption js-character-count-<%= type %>"
ArtOfCode- marked this conversation as resolved.
Show resolved Hide resolved
data-max="<%= max %>"
data-min="<%= min %>"
data-threshold="<%= threshold %>">
<i class="fas fa-ellipsis-h js-character-count__icon"></i>
<span class="js-character-count__count">
<%= cur %> / <%= cur < min ? min : max %>
</span>
</span>
Loading