<commit_before>/**
 * Copyright (c) Tiny Technologies, Inc. All rights reserved.
 * Licensed under the LGPL or a commercial license.
 * For LGPL see License.txt in the project root for license information.
 * For commercial licenses see https://www.tiny.cloud/
 */

import { AlloyComponent, Boxes, Channels, Docking, Focusing, Receiving } from '@ephox/alloy';
import { HTMLElement } from '@ephox/dom-globals';
import { Cell, Option, Result } from '@ephox/katamari';
import { Class, Classes, Compare, Css, Element, Focus, Height, Location, Scroll, Traverse, Visibility, Width } from '@ephox/sugar';

import Editor from 'tinymce/core/api/Editor';
import { ScrollIntoViewEvent } from 'tinymce/core/api/EventTypes';
import { isToolbarLocationTop } from '../../api/Settings';
import * as EditorChannels from '../../Channels';

const visibility = {
  fadeInClass: 'tox-editor-dock-fadein',
  fadeOutClass: 'tox-editor-dock-fadeout',
  transitionClass: 'tox-editor-dock-transition'
};

const editorStickyOnClass = 'tox-tinymce--toolbar-sticky-on';
const editorStickyOffClass = 'tox-tinymce--toolbar-sticky-off';

const scrollFromBehindHeader = (e: ScrollIntoViewEvent, containerHeader: Element) => {
  const doc = Traverse.owner(containerHeader);
  const viewHeight = doc.dom().defaultView.innerHeight;
  const scrollPos = Scroll.get(doc);

  const markerElement = Element.fromDom(e.elm);
  const markerPos = Boxes.absolute(markerElement);
  const markerHeight = Height.get(markerElement);
  const markerTop = markerPos.y();
  const markerBottom = markerTop + markerHeight;

  const editorHeaderPos = Location.absolute(containerHeader);
  const editorHeaderHeight = Height.get(containerHeader);
  const editorHeaderTop = editorHeaderPos.top();
  const editorHeaderBottom = editorHeaderTop + editorHeaderHeight;

  // Check to see if the header is docked to the top/bottom of the page (eg is floating)
  const editorHeaderDockedAtTop = Math.abs(editorHeaderTop - scrollPos.top()) < 2;
  const editorHeaderDockedAtBottom = Math.abs(editorHeaderBottom - (scrollPos.top() + viewHeight)) < 2;

  // If the element is behind the header at the top of the page, then
  // scroll the element down by the header height
  if (editorHeaderDockedAtTop && markerTop < editorHeaderBottom) {
    Scroll.to(scrollPos.left(), markerTop - editorHeaderHeight, doc);
    // If the element is behind the header at the bottom of the page, then
    // scroll the element up by the header height
  } else if (editorHeaderDockedAtBottom && markerBottom > editorHeaderTop) {
    const y = (markerTop - viewHeight) + markerHeight + editorHeaderHeight;
    Scroll.to(scrollPos.left(), y, doc);
  }
};

const updateIframeContentFlow = (header: AlloyComponent, isToolbarTop: boolean): void => {
  const elm = header.element();
  Traverse.parent(elm).each((parentElem: Element<HTMLElement>) => {
    const padding = 'padding-' + (isToolbarTop ? 'top' : 'bottom');

    if (Docking.isDocked(header)) {
      const parentWidth = Width.get(parentElem);
      Css.set(elm, 'width', parentWidth + 'px');
      const headerHeight = Height.getOuter(elm);
      Css.set(parentElem, padding, headerHeight + 'px');
    } else {
      Css.remove(elm, 'width');
      Css.remove(parentElem, padding);
    }
  });
};

const updateSinkVisibility = (sinkElem: Element<HTMLElement>, visible: boolean): void => {
  if (visible) {
    Class.remove(sinkElem, visibility.fadeOutClass);
    Classes.add(sinkElem, [ visibility.transitionClass, visibility.fadeInClass ]);
  } else {
    Class.remove(sinkElem, visibility.fadeInClass);
    Classes.add(sinkElem, [ visibility.fadeOutClass, visibility.transitionClass ]);
  }
};

