<commit_before>import Component from 'flarum/Component';
import ItemList from 'flarum/utils/ItemList';
import DiscussionHero from 'flarum/components/DiscussionHero';
import PostStream from 'flarum/components/PostStream';
import PostStreamScrubber from 'flarum/components/PostStreamScrubber';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
import SplitDropdown from 'flarum/components/SplitDropdown';
import listItems from 'flarum/helpers/listItems';
import mixin from 'flarum/utils/mixin';
import evented from 'flarum/utils/evented';
import DiscussionControls from 'flarum/utils/DiscussionControls';

/**
 * The `DiscussionPage` component displays a whole discussion page, including
 * the discussion list pane, the hero, the posts, and the sidebar.
 */
export default class DiscussionPage extends mixin(Component, evented) {
  constructor(...args) {
    super(...args);

    /**
     * The discussion that is being viewed.
     *
     * @type {Discussion}
     */
    this.discussion = null;

    /**
     * The number of the first post that is currently visible in the viewport.
     *
     * @type {Integer}
     */
    this.near = null;

    this.refresh();

    // If the discussion list has been loaded, then we'll enable the pane (and
    // hide it by default). Also, if we've just come from another discussion
    // page, then we don't want Mithril to redraw the whole page – if it did,
    // then the pane would which would be slow and would cause problems with
    // event handlers.
    if (app.cache.discussionList) {
      app.pane.enable();
      app.pane.hide();

      if (app.current instanceof DiscussionPage) {
        m.redraw.strategy('diff');
      }
    }

    // Push onto the history stack, but use a generalised key so that navigating
    // to a few different discussions won't override the behaviour of the back
    // button.
    app.history.push('discussion');
    app.current = this;
    app.drawer.hide();
    app.modal.close();

    app.session.on('loggedIn', this.loggedInHandler = this.refresh.bind(this));
  }

  onunload(e) {
    // If we have routed to the same discussion as we were viewing previously,
    // cancel the unloading of this controller and instead prompt the post
    // stream to jump to the new 'near' param.
    if (this.discussion) {
      if (m.route.param('id') === this.discussion.id()) {
        e.preventDefault();

        if (Number(m.route.param('near')) !== Number(this.near)) {
          this.stream.goToNumber(m.route.param('near') || 1);
        }

        this.near = null;
        return;
      }
    }

    // If we are indeed navigating away from this discussion, then disable the
    // discussion list pane. Also, if we're composing a reply to this
    // discussion, minimize the composer – unless it's empty, in which case
    // we'll just close it.
    app.pane.disable();
    app.session.off('loggedIn', this.loggedInHandler);

    if (app.composingReplyTo(this.discussion) && !app.composer.component.content()) {
      app.composer.hide();
    } else {
      app.composer.minimize();
    }
  }

  view() {
    const discussion = this.discussion;

    return (
      <div className="DiscussionPage">
        {app.cache.discussionList
          ? <div className="DiscussionPage-list" config={this.configPane.bind(this)}>
              {app.cache.discussionList.render()}
            </div>
          : ''}

        <div className="DiscussionPage-discussion">
          {discussion
            ? [
              DiscussionHero.component({discussion}),
              <div className="container">
                <nav className="DiscussionPage-nav">
                  <ul>{listItems(this.sidebarItems().toArray())}</ul>
                </nav>
                <div className="DiscussionPage-stream">
                  {this.stream.render()}
                </div>
              </div>
            ]
            : LoadingIndicator.component({className: 'LoadingIndicator--block'})}
        </div>
      </div>
    );
  }

  config(isInitialized, context) {
    if (isInitialized) return;

    // context.retain = true;

    $('#app').addClass('App--discussion');
    context.onunload = () => $('#app').removeClass('App--discussion');
  }

  /**
   * Clear and reload the discussion.
   */
  refresh() {
    this.near = m.route.param('near') || 0;
    this.discussion = null;

    const preloadedDiscussion = app.preloadedDocument();
    if (preloadedDiscussion) {
      // We must wrap this in a setTimeout because if we are mounting this
      // component for the first time on page load, then any calls to m.redraw
      // will be ineffective and thus any configs (scroll code) will be run
      // before stuff is drawn to the page.
      setTimeout(this.init.bind(this, preloadedDiscussion));
    } else {
      const params = this.params();
      params.include = params.include.join(',');

      app.store.find('discussions', m.route.param('id'), params)
        .then(this.init.bind(this));
    }

    // Since this may be called during the component's constructor, i.e. in the
    // middle of a redraw, forcing another redraw would not bode well. Instead
    // we start/end a computation so Mithril will only redraw if it isn't
    // already doing so.
    m.startComputation();
    m.endComputation();
  }

  /**
   * Get the parameters that should be passed in the API request to get the
   * discussion.
   *
   * @return {Object}
   */
  params() {
    return {
      page: {near: this.near},
      include: ['posts', 'posts.user', 'posts.user.groups']
    };
  }

