<commit_before>import Component from 'flarum/component';
import ItemList from 'flarum/utils/item-list';
import ActionButton from 'flarum/components/action-button';
import icon from 'flarum/helpers/icon';
import listItems from 'flarum/helpers/list-items';
import classList from 'flarum/utils/class-list';
import computed from 'flarum/utils/computed';

class Composer extends Component {
  constructor(props) {
    super(props);

    this.position = m.prop(Composer.PositionEnum.HIDDEN);
    this.height = m.prop();

    // Calculate the composer's current height, based on the intended height
    // (which is set when the resizing handle is dragged), and the composer's
    // current state.
    this.computedHeight = computed('height', 'position', function(height, position) {
      if (position === Composer.PositionEnum.MINIMIZED) {
        return '';
      } else if (position === Composer.PositionEnum.FULLSCREEN) {
        return $(window).height();
      } else {
        return Math.max(200, Math.min(height, $(window).height() - $('#header').outerHeight()));
      }
    });
  }

  view() {
    var classes = {
      'minimized': this.position() === Composer.PositionEnum.MINIMIZED,
      'full-screen': this.position() === Composer.PositionEnum.FULLSCREEN
    };
    classes.visible = this.position() === Composer.PositionEnum.NORMAL || classes.minimized || classes.fullScreen;

    if (this.component) this.component.props.disabled = classes.minimized;

    return m('div.composer', {config: this.onload.bind(this), className: classList(classes)}, [
      m('div.composer-handle', {config: this.configHandle.bind(this)}),
      m('ul.composer-controls', listItems(this.controlItems().toArray())),
      m('div.composer-content', {onclick: () => {
        if (this.position() === Composer.PositionEnum.MINIMIZED) this.show();
      }}, this.component ? this.component.view() : '')
    ]);
  }

  onload(element, isInitialized, context) {
    this.element(element);

    if (isInitialized) { return; }
    context.retain = true;

    // Hide the composer to begin with.
    this.height(localStorage.getItem('composerHeight') || this.$().height());
    this.$().hide();

    // Modulate the view's active property/class according to the focus
    // state of any inputs.
    this.$().on('focus blur', ':input', (e) => this.$().toggleClass('active', e.type === 'focusin'));

    // When the escape key is pressed on any inputs, close the composer.
    this.$().on('keydown', ':input', 'esc', () => this.close());

    context.onunload = this.ondestroy.bind(this);
    this.handlers = {};

    $(window).on('resize', this.handlers.onresize = this.onresize.bind(this)).resize();

    $(document)
      .on('mousemove', this.handlers.onmousemove = this.onmousemove.bind(this))
      .on('mouseup', this.handlers.onmouseup = this.onmouseup.bind(this));
  }

  configHandle(element, isInitialized) {
    if (isInitialized) { return; }

    var self = this;
    $(element).css('cursor', 'row-resize')
      .mousedown(function(e) {
        self.mouseStart = e.clientY;
        self.heightStart = self.$().height();
        self.handle = $(this);
        $('body').css('cursor', 'row-resize');
      }).bind('dragstart mousedown', function(e) {
        e.preventDefault();
      });
  }

  ondestroy() {
    $(window).off('resize', this.handlers.onresize);

    $(document)
      .off('mousemove', this.handlers.onmousemove)
      .off('mouseup', this.handlers.onmouseup);
  }

  updateHeight() {
    this.$().height(this.computedHeight());
    this.setContentHeight(this.computedHeight());
  }

  onresize() {
    this.updateHeight();
  }

  onmousemove(e) {
    if (!this.handle) { return; }

    // Work out how much the mouse has been moved, and set the height
    // relative to the old one based on that. Then update the content's
    // height so that it fills the height of the composer, and update the
    // body's padding.
    var deltaPixels = this.mouseStart - e.clientY;
    var height = this.heightStart + deltaPixels;
    this.height(height);
    this.updateHeight();

    var scrollTop = $(window).scrollTop();
    this.updateBodyPadding(scrollTop > 0 && scrollTop + $(window).height() >= $(document).height());

    localStorage.setItem('composerHeight', height);
  }

  onmouseup(e) {
    if (!this.handle) { return; }
    this.handle = null;
    $('body').css('cursor', '');
  }

  preventExit() {
    return this.component && this.component.preventExit();
  }