const updateEditorClasses = (editor: Editor, docked: boolean) => {
  const editorContainer = Element.fromDom(editor.getContainer());
  if (docked) {
    Class.add(editorContainer, editorStickyOnClass);
    Class.remove(editorContainer, editorStickyOffClass);
  } else {
    Class.add(editorContainer, editorStickyOffClass);
    Class.remove(editorContainer, editorStickyOnClass);
  }
};

const restoreFocus = (headerElem: Element, focusedElem: Element) => {
  // When the header is hidden, then the element that was focused will be lost
  // so we need to restore it if nothing else has already been focused (eg anything other than the body)
  const ownerDoc = Traverse.owner(focusedElem);
  Focus.active(ownerDoc).filter((activeElm) => {
    // Don't try to refocus the same element
    return !Compare.eq(focusedElem, activeElm);
  }).filter((activeElm) => {
    // Only attempt to refocus if the current focus is the body or is in the header element
    return Compare.eq(activeElm, Element.fromDom(ownerDoc.dom().body)) || Compare.contains(headerElem, activeElm);
  }).each(() => Focus.focus(focusedElem));
};

const findFocusedElem = (rootElm: Element, lazySink: () => Result<AlloyComponent, Error>): Option<Element> => {
  // Check to see if an element is focused inside the header or inside the sink
  // and if so store the element so we can restore it later
  return Focus.search(rootElm).orThunk(() => {
    return lazySink().toOption().bind((sink) => Focus.search(sink.element()));
  });
};

