<commit_before>import { AddEventsBehaviour, AlloyEvents, AlloySpec, AlloyTriggers, Behaviour, Bubble, GuiFactory, InlineView, Keying, Layout, Positioning } from '@ephox/alloy';
import { expandable } from '@ephox/alloy/lib/main/ts/ephox/alloy/positioning/layout/MaxHeight';
import { Objects } from '@ephox/boulder';
import { Toolbar } from '@ephox/bridge';
import { Arr, Cell, Id, Merger, Option, Thunk } from '@ephox/katamari';
import { Css, DomEvent, Element } from '@ephox/sugar';
import { Editor } from 'tinymce/core/api/Editor';
import Delay from 'tinymce/core/api/util/Delay';
import { showContextToolbarEvent } from './ui/context/ContextEditorEvents';
import { ContextForm } from './ui/context/ContextForm';
import { forwardSlideEvent, renderContextToolbar } from './ui/context/ContextUi';
import ToolbarLookup from './ui/context/ToolbarLookup';
import ToolbarScopes, { ScopedToolbars } from './ui/context/ToolbarScopes';
import { renderToolbar } from './ui/toolbar/CommonToolbar';
import { identifyButtons } from './ui/toolbar/Integration';
import { Element as DomElement } from '@ephox/dom-globals';

