<commit_before>/* https://github.com/DiemenDesign/summernote-cleaner */
/* Version: 1.0.5 */
(function(factory) {
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else if (typeof module === 'object'&&module.exports) {
    module.exports = factory(require('jquery'));
  } else {
    factory(window.jQuery);
  }
}
(function ($) {
  $.extend(true, $.summernote.lang, {
    'en-US': {
      cleaner: {
        tooltip: 'Cleaner',
        not: 'Text has been cleaned!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
    'de-DE': {
      cleaner: {
        tooltip: 'Reinigungskraft',
        not: 'Inhalt wurde bereinigt!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
  });
  $.extend( $.summernote.options, {
    cleaner: {
      action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
      icon: '<svg xmlns="http://www.w3.org/2000/svg" id="libre-paintbrush" viewBox="0 0 14 14" width="14" height="14"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"/></svg>',
      keepHtml: true,
      keepTagContents: ['span'], //Remove tags and keep the contents
      badTags: ['applet', 'col', 'colgroup', 'embed', 'noframes', 'noscript', 'script', 'style', 'title', 'meta', 'link', 'head'], //Remove full tags with contents
      badAttributes: ['bgcolor', 'border', 'height', 'cellpadding', 'cellspacing', 'lang', 'start', 'style', 'valign', 'width'], //Remove attributes from remaining tags
      limitChars: 0, // 0|# 0 disables option
      limitDisplay: 'both', // none|text|html|both
      limitStop: false, // true/false
      notTimeOut: 850, //time before status message is hidden in miliseconds
      imagePlaceholder: 'https://via.placeholder.com/200'
    }
  });
  $.extend( $.summernote.plugins, {
    'cleaner': function (context) {
      var self = this,
            ui = $.summernote.ui,
         $note = context.layoutInfo.note,
       $editor = context.layoutInfo.editor,
       options = context.options,
          lang = options.langInfo;
      if (options.cleaner.action == 'both' || options.cleaner.action == 'button') {
        context.memo('button.cleaner', function () {
          var button = ui.button({
            contents: options.cleaner.icon,
            container: options.container,
            tooltip: lang.cleaner.tooltip,
            placement: options.placement,
            click: function () {
              if ($note.summernote('createRange').toString())
                $note.summernote('pasteHTML', $note.summernote('createRange').toString());
              else
                $note.summernote('code', cleanPaste($note.summernote('code'), options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder), true);
              if ($editor.find('.note-status-output').length > 0)
                $editor.find('.note-status-output').html(lang.cleaner.not);
            }
          });
          return button.render();
        });
      }
      this.events = {
        'summernote.init': function () {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none'){
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/," ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (textLength.length > options.cleaner.limitChars&&options.cleaner.limitChars > 0)
              lengthStatus += 'note-text-danger">';
            else
              lengthStatus += '">';
            if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitText + ': ' + textLength.length;
            if (options.cleaner.limitDisplay == 'both')
              lengthStatus += ' / ';
            if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
            $editor.find('.note-status-output').html('<small class="note-pull-right ' + lengthStatus + ' ');
          }
        },
        'summernote.keydown': function (we, event) {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none') {
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/, " ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (options.cleaner.limitStop == true && textLength.length >= options.cleaner.limitChars) {
              var key = event.keyCode;
              allowed_keys = [8, 37, 38, 39, 40, 46];
              if ($.inArray(key,allowed_keys) != -1){
                $editor.find('.cleanerLimit').removeClass('note-text-danger');
                return true;
              } else {
                $editor.find('.cleanerLimit').addClass('note-text-danger');
                event.preventDefault();
                event.stopPropagation();
              }
            } else {
              if (textLength.length > options.cleaner.limitChars && options.cleaner.limitChars > 0)
                lengthStatus += 'note-text-danger">';
              else
                lengthStatus += '">';
              if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitText + ': '+textLength.length;
              if (options.cleaner.limitDisplay == 'both')
                lengthStatus += ' / ';
              if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
              $editor.find('.note-status-output').html('<small class="cleanerLimit note-pull-right ' + lengthStatus + ' ');
            }
          }
        },
        'summernote.paste': function(we, event) {
          if (options.cleaner.action=='both' || options.cleaner.action == 'paste') {
            event.preventDefault();
            var ua = window.navigator.userAgent;
            var msie = ua.indexOf("MSIE ");
                msie = msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./);
            var ffox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
            var text; var isHtmlData = false;
            if (msie)
              text = window.clipboardData.getData("Text");
            else
            {
              var dataType = 'text/plain';
              /*only get the html data if its avaialble else use plain text*/
              if (options.cleaner.keepHtml && event.originalEvent.clipboardData.types.indexOf('text/html') > -1) {
                  dataType = 'text/html';
                  isHtmlData = true;
              }
              text = event.originalEvent.clipboardData.getData(dataType);
            }
            if (text) {
              if (msie || ffox) {
                setTimeout(function () {
                  $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
                }, 1);
              } else
                $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
              if ($editor.find('.note-status-output').length > 0) {
                $editor.find('.note-status-output').html(lang.cleaner.not);
                /*now set a timeout to clear out the message */
                setTimeout(function(){
                  if($editor.find('.note-status-output').html() == lang.cleaner.not){
                    /*lets fade out the text, then clear it and show the control ready for next time */
                    $editor.find('.note-status-output').fadeOut(function(){
                      $(this).html("");
                      $(this).fadeIn();
                    });
                  }
                }, options.cleaner.notTimeOut)
              }
            }
          }
        }
      }
      var cleanPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder, isHtmlData) {
        if(isHtmlData) {
          return cleanHtmlPaste(input, badTags, keepTagContents, badAttributes, imagePlaceholder);
        } else {
          return cleanTextPaste(input);
        }
      };

      var cleanTextPaste = function(input) {
        var newLines = /(\r\n|\r|\n)/g;
        var parsedInput = input.split(newLines);
        if(parsedInput.length === 1) { return input; }
        var output = "";
        /*for larger blocks of text (such as multiple paragraphs) match summernote markup */
        for (let contentIndex = 0; contentIndex < parsedInput.length; contentIndex++) {
          const element = parsedInput[contentIndex];
          if(!newLines.test(element)) {
            var line = element == '' ? '
' : element;
            output += '
' + line + '
'
          }
        }
        return output;
      }

      var cleanHtmlPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder) {
        var stringStripper = /( class=(")?Mso[a-zA-Z]+(")?)/gmi;
        /*remove MS office class crud*/
        var output = input.replace(stringStripper, '');
        var commentStripper = new RegExp('', 'gmi');
        output = output.replace(commentStripper, '');
        /*remove MS office comment if else crud */
        var commentIfStripper = new RegExp('<![^>\v]*>', 'gmi');
        output = output.replace(commentIfStripper, '');
        var tagStripper = new RegExp('<(/)*(\\?xml:|st1:|o:|v:)[^>\v]*>', 'gmi');
        output = output.replace(/ src="(.*?)"/gmi, ' src="' + imagePlaceholder + '"');
        output = output.replace(/ name="(.*?)"/gmi, ' data-title="$1" alt="$1"');
        /*remove MS office tag crud*/
        output = output.replace(tagStripper, '');
        for (var i = 0; i < badTags.length; i++) {
          const badTag = badTags[i];
          /*remove the tag and its contents*/
          tagStripper = new RegExp('<' + badTag + '(.|\r|\n)*</' + badTag + '[^>\v]*>', 'gmi');
          output = output.replace(tagStripper, '');
          /*remove tags with no ending tag or rogue ending tags*/
          var singletonTagStripper = new RegExp('</?' + badTag + '[^>\v]*>', 'gmi');
          output = output.replace(singletonTagStripper, '');
        }
        for (var i = 0; i < keepTagContents.length; i++) {
          /*remove tags only*/
          tagStripper = new RegExp('</?' + keepTagContents[i] + '[^>\v]*>', 'gmi');
          output = output.replace(tagStripper, ' ');
        }
        for (var i = 0; i < badAttributes.length; i++) {
          const badAttribute = badAttributes[i];
          /*for attribute matching ensure we match a new line or some kind of space to prevents partial matching for attributes
          (e.g. color would modify bgcolor tag to be just bg) */
          var attributeWithSpeachMarksStripper = new RegExp('(\s|\r\n|\r|\n| )' + badAttribute + '="[^"\v]*"', 'gmi');
          output = output.replace(attributeWithSpeachMarksStripper, '');
          var attributeWithApostropheStripper = new RegExp('(\s|\r\n|\r|\n| )' + badAttribute + "='[^'\v]*'", 'gmi');
          output = output.replace(attributeWithApostropheStripper, '');
        }
        output = output.replace(/ align="(.*?)"/gi, ' class="text-$1"');
        output = output.replace(/ class="western"/gi, '');
        output = output.replace(/ class=""/gi, '');
        output = output.replace(/(.*?)<\/b>/gi, '$1');
        output = output.replace(/(.*?)<\/i>/gi, '$1');
        output = output.replace(/\s{2,}/g, ' ').trim();
        return output;
      }
    }
  });
}));
<commit_msg>bug\misspelt variable name<commit_after>/* https://github.com/DiemenDesign/summernote-cleaner */
/* Version: 1.0.5 */
(function(factory) {
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else if (typeof module === 'object'&&module.exports) {
    module.exports = factory(require('jquery'));
  } else {
    factory(window.jQuery);
  }
}
(function ($) {
  $.extend(true, $.summernote.lang, {
    'en-US': {
      cleaner: {
        tooltip: 'Cleaner',
        not: 'Text has been cleaned!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
    'de-DE': {
      cleaner: {
        tooltip: 'Reinigungskraft',
        not: 'Inhalt wurde bereinigt!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
  });
  $.extend( $.summernote.options, {
    cleaner: {
      action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
      icon: '<svg xmlns="http://www.w3.org/2000/svg" id="libre-paintbrush" viewBox="0 0 14 14" width="14" height="14"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"/></svg>',
      keepHtml: true,
      keepTagContents: ['span'], //Remove tags and keep the contents
      badTags: ['applet', 'col', 'colgroup', 'embed', 'noframes', 'noscript', 'script', 'style', 'title', 'meta', 'link', 'head'], //Remove full tags with contents
      badAttributes: ['bgcolor', 'border', 'height', 'cellpadding', 'cellspacing', 'lang', 'start', 'style', 'valign', 'width'], //Remove attributes from remaining tags
      limitChars: 0, // 0|# 0 disables option
      limitDisplay: 'both', // none|text|html|both
      limitStop: false, // true/false
      notTimeOut: 850, //time before status message is hidden in miliseconds
      imagePlaceholder: 'https://via.placeholder.com/200'
    }
  });
  $.extend( $.summernote.plugins, {
    'cleaner': function (context) {
      var self = this,
            ui = $.summernote.ui,
         $note = context.layoutInfo.note,
       $editor = context.layoutInfo.editor,
       options = context.options,
          lang = options.langInfo;
      if (options.cleaner.action == 'both' || options.cleaner.action == 'button') {
        context.memo('button.cleaner', function () {
          var button = ui.button({
            contents: options.cleaner.icon,
            container: options.container,
            tooltip: lang.cleaner.tooltip,
            placement: options.placement,
            click: function () {
              if ($note.summernote('createRange').toString())
                $note.summernote('pasteHTML', $note.summernote('createRange').toString());
              else
                $note.summernote('code', cleanPaste($note.summernote('code'), options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder), true);
              if ($editor.find('.note-status-output').length > 0)
                $editor.find('.note-status-output').html(lang.cleaner.not);
            }
          });
          return button.render();
        });
      }
      this.events = {
        'summernote.init': function () {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none'){
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/," ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (textLength.length > options.cleaner.limitChars&&options.cleaner.limitChars > 0)
              lengthStatus += 'note-text-danger">';
            else
              lengthStatus += '">';
            if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitText + ': ' + textLength.length;
            if (options.cleaner.limitDisplay == 'both')
              lengthStatus += ' / ';
            if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
            $editor.find('.note-status-output').html('<small class="note-pull-right ' + lengthStatus + ' ');
          }
        },
        'summernote.keydown': function (we, event) {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none') {
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/, " ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (options.cleaner.limitStop == true && textLength.length >= options.cleaner.limitChars) {
              var key = event.keyCode;
              allowed_keys = [8, 37, 38, 39, 40, 46];
              if ($.inArray(key,allowed_keys) != -1){
                $editor.find('.cleanerLimit').removeClass('note-text-danger');
                return true;
              } else {
                $editor.find('.cleanerLimit').addClass('note-text-danger');
                event.preventDefault();
                event.stopPropagation();
              }
            } else {
              if (textLength.length > options.cleaner.limitChars && options.cleaner.limitChars > 0)
                lengthStatus += 'note-text-danger">';
              else
                lengthStatus += '">';
              if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitText + ': '+textLength.length;
              if (options.cleaner.limitDisplay == 'both')
                lengthStatus += ' / ';
              if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
              $editor.find('.note-status-output').html('<small class="cleanerLimit note-pull-right ' + lengthStatus + ' ');
            }
          }
        },
        'summernote.paste': function(we, event) {
          if (options.cleaner.action=='both' || options.cleaner.action == 'paste') {
            event.preventDefault();
            var ua = window.navigator.userAgent;
            var msie = ua.indexOf("MSIE ");
                msie = msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./);
            var ffox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
            var text; var isHtmlData = false;
            if (msie)
              text = window.clipboardData.getData("Text");
            else
            {
              var dataType = 'text/plain';
              /*only get the html data if its avaialble else use plain text*/
              if (options.cleaner.keepHtml && event.originalEvent.clipboardData.types.indexOf('text/html') > -1) {
                  dataType = 'text/html';
                  isHtmlData = true;
              }
              text = event.originalEvent.clipboardData.getData(dataType);
            }
            if (text) {
              if (msie || ffox) {
                setTimeout(function () {
                  $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
                }, 1);
              } else
                $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
              if ($editor.find('.note-status-output').length > 0) {
                $editor.find('.note-status-output').html(lang.cleaner.not);
                /*now set a timeout to clear out the message */
                setTimeout(function(){
                  if($editor.find('.note-status-output').html() == lang.cleaner.not){
                    /*lets fade out the text, then clear it and show the control ready for next time */
                    $editor.find('.note-status-output').fadeOut(function(){
                      $(this).html("");
                      $(this).fadeIn();
                    });
                  }
                }, options.cleaner.notTimeOut)
              }
            }
          }
        }
      }
      var cleanPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder, isHtmlData) {
        if(isHtmlData) {
          return cleanHtmlPaste(input, badTags, keepTagContents, badAttributes, imagePlaceholder);
        } else {
          return cleanTextPaste(input);
        }
      };

      var cleanTextPaste = function(input) {
        var newLines = /(\r\n|\r|\n)/g;
        var parsedInput = input.split(newLines);
        if(parsedInput.length === 1) { return input; }
        var output = "";
        /*for larger blocks of text (such as multiple paragraphs) match summernote markup */
        for (let contentIndex = 0; contentIndex < parsedInput.length; contentIndex++) {
          const element = parsedInput[contentIndex];
          if(!newLines.test(element)) {
            var line = element == '' ? '
' : element;
            output += '
' + line + '
'
          }
        }
        return output;
      }

      var cleanHtmlPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder) {
        var stringStripper = /( class=(")?Mso[a-zA-Z]+(")?)/gmi;
        /*remove MS office class crud*/
        var output = input.replace(stringStripper, '');
        var commentStripper = new RegExp('', 'gmi');
        output = output.replace(commentStripper, '');
        /*remove MS office comment if else crud */
        var commentIfStripper = new RegExp('<![^>\v]*>', 'gmi');
        output = output.replace(commentIfStripper, '');
        var tagStripper = new RegExp('<(/)*(\\?xml:|st1:|o:|v:)[^>\v]*>', 'gmi');
        output = output.replace(/ src="(.*?)"/gmi, ' src="' + imagePlaceholder + '"');
        output = output.replace(/ name="(.*?)"/gmi, ' data-title="$1" alt="$1"');
        /*remove MS office tag crud*/
        output = output.replace(tagStripper, '');
        for (var i = 0; i < badTags.length; i++) {
          const badTag = badTags[i];
          /*remove the tag and its contents*/
          tagStripper = new RegExp('<' + badTag + '(.|\r|\n)*</' + badTag + '[^>\v]*>', 'gmi');
          output = output.replace(tagStripper, '');
          /*remove tags with no ending tag or rogue ending tags*/
          var singletonTagStripper = new RegExp('</?' + badTag + '[^>\v]*>', 'gmi');
          output = output.replace(singletonTagStripper, '');
        }
        for (var i = 0; i < keepTagContents.length; i++) {
          /*remove tags only*/
          tagStripper = new RegExp('</?' + keepTagContents[i] + '[^>\v]*>', 'gmi');
          output = output.replace(tagStripper, ' ');
        }
        for (var i = 0; i < badAttributes.length; i++) {
          const badAttribute = badAttributes[i];
          /*for attribute matching ensure we match a new line or some kind of space to prevents partial matching for attributes
          (e.g. color would modify bgcolor tag to be just bg) */
          var attributeWithSpeechMarksStripper = new RegExp('(\s|\r\n|\r|\n| )' + badAttribute + '="[^"\v]*"', 'gmi');
          output = output.replace(attributeWithSpeechMarksStripper, '');
          var attributeWithApostropheStripper = new RegExp('(\s|\r\n|\r|\n| )' + badAttribute + "='[^'\v]*'", 'gmi');
          output = output.replace(attributeWithApostropheStripper, '');
        }
        output = output.replace(/ align="(.*?)"/gi, ' class="text-$1"');
        output = output.replace(/ class="western"/gi, '');
        output = output.replace(/ class=""/gi, '');
        output = output.replace(/(.*?)<\/b>/gi, '$1');
        output = output.replace(/(.*?)<\/i>/gi, '$1');
        output = output.replace(/\s{2,}/g, ' ').trim();
        return output;
      }
    }
  });
}));
<|endoftext|>