  render(anchorToBottom) {
    var $composer = this.$().stop(true);
    var oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0;

    if (this.position() !== Composer.PositionEnum.HIDDEN) {
      m.redraw(true);
    }

    this.$().height(this.computedHeight());
    var newHeight = $composer.outerHeight();

    switch (this.position()) {
      case Composer.PositionEnum.HIDDEN:
        $composer.css({height: oldHeight}).animate({bottom: -newHeight}, 'fast', () => {
          $composer.hide();
          this.clear();
          m.redraw();
        });
        break;

      case Composer.PositionEnum.NORMAL:
        if (this.oldPosition !== Composer.PositionEnum.FULLSCREEN) {
          $composer.show();
          $composer.css({height: oldHeight}).animate({bottom: 0, height: newHeight}, 'fast', this.component.focus.bind(this.component));
        } else {
          this.component.focus();
        }
        break;

      case Composer.PositionEnum.MINIMIZED:
        $composer.css({height: oldHeight}).animate({height: newHeight}, 'fast', this.component.focus.bind(this.component));
        break;
    }

    if (this.position() !== Composer.PositionEnum.FULLSCREEN) {
      this.updateBodyPadding(anchorToBottom);
    } else {
      this.component.focus();
    }
    $('body').toggleClass('composer-open', this.position() !== Composer.PositionEnum.HIDDEN);
    this.oldPosition = this.position();

    if (this.position() !== Composer.PositionEnum.HIDDEN) {
      this.setContentHeight(this.computedHeight());
    }
  }

