Example #1
0
  return function (editor, shiftKey) {
    const isMatch = Arr.foldl(predicates, function (res, p) {
      return res && p(editor, shiftKey);
    }, true);

    return isMatch ? Option.some(action) : Option.none();
  };
Example #2
0
const generateContextMenu = (contextMenus: Record<string, Menu.ContextMenuApi>, menuConfig: string[], selectedElement: Element) => {
  const items = Arr.foldl(menuConfig, (acc, name) => {
    // Either read and convert the list of items out of the plugin, or assume it's a standard menu item reference
    if (Obj.has(contextMenus, name)) {
      const items = contextMenus[name].update(selectedElement);
      if (Type.isString(items)) {
        return addContextMenuGroup(acc, items.split(' '));
      } else if (items.length > 0) {
        // TODO: Should we add a ValueSchema check here?
        const allItems = Arr.map(items, makeContextItem);
        return addContextMenuGroup(acc, allItems);
      } else {
        return acc;
      }
    } else {
      return acc.concat([name]);
    }
  }, []);

  // Strip off any trailing separator
  if (items.length > 0 && isSeparator(items[items.length - 1])) {
    items.pop();
  }

  return items;
};
Example #3
0
const unwrapReferences = (items: Array<string | SingleMenuItemApi>, menuItems: MenuItemRegistry): SingleMenuItemApi[] => {
  // Unwrap any string based menu item references
  const realItems = Arr.foldl(items, (acc, item) => {
    if (isMenuItemReference(item)) {
      if (item === '') {
        return acc;
      } else if (item === '|') {
        // Ignore the separator if it's at the start or a duplicate
        return acc.length > 0 && !isSeparator(acc[acc.length - 1]) ? acc.concat([separator]) : acc;
      } else if (Obj.has(menuItems, item.toLowerCase())) {
        return acc.concat([ menuItems[item.toLowerCase()] ]);
      } else {
        // TODO: Add back after TINY-3232 is implemented
        // console.error('No representation for menuItem: ' + item);
        return acc;
      }
    } else {
      return acc.concat([ item ]);
    }
  }, []);

  // Remove any trailing separators
  if (realItems.length > 0 && isSeparator(realItems[realItems.length - 1])) {
    realItems.pop();
  }

  return realItems;
};
Example #4
0
const composeList = (scope: Document, entries: Entry[]): Option<Element> => {
  const cast: Segment[] = Arr.foldl(entries, (cast, entry) => {
    return entry.depth > cast.length ? writeDeep(scope, cast, entry) : writeShallow(scope, cast, entry);
  }, []);

  return Arr.head(cast).map((segment) => segment.list);
};
Example #5
0
const wrap = function (innerElm, elms) {
  const wrapped = Arr.foldl(elms, function (acc, elm) {
    Insert.append(elm, acc);
    return elm;
  }, innerElm);
  return elms.length > 0 ? Fragment.fromElements([wrapped]) : wrapped;
};
Example #6
0
const isXYWithinRange = function (clientX, clientY, range) {
  if (range.collapsed) {
    return false;
  }

  return Arr.foldl(range.getClientRects(), function (state, rect) {
    return state || containsXY(rect, clientX, clientY);
  }, false);
};
Example #7
0
const isAtomicContentEditableFalse = (node: Node): boolean => {
  if (!isNonUiContentEditableFalse(node)) {
    return false;
  }

  return Arr.foldl(Arr.from(node.getElementsByTagName('*')), function (result, elm) {
    return result || isContentEditableTrue(elm);
  }, false) !== true;
};
Example #8
0
const getLinkAttrs = (data: LinkDialogOutput): Record<string, string> => {
  return Arr.foldl([ 'title', 'rel', 'class', 'target' ], (acc, key) => {
    data[key].each((value) => {
      // If dealing with an empty string, then treat that as being null so the attribute is removed
      acc[key] = value.length > 0 ? value : null;
    });
    return acc;
  }, {
    href: data.href
  });
};
Example #9
0
const findClosestCorner = (corners: Corner[], x: number, y: number): Option<Corner> => {
  return Arr.foldl(corners, (acc, newCorner) => {
    return acc.fold(
      () => Option.some(newCorner),
      (oldCorner) => {
        const oldDist = Math.sqrt(Math.abs(oldCorner.x - x) + Math.abs(oldCorner.y - y));
        const newDist = Math.sqrt(Math.abs(newCorner.x - x) + Math.abs(newCorner.y - y));
        return Option.some(newDist < oldDist ? newCorner : oldCorner);
      }
    );
  }, Option.none());
};
Example #10
0
const resolvePath = (root: Node, path: number[]): Option<{node: Text, offset: number}> => {
  const nodePath = path.slice();
  const offset = nodePath.pop();
  return Arr.foldl(nodePath, (optNode: Option<Node>, index: number) => {
    return optNode.bind((node) => Option.from(node.childNodes[index]));
  }, Option.some(root)).bind((node) => {
    if (isText(node) && offset >= 0 && offset <= node.data.length) {
      return Option.some({node, offset});
    }
    return Option.none();
  });
};
Example #11
0
const findClosestHorizontalPositionFromPoint = (positions: CaretPosition[], x: number): Option<CaretPosition> => {
  return Arr.foldl(positions, (acc, newPos) => {
    return acc.fold(
      () => Option.some(newPos),
      (lastPos) => {
        return Options.liftN([Arr.head(lastPos.getClientRects()), Arr.head(newPos.getClientRects())], (lastRect, newRect) => {
          const lastDist = Math.abs(x - lastRect.left);
          const newDist = Math.abs(x - newRect.left);
          return newDist <= lastDist ? newPos : lastPos;
        }).or(acc);
      }
    );
  }, Option.none());
};
Example #12
0
const normalizeContent = (content: string, isStartOfContent: boolean, isEndOfContent: boolean): string => {
  const result = Arr.foldl(content.split(''), (acc, c) => {
    // Are we dealing with a char other than some collapsible whitespace or nbsp? if so then just use it as is
    if (isCollapsibleWhitespace(c) || c === '\u00a0') {
      if (acc.previousCharIsSpace || (acc.str === '' && isStartOfContent) || (acc.str.length === content.length - 1 && isEndOfContent)) {
        return { previousCharIsSpace: false, str: acc.str + '\u00a0' };
      } else {
        return { previousCharIsSpace: true, str: acc.str + ' ' };
      }
    } else {
      return { previousCharIsSpace: false, str: acc.str + c };
    }
  }, { previousCharIsSpace: false, str: '' });

  return result.str;
};
Example #13
0
  const getDataPath = (data) => {
    const parts = data || [];

    const newPathElements = Arr.map(parts, (part, index) => {
      return Button.sketch({
        dom: {
          tag: 'div',
          classes: [ 'tox-statusbar__path-item' ],
          attributes: {
            'role': 'button',
            'data-index': index,
            'tab-index': -1,
            'aria-level': index + 1
          },
          innerHtml: part.name
        },
        action: (btn) => {
          editor.focus();
          editor.selection.select(part.element);
          editor.nodeChanged();
        }
      });
    });

    const divider = {
      dom: {
        tag: 'div',
        classes: [ 'tox-statusbar__path-divider' ],
        attributes: {
          'aria-hidden': true
        },
        innerHtml: ` ${settings.delimiter} `
      }
    };

    return Arr.foldl(newPathElements.slice(1), (acc, element) => {
      const newAcc: any[] = acc;
      newAcc.push(divider);
      newAcc.push(element);
      return newAcc;
    }, [newPathElements[0]]);
  };