<commit_before>/* https://github.com/DiemenDesign/summernote-cleaner */
/* Version: 1.0.3 */
(function(factory) {
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else if (typeof module === 'object'&&module.exports) {
    module.exports = factory(require('jquery'));
  } else {
    factory(window.jQuery);
  }
}
(function ($) {
  $.extend(true, $.summernote.lang, {
    'en-US': {
      cleaner: {
        tooltip: 'Cleaner',
        not: 'Text has been Cleaned!!!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
    'de-DE': {
      cleaner: {
        tooltip: 'Cleaner',
        not: 'Inhalt wurde bereinigt!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
  });
  $.extend( $.summernote.options, {
    cleaner: {
      action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
      icon: '<svg xmlns="http://www.w3.org/2000/svg" id="libre-paintbrush" viewBox="0 0 14 14" width="14" height="14"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"/></svg>',
      keepHtml: true,
      keepTagContents: ['span'], //Remove tags and keep the contents
      badTags: ['applet', 'col', 'colgroup', 'embed', 'noframes', 'noscript', 'script', 'style', 'title'], //Remove full tags with contents
      badAttributes: ['bgcolor', 'border', 'height', 'cellpadding', 'cellspacing', 'lang', 'start', 'style', 'valign', 'width'], //Remove attributes from remaining tags
      limitChars: 0, // 0|# 0 disables option
      limitDisplay: 'both', // none|text|html|both
      limitStop: false, // true/false
      imagePlaceholder: 'https://via.placeholder.com/200'
    }
  });
  $.extend( $.summernote.plugins, {
    'cleaner': function (context) {
      var self = this,
            ui = $.summernote.ui,
         $note = context.layoutInfo.note,
       $editor = context.layoutInfo.editor,
       options = context.options,
          lang = options.langInfo;
      if (options.cleaner.action == 'both' || options.cleaner.action == 'button') {
        context.memo('button.cleaner', function () {
          var button = ui.button({
            contents: options.cleaner.icon,
            container: options.container,
            tooltip: lang.cleaner.tooltip,
            placement: options.placement,
            click: function () {
              if ($note.summernote('createRange').toString())
                $note.summernote('pasteHTML', $note.summernote('createRange').toString());
              else
                $note.summernote('code', cleanPaste($note.summernote('code'), options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder), true);
              if ($editor.find('.note-status-output').length > 0)
                $editor.find('.note-status-output').html(lang.cleaner.not);
            }
          });
          return button.render();
        });
      }
      this.events = {
        'summernote.init': function () {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none'){
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/," ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (textLength.length > options.cleaner.limitChars&&options.cleaner.limitChars > 0)
              lengthStatus += 'note-text-danger">';
            else
              lengthStatus += '">';
            if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitText + ': ' + textLength.length;
            if (options.cleaner.limitDisplay == 'both')
              lengthStatus += ' / ';
            if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
            $editor.find('.note-status-output').html('<small class="note-pull-right ' + lengthStatus + ' ');
          }
        },
        'summernote.keydown': function (we, event) {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none') {
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/, " ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (options.cleaner.limitStop == true && textLength.length >= options.cleaner.limitChars) {
              var key = event.keyCode;
              allowed_keys = [8, 37, 38, 39, 40, 46];
              if ($.inArray(key,allowed_keys) != -1){
                $editor.find('.cleanerLimit').removeClass('note-text-danger');
                return true;
              } else {
                $editor.find('.cleanerLimit').addClass('note-text-danger');
                event.preventDefault();
                event.stopPropagation();
              }
            } else {
              if (textLength.length > options.cleaner.limitChars && options.cleaner.limitChars > 0)
                lengthStatus += 'note-text-danger">';
              else
                lengthStatus += '">';
              if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitText + ': '+textLength.length;
              if (options.cleaner.limitDisplay == 'both')
                lengthStatus += ' / ';
              if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
              $editor.find('.note-status-output').html('<small class="cleanerLimit note-pull-right ' + lengthStatus + ' ');
            }
          }
        },
        'summernote.paste': function(we, event) {
          if (options.cleaner.action=='both' || options.cleaner.action == 'paste') {
            event.preventDefault();
            var ua = window.navigator.userAgent;
            var msie = ua.indexOf("MSIE ");
                msie = msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./);
            var ffox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
            var text; var isHtmlData = false;
            if (msie)
              text = window.clipboardData.getData("Text");
            else
            {
              var dataType = 'text/plain';
              /*only get the html data if its avaialble else use plain text*/
              if (options.cleaner.keepHtml && event.originalEvent.clipboardData.types.indexOf('text/html') > -1) {
                  dataType = 'text/html';
                  isHtmlData = true;
              }
              text = event.originalEvent.clipboardData.getData(dataType);
            }
            if (text) {
              if (msie || ffox) {
                setTimeout(function () {
                  $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
                }, 1);
              } else
                $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
              if ($editor.find('.note-status-output').length > 0)
                $editor.find('.note-status-output').html(lang.cleaner.not);
            }
          }
        }
      }
      var cleanPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder, isHtmlData) {
        if(isHtmlData) {
          return cleanHtmlPaste(input, badTags, keepTagContents, badAttributes, imagePlaceholder);
        } else {
          return cleanTextPaste(input);
        }
      };

      var cleanTextPaste = function(input) {
        var newLines = /(\r\n|\r|\n)/g;
        var parsedInput = input.split(newLines);
        if(parsedInput.length === 1) { return input; }
        var output = "";
        /*for larger blocks of text (such as multiple paragraphs) match summernote markup */
        for (let contentIndex = 0; contentIndex < parsedInput.length; contentIndex++) {
          const element = parsedInput[contentIndex];
          if(!newLines.test(element)) {
            var line = element == '' ? '
' : element;
            output += '
' + line + '
'
          }
        }
        return output;
      }

      var cleanHtmlPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder) {
        var stringStripper = /(\n|\r| class=(")?Mso[a-zA-Z]+(")? ^p)/g;
        var output = input.replace(stringStripper, '');
        var commentSripper = new RegExp('', 'g');
        var output = output.replace(commentSripper, '');
        var tagStripper = new RegExp('<(/)*(meta|link|span|\\?xml:|st1:|o:|font)(.*?)>', 'gi');
        output = output.replace(/ src="(.*?)"/gi, ' src="' + imagePlaceholder + '"');
        output = output.replace(/ name="(.*?)"/gi, ' data-title="$1" alt="$1"');
        output = output.replace(tagStripper, '');
        for (var i = 0; i < badTags.length; i++) {
          tagStripper = new RegExp('<' + badTags[i] + '.*?' + badTags[i] + '(.*?)>', 'gi');
          output = output.replace(tagStripper, '');
        }
        for (var i = 0; i < keepTagContents.length; i++) {
          tagStripper = new RegExp('</?' + keepTagContents[i] + '.*?>', 'gi');
          output = output.replace(tagStripper, ' ');
        }
        for (var i = 0; i < badAttributes.length; i++) {
          var attributeStripper = new RegExp(badAttributes[i] + '="(.*?)"', 'gi');
          output = output.replace(attributeStripper, '');
        }
        output = output.replace(/ align="(.*?)"/gi, ' class="text-$1"');
        output = output.replace(/ class="western"/gi, '');
        output = output.replace(/ class=""/gi, '');
        output = output.replace(/(.*?)<\/b>/gi, '$1');
        output = output.replace(/(.*?)<\/i>/gi, '$1');
        output = output.replace(/\s{2,}/g, ' ').trim();
        return output;
      }
    }
  });
}));
<commit_msg>made cleaner text more consistent<commit_after>/* https://github.com/DiemenDesign/summernote-cleaner */
/* Version: 1.0.3 */
(function(factory) {
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else if (typeof module === 'object'&&module.exports) {
    module.exports = factory(require('jquery'));
  } else {
    factory(window.jQuery);
  }
}
(function ($) {
  $.extend(true, $.summernote.lang, {
    'en-US': {
      cleaner: {
        tooltip: 'Cleaner',
        not: 'Text has been cleaned!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
    'de-DE': {
      cleaner: {
        tooltip: 'Cleaner',
        not: 'Inhalt wurde bereinigt!',
        limitText: 'Text',
        limitHTML: 'HTML'
      }
    },
  });
  $.extend( $.summernote.options, {
    cleaner: {
      action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
      icon: '<svg xmlns="http://www.w3.org/2000/svg" id="libre-paintbrush" viewBox="0 0 14 14" width="14" height="14"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"/></svg>',
      keepHtml: true,
      keepTagContents: ['span'], //Remove tags and keep the contents
      badTags: ['applet', 'col', 'colgroup', 'embed', 'noframes', 'noscript', 'script', 'style', 'title'], //Remove full tags with contents
      badAttributes: ['bgcolor', 'border', 'height', 'cellpadding', 'cellspacing', 'lang', 'start', 'style', 'valign', 'width'], //Remove attributes from remaining tags
      limitChars: 0, // 0|# 0 disables option
      limitDisplay: 'both', // none|text|html|both
      limitStop: false, // true/false
      imagePlaceholder: 'https://via.placeholder.com/200'
    }
  });
  $.extend( $.summernote.plugins, {
    'cleaner': function (context) {
      var self = this,
            ui = $.summernote.ui,
         $note = context.layoutInfo.note,
       $editor = context.layoutInfo.editor,
       options = context.options,
          lang = options.langInfo;
      if (options.cleaner.action == 'both' || options.cleaner.action == 'button') {
        context.memo('button.cleaner', function () {
          var button = ui.button({
            contents: options.cleaner.icon,
            container: options.container,
            tooltip: lang.cleaner.tooltip,
            placement: options.placement,
            click: function () {
              if ($note.summernote('createRange').toString())
                $note.summernote('pasteHTML', $note.summernote('createRange').toString());
              else
                $note.summernote('code', cleanPaste($note.summernote('code'), options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder), true);
              if ($editor.find('.note-status-output').length > 0)
                $editor.find('.note-status-output').html(lang.cleaner.not);
            }
          });
          return button.render();
        });
      }
      this.events = {
        'summernote.init': function () {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none'){
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/," ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (textLength.length > options.cleaner.limitChars&&options.cleaner.limitChars > 0)
              lengthStatus += 'note-text-danger">';
            else
              lengthStatus += '">';
            if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitText + ': ' + textLength.length;
            if (options.cleaner.limitDisplay == 'both')
              lengthStatus += ' / ';
            if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
              lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
            $editor.find('.note-status-output').html('<small class="note-pull-right ' + lengthStatus + ' ');
          }
        },
        'summernote.keydown': function (we, event) {
          if (options.cleaner.limitChars != 0 || options.cleaner.limitDisplay != 'none') {
            var textLength = $editor.find(".note-editable").text().replace(/(<([^>]+)>)/ig, "").replace(/( )/, " ");
            var codeLength = $editor.find('.note-editable').html();
            var lengthStatus = '';
            if (options.cleaner.limitStop == true && textLength.length >= options.cleaner.limitChars) {
              var key = event.keyCode;
              allowed_keys = [8, 37, 38, 39, 40, 46];
              if ($.inArray(key,allowed_keys) != -1){
                $editor.find('.cleanerLimit').removeClass('note-text-danger');
                return true;
              } else {
                $editor.find('.cleanerLimit').addClass('note-text-danger');
                event.preventDefault();
                event.stopPropagation();
              }
            } else {
              if (textLength.length > options.cleaner.limitChars && options.cleaner.limitChars > 0)
                lengthStatus += 'note-text-danger">';
              else
                lengthStatus += '">';
              if (options.cleaner.limitDisplay == 'text' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitText + ': '+textLength.length;
              if (options.cleaner.limitDisplay == 'both')
                lengthStatus += ' / ';
              if (options.cleaner.limitDisplay == 'html' || options.cleaner.limitDisplay == 'both')
                lengthStatus += lang.cleaner.limitHTML + ': ' + codeLength.length;
              $editor.find('.note-status-output').html('<small class="cleanerLimit note-pull-right ' + lengthStatus + ' ');
            }
          }
        },
        'summernote.paste': function(we, event) {
          if (options.cleaner.action=='both' || options.cleaner.action == 'paste') {
            event.preventDefault();
            var ua = window.navigator.userAgent;
            var msie = ua.indexOf("MSIE ");
                msie = msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./);
            var ffox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
            var text; var isHtmlData = false;
            if (msie)
              text = window.clipboardData.getData("Text");
            else
            {
              var dataType = 'text/plain';
              /*only get the html data if its avaialble else use plain text*/
              if (options.cleaner.keepHtml && event.originalEvent.clipboardData.types.indexOf('text/html') > -1) {
                  dataType = 'text/html';
                  isHtmlData = true;
              }
              text = event.originalEvent.clipboardData.getData(dataType);
            }
            if (text) {
              if (msie || ffox) {
                setTimeout(function () {
                  $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
                }, 1);
              } else
                $note.summernote('pasteHTML', cleanPaste(text, options.cleaner.badTags, options.cleaner.keepTagContents, options.cleaner.badAttributes, options.cleaner.imagePlaceholder, isHtmlData));
              if ($editor.find('.note-status-output').length > 0)
                $editor.find('.note-status-output').html(lang.cleaner.not);
            }
          }
        }
      }
      var cleanPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder, isHtmlData) {
        if(isHtmlData) {
          return cleanHtmlPaste(input, badTags, keepTagContents, badAttributes, imagePlaceholder);
        } else {
          return cleanTextPaste(input);
        }
      };

      var cleanTextPaste = function(input) {
        var newLines = /(\r\n|\r|\n)/g;
        var parsedInput = input.split(newLines);
        if(parsedInput.length === 1) { return input; }
        var output = "";
        /*for larger blocks of text (such as multiple paragraphs) match summernote markup */
        for (let contentIndex = 0; contentIndex < parsedInput.length; contentIndex++) {
          const element = parsedInput[contentIndex];
          if(!newLines.test(element)) {
            var line = element == '' ? '
' : element;
            output += '
' + line + '
'
          }
        }
        return output;
      }

      var cleanHtmlPaste = function(input, badTags, keepTagContents, badAttributes, imagePlaceholder) {
        var stringStripper = /(\n|\r| class=(")?Mso[a-zA-Z]+(")? ^p)/g;
        var output = input.replace(stringStripper, '');
        var commentSripper = new RegExp('', 'g');
        var output = output.replace(commentSripper, '');
        var tagStripper = new RegExp('<(/)*(meta|link|span|\\?xml:|st1:|o:|font)(.*?)>', 'gi');
        output = output.replace(/ src="(.*?)"/gi, ' src="' + imagePlaceholder + '"');
        output = output.replace(/ name="(.*?)"/gi, ' data-title="$1" alt="$1"');
        output = output.replace(tagStripper, '');
        for (var i = 0; i < badTags.length; i++) {
          tagStripper = new RegExp('<' + badTags[i] + '.*?' + badTags[i] + '(.*?)>', 'gi');
          output = output.replace(tagStripper, '');
        }
        for (var i = 0; i < keepTagContents.length; i++) {
          tagStripper = new RegExp('</?' + keepTagContents[i] + '.*?>', 'gi');
          output = output.replace(tagStripper, ' ');
        }
        for (var i = 0; i < badAttributes.length; i++) {
          var attributeStripper = new RegExp(badAttributes[i] + '="(.*?)"', 'gi');
          output = output.replace(attributeStripper, '');
        }
        output = output.replace(/ align="(.*?)"/gi, ' class="text-$1"');
        output = output.replace(/ class="western"/gi, '');
        output = output.replace(/ class=""/gi, '');
        output = output.replace(/(.*?)<\/b>/gi, '$1');
        output = output.replace(/(.*?)<\/i>/gi, '$1');
        output = output.replace(/\s{2,}/g, ' ').trim();
        return output;
      }
    }
  });
}));
<|endoftext|>


