Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format follows [Keep a Changelog] (https://keepachangelog.com/en/1.1.0/).
- **"View details" shows a banner and the readme's tabs** (#1714). Description, Installation, FAQ, Screenshots and Other notes come from the offered release's `readme.txt`, and the changelog renders its Markdown instead of printing it raw.
- **"View details" speaks Portuguese and opens screenshots in place** (#1716). The tabs and banner follow the viewer's language when the plugin ships a translation (`languages/readme-pt_BR.txt`), and a screenshot opens in an overlay with arrows, swipe and zoom instead of a new tab.
- **Choice fields take one option per row, and every field can carry limits** (#1729). Options may contain commas; a choice field can shuffle its options per participant; a checkbox can cap how many are ticked; text and textarea take a minimum and maximum length (server ceilings 255 and 2,000) with a live counter; number and date take a range. The participant sees each limit, and the editor says what each type is for.
- **Quiz questions are scored per field** (#1730). Each field can be scored or left out; choices by correct option(s), a checkbox only on the exact set, or by points per option; text when answered; number and date by a correct range. "Show which answers were correct" now works. New tokens `{{correct_count}}` and `{{question_count}}`; existing forms score as before.

### Changed

Expand Down
7 changes: 0 additions & 7 deletions assets/css/ffc-admin-utilities.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@
SPACING UTILITIES
========================================================================== */

/**
* Margin top utilities
*/
.ffc-mt-2xs {
margin-top: var(--ffc-spacing-2xs);
}

/**
* Margin bottom utilities
*/
Expand Down
2 changes: 1 addition & 1 deletion assets/css/ffc-admin-utilities.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 34 additions & 3 deletions assets/css/ffc-admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -133,25 +133,56 @@
gap: var(--ffc-spacing-xs);
}

/* A flex row rather than a grid: the quiz controls (#1730) come and go
between the text and the remove button. */
.ffc-choice-row {
display: grid;
grid-template-columns: 20px minmax(0, 1fr) 32px;
display: flex;
gap: var(--ffc-spacing-sm);
align-items: center;
margin: 0;
}

.ffc-choice-handle {
flex: 0 0 20px;
cursor: move;
color: var(--ffc-text-muted);
}

.ffc-choice-input {
width: 100%;
flex: 1 1 auto;
min-width: 0;
box-sizing: border-box;
}

.ffc-choice-quiz {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: var(--ffc-spacing-2xs);
white-space: nowrap;
}

/* The field's quiz settings (#1730): set apart from the field's own. */
.ffc-field-quiz {
margin-top: var(--ffc-spacing-lg);
padding-top: var(--ffc-spacing-md);
border-top: 1px dashed var(--ffc-border);
}

.ffc-field-quiz-head {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--ffc-spacing-sm) var(--ffc-spacing-lg);
}

.ffc-field-quiz-body .ffc-quiz-help {
flex-basis: 100%;
margin: 0;
}

.ffc-choice-remove {
flex: 0 0 32px;
display: inline-flex;
align-items: center;
justify-content: center;
Expand Down
2 changes: 1 addition & 1 deletion assets/css/ffc-admin.min.css

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions assets/css/ffc-frontend.css
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,25 @@
color: var(--ffc-warning-text);
}

/* Per-question verdict after a quiz attempt (#1730): the word carries the
meaning, the colour and the icon only repeat it. */
.ffc-shortcode .ffc-quiz-verdict {
display: flex;
align-items: center;
gap: var(--ffc-spacing-2xs);
margin: var(--ffc-spacing-xs) 0 0;
font-size: var(--ffc-font-size-sm);
font-weight: 600;
}

.ffc-shortcode .ffc-quiz-right .ffc-quiz-verdict {
color: var(--ffc-success-text);
}

.ffc-shortcode .ffc-quiz-wrong .ffc-quiz-verdict {
color: var(--ffc-danger-text);
}

/* ============================================
INFO BLOCK - v4.9.0
============================================ */
Expand Down
2 changes: 1 addition & 1 deletion assets/css/ffc-frontend.min.css

Large diffs are not rendered by default.

93 changes: 91 additions & 2 deletions assets/js/ffc-admin-field-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
'use strict';

var CHOICE_TYPES = ['select', 'radio', 'checkbox'];
var TEXT_TYPES = ['text', 'textarea'];
var RANGE_TYPES = ['number', 'date'];

var fieldCounter = 0;

Expand Down Expand Up @@ -70,12 +72,70 @@
$row.find('.ffc-hidden-value-field').toggleClass('ffc-hidden', type !== 'hidden');
$row.find('.ffc-standard-row').toggleClass('ffc-hidden', isInfo || isEmbed);
$row.find('.ffc-options-field').toggleClass('ffc-hidden', !isChoice);
$row.find('.ffc-quiz-points').toggleClass('ffc-hidden', !(isChoice && quizOn()));
$row.find('[data-ffc-types]').each(function() {
var types = String($(this).attr('data-ffc-types')).split(' ');
$(this).toggleClass('ffc-hidden', types.indexOf(type) === -1);
});
$row.find('.ffc-type-desc').text(typeDescription(type));
applyQuiz($row, type);
}

/**
* Show the quiz settings a row's type and choices call for (#1730).
*/
function applyQuiz($row, type) {
type = type || String($row.find('.ffc-field-type-selector').val() || '');
var $block = $row.find('.ffc-field-quiz');
var scorable = String($block.attr('data-ffc-quiz-types') || '').split(' ').indexOf(type) !== -1;
var on = quizOn() && scorable;
var scored = on && $block.find('.ffc-quiz-scored').is(':checked');
var isChoice = CHOICE_TYPES.indexOf(type) !== -1;
var mode = isChoice ? String($block.find('.ffc-quiz-mode').val()) : '';
var textNone = TEXT_TYPES.indexOf(type) !== -1 && $block.find('.ffc-quiz-text').val() === 'none';

$block.toggleClass('ffc-hidden', !on);
$block.find('.ffc-field-quiz-body').toggleClass('ffc-hidden', !scored);
$row.find('.ffc-choice-quiz').each(function() {
$(this).toggleClass('ffc-hidden', !(scored && isChoice && $(this).attr('data-ffc-quiz-mode') === mode));
});
$block.find('.ffc-quiz-points-setting').toggleClass('ffc-hidden', (isChoice && mode === 'points') || textNone);
$block.find('[data-ffc-types]').each(function() {
$(this).toggleClass('ffc-hidden', String($(this).attr('data-ffc-types')).split(' ').indexOf(type) === -1);
});
$block.find('[data-ffc-quiz-help="correct"]').toggleClass('ffc-hidden', !(isChoice && mode === 'correct'));
$block.find('[data-ffc-quiz-help="points"]').toggleClass('ffc-hidden', !(isChoice && mode === 'points'));
$block.find('[data-ffc-quiz-help="range"]').toggleClass('ffc-hidden', RANGE_TYPES.indexOf(type) === -1);
checkMaxSelect($row);
}

/**
* A checkbox scored by its correct options cannot cap the selections
* below how many are correct: nobody could answer it right.
*/
function checkMaxSelect($row) {
var $max = $row.find('input[name$="[max_select]"]');
if (!$max.length || !$max[0].setCustomValidity) {
return;
}
var type = String($row.find('.ffc-field-type-selector').val() || '');
var limit = parseInt($max.val(), 10);
var correct = $row.find('.ffc-choice-correct-value').filter(function() {
return this.value === '1';
}).length;
var applies = type === 'checkbox' && quizOn() &&
$row.find('.ffc-quiz-scored').is(':checked') &&
$row.find('.ffc-quiz-mode').val() === 'correct';
var message = '';
if (applies && limit > 0 && limit < correct) {
message = (strings().maxSelectBelowCorrect || 'The maximum selections is below the number of correct options (%d).').replace('%d', String(correct));
}
$max[0].setCustomValidity(message);
}

function refreshQuiz() {
$('#ffc-fields-container .ffc-field-row').each(function() {
applyQuiz($(this));
});
}

// A number above every index already on the page, so a new row's
Expand All @@ -98,10 +158,17 @@
}
var $row = $first.clone();
$row.find('.ffc-choice-input').val(value || '');
resetChoiceQuiz($row);
$list.append($row);
return $row;
}