const register = (editor: Editor, registryContextToolbars, sink, extras) => {
  const contextbar = GuiFactory.build(
    renderContextToolbar({
      sink,
      onEscape: () => {
        editor.focus();
        return Option.some(true);
      }
    })
  );

  const getBoxElement = () => Option.some(Element.fromDom(editor.contentAreaContainer));

  editor.on('init', () => {
    const scroller = editor.getBody().ownerDocument.defaultView;
    // FIX: Unbind AND make a lot nicer.
    DomEvent.bind(Element.fromDom(scroller), 'scroll', () => {
      lastAnchor.get().each((anchor) => {
        const elem = lastElement.get().getOr(editor.selection.getNode());
        const nodeBounds = elem.getBoundingClientRect();
        const contentAreaBounds = editor.contentAreaContainer.getBoundingClientRect();
        const aboveEditor = nodeBounds.bottom < 0;
        const belowEditor = nodeBounds.top > contentAreaBounds.height;
        if (aboveEditor || belowEditor) {
          Css.set(contextbar.element(), 'display', 'none');
        } else {
          Css.remove(contextbar.element(), 'display');
          Positioning.positionWithin(sink, anchor, contextbar, getBoxElement());
        }
      });
    });
  });

  const lastAnchor = Cell(Option.none());
  const lastElement = Cell<Option<DomElement>>(Option.none<DomElement>());
  const timer = Cell(null);

  const wrapInPopDialog = (toolbarSpec: AlloySpec) => {
    return {
      dom: {
        tag: 'div',
        classes: ['tox-pop__dialog'],
      },
      components: [toolbarSpec],
      behaviours: Behaviour.derive([
        Keying.config({
          mode: 'acyclic'
        }),

        AddEventsBehaviour.config('pop-dialog-wrap-events', [
          AlloyEvents.runOnAttached((comp) => {
            editor.shortcuts.add('ctrl+F9', 'focus statusbar', () => Keying.focusIn(comp));
          }),
          AlloyEvents.runOnDetached((comp) => {
            editor.shortcuts.remove('ctrl+F9');
          })
        ])
      ])
    };
  };

  const getScopes: () => ScopedToolbars = Thunk.cached(() => {
    return ToolbarScopes.categorise(registryContextToolbars, (toolbarApi) => {
      const alloySpec = buildToolbar(toolbarApi);
      AlloyTriggers.emitWith(contextbar, forwardSlideEvent, {
        forwardContents: wrapInPopDialog(alloySpec)
      });
    });
  });

  const buildToolbar = (ctx): AlloySpec => {
    const { buttons } = editor.ui.registry.getAll();

    const scopes = getScopes();
    return ctx.type === 'contexttoolbar' ? (() => {
      const allButtons = Merger.merge(buttons, scopes.formNavigators);
      const initGroups = identifyButtons(editor, { buttons: allButtons, toolbar: ctx.items.join(' ') }, extras);
      return renderToolbar({
        uid: Id.generate('context-toolbar'),
        initGroups: Arr.map(initGroups, (g) => ({ items: g })),
        onEscape: Option.none
      });
    })() : (() => {
      return ContextForm.renderContextForm(ctx, extras.backstage.shared.providers);
    })();
  };

  editor.on(showContextToolbarEvent, (e) => {
    const scopes = getScopes();
    // TODO: Have this stored in a better structure
    Objects.readOptFrom(scopes.lookupTable, e.toolbarKey).each((ctx) => {
      launchContext(ctx, e.target === editor ? Option.none() : Option.some(e as DomElement));
      // Forms launched via this way get immediate focus
      InlineView.getContent(contextbar).each(Keying.focusIn);
    });
  });

  const bubbleAlignments = {
    valignCentre: [],
    alignCentre: [],
    alignLeft: ['tox-pop--align-left'],
    alignRight: ['tox-pop--align-right'],
    right: ['tox-pop--right'],
    left: ['tox-pop--left'],
    bottom: ['tox-pop--bottom'],
    top: ['tox-pop--top']
  };

  const lineAnchorSpec = {
    bubble: Bubble.nu(12, 0, bubbleAlignments),
    layouts: {
      onLtr: () => [Layout.east],
      onRtl: () => [Layout.west]
    },
    overrides: {
      maxHeightFunction: expandable()
    }
  };

  const anchorSpec = {
    bubble: Bubble.nu(0, 12, bubbleAlignments),
    layouts: {
      onLtr: () => [Layout.north, Layout.south, Layout.northeast, Layout.southeast, Layout.northwest, Layout.southwest],
      onRtl: () => [Layout.north, Layout.south, Layout.northwest, Layout.southwest, Layout.northeast, Layout.southeast]
    },
    overrides: {
      maxHeightFunction: expandable()
    }
  };

  const getAnchor = (position: Toolbar.ContextToolbarPosition, element: Option<Element>) => {
    const anchor = Merger.deepMerge(
      extras.backstage.shared.anchors.node(element),
      position === 'line' ? lineAnchorSpec : anchorSpec
    );
    return anchor;
  };

  const launchContext = (toolbarApi: Toolbar.ContextToolbar | Toolbar.ContextForm, elem: Option<DomElement>) => {
    clearTimer();
    const toolbarSpec = buildToolbar(toolbarApi);
    const sElem = elem.map(Element.fromDom);
    const anchor = getAnchor(toolbarApi.position, sElem);
    lastAnchor.set(Option.some((anchor)));
    lastElement.set(elem);
    InlineView.showWithin(contextbar, anchor, wrapInPopDialog(toolbarSpec), getBoxElement());
    Css.remove(contextbar.element(), 'display');
  };

  const launchContextToolbar = () => {
    const scopes = getScopes();
    ToolbarLookup.lookup(scopes, editor).fold(
      () => {
        lastAnchor.set(Option.none());
        InlineView.hide(contextbar);
      },

      (info) => {
        launchContext(info.toolbarApi, Option.some(info.elem.dom()));
      }
    );
  };

  const clearTimer = () => {
    const current = timer.get();
    if (current !== null) {
      clearTimeout(current);
      timer.set(null);
    }
  };

  const resetTimer = (t) => {
    clearTimer();
    timer.set(t);
  };

  // FIX: Make it go away when the action makes it go away. E.g. deleting a column deletes the table.
  editor.on('click keyup setContent ObjectResized nodeChange', (e) => {
    // Fixing issue with chrome focus on img.
    resetTimer(
      Delay.setEditorTimeout(editor, launchContextToolbar, 0)
    );
  });
}

