Beispiel #1
0
/**
 * Gets the innerText of the specified element. It will handle edge cases
 * and works better than textContent on Gecko.
 *
 * @param {String} html HTML string to get text from.
 * @return {String} String of text with line feeds.
 */
function innerText(html: string) {
  const schema = Schema();
  const domParser = DomParser({}, schema);
  let text = '';
  const shortEndedElements = schema.getShortEndedElements();
  const ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ');
  const blockElements = schema.getBlockElements();

  function walk(node) {
    const name = node.name, currentNode = node;

    if (name === 'br') {
      text += '\n';
      return;
    }

    // img/input/hr
    if (shortEndedElements[name]) {
      text += ' ';
    }

    // Ingore script, video contents
    if (ignoreElements[name]) {
      text += ' ';
      return;
    }

    if (node.type === 3) {
      text += node.value;
    }

    // Walk all children
    if (!node.shortEnded) {
      if ((node = node.firstChild)) {
        do {
          walk(node);
        } while ((node = node.next));
      }
    }

    // Add \n or \n\n for blocks or P
    if (blockElements[name] && currentNode.next) {
      text += '\n';

      if (name === 'p') {
        text += '\n';
      }
    }
  }

  html = filter(html, [
    /<!\[[^\]]+\]>/g // Conditional comments
  ]);

  walk(domParser.parse(html));

  return text;
}
Beispiel #2
0
const defaultConvertSelectorToFormat = function (editor, selectorText) {
  let format;

  // Parse simple element.class1, .class1
  const selector = /^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(selectorText);
  if (!selector) {
    return;
  }

  const elementName = selector[1];
  const classes = selector[2].substr(1).split('.').join(' ');
  const inlineSelectorElements = Tools.makeMap('a,img');

  // element.class - Produce block formats
  if (selector[1]) {
    format = {
      title: selectorText
    };

    if (editor.schema.getTextBlockElements()[elementName]) {
      // Text block format ex: h1.class1
      format.block = elementName;
    } else if (editor.schema.getBlockElements()[elementName] || inlineSelectorElements[elementName.toLowerCase()]) {
      // Block elements such as table.class and special inline elements such as a.class or img.class
      format.selector = elementName;
    } else {
      // Inline format strong.class1
      format.inline = elementName;
    }
  } else if (selector[2]) {
    // .class - Produce inline span with classes
    format = {
      inline: 'span',
      title: selectorText.substr(1),
      classes
    };
  }

  // Append to or override class attribute
  if (Settings.shouldMergeClasses(editor) !== false) {
    format.classes = classes;
  } else {
    format.attributes = { class: classes };
  }

  return format;
};
Beispiel #3
0
const createMenuButtons = function (editor) {
  const menuButtons = [];
  const defaultMenuBar = getDefaultMenubar(editor);
  const removedMenuItems = Tools.makeMap(Settings.getRemovedMenuItems(editor).split(/[ ,]/));

  const menubar = Settings.getMenubar(editor);
  const enabledMenuNames = typeof menubar === 'string' ? menubar.split(/[ ,]/) : defaultMenuBar;
  for (let i = 0; i < enabledMenuNames.length; i++) {
    const menuItems = enabledMenuNames[i];
    const menu = createMenu(editor.menuItems, Settings.getMenu(editor), removedMenuItems, menuItems);
    if (menu) {
      menuButtons.push(menu);
    }
  }

  return menuButtons;
};
Beispiel #4
0
const filterWordContent = function (editor: Editor, content: string) {
  let retainStyleProperties, validStyles;

  retainStyleProperties = Settings.getRetainStyleProps(editor);
  if (retainStyleProperties) {
    validStyles = Tools.makeMap(retainStyleProperties.split(/[, ]/));
  }

  // Remove basic Word junk
  content = Utils.filter(content, [
    // Remove apple new line markers
    /<br class="?Apple-interchange-newline"?>/gi,

    // Remove google docs internal guid markers
    /<b[^>]+id="?docs-internal-[^>]*>/gi,

    // Word comments like conditional comments etc
    /<!--[\s\S]+?-->/gi,

    // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
    // MS Office namespaced tags, and a few other tags
    /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,

    // Convert <s> into <strike> for line-though
    [/<(\/?)s>/gi, '<$1strike>'],

    // Replace nsbp entites to char since it's easier to handle
    [/&nbsp;/gi, '\u00a0'],

    // Convert <span style="mso-spacerun:yes">___</span> to string of alternating
    // breaking/non-breaking spaces of same length
    [/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi,
      function (str, spaces) {
        return (spaces.length > 0) ?
          spaces.replace(/./, ' ').slice(Math.floor(spaces.length / 2)).split('').join('\u00a0') : '';
      }
    ]
  ]);

  const validElements = Settings.getWordValidElements(editor);

  // Setup strict schema
  const schema = Schema({
    valid_elements: validElements,
    valid_children: '-li[p]'
  });

  // Add style/class attribute to all element rules since the user might have removed them from
  // paste_word_valid_elements config option and we need to check them for properties
  Tools.each(schema.elements, function (rule) {
    /*eslint dot-notation:0*/
    if (!rule.attributes.class) {
      rule.attributes.class = {};
      rule.attributesOrder.push('class');
    }

    if (!rule.attributes.style) {
      rule.attributes.style = {};
      rule.attributesOrder.push('style');
    }
  });

  // Parse HTML into DOM structure
  const domParser = DomParser({}, schema);

  // Filter styles to remove "mso" specific styles and convert some of them
  domParser.addAttributeFilter('style', function (nodes) {
    let i = nodes.length, node;

    while (i--) {
      node = nodes[i];
      node.attr('style', filterStyles(editor, validStyles, node, node.attr('style')));

      // Remove pointess spans
      if (node.name === 'span' && node.parent && !node.attributes.length) {
        node.unwrap();
      }
    }
  });

  // Check the class attribute for comments or del items and remove those
  domParser.addAttributeFilter('class', function (nodes) {
    let i = nodes.length, node, className;

    while (i--) {
      node = nodes[i];

      className = node.attr('class');
      if (/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(className)) {
        node.remove();
      }

      node.attr('class', null);
    }
  });

  // Remove all del elements since we don't want the track changes code in the editor
  domParser.addNodeFilter('del', function (nodes) {
    let i = nodes.length;

    while (i--) {
      nodes[i].remove();
    }
  });

  // Keep some of the links and anchors
  domParser.addNodeFilter('a', function (nodes) {
    let i = nodes.length, node, href, name;

    while (i--) {
      node = nodes[i];
      href = node.attr('href');
      name = node.attr('name');

      if (href && href.indexOf('#_msocom_') !== -1) {
        node.remove();
        continue;
      }

      if (href && href.indexOf('file://') === 0) {
        href = href.split('#')[1];
        if (href) {
          href = '#' + href;
        }
      }

      if (!href && !name) {
        node.unwrap();
      } else {
        // Remove all named anchors that aren't specific to TOC, Footnotes or Endnotes
        if (name && !/^_?(?:toc|edn|ftn)/i.test(name)) {
          node.unwrap();
          continue;
        }

        node.attr({
          href,
          name
        });
      }
    }
  });

  // Parse into DOM structure
  const rootNode = domParser.parse(content);

  // Process DOM
  if (Settings.shouldConvertWordFakeLists(editor)) {
    convertFakeListsToProperLists(rootNode);
  }

  // Serialize DOM back to HTML
  content = Serializer({
    validate: editor.settings.validate
  }, schema).serialize(rootNode);

  return content;
};
Beispiel #5
0
  /**
   * Constructs a new control instance with the specified settings.
   *
   * @constructor
   * @param {Object} settings Name/value object with settings.
   */
  init (settings) {
    const self = this, editor = getActiveEditor(), editorSettings = editor.settings;
    let actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;
    const fileType = settings.filetype;

    settings.spellcheck = false;

    fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
    if (fileBrowserCallbackTypes) {
      fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
    }

    if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[fileType]) {
      fileBrowserCallback = editorSettings.file_picker_callback;
      if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[fileType])) {
        actionCallback = function () {
          let meta = self.fire('beforecall').meta;

          meta = Tools.extend({ filetype: fileType }, meta);

          // file_picker_callback(callback, currentValue, metaData)
          fileBrowserCallback.call(
            editor,
            function (value, meta) {
              self.value(value).fire('change', { meta });