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(), { }) ] : [ ] ]); });
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 ); }); };
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); }); };
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 ] : [ ]; }); };
editor.on('init', () => { const formats = getStyleFormats(editor); const enriched = FormatRegister.register(editor, formats, isSelectedFor, getPreviewFor); settingsFormats.set(enriched); settingsFlattenedFormats.set( Arr.bind(enriched, flatten) ); });
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; };
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 }; });
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(); };