export default {
  register
};<commit_msg>AP-318: collapse configuration<commit_after>import { AddEventsBehaviour, AlloyEvents, AlloySpec, AlloyTriggers, Behaviour, Bubble, GuiFactory, InlineView, Keying, Layout, Positioning } from '@ephox/alloy';
import { expandable } from '@ephox/alloy/lib/main/ts/ephox/alloy/positioning/layout/MaxHeight';
import { Objects } from '@ephox/boulder';
import { Toolbar } from '@ephox/bridge';
import { Arr, Cell, Id, Merger, Option, Thunk } from '@ephox/katamari';
import { Css, DomEvent, Element } from '@ephox/sugar';
import { Editor } from 'tinymce/core/api/Editor';
import Delay from 'tinymce/core/api/util/Delay';
import { showContextToolbarEvent } from './ui/context/ContextEditorEvents';
import { ContextForm } from './ui/context/ContextForm';
import { forwardSlideEvent, renderContextToolbar } from './ui/context/ContextUi';
import ToolbarLookup from './ui/context/ToolbarLookup';
import ToolbarScopes, { ScopedToolbars } from './ui/context/ToolbarScopes';
import { renderToolbar } from './ui/toolbar/CommonToolbar';
import { identifyButtons } from './ui/toolbar/Integration';
import { Element as DomElement } from '@ephox/dom-globals';

