Example #1
0
export const renderSelectBox = (spec: Types.SelectBox.SelectBox, providersBackstage: UiFactoryBackstageProviders): SketchSpec => {
  const translatedOptions = Arr.map(spec.items, (item) => {
    return {
      text: providersBackstage.translate(item.text),
      value: item.value
    };
  });

  // DUPE with TextField.
  const pLabel = spec.label.map((label) => renderLabel(label, providersBackstage));

  const pField = AlloyFormField.parts().field({
    // TODO: Alloy should not allow dom changing of an HTML select!
    dom: {  },
    selectAttributes: {
      size: spec.size
    },
    options: translatedOptions,
    factory: AlloyHtmlSelect,
    selectBehaviours: Behaviour.derive([
      Tabstopping.config({ }),
      AddEventsBehaviour.config('selectbox-change', [
        AlloyEvents.run(NativeEvents.change(), (component, _) => {
          AlloyTriggers.emitWith(component, formChangeEvent, { name: spec.name } );
        })
      ])
    ])
  });

  const chevron = spec.size > 1 ? Option.none() :
    Option.some({
        dom: {
          tag: 'div',
          classes: ['tox-selectfield__icon-js'],
          innerHtml: Icons.get('chevron-down', providersBackstage.icons)
        }
      });

  const selectWrap: SimpleSpec = {
    dom: {
      tag: 'div',
      classes: ['tox-selectfield']
    },
    components: Arr.flatten([[pField], chevron.toArray()])
  };

  return AlloyFormField.sketch({
    dom: {
      tag: 'div',
      classes: ['tox-form__group']
    },
    components: Arr.flatten<AlloySpec>([pLabel.toArray(), [selectWrap]])
  });
};
Example #2
0
    return ApproxStructure.build(function (s, str/*, arr*/) {
      const children = Arr.map(texts, function (text, i) {
        return Arr.flatten([
          index === i && before ? [ s.text(str.is(Zwsp.ZWSP)) ] : [ ],
          [
            s.element(
              'a',
              {
                attrs: {
                  'data-mce-selected': str.none('inline-boundary'),
                  'data-mce-href': str.is('#'),
                  'href': str.is('#')
                },
                children: [
                  s.text(str.is(text))
                ]
              }
            )
          ],
          index === i && before === false ? [ s.text(str.is(Zwsp.ZWSP)) ] : [ ]
        ]);
      });

      return s.element('p', {
        children: addGeckoBr(s, str, Arr.flatten(children))
      });
    });
  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 #4
0
 const sBookmarkTest = (namedChains) => {
   return Chain.asStep({}, [
     NamedChain.asChain(Arr.flatten([
       [ cCreateNamedEditor ],
       namedChains,
       [ cRemoveEditor ]
     ])
   )]);
 };
Example #5
0
const dom = (hasIcons: boolean, columns: Types.ColumnTypes, presets: Types.PresetTypes) => {
  const menuClasses = getMenuClasses(presets);
  return {
    tag: 'div',
    classes: Arr.flatten([
      [ menuClasses.menu, `tox-menu-${columns}-column` ],
      hasIcons ? [ menuClasses.hasIcons ] : [ ]
    ])
  };
};
 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: Arr.flatten([
       [ navigationButton(-1, 'previous', (i > 0)) ],
       [ parts.field(field.name, field.spec) ],
       [ navigationButton(+1, 'next', (i < spec.maxFieldIndex)) ]
     ])
   }) : parts.field(field.name, field.spec);
 })
