Example #1
0
const clobberStyles = function (container, editorBody) {
  const gatherSibilings = function (element) {
    const siblings = SelectorFilter.siblings(element, '*');
    return siblings;
  };

  const clobber = function (clobberStyle) {
    return function (element) {
      const styles = Attr.get(element, 'style');
      const backup = styles === undefined ? 'no-styles' : styles.trim();

      if (backup === clobberStyle) {
        return;
      } else {
        Attr.set(element, attr, backup);
        Attr.set(element, 'style', clobberStyle);
      }
    };
  };

  const ancestors = SelectorFilter.ancestors(container, '*');
  const siblings = Arr.bind(ancestors, gatherSibilings);
  const bgColor = matchColor(editorBody);

  /* NOTE: This assumes that container has no siblings itself */
  Arr.each(siblings, clobber(siblingStyles));
  Arr.each(ancestors, clobber(ancestorPosition + ancestorStyles + bgColor));
  // position absolute on the outer-container breaks Android flex layout
  const containerStyles = isAndroid === true ? '' : ancestorPosition;
  clobber(containerStyles + ancestorStyles + bgColor)(container);
};
  return Arr.bind(navigation.concat(navigation.slice(0, 1)), (nav, i) => {
    const exploration = (nav.subitems.length > 0) ? [
      Keyboard.sKeydown(doc, Keys.right(), { }),
      sAssertFocusOnItem(doc, nav.subitems[0])
    ].concat(
      Arr.bind(
        nav.subitems.slice(1).concat(nav.subitems.slice(0, 1)),
        (si) => [
          Keyboard.sKeydown(doc, Keys.down(), { }),
          sDelay,
          sAssertFocusOnItem(doc, si)
        ]
      )
    ).concat([
      Keyboard.sKeydown(doc, Keys.escape(), { })
    ]) : [
      // Should do nothing
      Keyboard.sKeydown(doc, Keys.right(), { })
    ];

    return Arr.flatten([
      [ sAssertFocusOnItem(doc, nav.item) ],
      exploration,
      [ sAssertFocusOnItem(doc, nav.item) ],
      [ sDelay ],
      // Move to the next one
      i < navigation.length ? [ Keyboard.sKeydown(doc, Keys.down(), { }) ] : [ ]
    ]);
  });
Example #3
0
  const getCombinedItems = (triggerChar: string, matches: AutocompleteLookupData[]): ItemTypes.ItemSpec[] => {
    const columns = Options.findMap(matches, (m) => Option.from(m.columns)).getOr(1);

    return Arr.bind(matches, (match) => {
      const choices = match.items;

      return createAutocompleteItems(
        choices,
        (itemValue, itemMeta) => {
          const nr = editor.selection.getRng();
          const textNode = nr.startContainer as Text; // TODO: Investigate if this is safe
          getContext(nr, triggerChar, textNode.data, nr.startOffset).fold(
            () => console.error('Lost context. Cursor probably moved'),
            ({ rng }) => {
              const autocompleterApi: InlineContent.AutocompleterInstanceApi = {
                hide: closeIfNecessary
              };
              match.onAction(autocompleterApi, rng, itemValue, itemMeta);
            }
          );
        },
        columns,
        ItemResponse.BUBBLE_TO_SANDBOX,
        sharedBackstage
      );
    });
  };
Example #4
0
const parseList: Parser = (depth: number, itemSelection: Option<ItemSelection>, selectionState: Cell<boolean>, list: Element): Entry[] => {
  return Arr.bind(Traverse.children(list), (element) => {
    const parser = isList(element) ? parseList : parseItem;
    const newDepth = depth + 1;
    return parser(newDepth, itemSelection, selectionState, element);
  });
};
Example #5
0
 const generateItem = (rawItem: FormatItem, response: IrrelevantStyleItemResponse, disabled: boolean): Option<Menu.NestedMenuItemContents> => {
   const translatedText = backstage.shared.providers.translate(rawItem.title);
   if (rawItem.type === 'separator') {
     return Option.some<Menu.SeparatorMenuItemApi>({
       type: 'separator',
       text: translatedText
     });
   } else if (rawItem.type === 'submenu') {
     const items = Arr.bind(rawItem.getStyleItems(), (si) => validate(si, response));
     if (response === IrrelevantStyleItemResponse.Hide && items.length <= 0) {
       return Option.none();
     } else {
       return Option.some<Menu.NestedMenuItemApi>({
         type: 'nestedmenuitem',
         text: translatedText,
         disabled: items.length <= 0,
         getSubmenuItems: () => Arr.bind(rawItem.getStyleItems(), (si) => validate(si, response))
       });
     }
   } else {
     return Option.some<Menu.ToggleMenuItemApi>({
       // ONLY TOGGLEMENUITEMS HANDLE STYLE META.
       // See ToggleMenuItem and ItemStructure for how it's handled.
       // If this type ever changes, we'll need to change that too
       type: 'togglemenuitem',
       text: translatedText,
       active: rawItem.isSelected(),
       disabled,
       onAction: spec.onAction(rawItem),
       ...rawItem.getStylePreview().fold(() => ({}), (preview) => ({ meta: { style: preview } as any }))
     });
   }
 };
 const getSkinCssFilenames = function () {
   return Arr.bind(SelectorFilter.descendants(Element.fromDom(document), 'link'), function (link) {
     const href = Attr.get(link, 'href');
     const fileName = href.split('/').slice(-1).join('');
     const isSkin = href.indexOf('lightgray/') > -1;
     return isSkin ? [ fileName ] : [ ];
   });
 };