<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

const log = require('../../net2/logger.js')(__filename);
const cp = require('child_process');
const ipTool = require('ip');
const util = require('util');
const routing = require('../routing/routing.js');

const iptables = require('../../net2/Iptables.js');
const wrapIptables = iptables.wrapIptables;
const ipset = require('../../net2/Ipset.js');

const execAsync = util.promisify(cp.exec);

const VPN_CLIENT_RULE_TABLE_PREFIX = "vpn_client";

class VPNClientEnforcer {
  constructor() {
    return this;
  }

  _getRoutingTableName(vpnIntf) {
    return `${VPN_CLIENT_RULE_TABLE_PREFIX}_${vpnIntf}`;
  }

  async getRtId(vpnIntf) {
    const tableName = this._getRoutingTableName(vpnIntf);
    return await routing.createCustomizedRoutingTable(tableName).catch((err) => null);
  }

  async enforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -A FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to enforce strict vpn on ${vpnIntf}`, err);
    });
  }

  async unenforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -D FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to unenforce strict vpn on ${vpnIntf}`, err);
      throw err;
    });
  }

  async enforceVPNClientRoutes(remoteIP, vpnIntf, routedSubnets = [], overrideDefaultRoute = true) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    // ensure customized routing table is created
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // add policy based rule, the priority 6000 is a bit higher than the firerouter's application defined fwmark
    await routing.createPolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`);
    let cmd = "ip route list";
    if (overrideDefaultRoute)
      // do not copy default route from main routing table
      cmd = "ip route list | grep -v default";
    const routes = await execAsync(cmd);
    await Promise.all(routes.stdout.split('\n').map(async route => {
      if (route.length > 0) {
        cmd = util.format("sudo ip route add %s table %s", route, tableName);
        await execAsync(cmd).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
      }
    }));
    for (let routedSubnet of routedSubnets) {
      const cidr = ipTool.cidrSubnet(routedSubnet);
      // change subnet to ip route acceptable format
      const formattedSubnet = `${cidr.networkAddress}/${cidr.subnetMaskLength}`;
      await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, tableName).catch((err) => {});
    }
    if (overrideDefaultRoute) {
      // then add remote IP as gateway of default route to vpn client table
      await routing.addRouteToTable("default", remoteIP, vpnIntf, tableName).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
    }
  }

  async flushVPNClientRoutes(vpnIntf) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // remove policy based rule
    await routing.removePolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`);
  }

  _getVPNClientIPSetName(vpnIntf) {
    return `vpn_client_${vpnIntf}_set`;
  }

  async enforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // add to vpn client routing table
      if (remoteIP)
        await routing.addRouteToTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }

  async unenforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // remove from vpn client routing table
      if (remoteIP)
        await routing.removeRouteFromTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }
}

