<commit_before>import $ from 'dom7';
import Utils from '../../utils/utils';
import Framework7Class from '../../utils/class';

const textEditorButtonsMap = {
  // f7-icon, material-icon, command
  bold: ['bold', 'format_bold', 'bold'],
  italic: ['italic', 'format_italic', 'italic'],
  underline: ['underline', 'format_underline', 'underline'],
  strikeThrough: ['strikethrough', 'strikethrough_s', 'strikeThrough'],
  orderedList: ['list_number', 'format_list_numbered', 'insertOrderedList'],
  unorderedList: ['list_bullet', 'format_list_bulleted', 'insertUnorderedList'],
  link: ['link', 'link', 'createLink'],
  image: ['photo', 'image', 'insertImage'],
  paragraph: ['paragraph', '<i class="icon">¶</i>', 'formatBlock.P'],
  h1: ['<i class="icon">H<sub>1</sub></i>', '<i class="icon">H<sub>1</sub></i>', 'formatBlock.H1'],
  h2: ['<i class="icon">H<sub>2</sub></i>', '<i class="icon">H<sub>2</sub></i>', 'formatBlock.H2'],
  h3: ['<i class="icon">H<sub>3</sub></i>', '<i class="icon">H<sub>3</sub></i>', 'formatBlock.H3'],
  alignLeft: ['text_alignleft', 'format_align_left', 'justifyLeft'],
  alignCenter: ['text_aligncenter', 'format_align_center', 'justifyCenter'],
  alignRight: ['text_alignright', 'format_align_right', 'justifyRight'],
  alignJustify: ['text_justify', 'format_align_justify', 'justifyFull'],
  subscript: ['textformat_subscript', '<i class="icon">A<sub>1</sub></i>', 'subscript'],
  superscript: ['textformat_superscript', '<i class="icon">A<sup>1</sup></i>', 'superscript'],
  indent: ['increase_indent', 'format_indent_increase', 'indent'],
  outdent: ['decrease_indent', 'format_indent_decrease', 'outdent'],
};