Example #7
0
 editor.on('init', () => {
   const formats = getStyleFormats(editor);
   const enriched = FormatRegister.register(editor, formats, isSelectedFor, getPreviewFor);
   settingsFormats.set(enriched);
   settingsFlattenedFormats.set(
     Arr.bind(enriched, flatten)
   );
 });
Example #8
0
const getCharMap = function (editor: Editor): CharMap[] {
  const groups = extendCharMap(editor, getDefaultCharMap());
  return groups.length > 1 ? [
    {
      name: 'All',
      characters: Arr.bind(groups, (g) => g.characters)
    }
  ].concat(groups) : groups;
};
Example #9
0
 const groups = Arr.map(toolbarGroups, (group) => {
   const items = Arr.bind(group.items, (toolbarItem) => {
     return toolbarItem.trim().length === 0 ? [] : lookupButton(editor, toolbarConfig.buttons, toolbarItem, extras, prefixes).toArray();
   });
   return {
     title: Option.from(editor.translate(group.name)),
     items
   };
 });
Example #10
0
const getAnchors = (editor): Option<ListValue[]> => {
  const anchorNodes = editor.dom.select('a:not([href])');
  const anchors = Arr.bind(anchorNodes, function (anchor: HTMLAnchorElement) {
    const id = anchor.name || anchor.id;
    return id ? [ { text: id, value: '#' + id } ] : [ ];
  });

  return anchors.length > 0 ? Option.some([ { text: 'None', value: '' } ].concat(anchors)) : Option.none();
};
Example #11
0
const getCorners = (getYAxisValue, tds: HTMLElement[]): Corner[] => {
  return Arr.bind(tds, (td) => {
    const rect = deflate(td.getBoundingClientRect(), -1);
    return [
      { x: rect.left, y: getYAxisValue(rect), cell: td },
      { x: rect.right, y: getYAxisValue(rect), cell: td }
    ];
  });
};
Example #12
0
  editor.on('addStyleModifications', (e) => {
    // Is there going to be an order issue here?
    const modifications = FormatRegister.register(editor, e.items, isSelectedFor, getPreviewFor);
    eventsFormats.set(modifications);
    replaceSettings.set(e.replace);

    eventsFlattenedFormats.set(
      Arr.bind(modifications, flatten)
    );
  });
 const doPrune = function (items) {
   return Arr.bind(items, function (item) {
     if (item.items !== undefined) {
       const newItems = doPrune(item.items);
       return newItems.length > 0 ? [ item ] : [ ];
     } else {
       const keep = Objects.hasKey(item, 'format') ? editor.formatter.canApply(item.format) : true;
       return keep ? [ item ] : [ ];
     }
   });
 };
Example #14
0
  const toFormats = () => {
    const groupItems = Arr.bind(groupOrder, (g) => {
      const items = groups[g];
      return items.length === 0 ? [ ] : [{
        title: g,
        items
      }];
    });

    return groupItems.concat(ungroupedOrder);
  };
Example #15
0
const createFromEditor = function (editor: Editor): UndoLevel {
  let fragments, content, trimmedFragments;

  fragments = Fragments.read(editor.getBody());
  trimmedFragments = Arr.bind(fragments, function (html) {
    const trimmed = TrimHtml.trimInternal(editor.serializer, html);
    return trimmed.length > 0 ? [trimmed] : [];
  });
  content = trimmedFragments.join('');

  return hasIframes(content) ? createFragmentedLevel(trimmedFragments) : createCompleteLevel(content);
};
Example #16
0
const detect = function (settings, features) {
  // Firstly, work out which items are in the toolbar
  const itemNames = identify(settings);

  // Now, build the list only including supported features and no duplicates.
  const present = { };
  return Arr.bind(itemNames, function (iName) {
    const r = !Objects.hasKey(present, iName) && Objects.hasKey(features, iName) && features[iName].isSupported() ? [ features[iName].sketch() ] : [];
    // NOTE: Could use fold to avoid mutation, but it might be overkill and not performant
    present[iName] = true;
    return r;
  });
};
Example #17
0
 const nodeChangeHandler = Option.some((comp) => {
   const getFormatItems = (fmt) => {
     const subs = fmt.items;
     return subs !== undefined && subs.length > 0 ? Arr.bind(subs, getFormatItems) : [ { title: fmt.title, format: fmt.format } ];
   };
   const flattenedItems = Arr.bind(getStyleFormats(editor), getFormatItems);
   return (e) => {
     const detectedFormat = findNearest(editor, () => flattenedItems, e);
     const text = detectedFormat.fold(() => 'Paragraph', (fmt) => fmt.title);
     AlloyTriggers.emitWith(comp, updateMenuText, {
       text
     });
   };
 });
Example #18
0
 getItems: () => Arr.bind(menu.items, (i) => {
   const itemName = i.toLowerCase();
   if (itemName.trim().length === 0) {
     return [ ];
   } else if (Arr.exists(removedMenuItems, (removedMenuItem) => removedMenuItem === itemName)) {
     return [ ];
   } else if (itemName === 'separator' || itemName === '|') {
     return [{
       type: 'separator'
     }];
   } else if (registry.menuItems[itemName]) {
     return [ registry.menuItems[itemName] ];
   } else {
     return [ ];
   }
 })
Example #19
0
 const addTeardown = function (steps) {
   return Arr.bind(steps, function (step) {
     return [step, Step.sync(teardown)];
   });
 };
