diff --git a/kitsune/sumo/static/sumo/js/markup.js b/kitsune/sumo/static/sumo/js/markup.js index 43a66d71b4c..1f05e742d2c 100644 --- a/kitsune/sumo/static/sumo/js/markup.js +++ b/kitsune/sumo/static/sumo/js/markup.js @@ -15,10 +15,10 @@ import KBox from "sumo/js/kbox"; //or, create a custom toolbar. Marky.createFullToolbar('#toolbar-container-id', '#textarea-id', [ - new Marky.SimpleButton( + new Marky.MarkupButton( gettext('Bold'), "'''", "'''", gettext('bold text'), 'btn-bold'), - new Marky.SimpleButton( + new Marky.MarkupButton( gettext('Italic'), "''", "''", gettext('italic text'), 'btn-italic') ]); @@ -34,18 +34,18 @@ var Marky = { var settings = $.extend({}, defaults, options); - var SB = Marky.SimpleButton; + var MB = Marky.MarkupButton; var buttons = [ - new SB(gettext('Bold'), "'''", "'''", gettext('bold text'), + new MB(gettext('Bold'), "'''", "'''", gettext('bold text'), 'btn-bold'), - new SB(gettext('Italic'), "''", "''", gettext('italic text'), + new MB(gettext('Italic'), "''", "''", gettext('italic text'), 'btn-italic'), new Marky.Separator(), new Marky.LinkButton(), new Marky.Separator(), - new SB(gettext('Numbered List'), '# ', '', + new MB(gettext('Numbered List'), '# ', '', gettext('Numbered list item'), 'btn-ol', true), - new SB(gettext('Bulleted List'), '* ', '', + new MB(gettext('Bulleted List'), '* ', '', gettext('Bulleted list item'), 'btn-ul', true) ]; if (settings.mediaButton) { @@ -63,8 +63,7 @@ var Marky = { Marky.createCustomToolbar(toolbarSel, textareaSel, buttons); }, createFullToolbar: function(toolbarSel, textareaSel) { - var SB = Marky.SimpleButton, - buttons = Marky.allButtons(); + var buttons = Marky.allButtons(); Marky.createCustomToolbar(toolbarSel, textareaSel, buttons); }, createCustomToolbar: function(toolbarSel, textareaSel, partsArray) { @@ -75,107 +74,153 @@ var Marky = { } }, allButtons: function() { - var SB = Marky.SimpleButton; + var MB = Marky.MarkupButton; return [ - new SB(gettext('Bold'), "'''", "'''", gettext('bold text'), + new MB(gettext('Bold'), "'''", "'''", gettext('bold text'), 'btn-bold'), - new SB(gettext('Italic'), "''", "''", gettext('italic text'), + new MB(gettext('Italic'), "''", "''", gettext('italic text'), 'btn-italic'), new Marky.Separator(), new Marky.LinkButton(), new Marky.MediaButton(), new Marky.Separator(), - new SB(gettext('Numbered List'), '# ', '', + new MB(gettext('Numbered List'), '# ', '', gettext('Numbered list item'), 'btn-ol', true), - new SB(gettext('Bulleted List'), '* ', '', + new MB(gettext('Bulleted List'), '* ', '', gettext('Bulleted list item'), 'btn-ul', true), new Marky.Separator(), - new SB(gettext('Heading 1'), '=', '=', gettext('Heading 1'), + new MB(gettext('Heading 1'), '=', '=', gettext('Heading 1'), 'btn-h1', true), - new SB(gettext('Heading 2'), '==', '==', gettext('Heading 2'), + new MB(gettext('Heading 2'), '==', '==', gettext('Heading 2'), 'btn-h2', true), - new SB(gettext('Heading 3'), '===', '===', gettext('Heading 3'), + new MB(gettext('Heading 3'), '===', '===', gettext('Heading 3'), 'btn-h3', true) ]; } }; -/* - * A simple button. - * Note: `everyline` is a boolean value that says whether or not the selected - * text should be broken into multiple lines and have the markup applied - * to each line or not. Default is false (do not apply this behavior). - * `classes` is a list of classes to include in the output - */ -Marky.SimpleButton = function(name, openTag, closeTag, defaultText, - classes, everyline) { - this.name = name; - this.classes = ''; - this.openTag = openTag; - this.closeTag = closeTag; - this.defaultText = defaultText; - this.everyline = everyline; - if (undefined !== classes) { +/** + * A simple button template. Do not use directly: + * use MarkupButton or InsertButton instead. + */ +class SimpleButton { + /** + * @param {string} name - Button title. + * @param {string} classes - A list of classes to include in the output. + */ + constructor(name, classes = '') { + this.name = name; this.classes = classes; - } - this.html = '' + - '' + gettext('Cancel') + - '' // whew, yuck!? - ), - selectedText = me.getSelectedText(), - kbox; - - $html.find('li input[type="text"]').on('focus', function() { - $(this).closest('li').find('input[type="radio"]').trigger("click"); + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + '
  1. ' + + '
  2. ' + + '
  3. ' + + '
  4. ' + + '