const setup = (editor: Editor, lazyHeader: () => Option<AlloyComponent>): void => {
  if (!editor.inline) {
    // No need to update the content flow in inline mode as the header always floats
    editor.on('ResizeWindow ResizeEditor ResizeContent', () => {
      lazyHeader().each((header) => updateIframeContentFlow(header, isToolbarLocationTop(editor)));
    });

    // Need to reset the docking position on skin loaded as the original position will have<commit_msg>TINY-4645: Fixed sticky toolbar not undocking when removing content with autoresize on<commit_after>/**
 * Copyright (c) <NAME>, Inc. All rights reserved.
 * Licensed under the LGPL or a commercial license.
 * For LGPL see License.txt in the project root for license information.
 * For commercial licenses see https://www.tiny.cloud/
 */

import { AlloyComponent, Boxes, Channels, Docking, Focusing, Receiving } from '@ephox/alloy';
import { HTMLElement } from '@ephox/dom-globals';
import { Cell, Option, Result } from '@ephox/katamari';
import { Class, Classes, Compare, Css, Element, Focus, Height, Location, Scroll, Traverse, Visibility, Width } from '@ephox/sugar';

import Editor from 'tinymce/core/api/Editor';
import { ScrollIntoViewEvent } from 'tinymce/core/api/EventTypes';
import { isToolbarLocationTop } from '../../api/Settings';
import * as EditorChannels from '../../Channels';

const visibility = {
  fadeInClass: 'tox-editor-dock-fadein',
  fadeOutClass: 'tox-editor-dock-fadeout',
  transitionClass: 'tox-editor-dock-transition'
};

const editorStickyOnClass = 'tox-tinymce--toolbar-sticky-on';
const editorStickyOffClass = 'tox-tinymce--toolbar-sticky-off';

const scrollFromBehindHeader = (e: ScrollIntoViewEvent, containerHeader: Element) => {
  const doc = Traverse.owner(containerHeader);
  const viewHeight = doc.dom().defaultView.innerHeight;
  const scrollPos = Scroll.get(doc);

  const markerElement = Element.fromDom(e.elm);
  const markerPos = Boxes.absolute(markerElement);
  const markerHeight = Height.get(markerElement);
  const markerTop = markerPos.y();
  const markerBottom = markerTop + markerHeight;

  const editorHeaderPos = Location.absolute(containerHeader);
  const editorHeaderHeight = Height.get(containerHeader);
  const editorHeaderTop = editorHeaderPos.top();
  const editorHeaderBottom = editorHeaderTop + editorHeaderHeight;

  // Check to see if the header is docked to the top/bottom of the page (eg is floating)
  const editorHeaderDockedAtTop = Math.abs(editorHeaderTop - scrollPos.top()) < 2;
  const editorHeaderDockedAtBottom = Math.abs(editorHeaderBottom - (scrollPos.top() + viewHeight)) < 2;

  // If the element is behind the header at the top of the page, then
  // scroll the element down by the header height
  if (editorHeaderDockedAtTop && markerTop < editorHeaderBottom) {
    Scroll.to(scrollPos.left(), markerTop - editorHeaderHeight, doc);
    // If the element is behind the header at the bottom of the page, then
    // scroll the element up by the header height
  } else if (editorHeaderDockedAtBottom && markerBottom > editorHeaderTop) {
    const y = (markerTop - viewHeight) + markerHeight + editorHeaderHeight;
    Scroll.to(scrollPos.left(), y, doc);
  }
};

const updateIframeContentFlow = (header: AlloyComponent, isToolbarTop: boolean): void => {
  const elm = header.element();
  Traverse.parent(elm).each((parentElem: Element<HTMLElement>) => {
    const padding = 'padding-' + (isToolbarTop ? 'top' : 'bottom');

    if (Docking.isDocked(header)) {
      const parentWidth = Width.get(parentElem);
      Css.set(elm, 'width', parentWidth + 'px');
      const headerHeight = Height.getOuter(elm);
      Css.set(parentElem, padding, headerHeight + 'px');
    } else {
      Css.remove(elm, 'width');
      Css.remove(parentElem, padding);
    }
  });
};

const updateSinkVisibility = (sinkElem: Element<HTMLElement>, visible: boolean): void => {
  if (visible) {
    Class.remove(sinkElem, visibility.fadeOutClass);
    Classes.add(sinkElem, [ visibility.transitionClass, visibility.fadeInClass ]);
  } else {
    Class.remove(sinkElem, visibility.fadeInClass);
    Classes.add(sinkElem, [ visibility.fadeOutClass, visibility.transitionClass ]);
  }
};

const updateEditorClasses = (editor: Editor, docked: boolean) => {
  const editorContainer = Element.fromDom(editor.getContainer());
  if (docked) {
    Class.add(editorContainer, editorStickyOnClass);
    Class.remove(editorContainer, editorStickyOffClass);
  } else {
    Class.add(editorContainer, editorStickyOffClass);
    Class.remove(editorContainer, editorStickyOnClass);
  }
};

const restoreFocus = (headerElem: Element, focusedElem: Element) => {
  // When the header is hidden, then the element that was focused will be lost
  // so we need to restore it if nothing else has already been focused (eg anything other than the body)
  const ownerDoc = Traverse.owner(focusedElem);
  Focus.active(ownerDoc).filter((activeElm) => {
    // Don't try to refocus the same element
    return !Compare.eq(focusedElem, activeElm);
  }).filter((activeElm) => {
    // Only attempt to refocus if the current focus is the body or is in the header element
    return Compare.eq(activeElm, Element.fromDom(ownerDoc.dom().body)) || Compare.contains(headerElem, activeElm);
  }).each(() => Focus.focus(focusedElem));
};

const findFocusedElem = (rootElm: Element, lazySink: () => Result<AlloyComponent, Error>): Option<Element> => {
  // Check to see if an element is focused inside the header or inside the sink
  // and if so store the element so we can restore it later
  return Focus.search(rootElm).orThunk(() => {
    return lazySink().toOption().bind((sink) => Focus.search(sink.element()));
  });
};

const setup = (editor: Editor, lazyHeader: () => Option<AlloyComponent>): void => {
  if (!editor.inline) {
    // If using bottom toolbar then when the editor resizes we need to reset docking
    // otherwise it won't know the original toolbar position has moved
    if (!isToolbarLocationTop(editor)) {
      editor.on('ResizeEditor', () => {
        lazyHeader().each(Docking.reset);
      });
    }

    // No need to update the content flow in inline mode as the header always floats
    editor.on('ResizeWindow ResizeEditor ResizeContent', () => {
      lazyHeader().each((header) => updateIframeContentFlow(header, isToolbarLocationTop(editor)));
    });

    // Need to reset the docking position on skin loaded as the original position will have
{'fraction_non_alphanumeric': 0.12136840295685061, 'fraction_numerical': 0.0006876396768093519, 'mean_word_length': 4.38159111933395, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 51, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, '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': '3523188', 'n_tokens_mistral': 3451, 'n_tokens_neox': 3381, 'n_words': 993}