const instance = new VPNClientEnforcer();
module.exports = instance;<commit_msg>remove unnecessary routes from vpn client routing table on firerouter-managed platform<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

const log = require('../../net2/logger.js')(__filename);
const cp = require('child_process');
const ipTool = require('ip');
const util = require('util');
const routing = require('../routing/routing.js');

const iptables = require('../../net2/Iptables.js');
const wrapIptables = iptables.wrapIptables;
const ipset = require('../../net2/Ipset.js');
const platformLoader = require('../../platform/PlatformLoader.js');
const platform = platformLoader.getPlatform();

const execAsync = util.promisify(cp.exec);

const VPN_CLIENT_RULE_TABLE_PREFIX = "vpn_client";

class VPNClientEnforcer {
  constructor() {
    return this;
  }

  _getRoutingTableName(vpnIntf) {
    return `${VPN_CLIENT_RULE_TABLE_PREFIX}_${vpnIntf}`;
  }

  async getRtId(vpnIntf) {
    const tableName = this._getRoutingTableName(vpnIntf);
    return await routing.createCustomizedRoutingTable(tableName).catch((err) => null);
  }

  async enforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -A FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to enforce strict vpn on ${vpnIntf}`, err);
    });
  }

  async unenforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -D FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to unenforce strict vpn on ${vpnIntf}`, err);
      throw err;
    });
  }

  async enforceVPNClientRoutes(remoteIP, vpnIntf, routedSubnets = [], overrideDefaultRoute = true) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    // ensure customized routing table is created
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // add policy based rule, the priority 6000 is a bit higher than the firerouter's application defined fwmark
    await routing.createPolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`);
    if (platform.isFireRouterManaged()) {
      // on firerouter-managed platform, no need to copy main routing table to the vpn client routing table
      // but need to grant access to wan_routable table for packets from vpn interface
      await routing.createPolicyRoutingRule("all", vpnIntf, "wan_routable", 5000, null, 4);
    } else {
      // copy all routes from main routing table on non-firerouter-managed rules
      let cmd = "ip route list";
      if (overrideDefaultRoute)
        // do not copy default route from main routing table
        cmd = "ip route list | grep -v default";
      const routes = await execAsync(cmd);
      await Promise.all(routes.stdout.split('\n').map(async route => {
        if (route.length > 0) {
          cmd = util.format("sudo ip route add %s table %s", route, tableName);
          await execAsync(cmd).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
        }
      }));
    }
    for (let routedSubnet of routedSubnets) {
      const cidr = ipTool.cidrSubnet(routedSubnet);
      // change subnet to ip route acceptable format
      const formattedSubnet = `${cidr.networkAddress}/${cidr.subnetMaskLength}`;
      await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, tableName).catch((err) => {});
    }
    if (overrideDefaultRoute) {
      // then add remote IP as gateway of default route to vpn client table
      await routing.addRouteToTable("default", remoteIP, vpnIntf, tableName).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
    }
  }

  async flushVPNClientRoutes(vpnIntf) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // remove policy based rule
    await routing.removePolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`);
    await routing.removePolicyRoutingRule("all", vpnIntf, "wan_routable", 5000, null, 4);
  }

  _getVPNClientIPSetName(vpnIntf) {
    return `vpn_client_${vpnIntf}_set`;
  }

  async enforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // add to vpn client routing table
      if (remoteIP)
        await routing.addRouteToTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }

  async unenforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // remove from vpn client routing table
      if (remoteIP)
        await routing.removeRouteFromTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }
}