Example #7
0
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    Pipeline.async({}, Arr.flatten([
      [
        sParseStyles(editor),
        sProtectConditionalCommentsInHeadFoot(editor)
      ],
      suite.toSteps(editor)
    ]), onSuccess, onFailure);

    teardown(editor);
  }, {
  TinyLoader.setup((editor: Editor, onSuccess, onFailure) => {
      const uiContainer = Element.fromDom(editor.getContainer());
      const contentAreaContainer = Element.fromDom(editor.getContentAreaContainer());

      const tinyApis = TinyApis(editor);

      Pipeline.async({ }, Arr.flatten([
        sUiContainerTest(editor, uiContainer, tinyApis),
        sContentAreaContainerTest(contentAreaContainer)
      ]), onSuccess, onFailure);
    },
Example #9
0
 const controlHWrapper = (): AlloySpec => {
   return {
     dom: {
       tag: 'div',
       classes: ['tox-form__controls-h-stack']
     },
     components: Arr.flatten([
       [memUrlBox.asSpec()],
       optUrlPicker.map(() => renderInputButton(spec.label, browseUrlEvent, 'tox-browse-url', 'browse', providersBackstage)).toArray()
     ])
   };
 };
Example #10
0
 (linkInfo) => {
   const history = filterByQuery(term, historyTargets(urlBackstage.getHistory(fileType)));
   return fileType === 'file' ? joinMenuLists([
     history,
     filterByQuery(term, headerTargets(linkInfo)),
     filterByQuery(term, Arr.flatten([
       anchorTargetTop(linkInfo),
       anchorTargets(linkInfo),
       anchorTargetBottom(linkInfo)
     ]))
   ])
     : history;
 }
Example #11
0
const cSetTabFieldValues = (data, tabSelectors) => {
  const chains = Arr.flatten(Obj.mapToArray(tabSelectors, (value, key): Chain<any, any>[] => {
    if (Obj.has(data, key)) {
      const newValue = typeof data[key] === 'object' ? data[key].value : data[key];
      return [ cSetFieldValue(tabSelectors[key], newValue) ];
    } else if (Obj.has(data, 'dimensions') && Obj.has(data.dimensions, key)) {
      return [ cSetFieldValue(tabSelectors[key], data.dimensions[key]) ];
    } else {
      return [];
    }
  }));
  return Chain.fromChains(chains);
};
Example #12
0
const part = (hasIcons: boolean, columns: Types.ColumnTypes, presets: Types.PresetTypes): Partial<TieredMenuTypes.TieredMenuSpec> => {
  const menuClasses = getMenuClasses(presets);
  const d = {
    tag: 'div',
    classes: Arr.flatten([
      [ menuClasses.tieredMenu ]
    ])
  };

  return {
    dom: d,
    markers: markers(presets)
  };
};
Example #13
0
 TinyLoader.setup(function (editor, onSuccess, onFailure) {
   Pipeline.async({}, Arr.flatten([
     [
       Step.sync(function () {
         editor.setContent('<p class="preview">x</p>');
       }),
       Waiter.sTryUntil(
         'Expected styles where not loaded',
         Step.sync(function () {
           const color = editor.dom.getStyle(editor.dom.select('p'), 'color', true);
           Assertions.assertEq('Did not get a color value of 255', true, color.indexOf('255') !== -1);
         }
       ), 10, 3000)
     ],
     suite.toSteps(editor)
   ]), onSuccess, onFailure);
 }, {
Example #14
0
const makeDialogBody = (info: ImageDialogInfo) => {
  if (info.hasAdvTab || info.hasUploadUrl || info.hasUploadHandler) {
    const tabPanel: Types.Dialog.TabPanelApi = {
      type: 'tabpanel',
      tabs: Arr.flatten([
        [MainTab.makeTab(info)],
        info.hasAdvTab ? [AdvTab.makeTab(info)] : [],
        info.hasUploadUrl || info.hasUploadHandler ? [UploadTab.makeTab(info)] : []
      ])
    };
    return tabPanel;
  } else {
    const panel: Types.Dialog.PanelApi = {
      type: 'panel',
      items: MainTab.makeItems(info)
    };
    return panel;
  }
};
const createCustomMenuItems = function (editor, names) {
  let items, nameList;

  if (typeof names === 'string') {
    nameList = names.split(' ');
  } else if (Tools.isArray(names)) {
    return Arr.flatten(Tools.map(names, function (names) {
      return createCustomMenuItems(editor, names);
    }));
  }

  items = Tools.grep(nameList, function (name) {
    return name === '|' || name in editor.menuItems;
  });

  return Tools.map(items, function (name) {
    return name === '|' ? { text: '-' } : editor.menuItems[name];
  });
};
Example #16
0
 ], ({ label, button }) => {
   const groups = identifyButtons(mockEditor, { buttons, toolbar: button }, helpers.extras, Option.none());
   const buttonComponents = Arr.flatten(Arr.map(groups, (group) => group.items));
   return {
     dom: {
       tag: 'div',
       classes: [ ],
       styles: {
         display: 'flex',
         border: '1px solid #ccc'
       }
     },
     components: [
       {
         dom: {
           tag: 'label',
           styles: {
             'margin-right': '3em',
             'display': 'flex',
             'align-items': 'center',
             'padding': '1em'
           },
           innerHtml: label
         }
       },
       {
         dom: {
           tag: 'div',
           classes: [ 'toolbar-row' ],
           styles: {
             'display': 'flex',
             'flex-direction': 'row-reverse',
             'flex-grow': '1',
             'align-items': 'center'
           }
         },
         components: buttonComponents
       }
     ]
   };
 })
Example #17
0
 const children = Arr.map(texts, function (text, i) {
   return Arr.flatten([
     index === i && before ? [ s.text(str.is(Zwsp.ZWSP)) ] : [ ],
     [
       s.element(
         'a',
         {
           attrs: {
             'data-mce-selected': str.none('inline-boundary'),
             'data-mce-href': str.is('#'),
             'href': str.is('#')
           },
           children: [
             s.text(str.is(text))
           ]
         }
       )
     ],
     index === i && before === false ? [ s.text(str.is(Zwsp.ZWSP)) ] : [ ]
   ]);
 });
  TinyLoader.setup(function (editor, onSuccess, onFailure) {
    const tinyApis = TinyApis(editor);

    Pipeline.async({}, Arr.flatten([
      [
        Logger.t('Fullscreen toggle scroll state', GeneralSteps.sequence([
          tinyApis.sExecCommand('mceFullScreen'),
          sAssertScroll(editor, true),
          tinyApis.sExecCommand('mceFullScreen'),
          sAssertScroll(editor, false)
        ])),
        Logger.t('Editor size increase based on content size', GeneralSteps.sequence([
          tinyApis.sSetContent('<div style="height: 5000px;">a</div>'),
          Waiter.sTryUntil('wait for editor height', sAssertEditorHeightAbove(editor, 5000), 10, 3000)
        ])),
        Logger.t('Editor size decrease based on content size', GeneralSteps.sequence([
          tinyApis.sSetContent('<div style="height: 1000px;">a</div>'),
          Waiter.sTryUntil('wait for editor height', sAssertEditorHeightBelow(editor, 2000), 10, 3000)
        ]))
      ],

      // These tests doesn't work on phantom since measuring things seems broken there
      navigator.userAgent.indexOf('PhantomJS') === -1 ? [
        Logger.t('Editor size decrease content to 1000 based and restrict by max height', GeneralSteps.sequence([
          tinyApis.sSetSetting('autoresize_max_height', 200),
          tinyApis.sSetContent('<div style="height: 1000px;">a</div>'),
          Waiter.sTryUntil('wait for editor height', sAssertEditorHeightBelow(editor, 500), 10, 3000),
          tinyApis.sSetSetting('autoresize_max_height', 0)
        ])),
        Logger.t('Editor size decrease content to 10 and set min height to 500', GeneralSteps.sequence([
          tinyApis.sSetSetting('autoresize_min_height', 500),
          tinyApis.sSetContent('<div style="height: 10px;">a</div>'),
          Waiter.sTryUntil('wait for editor height', sAssertEditorHeightAbove(editor, 300), 10, 3000),
          tinyApis.sSetSetting('autoresize_min_height', 0)
        ]))
      ] : []
    ]), onSuccess, onFailure);
  }, {
    TinyLoader.setup((editor, onSuccess, onFailure) => {
      const doc = Element.fromDom(document);

      const tinyUi = TinyUi(editor);

      const sOpenStyleMenu = GeneralSteps.sequence([
        tinyUi.sClickOnToolbar('Clicking on the styleselect dropdown', 'button')
      ]);

      const navigationSteps = MenuNavigationTestUtils.generateNavigation(doc, assertions.navigation);

      Pipeline.async({}, Arr.flatten([
        [
          Assertions.sAssertPresence(
            `${assertions.choice.presence} should NOT be present`,
            {
              [assertions.choice.presence]: 0
            },
            Element.fromDom(editor.getBody())
          )
        ],
        [ sOpenStyleMenu ],
        navigationSteps,
        Arr.map(assertions.choice.keysBeforeExecute, (k) => Keyboard.sKeydown(doc, k, { })),
        [ Keyboard.sKeydown(doc, Keys.enter(), { }) ],
        [
          Assertions.sAssertPresence(
            `${assertions.choice.presence} should now be present`,
            {
              [assertions.choice.presence]: 1
            },
            Element.fromDom(editor.getBody())
          )
        ]
      ]), onSuccess, onFailure);
    }, {
Example #20
0
export const renderUrlInput = (spec: Types.UrlInput.UrlInput, backstage: UiFactoryBackstage, urlBackstage: UiFactoryBackstageForUrlInput): SketchSpec => {
  const providersBackstage = backstage.shared.providers;

  const updateHistory = (component: AlloyComponent): void => {
    const urlEntry = Representing.getValue(component);
    urlBackstage.addToHistory(urlEntry.value, spec.filetype);
  };

  // TODO: Make alloy's typeahead only swallow enter and escape if menu is open
  const pField = AlloyFormField.parts().field({
    factory: AlloyTypeahead,
    dismissOnBlur: true,
    inputClasses: ['tox-textfield'],
    sandboxClasses: ['tox-dialog__popups'],
    inputAttributes: {
      'aria-errormessage': errorId
    },
    minChars: 0,
    responseTime: 0,
    fetch: (input: AlloyComponent) => {
      const items = getItems(spec.filetype, input, urlBackstage);
      const tdata = NestedMenus.build(items, ItemResponse.BUBBLE_TO_SANDBOX, backstage);
      return Future.pure(tdata);
    },

    getHotspot: (comp) => memUrlBox.getOpt(comp),
    onSetValue: (comp, newValue) => {
      if (comp.hasConfigured(Invalidating)) {
        Invalidating.run(comp).get(Fun.noop);
      }
    },

    typeaheadBehaviours: Behaviour.derive(Arr.flatten([
      urlBackstage.getValidationHandler().map(
        (handler) => Invalidating.config({
          getRoot: (comp) => Traverse.parent(comp.element()),
          invalidClass: 'tox-control-wrap--status-invalid',
          notify: {
            onInvalid: (comp: AlloyComponent, err: string) => {
              memInvalidIcon.getOpt(comp).each((invalidComp) => {
                Attr.set(invalidComp.element(), 'title', providersBackstage.translate(err));
              });
            }
          },
          validator: {
            validate: (input) => {
              const urlEntry = Representing.getValue(input);
              return FutureResult.nu((completer) => {
                handler({ type: spec.filetype, url: urlEntry.value }, (validation) => {
                  completer((validation.status === 'invalid' ? Result.error : Result.value)(validation.message));
                });
              });
            },
            validateOnLoad: false
          }
        })
      ).toArray(),
      [
        Tabstopping.config({}),
        AddEventsBehaviour.config('urlinput-events', Arr.flatten([
          // We want to get fast feedback for the link dialog, but not sure about others
          spec.filetype === 'file' ? [
            AlloyEvents.run(NativeEvents.input(), (comp) => {
              AlloyTriggers.emitWith(comp, formChangeEvent, { name: spec.name });
            })
          ] : [ ],
          [
            AlloyEvents.run(NativeEvents.change(), (comp) => {
              AlloyTriggers.emitWith(comp, formChangeEvent, { name: spec.name });
              updateHistory(comp);
            }),
            AlloyEvents.run(SystemEvents.postPaste(), (comp) => {
              AlloyTriggers.emitWith(comp, formChangeEvent, { name: spec.name });
              updateHistory(comp);
            })
          ]
        ]))
      ]
    ])),

    eventOrder: {
      [NativeEvents.input()]: [ 'streaming', 'urlinput-events', 'invalidating' ]
    },

    model: {
      getDisplayText: (itemData) => {
        return itemData.value;
      },
      selectsOver: false,
      populateFromBrowse: false
    },

    markers: {
      // FIX:
      openClass: 'dog'
    },

    lazySink: backstage.shared.getSink,

    parts: {
      menu: MenuParts.part(false, 1, 'normal')
    },
    onExecute: (_menu, component, _entry) => {
      AlloyTriggers.emitWith(component, formSubmitEvent, {});
    },
    onItemExecute: (typeahead, _sandbox, _item, _value) => {
      updateHistory(typeahead);
      AlloyTriggers.emitWith(typeahead, formChangeEvent, { name: spec.name });
    }
  });

  const pLabel = spec.label.map((label) => renderLabel(label, providersBackstage)) as Option<AlloySpec>;

  // TODO: Consider a way of merging with Checkbox.
  const makeIcon = (name, errId: Option<string>, icon = name, label = name) => {
    return ({
      dom: {
        tag: 'div',
        classes: ['tox-icon', 'tox-control-wrap__status-icon-' + name],
        innerHtml: Icons.get(icon, providersBackstage.icons),
        attributes: {
          'title': providersBackstage.translate(label),
          'aria-live': 'polite',
          ...errId.fold(() => ({ }), (id) => ({ id }))
        }
      }
    });
  };

  const memInvalidIcon = Memento.record(
    makeIcon('invalid', Option.some(errorId), 'warning')
  );

  const memStatus = Memento.record({
    dom: {
      tag: 'div',
      classes: ['tox-control-wrap__status-icon-wrap']
    },
    components: [
      // Include the 'valid' and 'unknown' icons here only if they are to be displayed
      memInvalidIcon.asSpec()
    ]
  });

  const optUrlPicker = urlBackstage.getUrlPicker(spec.filetype);

  const browseUrlEvent = Id.generate('browser.url.event');

  const memUrlBox = Memento.record(
    {
      dom: {
        tag: 'div',
        classes: ['tox-control-wrap']
      },
      components: [pField, memStatus.asSpec()]
    }
  );

  const controlHWrapper = (): AlloySpec => {
    return {
      dom: {
        tag: 'div',
        classes: ['tox-form__controls-h-stack']
      },
      components: Arr.flatten([
        [memUrlBox.asSpec()],
        optUrlPicker.map(() => renderInputButton(spec.label, browseUrlEvent, 'tox-browse-url', 'browse', providersBackstage)).toArray()
      ])
    };
  };

  const openUrlPicker = (comp: AlloyComponent) => {
    Composing.getCurrent(comp).each((field) => {
      const urlData = Representing.getValue(field);
      optUrlPicker.each((picker) => {
        picker(urlData).get((chosenData) => {
          Representing.setValue(field, chosenData);
          AlloyTriggers.emitWith(comp, formChangeEvent, { name: spec.name });
        });
      });
    });
  };

  return AlloyFormField.sketch({
    dom: renderFormFieldDom(),
    components: pLabel.toArray().concat([
      controlHWrapper()
    ]),
    fieldBehaviours: Behaviour.derive([
      AddEventsBehaviour.config('url-input-events', [
        AlloyEvents.run<CustomEvent>(browseUrlEvent, openUrlPicker)
      ])
    ])
  });
};
Example #21
0
const makeItems = function (info: ImageDialogInfo) {
  const imageUrl = {
    name: 'src',
    type: 'urlinput',
    filetype: 'image',
    label: 'Source'
  };
  const imageList = info.imageList.map((items) => ({
    name: 'images',
    type: 'selectbox',
    label: 'Image list',
    items
  }));
  const imageDescription = {
    name: 'alt',
    type: 'input',
    label: 'Image description'
  };
  const imageTitle = {
    name: 'title',
    type: 'input',
    label: 'Image title'
  };
  const imageDimensions = {
    name: 'dimensions',
    type: 'sizeinput'
  };

  interface DialogItems {
    type: string;
    name?: string;
    label: string;
    items?: Array<DialogItems | ListItem>;
  }
  // TODO: the original listbox supported styled items but bridge does not seem to support this
  const classList = info.classList.map((items): DialogItems  => ({
    name: 'classes',
    type: 'selectbox',
    label: 'Class',
    items
  }));
  const caption: DialogItems = {
    type: 'label',
    label: 'Caption',
    items: [
      {
        type: 'checkbox',
        name: 'caption',
        label: 'Show caption'
      }
    ]
  };

  return Arr.flatten<any>([
    [imageUrl],
    imageList.toArray(),
    info.hasDescription ? [imageDescription] : [],
    info.hasImageTitle ? [imageTitle] : [],
    info.hasDimensions ? [imageDimensions] : [],
    [{
      type: 'grid',
      columns: 2,
      items: Arr.flatten([
        classList.toArray(),
        info.hasImageCaption ? [caption] : []
      ])
    }]
  ]);
};
Example #22
0
UnitTest.asynctest('browser.tinymce.core.caret.LineReader', (success, failure) => {
  const viewBlock = ViewBlock();
  const browser = PlatformDetection.detect().browser;

  interface Path {
    path: number[];
    offset: number;
  }

  const cSetHtml = (html: string) => {
    return Chain.op(function () {
      viewBlock.update(html);
    });
  };

  const logPositions = (msg, positions) => {
    Arr.each(positions, (pos) => {
      // tslint:disable-next-line:no-console
      console.log(msg, pos.container(), pos.offset(), pos.getClientRects());
    });
  };

  const cGetPositionsUntilPreviousLine = (path: number[], offset: number) => {
    return Chain.mapper(function (scope: any) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsUntilPreviousLine(scope.get(), pos);
    });
  };

  const cGetPositionsUntilNextLine = (path: number[], offset: number) => {
    return Chain.mapper(function (scope: any) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsUntilNextLine(scope.get(), pos);
    });
  };

  const cGetAbovePositions = (path: number[], offset: number) => {
    return Chain.mapper(function (scope: any) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsAbove(scope.get(), pos);
    });
  };

  const cGetBelowPositions = (path: number[], offset: number) => {
    return Chain.mapper(function (scope: any) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return getPositionsBelow(scope.get(), pos);
    });
  };

  const cFindClosestHorizontalPosition = (path: number[], offset: number) => {
    return Chain.mapper(function (positions: CaretPosition[]) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return findClosestHorizontalPosition(positions, pos);
    });
  };

  const cAssertCaretPositions = (expectedPositions: Path[]) => {
    return Chain.op(function (actualPositions: CaretPosition[]) {
      if (expectedPositions.length !== actualPositions.length) {
        logPositions('cAssertCaretPositions', actualPositions);
      }

      Assertions.assertEq('Should be the expected amount of positions', expectedPositions.length, actualPositions.length);
      Arr.each(expectedPositions, (p: Path, i) => {
        const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), p.path).getOrDie();
        Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(actualPositions[i].container()));
        Assertions.assertEq('Should be the expected offset', p.offset, actualPositions[i].offset());
      });
    });
  };

  const cAssertNone = Chain.op(function (a: Option<any>) {
    Assertions.assertEq('Option return value should be none', true, a.isNone());
  });

  const cAssertLineInfoCaretPositions = (expectedPositions: Path[]) => {
    return Chain.op(function (lineInfo: LineInfo) {
      const actualPositions = lineInfo.positions;

      if (expectedPositions.length !== actualPositions.length) {
        if (expectedPositions.length !== actualPositions.length) {
          logPositions('cAssertLineInfoCaretPositions', actualPositions);
        }
      }

      Assertions.assertEq('Should be the expected amount of positions', expectedPositions.length, actualPositions.length);
      Arr.each(expectedPositions, (p: Path, i) => {
        const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), p.path).getOrDie();
        Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(actualPositions[i].container()));
        Assertions.assertEq('Should be the expected offset', p.offset, actualPositions[i].offset());
      });
    });
  };

  const cAssertBreakPositionNone = Chain.op(function (linebreak: LineInfo) {
    Assertions.assertEq('Should not be a line break position', true, linebreak.breakAt.isNone());
  });

  const cAssertBreakPosition = (path: number[], offset: number) => {
    return Chain.op(function (linebreak: LineInfo) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), path).getOrDie();
      const breakPos = linebreak.breakAt.getOrDie();

      Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(breakPos.container()));
      Assertions.assertEq('Should be the expected offset', offset, breakPos.offset());
    });
  };

  const cAssertBreakType = (expectedBreakType: BreakType) => {
    return Chain.op(function (linebreak: LineInfo) {
      const actualBreakType = linebreak.breakType;
      Assertions.assertEq('Should be the expected break type',  expectedBreakType, actualBreakType);
    });
  };

  const cAssertCaretPosition = (path: number[], offset: number) => {
    return Chain.op(function (posOption: Option<any>) {
      const container = Hierarchy.follow(Element.fromDom(viewBlock.get()), path).getOrDie();
      const pos = posOption.getOrDie('Needs to return a caret');

      Assertions.assertDomEq('Should be the expected container', container, Element.fromDom(pos.container()));
      Assertions.assertEq('Should be the expected offset', offset, pos.offset());
    });
  };

  const cVisualCaretCheck = (predicate, path: number[], offset: number) => {
    return Chain.mapper(function (scope: any) {
      const container = Hierarchy.follow(Element.fromDom(scope.get()), path).getOrDie();
      const pos = CaretPosition(container.dom(), offset);
      return predicate(scope.get(), pos);
    });
  };

  const cIsAtFirstLine = Fun.curry(cVisualCaretCheck, isAtFirstLine);
  const cIsAtLastLine = Fun.curry(cVisualCaretCheck, isAtLastLine);

  viewBlock.attach();
  Pipeline.async({}, [
    Logger.t('getPositionsUntilPreviousLine', GeneralSteps.sequence([
      Logger.t('Should be an empty array of positions and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetPositionsUntilPreviousLine([0, 0], 0),
        cAssertLineInfoCaretPositions([]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with the first position and second position and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetPositionsUntilPreviousLine([0, 0], 2),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 0 },
          { path: [0, 0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line 1 <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cGetPositionsUntilPreviousLine([0, 2], 1),
        cAssertLineInfoCaretPositions([
          { path: [0, 2], offset: 0 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line 2 <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>bc</p>'),
        cGetPositionsUntilPreviousLine([0, 2], 1),
        cAssertLineInfoCaretPositions([
          { path: [0, 2], offset: 0 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line <p>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cGetPositionsUntilPreviousLine([1, 0], 1),
        cAssertLineInfoCaretPositions([
          { path: [1, 0], offset: 0 }
        ]),
        cAssertBreakType(BreakType.Block),
        cAssertBreakPosition([0, 0], 1)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the first line (wrap)', Chain.asStep(viewBlock, Arr.flatten([
        [
          cSetHtml('<div style="width: 10px">abc def ghi</div>'),
          cGetPositionsUntilPreviousLine([0, 0], 6)
        ],
        browser.isSafari() ? [
          cAssertLineInfoCaretPositions([
            { path: [0, 0], offset: 4 },
            { path: [0, 0], offset: 5 }
          ]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 3)
        ] : [
          cAssertLineInfoCaretPositions([
            { path: [0, 0], offset: 5 }
          ]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 4)
        ]
      ]))),
      Logger.t('Should be an array with zero positions from the second line and a break on the first line', Chain.asStep(viewBlock, Arr.flatten([
        [
          cSetHtml('<div style="width: 10px">abc def ghi</div>'),
          cGetPositionsUntilPreviousLine([0, 0], 5)
        ],
        browser.isSafari() ? [
          cAssertLineInfoCaretPositions([
            { path: [0, 0], offset: 4 }
          ]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 3)
        ] : [
          cAssertLineInfoCaretPositions([]),
          cAssertBreakType(BreakType.Wrap),
          cAssertBreakPosition([0, 0], 4)
        ]
      ])))
    ])),

    Logger.t('getPositionsUntilNextLine', GeneralSteps.sequence([
      Logger.t('Should be an empty array of positions and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetPositionsUntilNextLine([0, 0], 1),
        cAssertLineInfoCaretPositions([]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with the first position and second position and no linebreak', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetPositionsUntilNextLine([0, 0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 1 },
          { path: [0, 0], offset: 2 }
        ]),
        cAssertBreakType(BreakType.Eol),
        cAssertBreakPositionNone
      ])),
      Logger.t('Should be an array with one position from the first line and a break on the first line <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cGetPositionsUntilNextLine([0, 0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 1 },
          { path: [0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the first line and a break on the first line <br>', Chain.asStep(viewBlock, [
        cSetHtml('<p><input><br>b</p>'),
        cGetPositionsUntilNextLine([0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Br),
        cAssertBreakPosition([0], 1)
      ])),
      Logger.t('Should be an array with one position from the first line and a break on the first line <p>', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cGetPositionsUntilNextLine([0, 0], 0),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 1 }
        ]),
        cAssertBreakType(BreakType.Block),
        cAssertBreakPosition([1, 0], 0)
      ])),
      Logger.t('Should be an array with one position from the second line and a break on the last line', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cGetPositionsUntilNextLine([0, 0], 6),
        cAssertLineInfoCaretPositions([
          { path: [0, 0], offset: 7 }
        ]),
        cAssertBreakType(BreakType.Wrap),
        cAssertBreakPosition([0, 0], 8)
      ])),
      Logger.t('Should be an array with zero positions from the second line and a break on the last line', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cGetPositionsUntilNextLine([0, 0], 7),
        cAssertLineInfoCaretPositions([]),
        cAssertBreakType(BreakType.Wrap),
        cAssertBreakPosition([0, 0], 8)
      ]))
    ])),

    Logger.t('isAtFirstLine', GeneralSteps.sequence([
      Logger.t('Should return true at first visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtFirstLine([0, 0], 0),
        Assertions.cAssertEq('Should be true on first position in paragraph', true)
      ])),
      Logger.t('Should return true at second visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtFirstLine([0, 0], 1),
        Assertions.cAssertEq('Should be true on second position in paragraph', true)
      ])),
      Logger.t('Should return false at second br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtFirstLine([0, 2], 0),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at second pos after br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtFirstLine([0, 2], 1),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at second paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cIsAtFirstLine([1, 0], 0),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at second line in a wrapped element', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cIsAtFirstLine([0, 0], 4),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return true at paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p></td></tr></tbody></table>'),
        cIsAtFirstLine([0, 0, 0, 0, 0, 0], 0),
        Assertions.cAssertEq('Should be true since it is the first line in td', true)
      ])),
      Logger.t('Should return false at second paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        cIsAtFirstLine([0, 0, 0, 0, 1, 0], 0),
        Assertions.cAssertEq('Should be false since it is the second line in td', false)
      ]))
    ])),

    Logger.t('isAtLastLine', GeneralSteps.sequence([
      Logger.t('Should return true at first visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtLastLine([0, 0], 0),
        Assertions.cAssertEq('Should be true on first position in paragraph', true)
      ])),
      Logger.t('Should return true at second visual position in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cIsAtLastLine([0, 0], 1),
        Assertions.cAssertEq('Should be true on second position in paragraph', true)
      ])),
      Logger.t('Should return false at before first br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtLastLine([0, 0], 0),
        Assertions.cAssertEq('Should be false on first line in paragraph', false)
      ])),
      Logger.t('Should return false at first line at second pos before br line in paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<br>b</p>'),
        cIsAtLastLine([0, 0], 1),
        Assertions.cAssertEq('Should be false on first line in paragraph', false)
      ])),
      Logger.t('Should return false at first paragraph', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>b</p>'),
        cIsAtLastLine([0, 0], 0),
        Assertions.cAssertEq('Should be false on first paragraph line', false)
      ])),
      Logger.t('Should return false at second line in a wrapped element', Chain.asStep(viewBlock, [
        cSetHtml('<div style="width: 10px">abc def ghi</div>'),
        cIsAtLastLine([0, 0], 6),
        Assertions.cAssertEq('Should be false on second line in paragraph', false)
      ])),
      Logger.t('Should return false at first paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        cIsAtLastLine([0, 0, 0, 0, 0, 0], 0),
        Assertions.cAssertEq('Should be false since it is the first line in td', false)
      ])),
      Logger.t('Should return true at second paragraph in td', Chain.asStep(viewBlock, [
        cSetHtml('<table><tbody><tr><td><p>a</p><p>b</p></td></tr></tbody></table>'),
        cIsAtLastLine([0, 0, 0, 0, 1, 0], 0),
        Assertions.cAssertEq('Should be true since it is the second line in td', true)
      ]))
    ])),

    Logger.t('getAbovePositions', GeneralSteps.sequence([
      Logger.t('Should return zero positions since there is no line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetAbovePositions([0, 0], 1),
        cAssertCaretPositions([])
      ])),
      Logger.t('Should return three positions for the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>a</p>'),
        cGetAbovePositions([1, 0], 0),
        cAssertCaretPositions([
          { path: [0, 0], offset: 0 },
          { path: [0, 0], offset: 1 },
          { path: [0, 0], offset: 2 }
        ])
      ])),
      Logger.t('Should return four positions for the line above2', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a</p>'),
        cGetAbovePositions([1, 0], 0),
        cAssertCaretPositions([
          { path: [0, 0], offset: 0 },
          { path: [0, 0], offset: 1 },
          { path: [0], offset: 1 },
          { path: [0], offset: 2 },
          { path: [0, 2], offset: 0 },
          { path: [0, 2], offset: 1 }
        ])
      ]))
    ])),

    Logger.t('getBelowPositions', GeneralSteps.sequence([
      Logger.t('Should return zero positions since there is no line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p>'),
        cGetBelowPositions([0, 0], 0),
        cAssertCaretPositions([])
      ])),
      Logger.t('Should return three positions for the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>ab</p>'),
        cGetBelowPositions([0, 0], 0),
        cAssertCaretPositions([
          { path: [1, 0], offset: 0 },
          { path: [1, 0], offset: 1 },
          { path: [1, 0], offset: 2 }
        ])
      ])),
      Logger.t('Should return five positions for the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a</p><p>a<input>b</p>'),
        cGetBelowPositions([0, 0], 0),
        cAssertCaretPositions([
          { path: [1, 0], offset: 0 },
          { path: [1, 0], offset: 1 },
          { path: [1], offset: 1 },
          { path: [1], offset: 2 },
          { path: [1, 2], offset: 0 },
          { path: [1, 2], offset: 1 }
        ])
      ]))
    ])),

    Logger.t('findClosestHoriontalPosition (above)', GeneralSteps.sequence([
      Logger.t('Should not return a position since there is no above positions', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetAbovePositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertNone
      ])),
      Logger.t('Should return first caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetAbovePositions([1, 0], 0),
        cFindClosestHorizontalPosition([1, 0], 0),
        cAssertCaretPosition([0, 0], 0)
      ])),
      Logger.t('Should return last caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetAbovePositions([1, 0], 0),
        cFindClosestHorizontalPosition([1, 0], 2),
        cAssertCaretPosition([0, 0], 2)
      ])),
      Logger.t('Should return first indexed caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetAbovePositions([1], 0),
        cFindClosestHorizontalPosition([1], 0),
        cAssertCaretPosition([0], 0)
      ])),
      Logger.t('Should return first indexed caret position on the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetAbovePositions([1], 0),
        cFindClosestHorizontalPosition([1], 1),
        cAssertCaretPosition([0], 1)
      ])),
      Logger.t('Should return last text node position at the line above', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a<input>b</p>'),
        cGetAbovePositions([1, 2], 0),
        cFindClosestHorizontalPosition([1, 2], 0),
        cAssertCaretPosition([0, 2], 0)
      ]))
    ])),

    Logger.t('findClosestHoriontalPosition (below)', GeneralSteps.sequence([
      Logger.t('Should not return a position since there is no below positions', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertNone
      ])),
      Logger.t('Should return first caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertCaretPosition([1, 0], 0)
      ])),
      Logger.t('Should return last caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>ab</p><p>cd</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 2),
        cAssertCaretPosition([1, 0], 2)
      ])),
      Logger.t('Should return first indexed caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetBelowPositions([0], 0),
        cFindClosestHorizontalPosition([0], 0),
        cAssertCaretPosition([1], 0)
      ])),
      Logger.t('Should return first indexed caret position on the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p><input></p><p><input></p>'),
        cGetBelowPositions([0], 0),
        cFindClosestHorizontalPosition([0], 1),
        cAssertCaretPosition([1], 1)
      ])),
      Logger.t('Should return first text node position at the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a<input>b</p>'),
        cGetBelowPositions([0, 0], 0),
        cFindClosestHorizontalPosition([0, 0], 0),
        cAssertCaretPosition([1, 0], 0)
      ])),
      Logger.t('Should return last text node position at the line below', Chain.asStep(viewBlock, [
        cSetHtml('<p>a<input>b</p><p>a<input>b</p>'),
        cGetBelowPositions([0, 2], 0),
        cFindClosestHorizontalPosition([0, 2], 0),
        cAssertCaretPosition([1, 2], 0)
      ]))
    ]))
  ], function () {
    viewBlock.detach();
    success();
  }, failure);
});
Example #23
0
const factory: UiSketcher.SingleSketchFactory<NotificationSketchDetail, NotificationSketchSpec> = (detail) => {
  // For using the alert banner as a standalone banner
  const memBannerText = Memento.record({
    dom: {
      tag: 'p',
      innerHtml: detail.translationProvider(detail.text)
    },
    behaviours: Behaviour.derive([
      Replacing.config({ })
    ])
  });

  const renderPercentBar = (percent: number) => ({
    dom: {
      tag: 'div',
      classes: [ 'tox-bar' ],
      attributes: {
        style: `width: ${percent}%`
      }
    }
  });

  const renderPercentText = (percent: number) => ({
    dom: {
      tag: 'div',
      classes: [ 'tox-text' ],
      innerHtml: `${percent}%`
    }
  });

  const memBannerProgress = Memento.record({
    dom: {
      tag: 'div',
      classes: detail.progress ? [ 'tox-progress-bar', 'tox-progress-indicator' ] : [ 'tox-progress-bar' ]
    },
    components: [
      {
        dom: {
          tag: 'div',
          classes: [ 'tox-bar-container' ]
        },
        components: [
          renderPercentBar(0)
        ]
      },
      renderPercentText(0)
    ],
    behaviours: Behaviour.derive([
      Replacing.config({ })
    ])
  });

  const updateProgress: NotificationSketchApis['updateProgress'] = (comp, percent) => {
    if (comp.getSystem().isConnected()) {
      memBannerProgress.getOpt(comp).each((progress) => {
        Replacing.set(progress, [
          {
            dom: {
              tag: 'div',
              classes: [ 'tox-bar-container' ]
            },
            components: [
              renderPercentBar(percent)
            ]
          },
          renderPercentText(percent)
        ]);
      });
    }
  };

  const updateText: NotificationSketchApis['updateText'] = (comp, text) => {
    if (comp.getSystem().isConnected()) {
      const banner = memBannerText.get(comp);
      Replacing.set(banner, [
        GuiFactory.text(text)
      ]);
    }
  };

  const apis: NotificationSketchApis = {
    updateProgress,
    updateText
  };

  const iconChoices = Arr.flatten([
    detail.icon.toArray(),
    detail.level.toArray(),
    detail.level.bind((level) => Option.from(notificationIconMap[level])).toArray()
  ]);

  return {
    uid: detail.uid,
    dom: {
      tag: 'div',
      attributes: {
        role: 'alert'
      },
      classes: detail.level.map((level) => [ 'tox-notification', 'tox-notification--in', `tox-notification--${level}` ]).getOr(
        [ 'tox-notification', 'tox-notification--in' ]
      )
    },
    components: [{
        dom: {
          tag: 'div',
          classes: [ 'tox-notification__icon' ],
          innerHtml: getFirst(iconChoices, detail.iconProvider)
        }
      } as AlloySpec,
      {
        dom: {
          tag: 'div',
          classes: [ 'tox-notification__body'],
        },
        components: [
          memBannerText.asSpec()
        ],
        behaviours: Behaviour.derive([
          Replacing.config({ })
        ])
      } as AlloySpec
    ]
    .concat(detail.progress ? [memBannerProgress.asSpec()] : [])
    .concat(Button.sketch({
        dom: {
          tag: 'button',
          classes: [ 'tox-notification__dismiss', 'tox-button', 'tox-button--naked', 'tox-button--icon' ]
        },
        components: [{
          dom: {
            tag: 'div',
            classes: ['tox-icon'],
            innerHtml: getIcon('close', detail.iconProvider),
            attributes: {
              'aria-label': detail.translationProvider('Close')
            }
          }
        }],
        action: (comp) => {
          detail.onAction(comp);
        }
      })
    ),
    apis
  };
};
Example #24
0
const makeDialog = (settings: LinkDialogInfo, onSubmit, editorSettings): Types.Dialog.DialogApi<LinkDialogData> => {

  const urlInput: Types.Dialog.BodyComponentApi[] = [
    {
      name: 'url',
      type: 'urlinput',
      filetype: 'file',
      label: 'URL'
    }
  ];

  const displayText = settings.anchor.text.map<Types.Dialog.BodyComponentApi>(() => (
    {
      name: 'text',
      type: 'input',
      label: 'Text to display'
    }
  )).toArray();

  const titleText: Types.Dialog.BodyComponentApi[] = settings.flags.titleEnabled ? [
    {
      name: 'title',
      type: 'input',
      label: 'Title'
    }
  ] : [];

  const defaultTarget: Option<string> = Settings.hasDefaultLinkTarget(editorSettings) ? Option.some(Settings.getDefaultLinkTarget(editorSettings)) : Option.none();

  const initialData = getInitialData(settings, defaultTarget);
  const dialogDelta = DialogChanges.init(initialData, settings);
  const catalogs = settings.catalogs;

  const body: Types.Dialog.PanelApi = {
    type: 'panel',
    items: Arr.flatten([
      urlInput,
      displayText,
      titleText,
      Options.cat<Types.Dialog.BodyComponentApi>([
        catalogs.anchor.map(ListOptions.createUi('anchor', 'Anchors')),
        catalogs.rels.map(ListOptions.createUi('rel', 'Rel')),
        catalogs.targets.map(ListOptions.createUi('target', 'Open link in...')),
        catalogs.link.map(ListOptions.createUi('link', 'Link list')),
        catalogs.classes.map(ListOptions.createUi('linkClass', 'Class'))
      ])
    ])
  };
  return {
    title: 'Insert/Edit Link',
    size: 'normal',
    body,
    buttons: [
      {
        type: 'cancel',
        name: 'cancel',
        text: 'Cancel'
      },
      {
        type: 'submit',
        name: 'save',
        text: 'Save',
        primary: true
      }
    ],
    initialData,
    onChange: (api: Types.Dialog.DialogInstanceApi<LinkDialogData>, {name}) => {
      dialogDelta.onChange(api.getData, { name }).each((newData) => {
        api.setData(newData);
      });
    },
    onSubmit
  };
};
Example #25
0
const showDialog = function (editor: Editor) {
  const editorData = getEditorData(editor);

  const defaultData: DialogData = {
    source1: '',
    source2: '',
    embed: getSource(editor),
    poster: '',
    dimensions: {
      height: editorData.height ? editorData.height : '',
      width: editorData.width ? editorData.width : ''
    }
  };

  // This is a bit of a lie because the editor data doesn't have dimensions sub-objects, but that's handled above
  const initialData: DialogData = wrap(Merger.merge(defaultData, editorData));

  const getSourceData = (api) => {
    const data = unwrap(api.getData());

    return Settings.hasDimensions(editor) ? Merger.merge(data, {
      width: data.dimensions.width,
      height: data.dimensions.height
    }) : data;
  };

  const handleSource1 = (api) => {
    const serviceData = getSourceData(api);
    Service.getEmbedHtml(editor, serviceData)
      .then(addEmbedHtml(win, editor))
      .catch(handleError(editor));
  };

  const handleEmbed = (api) => {
    const data = unwrap(api.getData());

    const dataFromEmbed = snippetToData(editor, data.embed);
    dataFromEmbed.dimensions = {
      width: dataFromEmbed.width ? dataFromEmbed.width : data.dimensions.width,
      height: dataFromEmbed.height ? dataFromEmbed.height : data.dimensions.height
    };

    api.setData(wrap(dataFromEmbed));
  };

  const mediaInput: Types.Dialog.BodyComponentApi[] = [{
    name: 'source1',
    type: 'urlinput',
    filetype: 'media',
    label: 'Source'
  }];
  const sizeInput: Types.Dialog.BodyComponentApi[] = !Settings.hasDimensions(editor) ? [] : [{
    type: 'sizeinput',
    name: 'dimensions',
    label: 'Constrain proportions',
    constrain: true
  }];

  const generalTab = {
      title: 'General',
      items: Arr.flatten<Types.Dialog.BodyComponentApi>([mediaInput, sizeInput])
    };

  const embedTextarea: Types.Dialog.BodyComponentApi = {
    type: 'textarea',
    name: 'embed',
    label: 'Paste your embed code below:'
  };
  const embedTab = {
    title: 'Embed',
    items: [
      embedTextarea
    ]
  };

  const advancedFormItems: Types.Dialog.BodyComponentApi[] = [];

  if (Settings.hasAltSource(editor)) {
    advancedFormItems.push({
        name: 'source2',
        type: 'urlinput',
        filetype: 'media',
        label: 'Alternative source URL'
      }
    );
  }

  if (Settings.hasPoster(editor)) {
    advancedFormItems.push({
      name: 'poster',
      type: 'urlinput',
      filetype: 'image',
      label: 'Media poster (Image URL)'
    });
  }

  const advancedTab = {
    title: 'Advanced',
    items: advancedFormItems
  };

  const tabs = [
    generalTab,
    embedTab
  ];

  if (advancedFormItems.length > 0) {
    tabs.push(advancedTab);
  }

  const body: Types.Dialog.TabPanelApi = {
    type: 'tabpanel',
    tabs
  };
  const win = editor.windowManager.open({
    title: 'Insert/Edit Media',
    size: 'normal',

    body,
    buttons: [
      {
        type: 'cancel',
        name: 'cancel',
        text: 'Cancel'
      },
      {
        type: 'submit',
        name: 'save',
        text: 'Save',
        primary: true
      }
    ],
    onSubmit (api) {
      const serviceData = getSourceData(api);
      submitForm(serviceData, editor);
      api.close();
    },
    onChange (api, detail) {
      switch (detail.name) {
        case 'source1':
          handleSource1(api);
          break;

        case 'embed':
          handleEmbed(api);
          break;

        default:
          break;
      }
    },
    initialData
  });
};
Example #26
0
    (doc, body, gui, component: AlloyComponent, store) => {
      const getButton = (selector: string) => {
        return component.getSystem().getByDom(
          SelectorFind.descendant(component.element(), selector).getOrDie(
            `Could not find button defined by: ${selector}`
          )
        ).getOrDie();
      };

      const sAssertButtonDisabledState = (label: string, expected: boolean, button: AlloyComponent) => {
        return Step.sync(() => {
          Assertions.assertEq('Checking if disabled attr is present: ' + label, expected, Attr.has(button.element(), 'disabled'));
        });
      };

      const sAssertButtonActiveState = (label: string, expected: boolean, button: AlloyComponent) => {
        return Step.sync(() => {
          Assertions.assertEq(label, expected, Class.has(button.element(), 'tox-tbtn--enabled'));
        });
      };

      const sAssertSplitButtonDisabledState = (label: string, expected: boolean, button: AlloyComponent) => {
        return Step.sync(() => {
          Assertions.assertEq('Checking if aria-disabled attr is present: ' + label, expected, Attr.get(button.element(), 'aria-disabled') === 'true');
          Assertions.assertEq('Checking if disabled class is present: ' + label, expected, Class.has(button.element(), 'tox-tbtn--disabled'));
        });
      };

      const sAssertSplitButtonActiveState = (label: string, expected: boolean, button: AlloyComponent) => {
        return Step.sync(() => {
          Assertions.assertEq(label, expected, Attr.get(button.element(), 'aria-pressed') === 'true');
        });
      };

      return Arr.flatten([
        (() => {
          return [
            store.sAssertEq('Store should have setups only', ['onSetup.1', 'onSetup.2', 'onSetup.3', 'onSetup.4']),
            store.sClear
          ];
        })(),

        (() => {
          const button1 = getButton('.button1-container .tox-tbtn');
          return Logger.ts(
            'First button (button1): normal button',
            [
              Assertions.sAssertStructure(
                'Checking initial structure',
                ApproxStructure.build((s, str, arr) => {
                  return s.element('button', {
                    classes: [ arr.has('tox-tbtn') ],
                    attrs: {
                      'type': str.is('button'),
                      'title': str.is('tooltip'),
                      'aria-label': str.is('tooltip')
                    },
                    children: [
                      s.element('span', {
                        classes: [ arr.has('tox-tbtn__select-label') ]
                      })
                    ]
                  });
                }),
                button1.element()
              ),
              Mouse.sClickOn(component.element(), '.button1-container .tox-tbtn'),
              store.sAssertEq('Store should now have action1', [ 'onAction.1' ]),
              sAssertButtonDisabledState('Enabled', false, button1),
              store.sClear,

              Step.sync(() => {
                shouldDisable.set(true);
              }),
              Mouse.sClickOn(component.element(), '.button1-container .tox-tbtn'),
              store.sAssertEq('Store have action', [ 'onAction.1' ]),
              sAssertButtonDisabledState('Disabled', true, button1),
              store.sClear,
              Mouse.sClickOn(component.element(), '.button1-container .tox-tbtn'),
              store.sAssertEq('No actions should come through a disabled button', [ ])
            ]
          );
        })(),

        (() => {
          const button2 = getButton('.button2-container .tox-tbtn');

          return Logger.ts('Second button (button2): toggle button', [
            Step.sync(() => {
              shouldDisable.set(false);
              shouldActivate.set(false);
            }),
            Mouse.sClickOn(component.element(), '.button2-container .tox-tbtn'),
            store.sAssertEq('Store should have action2', [ 'onToggleAction.2' ]),
            store.sClear,
            sAssertButtonDisabledState('Enabled', false, button2),
            sAssertButtonActiveState('Off', false, button2),

            Step.sync(() => {
              shouldActivate.set(true);
            }),
            Mouse.sClickOn(component.element(), '.button2-container .tox-tbtn'),
            store.sAssertEq('Store should have action2', [ 'onToggleAction.2' ]),
            store.sClear,
            sAssertButtonDisabledState('Disabled', false, button2),
            sAssertButtonActiveState('Off', true, button2),

            Step.sync(() => {
              shouldActivate.set(false);
            }),
            Mouse.sClickOn(component.element(), '.button2-container .tox-tbtn'),
            store.sAssertEq('Store should have action2', [ 'onToggleAction.2' ]),
            store.sClear,
            sAssertButtonDisabledState('Disabled', false, button2),
            sAssertButtonActiveState('Off', false, button2),

            Step.sync(() => {
              shouldDisable.set(true);
            }),
            Mouse.sClickOn(component.element(), '.button2-container .tox-tbtn'),
            store.sAssertEq('Store should now have action2', [ 'onToggleAction.2' ]),
            store.sClear,
            sAssertButtonDisabledState('Disabled', true, button2),
            sAssertButtonActiveState('Off still', false, button2)
          ]);
        })(),

        (() => {
          const button3 = getButton('.button3-container .tox-split-button');

          return Logger.ts('Third button (button3): split button', [
            Assertions.sAssertStructure(
              'Checking initial structure',
              ApproxStructure.build((s, str, arr) => {
                return s.element('div', {
                  classes: [ arr.has('tox-split-button') ],
                  attrs: {
                    'role': str.is('button'),
                    'title': str.is('tooltip'),
                    'aria-label': str.is('tooltip'),
                    'aria-expanded': str.is('false'),
                    'aria-haspopup': str.is('true'),
                    'aria-pressed': str.is('false')
                  },
                  children: [
                    s.element('span', {
                      attrs: {
                        role: str.is('presentation')
                      },
                      classes: [ arr.has('tox-tbtn'), arr.has('tox-tbtn--select') ]
                    }),
                    s.element('span', {
                      attrs: {
                        role: str.is('presentation')
                      },
                      classes: [ arr.has('tox-tbtn'), arr.has('tox-split-button__chevron') ]
                    }),
                    s.element('span', {
                      attrs: {
                        'aria-hidden': str.is('true'),
                        'style': str.contains('display: none;')
                      },
                      children: [
                        s.text(str.is('To open the popup, press Shift+Enter'))
                      ]
                    })
                  ]
                });
              }),
              button3.element()
            ),

            Step.sync(() => {
              shouldDisable.set(false);
              shouldActivate.set(false);
            }),
            // Toggle button
            Mouse.sClickOn(component.element(), '.button3-container .tox-split-button .tox-tbtn'),
            store.sAssertEq('Store should have action3', [ 'onToggleAction.3' ]),
            store.sClear,
            sAssertSplitButtonDisabledState('Enabled', false, button3),
            sAssertSplitButtonActiveState('Off', false, button3),

            // Menu item selected
            Mouse.sClickOn(component.element(), '.button3-container .tox-split-button .tox-split-button__chevron'),
            Waiter.sTryUntil('Wait for split button menu item to show',
              Mouse.sClickOn(body, '.tox-collection .tox-collection__item'),
              100, 1000
            ),
            store.sAssertEq('Store should have item action3', [ 'onItemAction.3' ]),
            store.sClear,
            sAssertSplitButtonDisabledState('Enabled', false, button3),
            sAssertSplitButtonActiveState('Off', true, button3),

            Step.sync(() => {
              shouldActivate.set(true);
            }),
            Mouse.sClickOn(component.element(), '.button3-container .tox-split-button .tox-tbtn'),
            store.sAssertEq('Store should have action3', [ 'onToggleAction.3' ]),
            store.sClear,
            sAssertSplitButtonDisabledState('Disabled', false, button3),
            sAssertSplitButtonActiveState('Off', true, button3),

            Step.sync(() => {
              shouldActivate.set(false);
            }),
            Mouse.sClickOn(component.element(), '.button3-container .tox-split-button .tox-tbtn'),
            store.sAssertEq('Store should have action3', [ 'onToggleAction.3' ]),
            store.sClear,
            sAssertSplitButtonDisabledState('Disabled', false, button3),
            sAssertSplitButtonActiveState('Off', false, button3),

            Step.sync(() => {
              shouldDisable.set(true);
            }),
            Mouse.sClickOn(component.element(), '.button3-container .tox-split-button .tox-tbtn'),
            store.sAssertEq('Store should now have action3', [ 'onToggleAction.3' ]),
            store.sClear,
            sAssertSplitButtonDisabledState('Disabled', true, button3),
            sAssertSplitButtonActiveState('Off still', false, button3)
          ]);
        })(),

        (() => {
          const button4 = getButton('.button4-container .tox-mbtn');

          return Logger.ts('Fourth button (button4): menu button', [
            Assertions.sAssertStructure(
              'Checking initial structure',
              ApproxStructure.build((s, str, arr) => {
                return s.element('button', {
                  classes: [
                    arr.has('tox-mbtn'),
                    arr.has('tox-mbtn--select')
                  ],
                  attrs: {
                    'type': str.is('button'),
                    'title': str.is('tooltip'),
                    'aria-label': str.is('tooltip'),
                    'aria-expanded': str.is('false'),
                    'aria-haspopup': str.is('true')
                  },
                  children: [
                    s.element('span', {
                      classes: [ arr.has('tox-mbtn__select-label') ]
                    }),
                    s.element('div', {
                      classes: [ arr.has('tox-mbtn__select-chevron') ]
                    })
                  ]
                });
              }),
              button4.element()
            ),

            // Select menu item
            Mouse.sClickOn(component.element(), '.button4-container .tox-mbtn'),
            Waiter.sTryUntil('Wait for button menu to show',
              Mouse.sClickOn(body, '.tox-collection .tox-collection__item'),
              100, 1000
            ),
            store.sAssertEq('Store should have item action4', [ 'onAction.4' ]),
            store.sClear
          ]);
        })()
      ]);
    },