  /**
   * Initialize the component to display the given discussion.
   *
   * @param {Discussion} discussion
   */
  init(discussion) {
    // If the slug in the URL doesn't match up, we'll redirect so we have the
    // correct one.
    if (m.route.param('id') === discussion.id() && m.route.param('slug') !== discussion.slug()) {
      m.route(app.route.discussion(discussion, m.route.param('near')), null, true);
      return;
    }

    this.discussion = discussion;

    app.setTitle(discussion.title());

    // When the API responds with a discussion, it will also include a number of<commit_msg>Don't allow includes to be specified<commit_after>import Component from 'flarum/Component';
import ItemList from 'flarum/utils/ItemList';
import DiscussionHero from 'flarum/components/DiscussionHero';
import PostStream from 'flarum/components/PostStream';
import PostStreamScrubber from 'flarum/components/PostStreamScrubber';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
import SplitDropdown from 'flarum/components/SplitDropdown';
import listItems from 'flarum/helpers/listItems';
import mixin from 'flarum/utils/mixin';
import evented from 'flarum/utils/evented';
import DiscussionControls from 'flarum/utils/DiscussionControls';

/**
 * The `DiscussionPage` component displays a whole discussion page, including
 * the discussion list pane, the hero, the posts, and the sidebar.
 */
export default class DiscussionPage extends mixin(Component, evented) {
  constructor(...args) {
    super(...args);

    /**
     * The discussion that is being viewed.
     *
     * @type {Discussion}
     */
    this.discussion = null;

    /**
     * The number of the first post that is currently visible in the viewport.
     *
     * @type {Integer}
     */
    this.near = null;

    this.refresh();

    // If the discussion list has been loaded, then we'll enable the pane (and
    // hide it by default). Also, if we've just come from another discussion
    // page, then we don't want Mithril to redraw the whole page – if it did,
    // then the pane would which would be slow and would cause problems with
    // event handlers.
    if (app.cache.discussionList) {
      app.pane.enable();
      app.pane.hide();

      if (app.current instanceof DiscussionPage) {
        m.redraw.strategy('diff');
      }
    }

    // Push onto the history stack, but use a generalised key so that navigating
    // to a few different discussions won't override the behaviour of the back
    // button.
    app.history.push('discussion');
    app.current = this;
    app.drawer.hide();
    app.modal.close();

    app.session.on('loggedIn', this.loggedInHandler = this.refresh.bind(this));
  }

  onunload(e) {
    // If we have routed to the same discussion as we were viewing previously,
    // cancel the unloading of this controller and instead prompt the post
    // stream to jump to the new 'near' param.
    if (this.discussion) {
      if (m.route.param('id') === this.discussion.id()) {
        e.preventDefault();

        if (Number(m.route.param('near')) !== Number(this.near)) {
          this.stream.goToNumber(m.route.param('near') || 1);
        }

        this.near = null;
        return;
      }
    }

    // If we are indeed navigating away from this discussion, then disable the
    // discussion list pane. Also, if we're composing a reply to this
    // discussion, minimize the composer – unless it's empty, in which case
    // we'll just close it.
    app.pane.disable();
    app.session.off('loggedIn', this.loggedInHandler);

    if (app.composingReplyTo(this.discussion) && !app.composer.component.content()) {
      app.composer.hide();
    } else {
      app.composer.minimize();
    }
  }

  view() {
    const discussion = this.discussion;

    return (
      <div className="DiscussionPage">
        {app.cache.discussionList
          ? <div className="DiscussionPage-list" config={this.configPane.bind(this)}>
              {app.cache.discussionList.render()}
            </div>
          : ''}

        <div className="DiscussionPage-discussion">
          {discussion
            ? [
              DiscussionHero.component({discussion}),
              <div className="container">
                <nav className="DiscussionPage-nav">
                  <ul>{listItems(this.sidebarItems().toArray())}</ul>
                </nav>
                <div className="DiscussionPage-stream">
                  {this.stream.render()}
                </div>
              </div>
            ]
            : LoadingIndicator.component({className: 'LoadingIndicator--block'})}
        </div>
      </div>
    );
  }

  config(isInitialized, context) {
    if (isInitialized) return;

    // context.retain = true;

    $('#app').addClass('App--discussion');
    context.onunload = () => $('#app').removeClass('App--discussion');
  }

  /**
   * Clear and reload the discussion.
   */
  refresh() {
    this.near = m.route.param('near') || 0;
    this.discussion = null;

    const preloadedDiscussion = app.preloadedDocument();
    if (preloadedDiscussion) {
      // We must wrap this in a setTimeout because if we are mounting this
      // component for the first time on page load, then any calls to m.redraw
      // will be ineffective and thus any configs (scroll code) will be run
      // before stuff is drawn to the page.
      setTimeout(this.init.bind(this, preloadedDiscussion));
    } else {
      const params = this.requestParams();

      app.store.find('discussions', m.route.param('id'), params)
        .then(this.init.bind(this));
    }

    // Since this may be called during the component's constructor, i.e. in the
    // middle of a redraw, forcing another redraw would not bode well. Instead
    // we start/end a computation so Mithril will only redraw if it isn't
    // already doing so.
    m.startComputation();
    m.endComputation();
  }

  /**
   * Get the parameters that should be passed in the API request to get the
   * discussion.
   *
   * @return {Object}
   */
  requestParams() {
    return {
      page: {near: this.near}
    };
  }

  /**
   * Initialize the component to display the given discussion.
   *
   * @param {Discussion} discussion
   */
  init(discussion) {
    // If the slug in the URL doesn't match up, we'll redirect so we have the
    // correct one.
    if (m.route.param('id') === discussion.id() && m.route.param('slug') !== discussion.slug()) {
      m.route(app.route.discussion(discussion, m.route.param('near')), null, true);
      return;
    }

    this.discussion = discussion;

    app.setTitle(discussion.title());

    // When the API responds with a discussion, it will also include a number of
{'fraction_non_alphanumeric': 0.1228741150622508, 'fraction_numerical': 0.0003254943445357637, 'mean_word_length': 2.559223863307269, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 33, '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': '3941606', 'n_tokens_mistral': 3625, 'n_tokens_neox': 3407, 'n_words': 1060}