const instance = new VPNClientEnforcer();
module.exports = instance;<|endoftext|>


<commit_before>const Discord = require('discord.js');
module.exports = (message) => {
    const embed = new Discord.RichEmbed();
    embed.setAuthor(message.author.username,message.author.avatarURL)
    embed.setDescription(message.content);
    embed.setFooter(message.createdAt)
    embed.setTitle("Quote");
    return embed;
}
<commit_msg>Update message2embed.js<commit_after>const Discord = require('discord.js');
module.exports = (message) => {
    const embed = new Discord.RichEmbed();
    embed.setAuthor(message.author.username,message.author.avatarURL)
    embed.setDescription(message.content);
    embed.setFooter(message.createdAt)
    //embed.setTitle("Quote");
    return embed;
}
<|endoftext|>


<commit_before>const settings = require('./../../settings.json');
module.exports = message => {
    if (message.member && message.member != null) {
        let muted = message.member.roles.find(function (r) {
            if (r.name.toLowerCase().includes('mute') && !r.name.toLowerCase().includes('non') && !r.name.toLowerCase().includes('test')) return r
        })
        if (muted && message.deletable)
            message.delete();
    }
    if (!message.author.bot && settings.slowmov && message.channel.deletable && message.member.bannable)
        setTimeout(async function () {
            if (message.member.lastMessage && message.member.lastMessage.id != message.id) {
                let duration = 10000;
                if(settings.muted)
                {
                    let is_muted = settings.muted.find(function(t){
                        if((t.id == message.author.id) && (t.time > (Date.now() - 10000) ))
                            return t;
                    });
                    if(is_muted)
                        return;
                    is_muted = settings.muted.filter(function(t){
                        if((t.id == message.author.id) && (t.time > (Date.now() - 120000) ))
                            return t;
                    });
                    if(is_muted.length > 0)
                        duration = is_muted.size * duration;
                }
                message.channel.overwritePermissions(message.author, { 'SEND_MESSAGES': false }, "Muted");
                let mm = await message.reply("You have been muted for "+(duration/1000)+" seconds by slowmov for sending messages too fast. Please calm down");
                settings.muted.push({ id:message.author.id , time: Date.now()})
                setTimeout(() => {
                    mm.delete();
                    let p = message.channel.permissionOverwrites.get(message.author.id);
                    if (p)
                        p.delete();
                }, duration)
            }
        }, 2000)

}
<commit_msg>Update slowmov.js<commit_after>const settings = require('./../../settings.json');
module.exports = message => {
    if (message.member && message.member != null) {
        let muted = message.member.roles.find(function (r) {
            if (r.name.toLowerCase().includes('mute') && !r.name.toLowerCase().includes('non') && !r.name.toLowerCase().includes('test')) return r
        })
        if (muted && message.deletable)
            message.delete();
    }
    if (!message.author.bot && settings.slowmov && message.channel.deletable && message.member.bannable)
        setTimeout(async function () {
            if (message.member.lastMessage && message.member.lastMessage.id != message.id) {
                let duration = 10000;
                if(settings.muted)
                {
                    let is_muted = settings.muted.find(function(t){
                        if((t.id == message.author.id) && (t.time > (Date.now() - 10000) ))
                            return t;
                    });
                    if(is_muted)
                        return;
                    is_muted = settings.muted.filter(function(t){
                        if((t.id == message.author.id) && (t.time > (Date.now() - 900000) ))
                            return t;
                    });
                    if(is_muted.length > 0)
                        duration = is_muted.size * duration;
                }
                message.channel.overwritePermissions(message.author, { 'SEND_MESSAGES': false }, "Muted");
                let mm = await message.reply("You have been muted for "+(duration/1000)+" seconds by slowmov for sending messages too fast. Please calm down");
                settings.muted.push({ id:message.author.id , time: Date.now()})
                setTimeout(() => {
                    mm.delete();
                    let p = message.channel.permissionOverwrites.get(message.author.id);
                    if (p)
                        p.delete();
                }, duration)
            }
        }, 2000)

}
<|endoftext|>