class TextEditor extends Framework7Class {
  constructor(app, params) {
    super(params, [app]);
    const self = this;

    const defaults = Utils.extend({}, app.params.textEditor);

    // Extend defaults with modules params
    self.useModulesParams(defaults);

    self.params = Utils.extend(defaults, params);

    const el = self.params.el;
    if (!el) return self;

    const $el = $(el);
    if ($el.length === 0) return self;

    if ($el[0].f7TextEditor) return $el[0].f7TextEditor;

    let $contentEl = $el.children('.text-editor-content');
    if (!$contentEl.length) {
      $el.append('<div class="text-editor-content" contenteditable></div>');
      $contentEl = $el.children('.text-editor-content');
    }

    Utils.extend(self, {
      app,
      $el,
      el: $el[0],
      $contentEl,
      contentEl: $contentEl[0],
    });
    if ('value' in params) {
      self.value = self.params.value;
    }

    if (self.params.mode === 'keyboard-toolbar') {
      if (!app.device.cordova && !app.device.android) {
        self.params.mode = 'popover';
      }
    }

    if (typeof self.params.buttons === 'string') {
      try {
        self.params.buttons = JSON.parse(self.params.buttons);
      } catch (err) {
        throw new Error('Framework7: TextEditor: wrong "buttons" parameter format');
      }
    }

    $el[0].f7TextEditor = self;

    // Bind
    self.onButtonClick = self.onButtonClick.bind(self);
    self.onFocus = self.onFocus.bind(self);
    self.onBlur = self.onBlur.bind(self);
    self.onInput = self.onInput.bind(self);
    self.onPaste = self.onPaste.bind(self);
    self.onSelectionChange = self.onSelectionChange.bind(self);

    // Handle Events
    self.attachEvents = function attachEvents() {
      if (self.params.mode === 'toolbar') {
        self.$el.find('.text-editor-toolbar').on('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'keyboard-toolbar') {
        self.$keyboardToolbarEl.on('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'popover' && self.popover) {
        self.popover.$el.on('click', 'button', self.onButtonClick);
      }
      self.$contentEl.on('paste', self.onPaste);
      self.$contentEl.on('focus', self.onFocus);
      self.$contentEl.on('blur', self.onBlur);
      self.$contentEl.on('input', self.onInput, true);
      $(document).on('selectionchange', self.onSelectionChange);
    };
    self.detachEvents = function detachEvents() {
      if (self.params.mode === 'toolbar') {
        self.$el.find('.text-editor-toolbar').off('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'keyboard-toolbar') {
        self.$keyboardToolbarEl.off('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'popover' && self.popover) {
        self.popover.$el.off('click', 'button', self.onButtonClick);
      }
      self.$contentEl.off('paste', self.onPaste);
      self.$contentEl.off('focus', self.onFocus);
      self.$contentEl.off('blur', self.onBlur);
      self.$contentEl.off('input', self.onInput, true);
      $(document).off('selectionchange', self.onSelectionChange);
    };

    // Install Modules
    self.useModules();

    // Init
    self.init();

    return self;
  }

  setValue(newValue) {
    const self = this;
    const currentValue = self.value;
    if (currentValue === newValue) return self;
    self.value = newValue;
    self.$contentEl.html(newValue);
    self.$el.trigger('texteditor:change', self.value);
    self.emit('local::change textEditorChange', self, self.value);
    return self;
  }

  getValue() {
    const self = this;
    return self.value;
  }

  createLink() {
    const self = this;
    const currentSelection = window.getSelection();
    const selectedNodes = [];
    let $selectedLinks;
    if (currentSelection && currentSelection.anchorNode && $(currentSelection.anchorNode).parents(self.$el).length) {
      let anchorNode = currentSelection.anchorNode;
      while (anchorNode) {
        selectedNodes.push(anchorNode);
        if (!anchorNode.nextSibling || anchorNode === currentSelection.focusNode) {
          anchorNode = null;
        }
        if (anchorNode) {
          anchorNode = anchorNode.nextSibling;
        }
      }
      $selectedLinks = $(selectedNodes).closest('a').add($(selectedNodes).children('a'));
    }
    if ($selectedLinks && $selectedLinks.length) {
      $selectedLinks.each((linkIndex, linkNode) => {
        const selection = window.getSelection();
        const range = document.createRange();
        range.selectNodeContents(linkNode);
        selection.removeAllRanges();
        selection.addRange(range);
        document.execCommand('unlink', false);
        selection.removeAllRanges();
      });
      return self;
    }
    const currentRange = self.get();
    if (!currentRange) return self;
    const dialog = self.app.dialog.prompt(self.params.linkUrlText, '', (link) => {
      if (link && link.trim().length) {
        self.setSelectionRange(currentRange);
        document.execCommand('createLink', false, link.trim());
      }
    });
    dialog.$el.find('input').focus();
    return self;
  }

  insertImage() {
    const self = this;
    const currentRange = self.get();<commit_msg>v5: Core: Text Editor - fix wrong method reference<commit_after>import $ from 'dom7';
import Utils from '../../utils/utils';
import Framework7Class from '../../utils/class';

const textEditorButtonsMap = {
  // f7-icon, material-icon, command
  bold: ['bold', 'format_bold', 'bold'],
  italic: ['italic', 'format_italic', 'italic'],
  underline: ['underline', 'format_underline', 'underline'],
  strikeThrough: ['strikethrough', 'strikethrough_s', 'strikeThrough'],
  orderedList: ['list_number', 'format_list_numbered', 'insertOrderedList'],
  unorderedList: ['list_bullet', 'format_list_bulleted', 'insertUnorderedList'],
  link: ['link', 'link', 'createLink'],
  image: ['photo', 'image', 'insertImage'],
  paragraph: ['paragraph', '<i class="icon">¶</i>', 'formatBlock.P'],
  h1: ['<i class="icon">H<sub>1</sub></i>', '<i class="icon">H<sub>1</sub></i>', 'formatBlock.H1'],
  h2: ['<i class="icon">H<sub>2</sub></i>', '<i class="icon">H<sub>2</sub></i>', 'formatBlock.H2'],
  h3: ['<i class="icon">H<sub>3</sub></i>', '<i class="icon">H<sub>3</sub></i>', 'formatBlock.H3'],
  alignLeft: ['text_alignleft', 'format_align_left', 'justifyLeft'],
  alignCenter: ['text_aligncenter', 'format_align_center', 'justifyCenter'],
  alignRight: ['text_alignright', 'format_align_right', 'justifyRight'],
  alignJustify: ['text_justify', 'format_align_justify', 'justifyFull'],
  subscript: ['textformat_subscript', '<i class="icon">A<sub>1</sub></i>', 'subscript'],
  superscript: ['textformat_superscript', '<i class="icon">A<sup>1</sup></i>', 'superscript'],
  indent: ['increase_indent', 'format_indent_increase', 'indent'],
  outdent: ['decrease_indent', 'format_indent_decrease', 'outdent'],
};

class TextEditor extends Framework7Class {
  constructor(app, params) {
    super(params, [app]);
    const self = this;

    const defaults = Utils.extend({}, app.params.textEditor);

    // Extend defaults with modules params
    self.useModulesParams(defaults);

    self.params = Utils.extend(defaults, params);

    const el = self.params.el;
    if (!el) return self;

    const $el = $(el);
    if ($el.length === 0) return self;

    if ($el[0].f7TextEditor) return $el[0].f7TextEditor;

    let $contentEl = $el.children('.text-editor-content');
    if (!$contentEl.length) {
      $el.append('<div class="text-editor-content" contenteditable></div>');
      $contentEl = $el.children('.text-editor-content');
    }

    Utils.extend(self, {
      app,
      $el,
      el: $el[0],
      $contentEl,
      contentEl: $contentEl[0],
    });
    if ('value' in params) {
      self.value = self.params.value;
    }

    if (self.params.mode === 'keyboard-toolbar') {
      if (!app.device.cordova && !app.device.android) {
        self.params.mode = 'popover';
      }
    }

    if (typeof self.params.buttons === 'string') {
      try {
        self.params.buttons = JSON.parse(self.params.buttons);
      } catch (err) {
        throw new Error('Framework7: TextEditor: wrong "buttons" parameter format');
      }
    }

    $el[0].f7TextEditor = self;

    // Bind
    self.onButtonClick = self.onButtonClick.bind(self);
    self.onFocus = self.onFocus.bind(self);
    self.onBlur = self.onBlur.bind(self);
    self.onInput = self.onInput.bind(self);
    self.onPaste = self.onPaste.bind(self);
    self.onSelectionChange = self.onSelectionChange.bind(self);

    // Handle Events
    self.attachEvents = function attachEvents() {
      if (self.params.mode === 'toolbar') {
        self.$el.find('.text-editor-toolbar').on('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'keyboard-toolbar') {
        self.$keyboardToolbarEl.on('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'popover' && self.popover) {
        self.popover.$el.on('click', 'button', self.onButtonClick);
      }
      self.$contentEl.on('paste', self.onPaste);
      self.$contentEl.on('focus', self.onFocus);
      self.$contentEl.on('blur', self.onBlur);
      self.$contentEl.on('input', self.onInput, true);
      $(document).on('selectionchange', self.onSelectionChange);
    };
    self.detachEvents = function detachEvents() {
      if (self.params.mode === 'toolbar') {
        self.$el.find('.text-editor-toolbar').off('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'keyboard-toolbar') {
        self.$keyboardToolbarEl.off('click', 'button', self.onButtonClick);
      }
      if (self.params.mode === 'popover' && self.popover) {
        self.popover.$el.off('click', 'button', self.onButtonClick);
      }
      self.$contentEl.off('paste', self.onPaste);
      self.$contentEl.off('focus', self.onFocus);
      self.$contentEl.off('blur', self.onBlur);
      self.$contentEl.off('input', self.onInput, true);
      $(document).off('selectionchange', self.onSelectionChange);
    };

    // Install Modules
    self.useModules();

    // Init
    self.init();

    return self;
  }

  setValue(newValue) {
    const self = this;
    const currentValue = self.value;
    if (currentValue === newValue) return self;
    self.value = newValue;
    self.$contentEl.html(newValue);
    self.$el.trigger('texteditor:change', self.value);
    self.emit('local::change textEditorChange', self, self.value);
    return self;
  }

  getValue() {
    const self = this;
    return self.value;
  }

  createLink() {
    const self = this;
    const currentSelection = window.getSelection();
    const selectedNodes = [];
    let $selectedLinks;
    if (currentSelection && currentSelection.anchorNode && $(currentSelection.anchorNode).parents(self.$el).length) {
      let anchorNode = currentSelection.anchorNode;
      while (anchorNode) {
        selectedNodes.push(anchorNode);
        if (!anchorNode.nextSibling || anchorNode === currentSelection.focusNode) {
          anchorNode = null;
        }
        if (anchorNode) {
          anchorNode = anchorNode.nextSibling;
        }
      }
      $selectedLinks = $(selectedNodes).closest('a').add($(selectedNodes).children('a'));
    }
    if ($selectedLinks && $selectedLinks.length) {
      $selectedLinks.each((linkIndex, linkNode) => {
        const selection = window.getSelection();
        const range = document.createRange();
        range.selectNodeContents(linkNode);
        selection.removeAllRanges();
        selection.addRange(range);
        document.execCommand('unlink', false);
        selection.removeAllRanges();
      });
      return self;
    }
    const currentRange = self.getSelectionRange();
    if (!currentRange) return self;
    const dialog = self.app.dialog.prompt(self.params.linkUrlText, '', (link) => {
      if (link && link.trim().length) {
        self.setSelectionRange(currentRange);
        document.execCommand('createLink', false, link.trim());
      }
    });
    dialog.$el.find('input').focus();
    return self;
  }

  insertImage() {
    const self = this;
    const currentRange = self.getSelectionRange();
{'fraction_non_alphanumeric': 0.17835815526296855, 'fraction_numerical': 0.004101014461472048, 'mean_word_length': 3.4071020925808497, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3134013', 'n_tokens_mistral': 4434, 'n_tokens_neox': 4393, 'n_words': 798}