Example #14
0
const mapFormats = (userFormats: AllowedFormat[]): CustomFormatMapping => {
  return Arr.foldl(userFormats, (acc, fmt) => {
    if (isNestedFormat(fmt)) {
      // Map the child formats
      const result = mapFormats(fmt.items);
      return {
        customFormats: acc.customFormats.concat(result.customFormats),
        formats: acc.formats.concat([{ title: fmt.title, items: result.formats }])
      };
    } else if (isInlineFormat(fmt) || isBlockFormat(fmt) || isSelectorFormat(fmt)) {
      // Convert the format to a reference and add the original to the custom formats to be registered
      const formatName = `custom-${fmt.title.toLowerCase()}`;
      return {
        customFormats: acc.customFormats.concat([{ name: formatName, format: fmt }]),
        formats: acc.formats.concat([{ title: fmt.title, format: formatName, icon: fmt.icon }])
      };
    } else {
      return { ...acc, formats: acc.formats.concat(fmt) };
    }
  }, { customFormats: [], formats: [] });
};
Example #15
0
    return content.replace(/src="(blob:[^"]+)"/g, function (match, blobUri) {
      const resultUri = uploadStatus.getResultUri(blobUri);

      if (resultUri) {
        return 'src="' + resultUri + '"';
      }

      let blobInfo = blobCache.getByUri(blobUri);

      if (!blobInfo) {
        blobInfo = Arr.foldl(editor.editorManager.get(), function (result, editor) {
          return result || editor.editorUpload && editor.editorUpload.blobCache.getByUri(blobUri);
        }, null);
      }

      if (blobInfo) {
        const blob: Blob = blobInfo.blob();
        return 'src="data:' + blob.type + ';base64,' + blobInfo.base64() + '"';
      }

      return match;
    });