<commit_before>/* eslint-disable no-underscore-dangle */

import {splitStringValue} from "./utils";

/**
 * Registers an event listener for the given events
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function(*):*} handler
 */
export function on (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = splitStringValue(type);

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.addEventListener(eventType, handler);

            if (typeof node._listeners === "undefined")
            {
                node._listeners = {};
            }

            if (typeof node._listeners[eventType] === "undefined")
            {
                node._listeners[eventType] = [];
            }

            node._listeners[eventType].push(handler);
        }
    }
}

/**
 * Removes an event listener for the given events
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function(*):*} handler
 */
export function off (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = splitStringValue(type);

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.removeEventListener(eventType, handler);

            if (typeof node._listeners !== "undefined" && typeof node._listeners[eventType] !== "undefined")
            {
                const index = node._listeners[eventType].indexOf(handler);

                if (-1 !== index)
                {
                    node._listeners[eventType].splice(index, 1);
                }
            }
        }
    }
}

/**
 * Registers an event listener, that is automatically removed after it was executed once.
 *
 * Returns the intermediate function, so that the event listener can be removed:
 *
 *      const intermediate = once(element, event, handler);
 *      off(element, event, intermediate);
 *
 * @param {HTMLElement} element
 * @param {string} type
 * @param {function(*):*} handler
 * @return {function():*}
 */