const withTeardown = function (steps, teardownStep) {
  return Arr.bind(steps, function (step) {
    return [step, teardownStep];
  });
};
Example #21
0
const composeEntries = (editor, entries: Entry[]): Element[] => {
  return Arr.bind(Arr.groupBy(entries, isIndented), (entries) => {
    const groupIsIndented = Arr.head(entries).map(isIndented).getOr(false);
    return groupIsIndented ? indentedComposer(editor, entries) : outdentedComposer(editor, entries);
  });
};
Example #22
0
const sketch = function (rawSpec) {
  const navigateEvent = 'navigateEvent';

  const wrapperAdhocEvents = 'serializer-wrapper-events';
  const formAdhocEvents = 'form-events';

  const schema = ValueSchema.objOf([
    FieldSchema.strict('fields'),
    // Used for when datafields are present.
    FieldSchema.defaulted('maxFieldIndex', rawSpec.fields.length - 1),
    FieldSchema.strict('onExecute'),
    FieldSchema.strict('getInitialValue'),
    FieldSchema.state('state', function () {
      return {
        dialogSwipeState: Singleton.value(),
        currentScreen: Cell(0)
      };
    })
  ]);

  const spec = ValueSchema.asRawOrDie('SerialisedDialog', schema, rawSpec);

  const navigationButton = function (direction, directionName, enabled) {
    return Button.sketch({
      dom: UiDomFactory.dom('<span class="${prefix}-icon-' + directionName + ' ${prefix}-icon"></span>'),
      action (button) {
        AlloyTriggers.emitWith(button, navigateEvent, { direction });
      },
      buttonBehaviours: Behaviour.derive([
        Disabling.config({
          disableClass: Styles.resolve('toolbar-navigation-disabled'),
          disabled: !enabled
        })
      ])
    });
  };

  const reposition = function (dialog, message) {
    SelectorFind.descendant(dialog.element(), '.' + Styles.resolve('serialised-dialog-chain')).each(function (parent) {
      Css.set(parent, 'left', (-spec.state.currentScreen.get() * message.width) + 'px');
    });
  };

  const navigate = function (dialog, direction) {
    const screens = SelectorFilter.descendants(dialog.element(), '.' + Styles.resolve('serialised-dialog-screen'));
    SelectorFind.descendant(dialog.element(), '.' + Styles.resolve('serialised-dialog-chain')).each(function (parent) {
      if ((spec.state.currentScreen.get() + direction) >= 0 && (spec.state.currentScreen.get() + direction) < screens.length) {
        Css.getRaw(parent, 'left').each(function (left) {
          const currentLeft = parseInt(left, 10);
          const w = Width.get(screens[0]);
          Css.set(parent, 'left', (currentLeft - (direction * w)) + 'px');
        });
        spec.state.currentScreen.set(spec.state.currentScreen.get() + direction);
      }
    });
  };

  // Unfortunately we need to inspect the DOM to find the input that is currently on screen
  const focusInput = function (dialog) {
    const inputs = SelectorFilter.descendants(dialog.element(), 'input');
    const optInput = Option.from(inputs[spec.state.currentScreen.get()]);
    optInput.each(function (input) {
      dialog.getSystem().getByDom(input).each(function (inputComp) {
        AlloyTriggers.dispatchFocus(dialog, inputComp.element());
      });
    });
    const dotitems = memDots.get(dialog);
    Highlighting.highlightAt(dotitems, spec.state.currentScreen.get());
  };

  const resetState = function () {
    spec.state.currentScreen.set(0);
    spec.state.dialogSwipeState.clear();
  };

  const memForm = Memento.record(
    Form.sketch(function (parts) {
      return {
        dom: UiDomFactory.dom('<div class="${prefix}-serialised-dialog"></div>'),
        components: [
          Container.sketch({
            dom: UiDomFactory.dom('<div class="${prefix}-serialised-dialog-chain" style="left: 0px; position: absolute;"></div>'),
            components: Arr.map(spec.fields, function (field, i) {
              return i <= spec.maxFieldIndex ? Container.sketch({
                dom: UiDomFactory.dom('<div class="${prefix}-serialised-dialog-screen"></div>'),
                components: [
                  navigationButton(-1, 'previous', (i > 0)),
                  parts.field(field.name, field.spec),
                  navigationButton(+1, 'next', (i < spec.maxFieldIndex))
                ]
              }) : parts.field(field.name, field.spec);
            })
          })
        ],

        formBehaviours: Behaviour.derive([
          Receivers.orientation(function (dialog, message) {
            reposition(dialog, message);
          }),
          Keying.config({
            mode: 'special',
            focusIn (dialog/*, specialInfo */) {
              focusInput(dialog);
            },
            onTab (dialog/*, specialInfo */) {
              navigate(dialog, +1);
              return Option.some(true);
            },
            onShiftTab (dialog/*, specialInfo */) {
              navigate(dialog, -1);
              return Option.some(true);
            }
          }),

          AddEventsBehaviour.config(formAdhocEvents, [
            AlloyEvents.runOnAttached(function (dialog, simulatedEvent) {
              // Reset state to first screen.
              resetState();
              const dotitems = memDots.get(dialog);
              Highlighting.highlightFirst(dotitems);
              spec.getInitialValue(dialog).each(function (v) {
                Representing.setValue(dialog, v);
              });
            }),

            AlloyEvents.runOnExecute(spec.onExecute),

            AlloyEvents.run(NativeEvents.transitionend(), function (dialog, simulatedEvent) {
              const event = simulatedEvent.event() as any;
              if (event.raw().propertyName === 'left') {
                focusInput(dialog);
              }
            }),

            AlloyEvents.run(navigateEvent, function (dialog, simulatedEvent) {
              const event = simulatedEvent.event() as any;
              const direction = event.direction();
              navigate(dialog, direction);
            })
          ])
        ])
      };
    })
  );

  const memDots = Memento.record({
    dom: UiDomFactory.dom('<div class="${prefix}-dot-container"></div>'),
    behaviours: Behaviour.derive([
      Highlighting.config({
        highlightClass: Styles.resolve('dot-active'),
        itemClass: Styles.resolve('dot-item')
      })
    ]),
    components: Arr.bind(spec.fields, function (_f, i) {
      return i <= spec.maxFieldIndex ? [
        UiDomFactory.spec('<div class="${prefix}-dot-item ${prefix}-icon-full-dot ${prefix}-icon"></div>')
      ] : [];
    })
  });

  return {
    dom: UiDomFactory.dom('<div class="${prefix}-serializer-wrapper"></div>'),
    components: [
      memForm.asSpec(),
      memDots.asSpec()
    ],

    behaviours: Behaviour.derive([
      Keying.config({
        mode: 'special',
        focusIn (wrapper) {
          const form = memForm.get(wrapper);
          Keying.focusIn(form);
        }
      }),

      AddEventsBehaviour.config(wrapperAdhocEvents, [
        AlloyEvents.run(NativeEvents.touchstart(), function (wrapper, simulatedEvent) {
          const event = simulatedEvent.event() as any;
          spec.state.dialogSwipeState.set(
            SwipingModel.init(event.raw().touches[0].clientX)
          );
        }),
        AlloyEvents.run(NativeEvents.touchmove(), function (wrapper, simulatedEvent) {
          const event = simulatedEvent.event() as any;
          spec.state.dialogSwipeState.on(function (state) {
            simulatedEvent.event().prevent();
            spec.state.dialogSwipeState.set(
              SwipingModel.move(state, event.raw().touches[0].clientX)
            );
          });
        }),
        AlloyEvents.run(NativeEvents.touchend(), function (wrapper/*, simulatedEvent */) {
          spec.state.dialogSwipeState.on(function (state) {
            const dialog = memForm.get(wrapper);
            // Confusing
            const direction = -1 * SwipingModel.complete(state);
            navigate(dialog, direction);
          });
        })
      ])
    ])
  };
};
Example #23
0
UnitTest.asynctest('WindowManager:custom-dialog Test', (success, failure) => {
  const helpers = TestExtras();
  const windowManager = WindowManager.setup(helpers.extras);

  const doc = Element.fromDom(document);

  const testLog = Cell([ ]);

  const sAssertFocusedCheckbox = (label: string, expected: boolean) => {
    return Logger.t(
      label,
      Chain.asStep(doc, [
        FocusTools.cGetFocused,
        Chain.op((checkbox) => {
          Assertions.assertEq('Checking checked status', expected, checkbox.dom().checked);
        })
      ])
    );
  };

  const selectors = {
    field1: 'input', // nothing more useful, because it does not have a label
    field2: 'label:contains("F2") + textarea',
    field3: 'label:contains("F3") + .tox-form__controls-h-stack input',
    field4_a: '.tox-collection__item:contains("a")',
    field4_b: '.tox-collection__item:contains("b")',
    field5: 'input[type="checkbox"]',
    field6: 'label:contains("nested1") + input',
    field7: 'label:contains("nested2") + input',
    field8: 'button:contains("Cancel")',
    field9: 'button:contains("Save")',
    browseButton: 'button[title=F3]'
  };

  Pipeline.async({ }, [
    GuiSetup.mAddStyles(doc, [
      '[role="dialog"] { background: white; }',
      'input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked { display: none; }',
      'input:checked + .tox-checkbox__icons .tox-checkbox-icon__indeterminate { display: none; }',

      'input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked { display: none; }',
      'input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__checked { display: none; }',

      'input:not(:checked):not(:indeterminate) + .tox-checkbox__icons .tox-checkbox-icon__indeterminate { display: none; }',
      'input:not(:checked):not(:indeterminate) + .tox-checkbox__icons .tox-checkbox-icon__checked { display: none; }',

      '.tox-checkbox__input { height: 1px; left: -10000px; oveflow: hidden; position: absolute; top: auto; width: 1px; }',

      '[role="dialog"] { border: 1px solid black; padding: 2em; background-color: rgb(131,193,249); top: 40px; position: absolute; }',

      ':focus { outline: 3px solid green; !important; }',

      '.tox-collection__item { display: inline-block; }'
    ]),

    Step.sync(() => {
      windowManager.open({
        title: 'Custom Dialog',
        body: {
          type: 'panel',
          items: [
            {
              name: 'f1-input',
              type: 'input'
            },
            {
              name: 'f2-textarea',
              label: 'F2',
              type: 'textarea'
            },
            {
              name: 'f3-urlinput',
              filetype: 'file',
              label: 'F3',
              type: 'urlinput'
            },
            {
              name: 'f4-charmap',
              type: 'collection',
              // columns: 'auto'
            },
            {
              name: 'f5-checkbox',
              label: 'Checkbox',
              type: 'checkbox'
            },
            {
              type: 'grid',
              columns: 2,
              items: [
                {
                  type: 'input',
                  label: 'nested1',
                  name: 'nested-input'
                },
                {
                  type: 'grid',
                  columns: 2,
                  items: [
                    {
                      type: 'input',
                      label: 'nested2',
                      name: 'nested-nested-input'
                    }
                  ]
                }
              ]
            }
          ]
        },
        buttons: [
          {
            type: 'custom',
            text: 'go',
            disabled: true
          },
          {
            type: 'cancel',
            text: 'Cancel'
          },
          {
            type: 'submit',
            text: 'Save'
          }
        ],
        initialData: {
          'f1-input': 'f1',
          'f2-textarea': 'f2',
          'f3-urlinput': {
            value: 'f3',
            text: 'F3',
            meta: { }
          },
          'f4-charmap': [
            { value: 'a', icon: 'a', text: 'a' },
            { value: 'b', icon: 'b', text: 'b' },
            { value: 'c', icon: 'c', text: 'c' },
            { value: 'd', icon: 'd', text: 'd' }
          ],
          'f5-checkbox': true,
          'nested-input': 'nested-input',
          'nested-nested-input': 'nested-nested-input'
        },
        onSubmit: () => {
          testLog.set(
            testLog.get().concat([ 'onSubmit' ])
          );
        }
      }, {}, () => {});
    }),

    FocusTools.sTryOnSelector(
      'Focus should start on first input',
      doc,
      selectors.field1
    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to second input (textarea)',
      doc,
      selectors.field2
    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to urlinput',
      doc,
      selectors.field3
    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to browse button',
      doc,
      selectors.browseButton
    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to charmap character a',
      doc,
      selectors.field4_a
    ),

    Keyboard.sKeydown(doc, Keys.right(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to charmap character b',
      doc,
      selectors.field4_b
    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to focusable part of checkboxes',
      doc,
      selectors.field5
    ),

    Keyboard.sKeydown(doc, Keys.enter(), { }),
    sAssertFocusedCheckbox('Pressing <enter> on checked checkbox', false),

    Keyboard.sKeydown(doc, Keys.space(), { }),
    sAssertFocusedCheckbox('Pressing <space> on unchecked checkbox', true),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to first nested input',
      doc,
      selectors.field6

    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to second nested input',
      doc,
      selectors.field7
    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should skip over disabled button',
      doc,
      selectors.field8
    ),

    Keyboard.sKeydown(doc, Keys.tab(), { }),
    FocusTools.sTryOnSelector(
      'Focus should move to ok',
      doc,
      selectors.field9
    ),

    Logger.t(
      'Now, navigate backwards',
      GeneralSteps.sequence(
        Arr.bind([
          { label: 'cancel', selector: selectors.field8 },
          { label: 'nested2', selector: selectors.field7 },
          { label: 'nested1', selector: selectors.field6 },
          { label: 'checkbox', selector: selectors.field5 },
          { label: 'charmap', selector: selectors.field4_a },
          { label: 'browse button', selector: selectors.browseButton },
          { label: 'f3', selector: selectors.field3 },
          { label: 'f2', selector: selectors.field2 },
          { label: 'first input', selector: selectors.field1 }
        ], (dest) => {
          return [
            Keyboard.sKeydown(doc, Keys.tab(), { shiftKey: true }),
            FocusTools.sTryOnSelector(
              'Focus should move to ' + dest.label,
              doc,
              dest.selector
            )
          ];
        })
      )
    ),

    Logger.t(
      'Checking the testLog is empty',
      Step.sync(() => {
        Assertions.assertEq('testLog contents', [ ], testLog.get());
      })
    ),

    FocusTools.sTryOnSelector('Checking on an input field', doc, 'input'),
    Keyboard.sKeydown(doc, Keys.enter(), { }),

    Logger.t(
      'Checking the testLog has a submit after hitting enter in an input field',
      Step.sync(() => {
        Assertions.assertEq('testLog contents', [ 'onSubmit' ], testLog.get());
      })
    ),

    GeneralSteps.sequence([
      Mouse.sClickOn(Body.body(), '.tox-button--icon[aria-label="Close"]'),
      Waiter.sTryUntil(
        'Wait for the dialog to disappear',
        UiFinder.sNotExists(Body.body(), '.tox-button--icon[aria-label="Close"]'),
        100,
        1000
      ),
    ])
  ], () => {
    helpers.destroy();
    success();
  }, failure);
});
Example #24
0
const identifyFromArray = function (toolbar) {
  return Arr.bind(toolbar, function (item) {
    return Type.isArray(item) ? identifyFromArray(item) : extract(item);
  });
};
const match = function (patterns, evt) {
  return Arr.bind(defaultPatterns(patterns), function (pattern) {
    return matchesEvent(pattern, evt) ? [pattern] : [ ];
  });
};
Example #26
0
UnitTest.asynctest('browser.tinymce.core.dom.EventUtilsTest', function (success, failure) {
  const suite = LegacyUnit.createSuite();
  const eventUtils = EventUtils.Event;

  suite.test('unbind all', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click = true;
    });

    eventUtils.bind(window, 'keydown', function () {
      result.keydown1 = true;
    });

    eventUtils.bind(window, 'keydown', function () {
      result.keydown2 = true;
    });

    result = {};
    eventUtils.fire(window, 'click');
    eventUtils.fire(window, 'keydown');
    LegacyUnit.deepEqual(result, { click: true, keydown1: true, keydown2: true });

    eventUtils.unbind(window);
    result = {};
    eventUtils.fire(window, 'click');
    eventUtils.fire(window, 'keydown');
    LegacyUnit.deepEqual(result, {});
  });

  suite.test('unbind event', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click = true;
    });

    eventUtils.bind(window, 'keydown', function () {
      result.keydown1 = true;
    });

    eventUtils.bind(window, 'keydown', function () {
      result.keydown2 = true;
    });

    result = {};
    eventUtils.fire(window, 'click');
    eventUtils.fire(window, 'keydown');
    LegacyUnit.deepEqual(result, { click: true, keydown1: true, keydown2: true });

    eventUtils.unbind(window, 'click');
    result = {};
    eventUtils.fire(window, 'click');
    eventUtils.fire(window, 'keydown');
    LegacyUnit.deepEqual(result, { keydown1: true, keydown2: true });
  });

  suite.test('unbind event non existing', function () {
    eventUtils.unbind(window, 'noevent');
    LegacyUnit.equal(true, true, 'No exception');
  });

  suite.test('unbind callback', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click = true;
    });

    eventUtils.bind(window, 'keydown', function () {
      result.keydown1 = true;
    });

    const callback2 = function () {
      result.keydown2 = true;
    };

    eventUtils.bind(window, 'keydown', callback2);

    result = {};
    eventUtils.fire(window, 'click');
    eventUtils.fire(window, 'keydown');
    LegacyUnit.deepEqual(result, { click: true, keydown1: true, keydown2: true });

    eventUtils.unbind(window, 'keydown', callback2);
    result = {};
    eventUtils.fire(window, 'click');
    eventUtils.fire(window, 'keydown');
    LegacyUnit.deepEqual(result, { click: true, keydown1: true });
  });

  suite.test('unbind multiple', function () {
    let result;

    eventUtils.bind(window, 'mouseup mousedown click', function (e) {
      result[e.type] = true;
    });

    eventUtils.unbind(window, 'mouseup mousedown');

    result = {};
    eventUtils.fire(window, 'mouseup');
    eventUtils.fire(window, 'mousedown');
    eventUtils.fire(window, 'click');
    LegacyUnit.deepEqual(result, { click: true });
  });

  suite.test('bind multiple', function () {
    let result;

    eventUtils.bind(window, 'mouseup mousedown', function (e) {
      result[e.type] = true;
    });

    result = {};
    eventUtils.fire(window, 'mouseup');
    eventUtils.fire(window, 'mousedown');
    eventUtils.fire(window, 'click');
    LegacyUnit.deepEqual(result, { mouseup: true, mousedown: true });
  });

  suite.test('bind/fire bubbling', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.window = true;
    });

    eventUtils.bind(document, 'click', function () {
      result.document = true;
    });

    eventUtils.bind(document.body, 'click', function () {
      result.body = true;
    });

    eventUtils.bind(document.getElementById('content'), 'click', function () {
      result.content = true;
    });

    eventUtils.bind(document.getElementById('inner'), 'click', function () {
      result.inner = true;
    });

    result = {};
    eventUtils.fire(window, 'click');
    LegacyUnit.deepEqual(result, { window: true });

    result = {};
    eventUtils.fire(document, 'click');
    LegacyUnit.deepEqual(result, { document: true, window: true });

    result = {};
    eventUtils.fire(document.body, 'click');
    LegacyUnit.deepEqual(result, { body: true, document: true, window: true });

    result = {};
    eventUtils.fire(document.getElementById('content'), 'click');
    LegacyUnit.deepEqual(result, { content: true, body: true, document: true, window: true });

    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, { inner: true, content: true, body: true, document: true, window: true });
  });

  suite.test('bind/fire stopImmediatePropagation', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click1 = true;
    });

    eventUtils.bind(window, 'click', function (e) {
      result.click2 = true;
      e.stopImmediatePropagation();
    });

    eventUtils.bind(window, 'click', function () {
      result.click3 = true;
    });

    result = {};
    eventUtils.fire(window, 'click');
    LegacyUnit.deepEqual(result, { click1: true, click2: true });
  });

  suite.test('bind/fire stopPropagation', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click1 = true;
    });

    eventUtils.bind(document.body, 'click', function () {
      result.click2 = true;
    });

    eventUtils.bind(document.getElementById('inner'), 'click', function (e) {
      result.click3 = true;
      e.stopPropagation();
    });

    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, { click3: true });
  });

  suite.test('clean window', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click1 = true;
    });

    eventUtils.bind(document.body, 'click', function () {
      result.click2 = true;
    });

    eventUtils.bind(document.getElementById('content'), 'click', function () {
      result.click3 = true;
    });

    eventUtils.bind(document.getElementById('inner'), 'click', function () {
      result.click4 = true;
    });

    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, { click1: true, click2: true, click3: true, click4: true });

    eventUtils.clean(window);
    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, {});
  });

  suite.test('clean document', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click1 = true;
    });

    eventUtils.bind(document, 'click', function () {
      result.click2 = true;
    });

    eventUtils.bind(document.body, 'click', function () {
      result.click3 = true;
    });

    eventUtils.bind(document.getElementById('content'), 'click', function () {
      result.click4 = true;
    });

    eventUtils.bind(document.getElementById('inner'), 'click', function () {
      result.click5 = true;
    });

    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, { click1: true, click2: true, click3: true, click4: true, click5: true });

    eventUtils.clean(document);
    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, { click1: true });
  });

  suite.test('clean element', function () {
    let result;

    eventUtils.bind(window, 'click', function () {
      result.click1 = true;
    });

    eventUtils.bind(document.body, 'click', function () {
      result.click2 = true;
    });

    eventUtils.bind(document.getElementById('content'), 'click', function () {
      result.click3 = true;
    });

    eventUtils.bind(document.getElementById('inner'), 'click', function () {
      result.click4 = true;
    });

    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, { click1: true, click2: true, click3: true, click4: true });

    eventUtils.clean(document.getElementById('content'));
    result = {};
    eventUtils.fire(document.getElementById('inner'), 'click');
    LegacyUnit.deepEqual(result, { click1: true, click2: true });
  });

  suite.test('mouseenter/mouseleave bind/unbind', function () {
    let result = {};

    eventUtils.bind(document.body, 'mouseenter mouseleave', function (e) {
      result[e.type] = true;
    });

    eventUtils.fire(document.body, 'mouseenter');
    eventUtils.fire(document.body, 'mouseleave');

    LegacyUnit.deepEqual(result, { mouseenter: true, mouseleave: true });

    result = {};
    eventUtils.clean(document.body);
    eventUtils.fire(document.body, 'mouseenter');
    eventUtils.fire(document.body, 'mouseleave');
    LegacyUnit.deepEqual(result, {});
  });

  /*
  asyncTest("focusin/focusout bind/unbind", function() {
    var result = {};

    window.setTimeout(function() {
      eventUtils.bind(document.body, 'focusin focusout', function(e) {
        // IE will fire a focusout on the parent element if you focus an element within not a big deal so lets detect it in the test
        if (e.type == "focusout" && e.target.contains(document.activeElement)) {
          return;
        }

        result[e.type] = result[e.type] ? ++result[e.type] : 1;
      });

      start();
      document.getElementById('content').focus();
      document.getElementById('inner').focus();

      LegacyUnit.deepEqual(result, {focusin: 2, focusout: 1});
    }, 0);
  });
  */

  suite.test('bind unbind fire clean on null', function () {
    eventUtils.bind(null, 'click', function () {});
    eventUtils.unbind(null, 'click', function () {});
    eventUtils.fire(null, 'click', {});
    eventUtils.clean(null);
    LegacyUnit.equal(true, true, 'No exception');
  });

  suite.test('bind ready when page is loaded', function () {
    let ready;

    eventUtils.bind(window, 'ready', function () {
      ready = true;
    });

    LegacyUnit.equal(true, eventUtils.domLoaded, 'DomLoaded state true');
    LegacyUnit.equal(true, ready, 'Window is ready.');
  });

  suite.test('event states when event object is fired twice', function () {
    const result = {};

    eventUtils.bind(window, 'keydown', function (e) {
      result[e.type] = true; e.preventDefault(); e.stopPropagation();
    });
    eventUtils.bind(window, 'keyup', function (e) {
      result[e.type] = true; e.stopImmediatePropagation();
    });

    const event: any = {};
    eventUtils.fire(window, 'keydown', event);
    eventUtils.fire(window, 'keyup', event);

    LegacyUnit.equal(true, event.isDefaultPrevented(), 'Default is prevented.');
    LegacyUnit.equal(true, event.isPropagationStopped(), 'Propagation is stopped.');
    LegacyUnit.equal(true, event.isImmediatePropagationStopped(), 'Immediate propagation is stopped.');

    LegacyUnit.deepEqual(result, { keydown: true, keyup: true });
  });

  suite.test('unbind inside callback', function () {
    let data;

    const append = function (value) {
      return function () {
        data += value;
      };
    };

    const callback = function () {
      eventUtils.unbind(window, 'click', callback);
      data += 'b';
    };

    data = '';
    eventUtils.bind(window, 'click', append('a'));
    eventUtils.bind(window, 'click', callback);
    eventUtils.bind(window, 'click', append('c'));

    eventUtils.fire(window, 'click', {});
    LegacyUnit.equal(data, 'abc');

    data = '';
    eventUtils.fire(window, 'click', {});
    LegacyUnit.equal(data, 'ac');
  });

  suite.test('ready/DOMContentLoaded (domLoaded = true)', function () {
    let evt;

    eventUtils.bind(window, 'ready', function (e) {
      evt = e;
    });
    LegacyUnit.equal(evt.type, 'ready');
  });

  suite.test('ready/DOMContentLoaded (document.readyState check)', function () {
    let evt;

    try {
      document.readyState = 'loading';
    } catch (e) {
      LegacyUnit.equal(true, true, 'IE doesn\'t allow us to set document.readyState');
      return;
    }

    eventUtils.domLoaded = false;
    document.readyState = 'loading';
    eventUtils.bind(window, 'ready', function (e) {
      evt = e;
    });
    LegacyUnit.equal(true, typeof (evt) !== 'undefined');

    eventUtils.domLoaded = false;
    document.readyState = 'complete';
    eventUtils.bind(window, 'ready', function (e) {
      evt = e;
    });
    LegacyUnit.equal(evt.type, 'ready');
  });

  suite.test('isDefaultPrevented', function () {
    const testObj: any = {};
    const testCallback = function () {
      return 'hello';
    };
    testObj.isDefaultPrevented = testCallback;
    eventUtils.fire(window, 'testEvent', testObj);

    LegacyUnit.equal(testObj.isDefaultPrevented !== testCallback, true, 'Is overwritten by our isDefaultPrevented');
    LegacyUnit.equal(typeof testObj.isPropagationStopped, 'function', 'Has our isPropagationStopped');
    LegacyUnit.equal(typeof testObj.isImmediatePropagationStopped, 'function', 'Has our isImmediatePropagationStopped');
  });

  const sAddTestDiv = Step.sync(function () {
    const testDiv = document.createElement('div');
    testDiv.id = 'testDiv';
    testDiv.innerHTML = (
      '<div id="content" tabindex="0">' +
      '<div id="inner" tabindex="0"></div>' +
      '</div>'
    );

    document.body.appendChild(testDiv);
  });

  const sRemoveTestDiv = Step.sync(function () {
    const testDiv = document.querySelector('#testDiv');
    testDiv.parentNode.removeChild(testDiv);
  });

  let steps = Arr.bind(suite.toSteps({}), function (step) {
    return [
      step,
      Step.sync(function () {
        eventUtils.clean(window);
      })
    ];
  });

  steps = [sAddTestDiv].concat(steps).concat(sRemoveTestDiv);

  Pipeline.async({}, steps, function () {
    success();
  }, failure);
});
 const appendTeardown = function (editor, steps) {
   return Arr.bind(steps, function (step) {
     return [step, sTeardown(editor)];
   });
 };
