<commit_before>function DragAndDropTemplates(configuration) {
    "use strict";
    var h = virtualDom.h;

    var keyboardHelpButtonClass = "keyboard-help-button";
    var keyboardHelpText = gettext('Keyboard Help');

    var itemSpinnerTemplate = function(item) {
        if (!item.xhr_active) {
            return null;
        }
        return (
            h("div.spinner-wrapper", {key: item.value + '-spinner'}, [
                h("i.fa.fa-spin.fa-spinner")
            ])
        );
    };

    var renderCollection = function(template, collection, ctx) {
        return collection.map(function(item) {
            return template(item, ctx);
        });
    };

    var getZone = function(zoneUID, ctx) {
        for (var i = 0; i < ctx.zones.length; i++) {
            if (ctx.zones[i].uid === zoneUID) {
                return ctx.zones[i];
            }
        }
    };

    var bankItemWidthStyles = function(item, ctx) {
        var style = {};
        if (item.widthPercent) {
            // The item bank container is often wider than the background image, and the
            // widthPercent is specified relative to the background image so we have to
            // convert it to pixels. But if the browser window / mobile screen is not as
            // wide as the image, then the background image will be scaled down and this
            // pixel value would be too large, so we also specify it as a max-width
            // percentage.
            style.width = (item.widthPercent / 100 * ctx.bg_image_width) + "px";
            style.maxWidth = item.widthPercent + "%";
        }
        return style;
    };

    var itemContentTemplate = function(item) {
        var item_content_html = item.displayName;
        if (item.imageURL) {
            item_content_html = '<img src="' + item.imageURL + '" alt="' + item.imageDescription + '" />';
        }
        var key = item.value + '-content';
        return h('div', { key: key, innerHTML: item_content_html, className: "item-content" });
    };

    var itemTemplate = function(item, ctx) {
        // Define properties
        var descriptionClassName = "sr description";
        var className = (item.class_name) ? item.class_name : "";
        var zone = getZone(item.zone, ctx) || {};
        if (item.has_image) {
            className += " " + "option-with-image";
        }
        if (item.widthPercent) {
            className += " specified-width";  // The author has specified a width for this item.
        }
        if (item.grabbed_with) {
            className += " grabbed-with-" + item.grabbed_with;
        }
        var attributes = {
            'role': 'button',
            'draggable': !item.drag_disabled,
            'aria-grabbed': item.grabbed,
            'data-value': item.value,
            'tabindex': item.focusable ? 0 : undefined
        };
        var style = {};
        if (item.background_color) {
            style['background-color'] = item.background_color;
        }
        if (item.color) {
            style.color = item.color;
            // Ensure contrast between outline-color and background color
            // matches contrast between text color and background color:
            style['outline-color'] = item.color;
        }
        if (item.is_placed) {
            var maxWidth = (item.widthPercent || 30) / 100;
            var widthPercent = zone.width_percent / 100;
            style.maxWidth = ((1 / (widthPercent / maxWidth)) * 100) + '%';
            if (item.widthPercent) {
                style.width = style.maxWidth;
            }
            // Finally, if the item is using automatic sizing and contains an image, we
            // always prefer the natural width of the image (subject to the max-width):
            if (item.imgNaturalWidth && !item.widthPercent) {
                style.width = (item.imgNaturalWidth + 22) + "px"; // 22px is for 10px padding + 1px border each side
                // ^ Hack to detect image width at runtime and make webkit consistent with Firefox
            }
        } else {
            $.extend(style, bankItemWidthStyles(item, ctx));
        }
        // Define children
        var children = [
            itemSpinnerTemplate(item)
        ];
        var item_content = itemContentTemplate(item);
        var item_description = null;
        // Insert information about zone in which this item has been placed

        if (item.is_placed) {
            var zone_title = (zone.title || "Unknown Zone");  // This "Unknown" text should never be seen, so does not need i18n
            var description_content;
            if (configuration.mode === DragAndDropBlock.ASSESSMENT_MODE && !ctx.showing_answer) {
                // In assessment mode placed items will "stick" even when not in correct zone.
                description_content = gettext('Placed in: {zone_title}').replace('{zone_title}', zone_title);
            } else {
                // In standard mode item is immediately returned back to the bank if dropped on a wrong zone,
                // so all placed items are always in the correct zone.
                description_content = gettext('Correctly placed in: {zone_title}').replace('{zone_title}', zone_title);
            }
            var item_description_id = configuration.url_name + '-item-' + item.value + '-description';
            item_content.properties.attributes = { 'aria-describedby': item_description_id };
            item_description = h(
                'div',
                { key: item.value + '-description', id: item_description_id, className: descriptionClassName },
                description_content
            );
        }
        children.splice(1, 0, item_description);
        children.splice(1, 0, item_content);

        var itemSRNote = h(
            'span',
            { className: 'sr draggable' },
            (item.grabbed) ? gettext("draggable, grabbed") : gettext("draggable")
        );
        children.splice(2, 0, itemSRNote);

        return (
            h(
                'div.option',
                {
                    // Unique key for virtual dom change tracking. Key must be different for
                    // Placed vs Unplaced, or weird bugs can occur.
                    key: item.value + (item.is_placed ? "-p" : "-u"),
                    className: className,
                    attributes: attributes,
                    style: style
                },
                children
            )
        );
    };

    // When an item is dragged out of the bank, a hidden placeholder of the same width and height as
    // the original item is rendered in the bank. The function of the placeholder is to take up the
    // same amount of space as the original item so that the bank does not collapse when you've dragged
    // all items out.
    var itemPlaceholderTemplate = function(item, ctx) {
        var className = "";
        if (item.has_image) {
            className += " " + "option-with-image";
        }
        if (item.widthPercent) {
            className += " specified-width";  // The author has specified a width for this item.
        }
        var style = bankItemWidthStyles(item, ctx);
        // Placeholder should never be visible.
        style.visibility = 'hidden';
        return (
            h(
                'div.option',
                {
                    key: 'placeholder-' + item.value,
                    className: className,
                    attributes: {draggable: false},
                    style: style
                },
                itemContentTemplate(item)
            )
        );
    };

    var zoneTemplate = function(zone, ctx) {
        var className = ctx.display_zone_labels ? 'zone-name' : 'zone-name sr';
        var selector = ctx.display_zone_borders ? 'div.zone.zone-with-borders' : 'div.zone';
        // Mark item alignment and render its placed items as children
        var item_wrapper = 'div.item-wrapper.item-align.item-align-' + zone.align;
        var is_item_in_zone = function(i) { return i.is_placed && (i.zone === zone.uid); };
        var items_in_zone = $.grep(ctx.items, is_item_in_zone);
        var zone_description_id = configuration.url_name + '-zone-' + zone.uid + '-description';
        if (items_in_zone.length == 0) {
          var zone_description = h(
            'div',
            { id: zone_description_id, className: 'sr'},
            gettext("No items placed here")
          );
        } else {
          var zone_description = h(
            'div',
            { id: zone_description_id, className: 'sr'},
            gettext('Items placed here: ') + items_in_zone.map(function (item) { return item.displayName; }).join(", ")
          );
        }

        return (
            h(
                selector,
                {
                    key: zone.prefixed_uid,
                    id: zone.prefixed_uid,
                    attributes: {
                        'tabindex': 0,
                        'dropzone': 'move',
                        'aria-dropeffect': 'move',
                        'data-uid': zone.uid,
                        'data-zone_align': zone.align,
                        'role': 'button',
                        'aria-describedby': zone_description_id,
                    },
                    style: {
                        top: zone.y_percent + '%', left: zone.x_percent + "%",
                        width: zone.width_percent + '%', height: zone.height_percent + "%",
                    }
                },
                [
                    h('p', { className: className }, zone.title),
                    h('p', { className: 'zone-description sr' }, zone.description || gettext("droppable")),
                    h(item_wrapper, renderCollection(itemTemplate, items_in_zone, ctx)),
                    zone_description
                ]
            )
        );
    };

    var feedbackTemplate = function(ctx) {
        var messages = ctx.overall_feedback_messages || [];
        var feedback_display = messages.length > 0 ? 'block' : 'none';
        var feedback_messages = messages.map(function(message) {
            var selector = "p.message";
            if (message.message_class) {
                selector += "."+message.message_class;
            }
            return h(selector, {innerHTML: message.message}, []);
        });

        return (
            h('section.feedback', {}, [
                h(
                    "div.feedback-content",
                    { attributes: { 'aria-live': 'polite' } },
                    [
                        h('h3.title1', { style: { display: feedback_display } }, gettext('Feedback')),
                        h('div.messages', { style: { display: feedback_display } }, feedback_messages),
                    ]
                )
            ])
        );
    };

    var popupTemplate = function(ctx) {
        var popupSelector = 'div.popup';
        if (ctx.popup_html && !ctx.last_action_correct) {
            popupSelector += '.popup-incorrect';
        }

        return (
            h(
                "div.popup-wrapper",
                {
                    attributes: {
                        'aria-live': 'polite',
                        'aria-atomic': 'true',
                        'aria-relevant': 'additions',
                    }
                },
                [
                    h(
                        popupSelector,
                        {
                            style: {display: ctx.popup_html ? 'block' : 'none'},
                        },
                        [
                            h('div.close.icon-remove-sign.fa-times-circle'),
                            h('p.popup-content', {innerHTML: ctx.popup_html}),
                        ]
                    )
                ]
            )
        )
    };

    var keyboardHelpPopupTemplate = function(ctx) {
        var labelledby_id = 'modal-window-title-'+configuration.url_name;
        return (
            h('div.keyboard-help-dialog', [
                h('div.modal-window-overlay'),
                h('div.modal-window', {attributes: {role: 'dialog', 'aria-labelledby': labelledby_id}}, [
                    h('div.modal-header', [
                        h('h2.modal-window-title#'+labelledby_id, gettext('Keyboard Help'))
                    ]),
                    h('div.modal-content', [
                        h('p', {className:'sr'}, gettext('This is a screen reader-friendly problem')),
                        h('p', gettext('You can complete this problem using only your keyboard by following the guidance below:')),
                        h('p', {className:'sr'}, gettext('Drag and Drop problems consist of draggable items and dropzones. Users should select a draggable item with their keyboard and then navigate to an appropriate dropzone to drop it.')),
                        h('ul', [
                            h('li', gettext('Use only TAB and SHIFT+TAB to navigate between draggable items and drop zones.')),
                            h('li', gettext('Press CTRL+M to select a draggable item (effectively picking it up).')),
                            h('li', gettext('Navigate using TAB and SHIFT+TAB to the appropriate dropzone and press CTRL+M once more to drop it here.')),
                            h('li', gettext('Press "Esc" if you want to cancel the drop operation (for example, to select a different item).')),
                            h('li', gettext('TAB back to the list of draggable items and repeat this process until all of the draggable items have been placed on their respective dropzones.')),
                        ])
                    ]),
                    h('div.modal-actions', [
                        h('button.modal-dismiss-button', gettext("OK"))
                    ])
                ])
            ])
        );
    };

    var submitAnswerTemplate = function(ctx) {
        var attemptsUsedId = "attempts-used-"+configuration.url_name;
        var attemptsUsedDisplay = (ctx.max_attempts && ctx.max_attempts > 0) ? 'inline': 'none';

        return (
          h("section.action-toolbar-item.submit-answer", {}, [
              h(
                  "button.btn-brand.submit-answer-button",
                  {
                      disabled: ctx.disable_submit_button || ctx.submit_spinner, 
                      attributes: {"aria-describedby": attemptsUsedId}},
                  [
                      (ctx.submit_spinner ? h("span.fa.fa-spin.fa-spinner") : null),
                      gettext("Submit")
                  ]
              ),
              h(
                  "span.attempts-used#"+attemptsUsedId, {style: {display: attemptsUsedDisplay}},
                  gettext("You have used {used} of {total} attempts.")
                      .replace("{used}", ctx.attempts).replace("{total}", ctx.max_attempts)
              )
          ])
        );
    };

    var sidebarButtonTemplate = function(buttonClass, iconClass, buttonText, disabled, spinner) {
        if (spinner) {
            iconClass = 'fa-spin.fa-spinner';
        }
        return (
            h('span.sidebar-button-wrapper', {}, [
                h(
                    'button.unbutton.btn-default.btn-small.'+buttonClass,
                    {disabled: disabled || spinner || false, attributes: {tabindex: 0}},
                    [
                        h(
                            "span.btn-icon.fa." + iconClass,
                            {attributes: {"aria-hidden": true}},
                            []
                        ),
                        buttonText
                    ]
                )
            ])
        );
    };

    var sidebarTemplate = function(ctx) {
        var showAnswerButton = null;
        if (ctx.show_show_answer) {
            showAnswerButton = sidebarButtonTemplate(
                "show-answer-button",
                "fa-info-circle",
                gettext('Show Answer'),
                ctx.showing_answer ? true : ctx.disable_show_answer_button,
                ctx.show_answer_spinner
            );
        }
        return(
            h("section.action-toolbar-item.sidebar-buttons", {}, [
                sidebarButtonTemplate(keyboardHelpButtonClass, "fa-question", keyboardHelpText),
                sidebarButtonTemplate("reset-button", "fa-refresh", gettext('Reset'), ctx.disable_reset_button),
                showAnswerButton,
            ])
        )
    };

    var itemFeedbackPopupTemplate = function(ctx) {
        var popupSelector = 'div.popup.item-feedback-popup.popup-wrapper';
        var msgs = ctx.feedback_messages || [];
        var have_messages = msgs.length > 0;
        var popup_content;

        var close_button_describedby_id = "close-popup-"+configuration.url_name;

        if (msgs.length > 0 && !ctx.last_action_correct) {
            popupSelector += '.popup-incorrect';
        }

        if (ctx.mode == DragAndDropBlock.ASSESSMENT_MODE) {
            var content_items = [
                (!ctx.last_action_correct) ? h("p", {}, gettext("Some of your answers were not correct.")) : null,
                h("p", {}, gettext("Hints:")),
                h("ul", {}, msgs.map(function(message) {
                    return h("li", {innerHTML: message.message});
                }))
            ];
            popup_content = h("div.popup-content", {}, have_messages ? content_items : []);
        } else {
            popup_content = h("div.popup-content", {}, msgs.map(function(message) {
                return h("p", {innerHTML: message.message});
            }))
        }

        return h(
            popupSelector,
            {
                style: {display: have_messages ? 'block' : 'none'},
                attributes: {
                    "tabindex": "-1",
                    'aria-live': 'polite',
                    'aria-atomic': 'true',
                    'aria-relevant': 'additions',
                }
            },
            [
                h(
                    'button.unbutton.close-feedback-popup-button',
                    {},
                    [
                        h(
                            'span.sr',
                            {
                                innerHTML: gettext("Close item feedback popup")
                            }
                        ),
                        h(
                            'span.icon.fa.fa-times-circle',
                            {
                                attributes: {
                                    'aria-hidden': true
                                }
                            }
                        )
                    ]
                ),
                popup_content
            ]
        )
    };

    var forwardKeyboardHelpButtonTemplate = function(ctx) {<commit_msg>[TNL-6019] Less verbose close button help text<commit_after>function DragAndDropTemplates(configuration) {
    "use strict";
    var h = virtualDom.h;

    var keyboardHelpButtonClass = "keyboard-help-button";
    var keyboardHelpText = gettext('Keyboard Help');

    var itemSpinnerTemplate = function(item) {
        if (!item.xhr_active) {
            return null;
        }
        return (
            h("div.spinner-wrapper", {key: item.value + '-spinner'}, [
                h("i.fa.fa-spin.fa-spinner")
            ])
        );
    };

    var renderCollection = function(template, collection, ctx) {
        return collection.map(function(item) {
            return template(item, ctx);
        });
    };

    var getZone = function(zoneUID, ctx) {
        for (var i = 0; i < ctx.zones.length; i++) {
            if (ctx.zones[i].uid === zoneUID) {
                return ctx.zones[i];
            }
        }
    };

    var bankItemWidthStyles = function(item, ctx) {
        var style = {};
        if (item.widthPercent) {
            // The item bank container is often wider than the background image, and the
            // widthPercent is specified relative to the background image so we have to
            // convert it to pixels. But if the browser window / mobile screen is not as
            // wide as the image, then the background image will be scaled down and this
            // pixel value would be too large, so we also specify it as a max-width
            // percentage.
            style.width = (item.widthPercent / 100 * ctx.bg_image_width) + "px";
            style.maxWidth = item.widthPercent + "%";
        }
        return style;
    };

    var itemContentTemplate = function(item) {
        var item_content_html = item.displayName;
        if (item.imageURL) {
            item_content_html = '<img src="' + item.imageURL + '" alt="' + item.imageDescription + '" />';
        }
        var key = item.value + '-content';
        return h('div', { key: key, innerHTML: item_content_html, className: "item-content" });
    };

    var itemTemplate = function(item, ctx) {
        // Define properties
        var descriptionClassName = "sr description";
        var className = (item.class_name) ? item.class_name : "";
        var zone = getZone(item.zone, ctx) || {};
        if (item.has_image) {
            className += " " + "option-with-image";
        }
        if (item.widthPercent) {
            className += " specified-width";  // The author has specified a width for this item.
        }
        if (item.grabbed_with) {
            className += " grabbed-with-" + item.grabbed_with;
        }
        var attributes = {
            'role': 'button',
            'draggable': !item.drag_disabled,
            'aria-grabbed': item.grabbed,
            'data-value': item.value,
            'tabindex': item.focusable ? 0 : undefined
        };
        var style = {};
        if (item.background_color) {
            style['background-color'] = item.background_color;
        }
        if (item.color) {
            style.color = item.color;
            // Ensure contrast between outline-color and background color
            // matches contrast between text color and background color:
            style['outline-color'] = item.color;
        }
        if (item.is_placed) {
            var maxWidth = (item.widthPercent || 30) / 100;
            var widthPercent = zone.width_percent / 100;
            style.maxWidth = ((1 / (widthPercent / maxWidth)) * 100) + '%';
            if (item.widthPercent) {
                style.width = style.maxWidth;
            }
            // Finally, if the item is using automatic sizing and contains an image, we
            // always prefer the natural width of the image (subject to the max-width):
            if (item.imgNaturalWidth && !item.widthPercent) {
                style.width = (item.imgNaturalWidth + 22) + "px"; // 22px is for 10px padding + 1px border each side
                // ^ Hack to detect image width at runtime and make webkit consistent with Firefox
            }
        } else {
            $.extend(style, bankItemWidthStyles(item, ctx));
        }
        // Define children
        var children = [
            itemSpinnerTemplate(item)
        ];
        var item_content = itemContentTemplate(item);
        var item_description = null;
        // Insert information about zone in which this item has been placed

        if (item.is_placed) {
            var zone_title = (zone.title || "Unknown Zone");  // This "Unknown" text should never be seen, so does not need i18n
            var description_content;
            if (configuration.mode === DragAndDropBlock.ASSESSMENT_MODE && !ctx.showing_answer) {
                // In assessment mode placed items will "stick" even when not in correct zone.
                description_content = gettext('Placed in: {zone_title}').replace('{zone_title}', zone_title);
            } else {
                // In standard mode item is immediately returned back to the bank if dropped on a wrong zone,
                // so all placed items are always in the correct zone.
                description_content = gettext('Correctly placed in: {zone_title}').replace('{zone_title}', zone_title);
            }
            var item_description_id = configuration.url_name + '-item-' + item.value + '-description';
            item_content.properties.attributes = { 'aria-describedby': item_description_id };
            item_description = h(
                'div',
                { key: item.value + '-description', id: item_description_id, className: descriptionClassName },
                description_content
            );
        }
        children.splice(1, 0, item_description);
        children.splice(1, 0, item_content);

        var itemSRNote = h(
            'span',
            { className: 'sr draggable' },
            (item.grabbed) ? gettext("draggable, grabbed") : gettext("draggable")
        );
        children.splice(2, 0, itemSRNote);

        return (
            h(
                'div.option',
                {
                    // Unique key for virtual dom change tracking. Key must be different for
                    // Placed vs Unplaced, or weird bugs can occur.
                    key: item.value + (item.is_placed ? "-p" : "-u"),
                    className: className,
                    attributes: attributes,
                    style: style
                },
                children
            )
        );
    };

    // When an item is dragged out of the bank, a hidden placeholder of the same width and height as
    // the original item is rendered in the bank. The function of the placeholder is to take up the
    // same amount of space as the original item so that the bank does not collapse when you've dragged
    // all items out.
    var itemPlaceholderTemplate = function(item, ctx) {
        var className = "";
        if (item.has_image) {
            className += " " + "option-with-image";
        }
        if (item.widthPercent) {
            className += " specified-width";  // The author has specified a width for this item.
        }
        var style = bankItemWidthStyles(item, ctx);
        // Placeholder should never be visible.
        style.visibility = 'hidden';
        return (
            h(
                'div.option',
                {
                    key: 'placeholder-' + item.value,
                    className: className,
                    attributes: {draggable: false},
                    style: style
                },
                itemContentTemplate(item)
            )
        );
    };

    var zoneTemplate = function(zone, ctx) {
        var className = ctx.display_zone_labels ? 'zone-name' : 'zone-name sr';
        var selector = ctx.display_zone_borders ? 'div.zone.zone-with-borders' : 'div.zone';
        // Mark item alignment and render its placed items as children
        var item_wrapper = 'div.item-wrapper.item-align.item-align-' + zone.align;
        var is_item_in_zone = function(i) { return i.is_placed && (i.zone === zone.uid); };
        var items_in_zone = $.grep(ctx.items, is_item_in_zone);
        var zone_description_id = configuration.url_name + '-zone-' + zone.uid + '-description';
        if (items_in_zone.length == 0) {
          var zone_description = h(
            'div',
            { id: zone_description_id, className: 'sr'},
            gettext("No items placed here")
          );
        } else {
          var zone_description = h(
            'div',
            { id: zone_description_id, className: 'sr'},
            gettext('Items placed here: ') + items_in_zone.map(function (item) { return item.displayName; }).join(", ")
          );
        }

        return (
            h(
                selector,
                {
                    key: zone.prefixed_uid,
                    id: zone.prefixed_uid,
                    attributes: {
                        'tabindex': 0,
                        'dropzone': 'move',
                        'aria-dropeffect': 'move',
                        'data-uid': zone.uid,
                        'data-zone_align': zone.align,
                        'role': 'button',
                        'aria-describedby': zone_description_id,
                    },
                    style: {
                        top: zone.y_percent + '%', left: zone.x_percent + "%",
                        width: zone.width_percent + '%', height: zone.height_percent + "%",
                    }
                },
                [
                    h('p', { className: className }, zone.title),
                    h('p', { className: 'zone-description sr' }, zone.description || gettext("droppable")),
                    h(item_wrapper, renderCollection(itemTemplate, items_in_zone, ctx)),
                    zone_description
                ]
            )
        );
    };

    var feedbackTemplate = function(ctx) {
        var messages = ctx.overall_feedback_messages || [];
        var feedback_display = messages.length > 0 ? 'block' : 'none';
        var feedback_messages = messages.map(function(message) {
            var selector = "p.message";
            if (message.message_class) {
                selector += "."+message.message_class;
            }
            return h(selector, {innerHTML: message.message}, []);
        });

        return (
            h('section.feedback', {}, [
                h(
                    "div.feedback-content",
                    { attributes: { 'aria-live': 'polite' } },
                    [
                        h('h3.title1', { style: { display: feedback_display } }, gettext('Feedback')),
                        h('div.messages', { style: { display: feedback_display } }, feedback_messages),
                    ]
                )
            ])
        );
    };

    var popupTemplate = function(ctx) {
        var popupSelector = 'div.popup';
        if (ctx.popup_html && !ctx.last_action_correct) {
            popupSelector += '.popup-incorrect';
        }

        return (
            h(
                "div.popup-wrapper",
                {
                    attributes: {
                        'aria-live': 'polite',
                        'aria-atomic': 'true',
                        'aria-relevant': 'additions',
                    }
                },
                [
                    h(
                        popupSelector,
                        {
                            style: {display: ctx.popup_html ? 'block' : 'none'},
                        },
                        [
                            h('div.close.icon-remove-sign.fa-times-circle'),
                            h('p.popup-content', {innerHTML: ctx.popup_html}),
                        ]
                    )
                ]
            )
        )
    };

    var keyboardHelpPopupTemplate = function(ctx) {
        var labelledby_id = 'modal-window-title-'+configuration.url_name;
        return (
            h('div.keyboard-help-dialog', [
                h('div.modal-window-overlay'),
                h('div.modal-window', {attributes: {role: 'dialog', 'aria-labelledby': labelledby_id}}, [
                    h('div.modal-header', [
                        h('h2.modal-window-title#'+labelledby_id, gettext('Keyboard Help'))
                    ]),
                    h('div.modal-content', [
                        h('p', {className:'sr'}, gettext('This is a screen reader-friendly problem')),
                        h('p', gettext('You can complete this problem using only your keyboard by following the guidance below:')),
                        h('p', {className:'sr'}, gettext('Drag and Drop problems consist of draggable items and dropzones. Users should select a draggable item with their keyboard and then navigate to an appropriate dropzone to drop it.')),
                        h('ul', [
                            h('li', gettext('Use only TAB and SHIFT+TAB to navigate between draggable items and drop zones.')),
                            h('li', gettext('Press CTRL+M to select a draggable item (effectively picking it up).')),
                            h('li', gettext('Navigate using TAB and SHIFT+TAB to the appropriate dropzone and press CTRL+M once more to drop it here.')),
                            h('li', gettext('Press "Esc" if you want to cancel the drop operation (for example, to select a different item).')),
                            h('li', gettext('TAB back to the list of draggable items and repeat this process until all of the draggable items have been placed on their respective dropzones.')),
                        ])
                    ]),
                    h('div.modal-actions', [
                        h('button.modal-dismiss-button', gettext("OK"))
                    ])
                ])
            ])
        );
    };

    var submitAnswerTemplate = function(ctx) {
        var attemptsUsedId = "attempts-used-"+configuration.url_name;
        var attemptsUsedDisplay = (ctx.max_attempts && ctx.max_attempts > 0) ? 'inline': 'none';

        return (
          h("section.action-toolbar-item.submit-answer", {}, [
              h(
                  "button.btn-brand.submit-answer-button",
                  {
                      disabled: ctx.disable_submit_button || ctx.submit_spinner, 
                      attributes: {"aria-describedby": attemptsUsedId}},
                  [
                      (ctx.submit_spinner ? h("span.fa.fa-spin.fa-spinner") : null),
                      gettext("Submit")
                  ]
              ),
              h(
                  "span.attempts-used#"+attemptsUsedId, {style: {display: attemptsUsedDisplay}},
                  gettext("You have used {used} of {total} attempts.")
                      .replace("{used}", ctx.attempts).replace("{total}", ctx.max_attempts)
              )
          ])
        );
    };

    var sidebarButtonTemplate = function(buttonClass, iconClass, buttonText, disabled, spinner) {
        if (spinner) {
            iconClass = 'fa-spin.fa-spinner';
        }
        return (
            h('span.sidebar-button-wrapper', {}, [
                h(
                    'button.unbutton.btn-default.btn-small.'+buttonClass,
                    {disabled: disabled || spinner || false, attributes: {tabindex: 0}},
                    [
                        h(
                            "span.btn-icon.fa." + iconClass,
                            {attributes: {"aria-hidden": true}},
                            []
                        ),
                        buttonText
                    ]
                )
            ])
        );
    };

    var sidebarTemplate = function(ctx) {
        var showAnswerButton = null;
        if (ctx.show_show_answer) {
            showAnswerButton = sidebarButtonTemplate(
                "show-answer-button",
                "fa-info-circle",
                gettext('Show Answer'),
                ctx.showing_answer ? true : ctx.disable_show_answer_button,
                ctx.show_answer_spinner
            );
        }
        return(
            h("section.action-toolbar-item.sidebar-buttons", {}, [
                sidebarButtonTemplate(keyboardHelpButtonClass, "fa-question", keyboardHelpText),
                sidebarButtonTemplate("reset-button", "fa-refresh", gettext('Reset'), ctx.disable_reset_button),
                showAnswerButton,
            ])
        )
    };

    var itemFeedbackPopupTemplate = function(ctx) {
        var popupSelector = 'div.popup.item-feedback-popup.popup-wrapper';
        var msgs = ctx.feedback_messages || [];
        var have_messages = msgs.length > 0;
        var popup_content;

        var close_button_describedby_id = "close-popup-"+configuration.url_name;

        if (msgs.length > 0 && !ctx.last_action_correct) {
            popupSelector += '.popup-incorrect';
        }

        if (ctx.mode == DragAndDropBlock.ASSESSMENT_MODE) {
            var content_items = [
                (!ctx.last_action_correct) ? h("p", {}, gettext("Some of your answers were not correct.")) : null,
                h("p", {}, gettext("Hints:")),
                h("ul", {}, msgs.map(function(message) {
                    return h("li", {innerHTML: message.message});
                }))
            ];
            popup_content = h("div.popup-content", {}, have_messages ? content_items : []);
        } else {
            popup_content = h("div.popup-content", {}, msgs.map(function(message) {
                return h("p", {innerHTML: message.message});
            }))
        }

        return h(
            popupSelector,
            {
                style: {display: have_messages ? 'block' : 'none'},
                attributes: {
                    "tabindex": "-1",
                    'aria-live': 'polite',
                    'aria-atomic': 'true',
                    'aria-relevant': 'additions',
                }
            },
            [
                h(
                    'button.unbutton.close-feedback-popup-button',
                    {},
                    [
                        h(
                            'span.sr',
                            {
                                innerHTML: gettext("Close")
                            }
                        ),
                        h(
                            'span.icon.fa.fa-times-circle',
                            {
                                attributes: {
                                    'aria-hidden': true
                                }
                            }
                        )
                    ]
                ),
                popup_content
            ]
        )
    };

    var forwardKeyboardHelpButtonTemplate = function(ctx) {
{'fraction_non_alphanumeric': 0.11248775580441056, 'fraction_numerical': 0.002382654276864427, 'mean_word_length': 1.3997204751921732, 'pattern_counts': {'":': 6, '<': 7, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2356256', 'n_tokens_mistral': 9878, 'n_tokens_neox': 9286, 'n_words': 2327}