export function once (element, type, handler)
{
    const intermediate = (...args) => {
        handler(...args);
        off(element, type, intermediate);
    };
    on(element, type, intermediate);

    return intermediate;
}

/**
 * Registers an delegated event listener, that reacts to events thrown on specific child elements.
 *
 * Returns the intermediate function, so that the event listener can be removed:
 *
 *      const intermediate = once(element, event, handler);
 *      off(element, event, intermediate);
 *
 * @param {HTMLElement} element
 * @param {string} selector
 * @param {string} type
 * @param {function(*):*} handler
 * @return {function():*}
 */
export function live (element, selector, type, handler)
{
    const intermediate = (event) => {
        if (event.target.matches(selector))
        {
            handler(event);
        }
    };
    on(element, type, intermediate);

    return intermediate;
}

/**
 * Dispatches an event
 *
 * @param {HTMLElement} element
 * @param {string} type
 * @param {*} data
 */
export function trigger (element, type, data = null)
{
    const event = createEvent(type, {
        bubbles: true,
        cancelable: true,
        preventDefault: false,
        detail: data,
    });

    element.dispatchEvent(event);
}

/**
 * Creates an event
 *
 * @private
 * @param {string} type
 * @param {CustomEventInit|{bubbles: boolean, cancelable: boolean, preventDefault: boolean, detail: *}} args
 * @return {CustomEvent}
 */
function createEvent (type, args)
{
    // @legacy IE <= 11 doesn't support the global CustomEvent
    if (typeof window.CustomEvent !== "function")
    {
        /** @type {CustomEvent|Event} event */
        const event = document.createEvent('CustomEvent');
        event.initCustomEvent(type, args.bubbles, args.cancelable, args.detail);
        return event;
    }

    return new CustomEvent(type, args);
}
<commit_msg>Even more doc fixes<commit_after>/* eslint-disable no-underscore-dangle */

import {splitStringValue} from "./utils";

/**
 * Registers an event listener for the given events
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function(*):*} handler
 */
export function on (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = splitStringValue(type);

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.addEventListener(eventType, handler);

            if (typeof node._listeners === "undefined")
            {
                node._listeners = {};
            }

            if (typeof node._listeners[eventType] === "undefined")
            {
                node._listeners[eventType] = [];
            }

            node._listeners[eventType].push(handler);
        }
    }
}

/**
 * Removes an event listener for the given events
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function(*):*} handler
 */
export function off (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = splitStringValue(type);

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.removeEventListener(eventType, handler);

            if (typeof node._listeners !== "undefined" && typeof node._listeners[eventType] !== "undefined")
            {
                const index = node._listeners[eventType].indexOf(handler);

                if (-1 !== index)
                {
                    node._listeners[eventType].splice(index, 1);
                }
            }
        }
    }
}