' + + '' + gettext('Cancel') + + '
' // whew, yuck!? + ); + + const $linkText = $html.find('input[name="link-text"]'); + const $internal = $html.find('input[name="internal"]'); + const $external = $html.find('input[name="external"]'); + const $submitButton = $html.find('button[type="submit"]'); + + $html.find('li input[type="text"]').on('focus', function () { + $(this).closest('li').find('input[type="radio"]').trigger('click'); }); + // Make the text input of the selected link-type radio required. + function updateRequiredTarget() { + const val = $html.find('input[name="link-type"]:checked').val(); + + $internal.prop('required', val === 'internal'); + $external.prop('required', val === 'external'); + } + + $html.find('input[name="link-type"]').prop('required', true); + $html.find('input[name="link-type"]').on('change', updateRequiredTarget); + + // Disable the Submit button while the form is invalid. + function updateSubmitButton() { + $submitButton.prop('disabled', !$html[0].checkValidity()); + } + + $html.find('li input[type="text"]').on('change', updateSubmitButton); + $html.find('input[name="link-type"]').on('change', updateSubmitButton); + // Perform a query for the sections of an article if // last character is a pound: - var performSectionSearch = function(request) { - return (request.term.indexOf('#') === request.term.length - 1); - }; + function performSectionSearch(request) { + return request.term.endsWith('#'); + } - var results = []; + let results = []; // Get the article URL by providing the article name: - var getArticleURL = function(name) { - for (var i = 0; i < results.length; i++) { + function getArticleURL(name) { + for (let i = 0; i < results.length; i++) { if (name === results[i].label) { return results[i].url; } } return null; - }; + } - var articleSearch = function(request, response) { + function articleSearch(request, response) { results = []; $.ajax({ url: '/en-US/search', @@ -356,22 +436,22 @@ Marky.LinkButton.prototype = $.extend({}, Marky.SimpleButton.prototype, { q: request.term, a: 1, w: 1, - language: $('html').attr('lang') + language: $('html').attr('lang'), }, dataType: 'json', - success: function(data, textStatus) { - $.map(data.results, function(val, i) { + success: function (data, textStatus) { + $.map(data.results, function (val, i) { results.push({ label: val.title, - url: val.url + url: val.url, }); }); response(results); - } + }, }); - }; + } - var sectionSearch = function(request, response) { + function sectionSearch(request, response) { var articleName = request.term.split('#')[0]; var articleURL = getArticleURL(articleName); @@ -382,107 +462,79 @@ Marky.LinkButton.prototype = $.extend({}, Marky.SimpleButton.prototype, { $.ajax({ url: articleURL, dataType: 'text', - success: function(data, status) { - var headings = $("[id^='w_']", data); - var array = []; + success: function (data, status) { + const headings = $("[id^='w_']", data); + const array = []; if (headings.length === 0) { array.push({ label: gettext('No sections found'), value: request.term.replace('#', ''), - target: '' + target: '', }); } - headings.each(function() { - var element = this.nodeName; - var level = element.substring(1); - var label = $(this).text(); - var target = $(this).attr('id'); - var value = request.term + target; + headings.each(function () { + const label = $(this).text(); + const target = $(this).attr('id'); + const value = request.term + target; array.push({ label: label, value: value, - target: target + target: target, }); }); response(array); - } + }, }); - }; + } - $html.find('input[name="internal"]').autocomplete({ - source: function(request, response) { + $internal.autocomplete({ + source: function (request, response) { if (performSectionSearch(request)) { sectionSearch(request, response); } else { articleSearch(request, response); } }, - select: function(event, ui) { + select: function (event, ui) { if (!ui.item.target) { return; } - var $linktext = $html.find('input[name=link-text]'); - if ($linktext.val() === '') { - $linktext.val(ui.item.label); + if ($linkText.val() === '') { + $linkText.val(ui.item.label); } - } + }, }); - $html.find('button').text(gettext('Insert Link')).on("click", function(e) { + $html.on('submit', function (e) { // Generate the wiki markup based on what the user has selected // (interval vs external links) and entered into the textboxes, // if anything. - var val = $html.find('input[type="radio"]:checked').val(), - text = $html.find('input[name=link-text]').val(), - $internal = $html.find('input[name="internal"]'), - $external = $html.find('input[name="external"]'); + e.preventDefault(); + + const val = $html.find('input[type="radio"]:checked').val(), + text = $html.find('input[name=link-text]').val(); + me.reset(); if (val === 'internal') { - var title = $internal.val(); - if (title) { - if (title === selectedText) { - // The title wasn't changed, so lets keep it selected. - me.openTag = '[['; - me.closeTag = ']]'; - if (text) { - me.closeTag = '|' + text + me.closeTag; - } - } else { - // The title changed, so lets insert link before the cursor. - me.openTag = '[[' + title; - if (text) { - me.openTag += '|' + text; - } - me.openTag += ']] '; - me.closeTag = ''; - me.defaultText = ''; - } + const title = $internal.val() || gettext('Knowledge Base Article'); + if (!text || title === text) { + me.markup = '[[' + title + ']]'; } else { - me.openTag = '[['; - me.closeTag = ']]'; - if (text) { - me.closeTag = '|' + text + ']]'; - } - me.defaultText = gettext('Knowledge Base Article'); + me.markup = '[[' + title + '|' + text + ']]'; } } else { - var link = $external.val(); - if (link) { - if (link.indexOf('http') !== 0) { - link = 'http://' + link; - } - me.openTag = '[' + link + ' '; - if (text) { - me.openTag += text + '] '; - me.closeTag = ''; - me.defaultText = ''; - } - } else if (text) { - me.defaultText = text; + let link = $external.val() || 'https://example.com'; + if (link.indexOf('http') !== 0) { + link = 'http://' + link; + } + if (!text) { + me.markup = '[' + link + ']'; + } else { + me.markup = '[' + link + ' ' + text + ']'; } } @@ -490,57 +542,42 @@ Marky.LinkButton.prototype = $.extend({}, Marky.SimpleButton.prototype, { kbox.close(); }); + const selectedText = me.getSelectedText(); if (selectedText) { - // If there user has selected text, lets default to it being - // the Article Title. - $html.find('input[name="internal"]').val(selectedText).trigger("focus"); + // If there is selected text, make it the link text. + $linkText.val(selectedText); } - kbox = new KBox($html, { + const kbox = new KBox($html, { title: this.name, destroy: true, modal: true, id: 'link-modal', container: $('body'), - position: 'none' + position: 'none', }); kbox.open(); + updateRequiredTarget(); + updateSubmitButton(); + + // Focus the link text field to remove focus from the editor tools button. + $linkText.trigger('focus'); + ev.preventDefault(); return false; } -}); - -/* - * The media helper. - */ -Marky.MediaButton = function() { - this.name = gettext('Insert media...'); - this.classes = 'btn-media'; - this.openTag = ''; - this.closeTag = ''; - this.defaultText = gettext('media'); - this.everyline = false; - - this.html = '