  // Update the amount of padding-bottom on the body so that the page's
  // content will still be visible above the composer when the page is
  // scrolled right to the bottom.
  updateBodyPadding(anchorToBottom) {
    var paddingBottom = this.position() !== Composer.PositionEnum.HIDDEN && this.position() !== Composer.PositionEnum.MINIMIZED
      ? this.computedHeight() - parseInt($('#page').css('padding-bottom'))
      : 0;
    $('#content').css({paddingBottom});

    if (anchorToBottom) {
      $('html, body').scrollTop($(document).height());
    }<commit_msg>Maintain scroll position when hiding the composer<commit_after>import Component from 'flarum/component';
import ItemList from 'flarum/utils/item-list';
import ActionButton from 'flarum/components/action-button';
import icon from 'flarum/helpers/icon';
import listItems from 'flarum/helpers/list-items';
import classList from 'flarum/utils/class-list';
import computed from 'flarum/utils/computed';

class Composer extends Component {
  constructor(props) {
    super(props);

    this.position = m.prop(Composer.PositionEnum.HIDDEN);
    this.height = m.prop();

    // Calculate the composer's current height, based on the intended height
    // (which is set when the resizing handle is dragged), and the composer's
    // current state.
    this.computedHeight = computed('height', 'position', function(height, position) {
      if (position === Composer.PositionEnum.MINIMIZED) {
        return '';
      } else if (position === Composer.PositionEnum.FULLSCREEN) {
        return $(window).height();
      } else {
        return Math.max(200, Math.min(height, $(window).height() - $('#header').outerHeight()));
      }
    });
  }

  view() {
    var classes = {
      'minimized': this.position() === Composer.PositionEnum.MINIMIZED,
      'full-screen': this.position() === Composer.PositionEnum.FULLSCREEN
    };
    classes.visible = this.position() === Composer.PositionEnum.NORMAL || classes.minimized || classes.fullScreen;

    if (this.component) this.component.props.disabled = classes.minimized;

    return m('div.composer', {config: this.onload.bind(this), className: classList(classes)}, [
      m('div.composer-handle', {config: this.configHandle.bind(this)}),
      m('ul.composer-controls', listItems(this.controlItems().toArray())),
      m('div.composer-content', {onclick: () => {
        if (this.position() === Composer.PositionEnum.MINIMIZED) this.show();
      }}, this.component ? this.component.view() : '')
    ]);
  }

  onload(element, isInitialized, context) {
    this.element(element);

    if (isInitialized) { return; }
    context.retain = true;

    // Hide the composer to begin with.
    this.height(localStorage.getItem('composerHeight') || this.$().height());
    this.$().hide();

    // Modulate the view's active property/class according to the focus
    // state of any inputs.
    this.$().on('focus blur', ':input', (e) => this.$().toggleClass('active', e.type === 'focusin'));

    // When the escape key is pressed on any inputs, close the composer.
    this.$().on('keydown', ':input', 'esc', () => this.close());

    context.onunload = this.ondestroy.bind(this);
    this.handlers = {};

    $(window).on('resize', this.handlers.onresize = this.onresize.bind(this)).resize();

    $(document)
      .on('mousemove', this.handlers.onmousemove = this.onmousemove.bind(this))
      .on('mouseup', this.handlers.onmouseup = this.onmouseup.bind(this));
  }

  configHandle(element, isInitialized) {
    if (isInitialized) { return; }

    var self = this;
    $(element).css('cursor', 'row-resize')
      .mousedown(function(e) {
        self.mouseStart = e.clientY;
        self.heightStart = self.$().height();
        self.handle = $(this);
        $('body').css('cursor', 'row-resize');
      }).bind('dragstart mousedown', function(e) {
        e.preventDefault();
      });
  }

  ondestroy() {
    $(window).off('resize', this.handlers.onresize);

    $(document)
      .off('mousemove', this.handlers.onmousemove)
      .off('mouseup', this.handlers.onmouseup);
  }

  updateHeight() {
    this.$().height(this.computedHeight());
    this.setContentHeight(this.computedHeight());
  }

  onresize() {
    this.updateHeight();
  }

  onmousemove(e) {
    if (!this.handle) { return; }

    // Work out how much the mouse has been moved, and set the height
    // relative to the old one based on that. Then update the content's
    // height so that it fills the height of the composer, and update the
    // body's padding.
    var deltaPixels = this.mouseStart - e.clientY;
    var height = this.heightStart + deltaPixels;
    this.height(height);
    this.updateHeight();

    var scrollTop = $(window).scrollTop();
    this.updateBodyPadding(scrollTop > 0 && scrollTop + $(window).height() >= $(document).height());

    localStorage.setItem('composerHeight', height);
  }

  onmouseup(e) {
    if (!this.handle) { return; }
    this.handle = null;
    $('body').css('cursor', '');
  }

  preventExit() {
    return this.component && this.component.preventExit();
  }

  render(anchorToBottom) {
    var $composer = this.$().stop(true);
    var oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0;

    var scrollTop = $(window).scrollTop();

    m.redraw(true);

    this.$().height(this.computedHeight());
    var newHeight = $composer.outerHeight();

    switch (this.position()) {
      case Composer.PositionEnum.HIDDEN:
        $composer.css({height: oldHeight}).animate({bottom: -newHeight}, 'fast', () => {
          $composer.hide();
          this.clear();
          m.redraw();
        });
        break;

      case Composer.PositionEnum.NORMAL:
        if (this.oldPosition !== Composer.PositionEnum.FULLSCREEN) {
          $composer.show();
          $composer.css({height: oldHeight}).animate({bottom: 0, height: newHeight}, 'fast', this.component.focus.bind(this.component));
        } else {
          this.component.focus();
        }
        break;

      case Composer.PositionEnum.MINIMIZED:
        $composer.css({height: oldHeight}).animate({height: newHeight}, 'fast', this.component.focus.bind(this.component));
        break;
    }

    if (this.position() !== Composer.PositionEnum.FULLSCREEN) {
      this.updateBodyPadding();
      $('html, body').scrollTop(anchorToBottom ? $(document).height() : scrollTop);
    } else {
      this.component.focus();
    }
    $('body').toggleClass('composer-open', this.position() !== Composer.PositionEnum.HIDDEN);
    this.oldPosition = this.position();

    if (this.position() !== Composer.PositionEnum.HIDDEN) {
      this.setContentHeight(this.computedHeight());
    }
  }

  // Update the amount of padding-bottom on the body so that the page's
  // content will still be visible above the composer when the page is
  // scrolled right to the bottom.
  updateBodyPadding() {
    var paddingBottom = this.position() !== Composer.PositionEnum.HIDDEN && this.position() !== Composer.PositionEnum.MINIMIZED
      ? this.computedHeight() - parseInt($('#page').css('padding-bottom'))
      : 0;
    $('#content').css({paddingBottom});
{'fraction_non_alphanumeric': 0.16456657051768636, 'fraction_numerical': 0.0010626992561105207, 'mean_word_length': 3.2214033963473248, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, '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': '3380281', 'n_tokens_mistral': 4051, 'n_tokens_neox': 3942, 'n_words': 826}