/**
 * Registers an event listener, that is automatically removed after it was executed once.
 *
 * Returns the intermediate function, so that the event listener can be removed:
 *
 *      const intermediate = once(element, event, handler);
 *      off(element, event, intermediate);
 *
 * @param {HTMLElement} element
 * @param {string} type
 * @param {function(*):*} handler
 * @return {function():*}
 */
export function once (element, type, handler)
{
    const intermediate = (...args) => {
        handler(...args);
        off(element, type, intermediate);
    };
    on(element, type, intermediate);

    return intermediate;
}

/**
 * Registers an delegated event listener, that reacts to events thrown on specific child elements.
 *
 * Returns the intermediate function, so that the event listener can be removed:
 *
 *      const intermediate = live(element, selector, type, handler);
 *      off(element, event, intermediate);
 *
 * @param {HTMLElement} element
 * @param {string} selector
 * @param {string} type
 * @param {function(*):*} handler
 * @return {function():*}
 */
export function live (element, selector, type, handler)
{
    const intermediate = (event) => {
        if (event.target.matches(selector))
        {
            handler(event);
        }
    };
    on(element, type, intermediate);

    return intermediate;
}

/**
 * Dispatches an event
 *
 * @param {HTMLElement} element
 * @param {string} type
 * @param {*} data
 */
export function trigger (element, type, data = null)
{
    const event = createEvent(type, {
        bubbles: true,
        cancelable: true,
        preventDefault: false,
        detail: data,
    });

    element.dispatchEvent(event);
}

/**
 * Creates an event
 *
 * @private
 * @param {string} type
 * @param {CustomEventInit|{bubbles: boolean, cancelable: boolean, preventDefault: boolean, detail: *}} args
 * @return {CustomEvent}
 */
function createEvent (type, args)
{
    // @legacy IE <= 11 doesn't support the global CustomEvent
    if (typeof window.CustomEvent !== "function")
    {
        /** @type {CustomEvent|Event} event */
        const event = document.createEvent('CustomEvent');
        event.initCustomEvent(type, args.bubbles, args.cancelable, args.detail);
        return event;
    }

    return new CustomEvent(type, args);
}
<|endoftext|>


<commit_before>var ram = require('./memory.js')
var what_max_length = 32

var get_kudos = () => {
	var kudos;
	kudos = ram.get('kudos');
	if (typeof kudos == 'undefined') kudos = {};
	return kudos;
}

var best_and_worst = () => {
	var kudos = get_kudos();
	var sordid = [];
	for (var word in kudos) sordid.push([word, kudos[word]]);
	kudos = sordid.sort(function(ass, bean) {
		return bean[1] - ass[1];
	});
	var THE_TRUTH = [];
	var tops = kudos.slice(0, 10);
	tops.forEach((ass) => {
		THE_TRUTH.push(ass[0] + ' ' + ass[1]);
	});
	var bottoms = kudos.slice(-10);
	bottoms.forEach((ass) => {
		THE_TRUTH.push(ass[0] + ' ' + ass[1]);;
	});
	return THE_TRUTH.join('  •  ');
}

var process = (words, operator) => {
	// build what
	var what = words.join(' ')
	// shave operator off
	what = what.substr(0, what.length - 2)
	// remove dicksword identifier and trim
	what = what.replace(/\s*\[[^)]*\]\s*/, '').trim()
	// check what length
	if (what.length > what_max_length)
		return `kudos max length : ${what_max_length}`
	var display = what
	what = what.toLowerCase()
	// special cases? :shrug:
	if (what == '56') return `56 has 56 kudos`;
	// load kudos object from memory or setup
	kudos = get_kudos();
	if (typeof kudos[what] == 'undefined') kudos[what] = 0
	// operate on what
	if (operator == 'minus') {
		kudos[what]--
	} else if (operator == 'plus') {
		kudos[what]++
	}
	// save and return
	ram.set('kudos', kudos)
	return `${display} has ${kudos[what]} kudos`
}

module.exports = {

	info: words => {
		words.shift()
		what = words.join(' ').trim()
		if (what == '') {
			return best_and_worst();
		}
		console.log(what)
		kudos = ram.get('kudos')
		if (typeof kudos[what] == 'undefined')
			return `${what} has no <3`
		return `${what} has ${kudos[what]} kudos`
	},

	minus: words => process(words, 'minus'),
	plus: words => process(words, 'plus')

}
<commit_msg>closes #78 kudos memory case insensitive<commit_after>var ram = require('./memory.js')
var what_max_length = 32

var get_kudos = () => {
	var kudos;
	kudos = ram.get('kudos');
	if (typeof kudos == 'undefined') kudos = {};
	return kudos;
}

var best_and_worst = () => {
	var kudos = get_kudos();
	var sordid = [];
	for (var word in kudos) sordid.push([word, kudos[word]]);
	kudos = sordid.sort(function(ass, bean) {
		return bean[1] - ass[1];
	});
	var THE_TRUTH = [];
	var tops = kudos.slice(0, 10);
	tops.forEach((ass) => {
		THE_TRUTH.push(ass[0] + ' ' + ass[1]);
	});
	var bottoms = kudos.slice(-10);
	bottoms.forEach((ass) => {
		THE_TRUTH.push(ass[0] + ' ' + ass[1]);;
	});
	return THE_TRUTH.join('  •  ');
}

var process = (words, operator) => {
	// build what
	var what = words.join(' ')
	// shave operator off
	what = what.substr(0, what.length - 2)
	// remove dicksword identifier and trim
	what = what.replace(/\s*\[[^)]*\]\s*/, '').trim()
	// check what length
	if (what.length > what_max_length)
		return `kudos max length : ${what_max_length}`
	var display = what
	what = what.toLowerCase()
	// special cases? :shrug:
	if (what == '56') return `56 has 56 kudos`;
	// load kudos object from memory or setup
	kudos = get_kudos();
	if (typeof kudos[what] == 'undefined') kudos[what] = 0
	// operate on what
	if (operator == 'minus') {
		kudos[what]--
	} else if (operator == 'plus') {
		kudos[what]++
	}
	// save and return
	ram.set('kudos', kudos)
	return `${display} has ${kudos[what]} kudos`
}

module.exports = {

	info: words => {
		words.shift()
		what = words.join(' ').trim()
		if (what == '') {
			return best_and_worst();
		}
		console.log(what)
		kudos = ram.get('kudos')
		for (let prop in kudos) if (prop.toLowerCase() == what.toLowerCase()) {
			return `${prop} has ${kudos[prop]} kudos`;
		}
		return `${what} has no <3`
	},

	minus: words => process(words, 'minus'),
	plus: words => process(words, 'plus')

}
<|endoftext|>


<commit_before><commit_msg>time -> seed<commit_after><|endoftext|>