Example #28
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 #29
0
 const appendTeardown = function (steps) {
   return Arr.bind(steps, function (step) {
     return [step, teardown];
   });
 };
    function (editor, onSuccess, onFailure) {
      const tinyApis = TinyApis(editor);
      const stepsWithTeardown = Arr.bind([
        Logger.t('incorrect service url no api key', GeneralSteps.sequence([
          uploadHandlerState.sResetState,
          tinyApis.sSetSetting('imagetools_proxy', 'http://0.0.0.0.0.0/'),
          tinyApis.sSetSetting('api_key', undefined),
          ImageUtils.sLoadImage(editor, corsUrl),
          tinyApis.sSelect('img', []),
          ImageUtils.sExecCommand(editor, 'mceImageFlipHorizontal'),
          sAssertErrorMessage('ImageProxy HTTP error: Incorrect Image Proxy URL')
        ])),

        Logger.t('incorrect service url with api key', GeneralSteps.sequence([
          uploadHandlerState.sResetState,
          tinyApis.sSetSetting('imagetools_proxy', 'http://0.0.0.0.0.0/'),
          tinyApis.sSetSetting('api_key', 'fake_key'),
          ImageUtils.sLoadImage(editor, corsUrl),
          tinyApis.sSelect('img', []),
          ImageUtils.sExecCommand(editor, 'mceImageFlipHorizontal'),
          sAssertErrorMessage('ImageProxy HTTP error: Incorrect Image Proxy URL')
        ])),

        Logger.t('403 no api key', GeneralSteps.sequence([
          uploadHandlerState.sResetState,
          tinyApis.sSetSetting('imagetools_proxy', '/custom/403'),
          tinyApis.sSetSetting('api_key', undefined),
          ImageUtils.sLoadImage(editor, corsUrl),
          tinyApis.sSelect('img', []),
          ImageUtils.sExecCommand(editor, 'mceImageFlipHorizontal'),
          sAssertErrorMessage('ImageProxy HTTP error: Rejected request')
        ])),

        Logger.t('403 with api key', GeneralSteps.sequence([
          uploadHandlerState.sResetState,
          tinyApis.sSetSetting('imagetools_proxy', '/custom/403'),
          tinyApis.sSetSetting('api_key', 'fake_key'),
          ImageUtils.sLoadImage(editor, corsUrl),
          tinyApis.sSelect('img', []),
          ImageUtils.sExecCommand(editor, 'mceImageFlipHorizontal'),
          sAssertErrorMessage('ImageProxy Service error: Invalid JSON in service error message')
        ])),

        Logger.t('403 with api key and return error data', GeneralSteps.sequence([
          uploadHandlerState.sResetState,
          tinyApis.sSetSetting('imagetools_proxy', '/custom/403data'),
          tinyApis.sSetSetting('api_key', 'fake_key'),
          ImageUtils.sLoadImage(editor, corsUrl),
          tinyApis.sSelect('img', []),
          ImageUtils.sExecCommand(editor, 'mceImageFlipHorizontal'),
          sAssertErrorMessage('ImageProxy Service error: Unknown service error')
        ])),

        Logger.t('404 no api key', GeneralSteps.sequence([
          uploadHandlerState.sResetState,
          tinyApis.sSetSetting('imagetools_proxy', '/custom/404'),
          tinyApis.sSetSetting('api_key', undefined),
          ImageUtils.sLoadImage(editor, corsUrl),
          tinyApis.sSelect('img', []),
          ImageUtils.sExecCommand(editor, 'mceImageFlipHorizontal'),
          sAssertErrorMessage('ImageProxy HTTP error: Could not find Image Proxy')
        ])),

        Logger.t('404 with api key', GeneralSteps.sequence([
          uploadHandlerState.sResetState,
          tinyApis.sSetSetting('imagetools_proxy', '/custom/404'),
          tinyApis.sSetSetting('api_key', 'fake_key'),
          ImageUtils.sLoadImage(editor, corsUrl),
          tinyApis.sSelect('img', []),
          ImageUtils.sExecCommand(editor, 'mceImageFlipHorizontal'),
          sAssertErrorMessage('ImageProxy HTTP error: Could not find Image Proxy')
        ]))
      ], function (step) {
        return [
          step,
          GeneralSteps.sequence([
            sCloseErrorMessage,
            tinyApis.sSetContent('')
          ])
        ];
      });

      Pipeline.async({}, stepsWithTeardown, onSuccess, onFailure);
    },