Example #27
0
const renderTextField = function (spec: TextFieldFoo, providersBackstage: UiFactoryBackstageProviders) {
  const pLabel = spec.label.map((label) => renderLabel(label, providersBackstage));

  const baseInputBehaviours = [
    Keying.config({
      mode: 'execution',
      useEnter: spec.multiline !== true,
      useControlEnter: spec.multiline === true,
      execute: (comp) => {
        AlloyTriggers.emit(comp, formSubmitEvent);
        return Option.some(true);
      },
    }),
    AddEventsBehaviour.config('textfield-change', [
      AlloyEvents.run(NativeEvents.input(), (component, _) => {
        AlloyTriggers.emitWith(component, formChangeEvent, { name: spec.name } );
      }),
      AlloyEvents.run(SystemEvents.postPaste(), (component, _) => {
        AlloyTriggers.emitWith(component, formChangeEvent, { name: spec.name } );
      })
    ]),
    Tabstopping.config({})
  ];

  const validatingBehaviours = spec.validation.map((vl) => {
    return Invalidating.config({
      getRoot(input) {
        return Traverse.parent(input.element());
      },
      invalidClass: 'tox-invalid',
      validator: {
        validate(input) {
          const v = Representing.getValue(input);
          const result = vl.validator(v);
          return Future.pure(result === true ? Result.value(v) : Result.error(result));
        },
        validateOnLoad: vl.validateOnLoad
      }
    });
  }).toArray();

  const pField = AlloyFormField.parts().field({
    tag: spec.multiline === true ? 'textarea' : 'input',
    inputAttributes: spec.placeholder.fold(
      () => {},
      (placeholder) => ({ placeholder: providersBackstage.translate(placeholder) })
    ),
    inputClasses: [spec.classname],
    inputBehaviours: Behaviour.derive(
      Arr.flatten<Behaviour.NamedConfiguredBehaviour<Behaviour.BehaviourConfigSpec, Behaviour.BehaviourConfigDetail>>([
        baseInputBehaviours,
        validatingBehaviours
      ])
    ),
    selectOnFocus: false,
    factory: AlloyInput
  });

  const extraClasses = spec.flex ? ['tox-form__group--stretched'] : [];

  return renderFormFieldWith(pLabel, pField, extraClasses);
};
Example #28
0
const setup = (editor: Editor): RenderInfo => {
  const isInline = editor.getParam('inline', false, 'boolean');
  const mode = isInline ? Inline : Iframe;
  let lazyOuterContainer: Option<AlloyComponent> = Option.none();

  const dirAttributes = I18n.isRtl() ? {
    attributes: {
      dir: 'rtl'
    }
  } : {};

  const sink = GuiFactory.build({
    dom: {
      tag: 'div',
      classes: ['tox', 'tox-silver-sink', 'tox-tinymce-aux'],
      ...dirAttributes
    },
    behaviours: Behaviour.derive([
      Positioning.config({
        useFixed: false // this allows menus to scroll with the outer page, we don't want position: fixed
      })
    ])
  });

  const memAnchorBar = Memento.record({
    dom: {
      tag: 'div',
      classes: [ 'tox-anchorbar']
    }
  });

  const lazyAnchorBar = () => lazyOuterContainer.bind((container) => {
    return memAnchorBar.getOpt(container);
  }).getOrDie('Could not find a anchor bar element');

  const lazyMoreButton = () => lazyOuterContainer.bind((container) => {
    return OuterContainer.getMoreButton(container);
  }).getOrDie('Could not find more button element');

  const lazyToolbar = () => lazyOuterContainer.bind((container) => {
    return OuterContainer.getToolbar(container);
  }).getOrDie('Could not find more toolbar element');

  const lazyThrobber = () => lazyOuterContainer.bind((container) => {
    return OuterContainer.getThrobber(container);
  }).getOrDie('Could not find throbber element');

  const backstage: Backstage.UiFactoryBackstage = Backstage.init(sink, editor, lazyAnchorBar, lazyMoreButton);

  const lazySink = () => Result.value<AlloyComponent, Error>(sink);

  const partMenubar: AlloySpec = OuterContainer.parts().menubar({
    dom: {
      tag: 'div',
      classes: [ 'tox-menubar' ]
    },
    backstage,
    onEscape () {
      editor.focus();
    }
  });

  const partToolbar: AlloySpec = OuterContainer.parts().toolbar({
    dom: {
      tag: 'div',
      classes: [ 'tox-toolbar' ]
    },
    getSink: lazySink,
    backstage,
    onEscape() {
      editor.focus();
    },
    split: getToolbarDrawer(editor),
    lazyToolbar,
    lazyMoreButton
  });

  const partSocket: AlloySpec = OuterContainer.parts().socket({
    dom: {
      tag: 'div',
      classes: [ 'tox-edit-area' ]
    }
  });

  const partSidebar: AlloySpec = OuterContainer.parts().sidebar({
    dom: {
      tag: 'div',
      classes: ['tox-sidebar']
    }
  });

  const partThrobber: AlloySpec = OuterContainer.parts().throbber({
    dom: {
      tag: 'div',
      classes: ['tox-throbber']
    },
    backstage
  });

  const statusbar = editor.getParam('statusbar', true, 'boolean') && !isInline ? Option.some(renderStatusbar(editor, backstage.shared.providers)) : Option.none();

  const socketSidebarContainer: SimpleSpec = {
    dom: {
      tag: 'div',
      classes: ['tox-sidebar-wrap']
    },
    components: [
      partSocket,
      partSidebar
    ]
  };

  // False should stop the menubar and toolbar rendering altogether
  const hasToolbar = isToolbarEnabled(editor) || getMultipleToolbarsSetting(editor).isSome();
  const hasMenubar = isMenubarEnabled(editor);

  // We need the statusbar to be separate to everything else so resizing works properly
  const editorComponents = Arr.flatten<AlloySpec>([
    hasMenubar ? [ partMenubar ] : [ ],
    hasToolbar ? [ partToolbar ] : [ ],
    // fixed_toolbar_container anchors to the editable area, else add an anchor bar
    useFixedContainer(editor) ? [ ] : [ memAnchorBar.asSpec() ],
    // Inline mode does not have a socket/sidebar
    isInline ? [ ] : [ socketSidebarContainer ]
  ]);

  const editorContainer = {
    dom: {
      tag: 'div',
      classes: ['tox-editor-container']
    },
    components: editorComponents,
  };

  const containerComponents = Arr.flatten<SimpleSpec>([
    [editorContainer],
    // Inline mode does not have a status bar
    isInline ? [ ] : statusbar.toArray(),
    [ partThrobber ]
  ]);

  const attributes = {
    role: 'application',
    ...I18n.isRtl() ? { dir: 'rtl' } : {}
  };

  const outerContainer = GuiFactory.build(
    OuterContainer.sketch({
      dom: {
        tag: 'div',
        classes: ['tox', 'tox-tinymce'].concat(isInline ? ['tox-tinymce-inline'] : []),
        styles: {
          // This is overridden by the skin, it helps avoid FOUC
          visibility: 'hidden'
        },
        attributes
      },
      components: containerComponents,
      behaviours: Behaviour.derive(mode.getBehaviours(editor).concat([
        Keying.config({
          mode: 'cyclic',
          selector: '.tox-menubar, .tox-toolbar, .tox-toolbar__primary, .tox-toolbar__overflow--open, .tox-sidebar__overflow--open, .tox-statusbar__path, .tox-statusbar__wordcount, .tox-statusbar__branding a'
        })
      ]))
    } as OuterContainerSketchSpec)
  );

  lazyOuterContainer = Option.some(outerContainer);

  editor.shortcuts.add('alt+F9', 'focus menubar', function () {
    OuterContainer.focusMenubar(outerContainer);
  });
  editor.shortcuts.add('alt+F10', 'focus toolbar', function () {
    OuterContainer.focusToolbar(outerContainer);
  });

  const mothership = Gui.takeover(
    outerContainer
  );

  const uiMothership = Gui.takeover(sink);

  Events.setup(editor, mothership, uiMothership);

  const getUi = () => {
    const channels = {
      broadcastAll: uiMothership.broadcast,
      broadcastOn: uiMothership.broadcastOn,
      register: () => {}
    };

    return { channels };
  };

  const setEditorSize = (elm) => {
    // Set height and width if they were given, though height only applies to iframe mode
    const DOM = DOMUtils.DOM;

    const baseWidth = editor.getParam('width', DOM.getStyle(elm, 'width'));
    const baseHeight = getHeightSetting(editor);
    const minWidth = getMinWidthSetting(editor);
    const minHeight = getMinHeightSetting(editor);

    const parsedWidth = Utils.parseToInt(baseWidth).bind((w) => {
      return Utils.numToPx(minWidth.map((mw) => Math.max(w, mw)));
    }).getOr(Utils.numToPx(baseWidth));

    const parsedHeight = Utils.parseToInt(baseHeight).bind((h) => {
      return minHeight.map((mh) => Math.max(h, mh));
    }).getOr(baseHeight);

    const stringWidth = Utils.numToPx(parsedWidth);
    if (Css.isValidValue('div', 'width', stringWidth)) {
      Css.set(outerContainer.element(), 'width', stringWidth);
    }

    if (!editor.inline) {
      const stringHeight = Utils.numToPx(parsedHeight);
      if (Css.isValidValue('div', 'height', stringHeight)) {
        Css.set(outerContainer.element(), 'height', stringHeight);
      } else {
        Css.set(outerContainer.element(), 'height', '200px');
      }
    }

    return parsedHeight;
  };

  const renderUI = function (): ModeRenderInfo {
    SilverContextMenu.setup(editor, lazySink, backstage);
    Sidebar.setup(editor);
    Throbber.setup(editor, lazyThrobber, backstage.shared);

    // Apply Bridge types
    const { buttons, menuItems, contextToolbars, sidebars } = editor.ui.registry.getAll();
    const rawUiConfig: RenderUiConfig = {
      menuItems,
      buttons,

      // Apollo, not implemented yet, just patched to work
      menus: !editor.settings.menu ? {} : Obj.map(editor.settings.menu, (menu) => Merger.merge(menu, { items: menu.items })),
      menubar: editor.settings.menubar,
      toolbar: getMultipleToolbarsSetting(editor).getOr(editor.getParam('toolbar', true)),

      // Apollo, not implemented yet
      sidebar: sidebars
    };

    ContextToolbar.register(editor, contextToolbars, sink, { backstage });

    const elm = editor.getElement();
    const height = setEditorSize(elm);

    const uiComponents: RenderUiComponents = { mothership, uiMothership, outerContainer };
    const args: RenderArgs = { targetNode: elm, height };
    return mode.render(editor, uiComponents, rawUiConfig, backstage, args);
  };

  return {mothership, uiMothership, backstage, renderUI, getUi};
};
Example #29
0
  TinyLoader.setup(function (editor: Editor, onSuccess, onFailure) {
    const sOverrideDefaultMode = Step.label('validate default modes cannot be overwritten', Step.async((next, die) => {
      // TODO: once `assert.throws` supports error objects simplify this
      try {
        editor.mode.register('design', {
          activate: Fun.noop,
          deactivate: Fun.noop,
          editorReadOnly: false
        });
        die('registering a new design mode should fail');
        return;
      } catch (e) {
        // pass
      }
      try {
        editor.mode.register('readonly', {
          activate: Fun.noop,
          deactivate: Fun.noop,
          editorReadOnly: false
        });
        die('registering a new readonly mode should fail');
        return;
      } catch (e) {
        // pass
      }
      next();
    }));

    const sRegisterTestModes = Step.sync(() => {
      editor.mode.register('customDesign', {
        activate: Fun.noop,
        deactivate: Fun.noop,
        editorReadOnly: false
      });
      editor.mode.register('customReadonly', {
        activate: Fun.noop,
        deactivate: Fun.noop,
        editorReadOnly: true
      });

      editor.mode.register('failingActivateReadonly', {
        activate: Fun.die('whoops'),
        deactivate: Fun.noop,
        editorReadOnly: true
      });
      editor.mode.register('failingDeactivateDesign', {
        activate: Fun.noop,
        deactivate: Fun.die('haha'),
        editorReadOnly: false
      });
    });

    const sAssertMode = (expectedMode: string) => {
      return Step.label('sAssertMode: checking editor is in mode ' + expectedMode, Step.sync(() => {
        Assertions.assertEq('Should be the expected mode', expectedMode, editor.mode.get());
      }));
    };

    const sSetMode = (mode: string) => {
      return Step.label('sSetMode: setting the editor mode to ' + mode, Step.sync(() => {
        editor.mode.set(mode);
      }));
    };

    Pipeline.async({}, Arr.flatten([
      [
        sOverrideDefaultMode,
        sRegisterTestModes,
      ],
      Logger.ts('test default API', [
        sAssertMode('readonly'),
        sAssertBodyClass(editor, 'mce-content-readonly', true),
        sSetMode('design'),
        sAssertMode('design'),
        sAssertBodyClass(editor, 'mce-content-readonly', false),
        sSetMode('readonly'),
        sAssertMode('readonly'),
        sAssertBodyClass(editor, 'mce-content-readonly', true),
      ]),
      Logger.ts('test custom modes (aliases of design and readonly)', [
        sSetMode('customDesign'),
        sAssertMode('customDesign'),
        sAssertBodyClass(editor, 'mce-content-readonly', false),
        sSetMode('customReadonly'),
        sAssertMode('customReadonly'),
        sAssertBodyClass(editor, 'mce-content-readonly', true),
      ]),
      Logger.ts('test failing to activate a readonly-like mode leaves the editor in design', [
        sSetMode('design'),
        sSetMode('failingActivateReadonly'),
        sAssertMode('design'),
        sAssertBodyClass(editor, 'mce-content-readonly', false),
      ]),
      Logger.ts('test failing to deactivate a design-like mode still switches to readonly', [
        sSetMode('failingDeactivateDesign'),
        sSetMode('readonly'),
        sAssertMode('readonly'),
        sAssertBodyClass(editor, 'mce-content-readonly', true),
      ])
    ]), onSuccess, onFailure);
  }, {
Example #30
0
 (items) => Arr.flatten([
   [{ text: 'None', value: '' }],
   items
 ])