const register = (editor: Editor, registryContextToolbars, sink, extras) => {
  const contextbar = GuiFactory.build(
    renderContextToolbar({
      sink,
      onEscape: () => {
        editor.focus();
        return Option.some(true);
      }
    })
  );

  const getBoxElement = () => Option.some(Element.fromDom(editor.contentAreaContainer));

  editor.on('init', () => {
    const scroller = editor.getBody().ownerDocument.defaultView;
    // FIX: Unbind AND make a lot nicer.
    DomEvent.bind(Element.fromDom(scroller), 'scroll', () => {
      lastAnchor.get().each((anchor) => {
        const elem = lastElement.get().getOr(editor.selection.getNode());
        const nodeBounds = elem.getBoundingClientRect();
        const contentAreaBounds = editor.contentAreaContainer.getBoundingClientRect();
        const aboveEditor = nodeBounds.bottom < 0;
        const belowEditor = nodeBounds.top > contentAreaBounds.height;
        if (aboveEditor || belowEditor) {
          Css.set(contextbar.element(), 'display', 'none');
        } else {
          Css.remove(contextbar.element(), 'display');
          Positioning.positionWithin(sink, anchor, contextbar, getBoxElement());
        }
      });
    });
  });

  const lastAnchor = Cell(Option.none());
  const lastElement = Cell<Option<DomElement>>(Option.none<DomElement>());
  const timer = Cell(null);

  const wrapInPopDialog = (toolbarSpec: AlloySpec) => {
    return {
      dom: {
        tag: 'div',
        classes: ['tox-pop__dialog'],
      },
      components: [toolbarSpec],
      behaviours: Behaviour.derive([
        Keying.config({
          mode: 'acyclic'
        }),

        AddEventsBehaviour.config('pop-dialog-wrap-events', [
          AlloyEvents.runOnAttached((comp) => {
            editor.shortcuts.add('ctrl+F9', 'focus statusbar', () => Keying.focusIn(comp));
          }),
          AlloyEvents.runOnDetached((comp) => {
            editor.shortcuts.remove('ctrl+F9');
          })
        ])
      ])
    };
  };

  const getScopes: () => ScopedToolbars = Thunk.cached(() => {
    return ToolbarScopes.categorise(registryContextToolbars, (toolbarApi) => {
      const alloySpec = buildToolbar(toolbarApi);
      AlloyTriggers.emitWith(contextbar, forwardSlideEvent, {
        forwardContents: wrapInPopDialog(alloySpec)
      });
    });
  });

  const buildToolbar = (ctx): AlloySpec => {
    const { buttons } = editor.ui.registry.getAll();

    const scopes = getScopes();
    return ctx.type === 'contexttoolbar' ? (() => {
      const allButtons = Merger.merge(buttons, scopes.formNavigators);
      const initGroups = identifyButtons(editor, { buttons: allButtons, toolbar: ctx.items.join(' ') }, extras);
      return renderToolbar({
        uid: Id.generate('context-toolbar'),
        initGroups: Arr.map(initGroups, (g) => ({ items: g })),
        onEscape: Option.none
      });
    })() : (() => {
      return ContextForm.renderContextForm(ctx, extras.backstage.shared.providers);
    })();
  };

  editor.on(showContextToolbarEvent, (e) => {
    const scopes = getScopes();
    // TODO: Have this stored in a better structure
    Objects.readOptFrom(scopes.lookupTable, e.toolbarKey).each((ctx) => {
      launchContext(ctx, e.target === editor ? Option.none() : Option.some(e as DomElement));
      // Forms launched via this way get immediate focus
      InlineView.getContent(contextbar).each(Keying.focusIn);
    });
  });

  const bubbleAlignments = {
    valignCentre: [],
    alignCentre: [],
    alignLeft: ['tox-pop--align-left'],
    alignRight: ['tox-pop--align-right'],
    right: ['tox-pop--right'],
    left: ['tox-pop--left'],
    bottom: ['tox-pop--bottom'],
    top: ['tox-pop--top']
  };

  const anchorOverrides = {
    maxHeightFunction: expandable()
  };

  const lineAnchorSpec = {
    bubble: Bubble.nu(12, 0, bubbleAlignments),
    layouts: {
      onLtr: () => [Layout.east],
      onRtl: () => [Layout.west]
    },
    overrides: anchorOverrides
  };

  const anchorSpec = {
    bubble: Bubble.nu(0, 12, bubbleAlignments),
    layouts: {
      onLtr: () => [Layout.north, Layout.south, Layout.northeast, Layout.southeast, Layout.northwest, Layout.southwest],
      onRtl: () => [Layout.north, Layout.south, Layout.northwest, Layout.southwest, Layout.northeast, Layout.southeast]
    },
    overrides: anchorOverrides
  };

  const getAnchor = (position: Toolbar.ContextToolbarPosition, element: Option<Element>) => {
    const anchor = Merger.deepMerge(
      extras.backstage.shared.anchors.node(element),
      position === 'line' ? lineAnchorSpec : anchorSpec
    );
    return anchor;
  };

  const launchContext = (toolbarApi: Toolbar.ContextToolbar | Toolbar.ContextForm, elem: Option<DomElement>) => {
    clearTimer();
    const toolbarSpec = buildToolbar(toolbarApi);
    const sElem = elem.map(Element.fromDom);
    const anchor = getAnchor(toolbarApi.position, sElem);
    lastAnchor.set(Option.some((anchor)));
    lastElement.set(elem);
    InlineView.showWithin(contextbar, anchor, wrapInPopDialog(toolbarSpec), getBoxElement());
    Css.remove(contextbar.element(), 'display');
  };

  const launchContextToolbar = () => {
    const scopes = getScopes();
    ToolbarLookup.lookup(scopes, editor).fold(
      () => {
        lastAnchor.set(Option.none());
        InlineView.hide(contextbar);
      },

      (info) => {
        launchContext(info.toolbarApi, Option.some(info.elem.dom()));
      }
    );
  };

  const clearTimer = () => {
    const current = timer.get();
    if (current !== null) {
      clearTimeout(current);
      timer.set(null);
    }
  };

  const resetTimer = (t) => {
    clearTimer();
    timer.set(t);
  };

  // FIX: Make it go away when the action makes it go away. E.g. deleting a column deletes the table.
  editor.on('click keyup setContent ObjectResized nodeChange', (e) => {
    // Fixing issue with chrome focus on img.
    resetTimer(
      Delay.setEditorTimeout(editor, launchContextToolbar, 0)
    );
  });
};

export default {
  register
};
{'fraction_non_alphanumeric': 0.14976926304013424, 'fraction_numerical': 0.0016081666899734302, 'mean_word_length': 3.2391819798458803, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1359065', 'n_tokens_mistral': 4427, 'n_tokens_neox': 4374, 'n_words': 860}