function resetChoiceQuiz($choice) {
$choice.find('.ffc-choice-correct-value').val('0');
$choice.find('.ffc-choice-correct-input').prop('checked', false);
$choice.find('.ffc-choice-points-input').val('');
}

function makeChoicesSortable($scope) {
if ($.fn.sortable) {
$scope.find('.ffc-choice-list').sortable({ handle: '.ffc-choice-handle', axis: 'y' });
Expand Down Expand Up @@ -150,13 +217,34 @@
$container.on('click', '.ffc-choice-remove', function(e) {
e.preventDefault();
var $choice = $(this).closest('.ffc-choice-row');
var $fieldRow = $choice.closest('.ffc-field-row');
if ($choice.siblings('.ffc-choice-row').length) {
$choice.remove();
} else {
$choice.find('.ffc-choice-input').val('');
resetChoiceQuiz($choice);
}
checkMaxSelect($fieldRow);
});

// The visible box drives the aligned 0/1 the server reads.
$container.on('change', '.ffc-choice-correct-input', function() {
$(this).closest('.ffc-choice-row').find('.ffc-choice-correct-value').val(this.checked ? '1' : '0');
checkMaxSelect($(this).closest('.ffc-field-row'));
});

$container.on('change', '.ffc-quiz-scored, .ffc-quiz-mode, .ffc-quiz-text', function() {
applyQuiz($(this).closest('.ffc-field-row'));
});

$container.on('input change', 'input[name$="[max_select]"]', function() {
checkMaxSelect($(this).closest('.ffc-field-row'));
});

$(document).off('change.ffcFieldBuilder');
$(document).on('change.ffcFieldBuilder', '#ffc_quiz_enabled', refreshQuiz);
refreshQuiz();

// Enter in an option adds the next one instead of submitting the post.
$container.on('keydown', '.ffc-choice-input', function(e) {
if (e.key === 'Enter') {
Expand Down Expand Up @@ -253,7 +341,8 @@
window.FFC.Admin.FieldBuilder = {
init: initFormBuilder,
addField: addFieldToBuilder,
applyType: applyType
applyType: applyType,
refreshQuiz: refreshQuiz
};

if (FFC && FFC.registerModule) {
Expand Down
Loading
Loading