Example #16
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, [
      Logger.t('Font family and font size on initial page load', GeneralSteps.sequence([
        sAssertSelectBoxDisplayValue(editor, 'Font sizes', '12px'),
        sAssertSelectBoxDisplayValue(editor, 'Fonts', 'Arial')
      ])),

      Logger.t('Font family and font size on paragraph with no styles', GeneralSteps.sequence([
        tinyApis.sSetContent('<p>a</p>'),
        tinyApis.sFocus,
        tinyApis.sSetCursor([0, 0], 0),
        tinyApis.sNodeChanged,
        // p content style is 12px which does not match any pt values in the font size select values
        sAssertSelectBoxDisplayValue(editor, 'Font sizes', '12px'),
        sAssertSelectBoxDisplayValue(editor, 'Fonts', 'Arial')
      ])),

      Logger.t('Font family and font size on heading with no styles', GeneralSteps.sequence([
        tinyApis.sSetContent('<h1>a</h1>'),
        tinyApis.sFocus,
        tinyApis.sSetCursor([0, 0], 0),
        tinyApis.sNodeChanged,
        // h1 content style is 32px which matches 24pt in the font size select values so it should be converted
        sAssertSelectBoxDisplayValue(editor, 'Font sizes', '24pt'),
        sAssertSelectBoxDisplayValue(editor, 'Fonts', 'Arial')
      ])),

      Logger.t('Font family and font size on paragraph with styles that do match font size select values', GeneralSteps.sequence([
        tinyApis.sSetContent('<p style="font-family: Times; font-size: 17px;">a</p>'),
        tinyApis.sFocus,
        tinyApis.sSetCursor([0, 0], 0),
        tinyApis.sNodeChanged,
        // the following should be converted and pick up 12.75pt, although there's a rounded 13pt in the dropdown as well
        sAssertSelectBoxDisplayValue(editor, 'Font sizes', '12.75pt'),
        sAssertSelectBoxDisplayValue(editor, 'Fonts', 'Times')
      ])),

      Logger.t('Font family and font size on paragraph with styles that do not match font size select values', GeneralSteps.sequence([
        tinyApis.sSetContent('<p style="font-family: Times; font-size: 18px;">a</p>'),
        tinyApis.sFocus,
        tinyApis.sSetCursor([0, 0], 0),
        tinyApis.sNodeChanged,
        // the following should stay as 18px because there's no matching pt value in the font size select values
        sAssertSelectBoxDisplayValue(editor, 'Font sizes', '18px'),
        sAssertSelectBoxDisplayValue(editor, 'Fonts', 'Times')
      ])),

      Logger.t('System font stack variants on a paragraph show "System Font" as the font name', GeneralSteps.sequence([
        tinyApis.sSetContent(Arr.foldl(systemFontStackVariants, (acc, font) => {
          return acc + '<p style="font-family: ' + font.replace(/"/g, '\'') + '"></p>';
        }, '')),
        tinyApis.sFocus,
        ...Arr.bind(systemFontStackVariants, (_, idx) => {
          return [
            tinyApis.sSetCursor([idx, 0], 0),
            tinyApis.sNodeChanged,
            sAssertSelectBoxDisplayValue(editor, 'Fonts', 'System Font')
          ];
        })
      ]))
    ], onSuccess, onFailure);
  }, {
Example #17
0
const composeList = (scope: Document, entries: Entry[]): Option<Element> => {
  const outline: Section[] = Arr.foldl(entries, (outline, entry) => {
    return entry.depth > outline.length ? writeDeep(scope, outline, entry) : writeShallow(scope, outline, entry);
  }, []);
  return Arr.head(outline).map((section) => section.list);
};
Example #18
0
const getWidth = function (rows) {
  return Arr.foldl(rows, function (acc, row) {
    return row.cells().length > acc ? row.cells().length : acc;
  }, 0);
};
Example #19
0
 const join = function (items) {
   return Arr.foldl(items, function (a, b) {
     const bothEmpty = a.length === 0 || b.length === 0;
     return bothEmpty ? a.concat(b) : a.concat(separator, b);
   }, []);
 };
Example #20
0
const normalizeEntries = (entries: Entry[]): void => {
  Arr.foldl(entries, (outline: Array<Option<Entry>>, entry) => {
    return entry.depth > outline.length ? normalizeDeep(outline, entry) : normalizeShallow(outline, entry);
  }, []);
};
Example #21
0
const getClientRects = (node: Node[]): NodeClientRect[] => {
  return Arr.foldl(node, function (result, node) {
    return result.concat(getNodeClientRects(node));
  }, []);
};