HEX
Server: nginx/1.26.1
System: Linux 850a3e23ecee 5.15.0-122-generic #132-Ubuntu SMP Thu Aug 29 13:45:52 UTC 2024 x86_64
User: (1000)
PHP: 8.2.27
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/fixed-toc/frontend/assets/js/ftoc.js
/**
 * Functions for the Fixed TOC Plugin
 *
 * @since 3.0.0
 */

/*global fixedtocOption */

'use strict';

var fixedtoc = (function($) {
  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Set fixedtocOption.
   *
   * @since 3.0.0
   */
  var option = (function() {
    var init = function() {
      fixedtocOption.scrollOffset = parseToInt(fixedtocOption.scrollOffset);
      fixedtocOption.fixedOffsetX = parseToInt(fixedtocOption.fixedOffsetX);
      fixedtocOption.fixedOffsetY = parseToInt(fixedtocOption.fixedOffsetY);
      fixedtocOption.contentsFixedHeight = parseToInt(fixedtocOption.contentsFixedHeight);
      fixedtocOption.contentsWidthInPost = parseToInt(fixedtocOption.contentsWidthInPost);
      fixedtocOption.contentsHeightInPost = parseToInt(fixedtocOption.contentsHeightInPost);
      fixedtocOption.triggerBorderWidth = getBorderWidth(fixedtocOption.triggerBorder);
      fixedtocOption.contentsBorderWidth = getBorderWidth(fixedtocOption.contentsBorder);
      fixedtocOption.triggerSize = parseToInt(fixedtocOption.triggerSize);
    };

    var set = function(name, val, type) {
      if ('int' == type) {
        fixedtocOption[name] = parseToInt(val);
      } else if ('float' == type) {
        fixedtocOption[name] = parseToFloat(val);
      } else {
        fixedtocOption[name] = val;
      }
    };

    var update = function(name, val, type) {
      set(name, val, type);
    };

    var remove = function(name) {
      if (undefined !== fixedtocOption[name]) {
        delete fixedtocOption[name];
      }
    };

    var getBorderWidth = function(border) {
      switch (border) {
        case 'thin':
          return 1;
        case 'medium':
          return 2;
        case 'bold':
          return 5;
        default:
          return 0;
      }
    };

    return {
      init: init,
      set: set,
      update: update,
      remove: remove
    };
  })();

  // noinspection JSUnusedGlobalSymbols
  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Condition methods to control on/off.
   *
   * @since 3.0.0
   */
  var conditionObj = {
    inWidgetProp: undefined,

    showAdminbar: function() {
      return fixedtocOption.showAdminbar;
    },

    isQuickMin: function() {
      return fixedtocOption.isQuickMin;
    },

    isEscMin: function() {
      return fixedtocOption.isEscMin;
    },

    isEnterMax: function() {
      return fixedtocOption.isEnterMax;
    },

    isNestedList: function() {
      return fixedtocOption.isNestedList;
    },

    isColExpList: function() {
      return fixedtocOption.isColExpList;
    },

    showColExpIcon: function() {
      return fixedtocOption.showColExpIcon;
    },

    isAccordionList: function() {
      return fixedtocOption.isAccordionList;
    },

    showTargetHint: function() {
      return true;
    },

    supportInPost: function() {
      return fixedtocOption.inPost;
    },

    inWidget: function() {
      if (!fixedtocOption.inWidget) {
        return false;
      }

      if (undefined === this.inWidgetProp) {
        this.inWidgetProp = !!$('#ftwp-widget-container').length;
      }

      return this.inWidgetProp;
    },

    fixedWidget: function() {
      if (!this.inWidget()) {
        return false;
      }

      return fixedtocOption.fixedWidget;
    },

    isAutoHeightFixedToPost: function() {
      return (0 == fixedtocOption.contentsFixedHeight);
    },

    isFloat: function() {
      return 'none' != fixedtocOption.contentsFloatInPost;
    },

    isAutoHeightInPost: function() {
      return (0 == fixedtocOption.contentsHeightInPost);
    },

    isPositionAtFixed: function(position) {
      return -1 != fixedtocOption.fixedPosition.indexOf(position);
    },

    isDebug: function() {
      return 1 == fixedtocOption.debug;
    },

    isNotBlur: function() {
      var ua = navigator.userAgent.toLowerCase();
      return (ua.indexOf('android') > -1) || (ua.indexOf('firefox') > -1);
    },

    isMobile: function() {
      return dataObj.data.window.width <= parseToInt(fixedtocOption.mobileMaxWidth);
    },

    isClickableHeader: function() {
      return 1 == fixedtocOption.isClickableHeader;
    },

    isColExpInitMobile: function() {
      return 1 == fixedtocOption.contentsColexpInitMobile;
    },

    isSmoothScroll: function() {
      return 1 == fixedtocOption.smoothScroll;
    }

  }; // End conditionObj.

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Common functions.
   *
   * @since 3.0.0
   */

  /**
   * Parse to integer.
   *
   * @since 3.0.0
   *
   * @param {string} str
   * @return int
   */
  function parseToInt(str) {
    return parseInt(str) || 0;
  }

  /**
   * Parse to float number.
   *
   * @since 3.0.0
   *
   * @param {string} str
   * @return float|int
   */
  function parseToFloat(str) {
    return parseFloat(str) || 0;
  }

  /**
   * Get height of a fixed element.
   *
   * @since 3.0.0
   *
   * @param {Element} eles
   * @return float|int
   */
  function getFixedHeight(eles) {
    if (!eles.length) {
      return 0;
    }

    var fixedHeight = 0;
    eles.each(function() {
      var thisEle = $(this);
      if ('fixed' == thisEle.css('position')) {
        fixedHeight += parseToInt(thisEle.outerHeight());
      }
    });

    return fixedHeight;
  }

  /**
   * Prevent default event.
   *
   * @since 3.0.0
   *
   * @param {Event} evt
   * @return undefined
   */
  function preventDefaultEvt(evt) {
    evt.preventDefault();
  }

  // noinspection JSUnusedLocalSymbols
  /**
   * Return undefined.
   *
   * @since 3.0.0
   *
   * @return undefined
   */
  function __return() {
  }

  /**
   * Get contents height.
   * Exclude padding and border.
   *
   * @since 3.0.1
   *
   * @param {int} contentsOuterHeight outer height.
   * @return {int}.
   */
  function getContentsHeight(contentsOuterHeight) {
    return parseToInt(
        contentsOuterHeight - 2 * fixedtocOption.contentsBorderWidth);
  }

  /**
   * Output console.log result if debug enable.
   *
   * @since 3.1.0
   *
   * @param {string} str
   */
  function consoleLog(str) {
    if (conditionObj.isDebug()) {
      console.log(str);
    }
  }

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Create elements.
   *
   * @since 3.0.0
   */
  var $e;

  function createElements() {
    $e = {
      window: $(window),
      document: $(document),
      body: $('body'),
      container: $('#ftwp-container'),
      trigger: $('#ftwp-trigger'),
      contents: $('#ftwp-contents'),
      header: $('#ftwp-header'),
      minIcon: $('#ftwp-header-minimize'),
      list: $('#ftwp-list'),
      postContent: $(fixedtocOption.postContentSelector),
      headings: $('.ftwp-heading')
    };
    $e.anchors = $e.list.find('.ftwp-anchor').not('.ftwp-otherpage-anchor');

    if (conditionObj.isNestedList()) {
      $e.hasSubItems = $e.list.find('.ftwp-has-sub');
    }

    if (conditionObj.showColExpIcon()) {
      $e.colExpIcons = $e.list.find('.ftwp-icon-expand, .ftwp-icon-collapse');
    }

    if (conditionObj.inWidget()) {
      $e.widget = $('.ftwp-widget');
      $e.widgetContainer = $('#ftwp-widget-container');
    }

    if (conditionObj.supportInPost()) {
      $e.containerOuter = $('#ftwp-container-outer');
    }

    consoleLog($e);
  }

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Set index data to anchor element.
   *
   * @since 3.0.0
   */
  function setAnchorIndex() {
    $e.anchors.each(function(i) {
      $(this).data('index', i);
    });
  }

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Data
   *
   * @since 3.0.0
   */
  var dataObj = (function() {
    var data = {};

    // Set scroll top.
    var setScrollTop = function() {
      data.scrollTop = $e.window.scrollTop();
    };

    // Set window width and height.
    var setWindowSize = function() {
      data.window = {};

      data.window.width = window.innerWidth;
      data.window.height = window.innerHeight;
    };

    // Set document height.
    var setDocumentHeight = function() {
      data.document = {};
      data.document.height = Math.round($e.document.height());
    };

    // Set adminbar height.
    var setAdminbarHeight = function() {
      data.adminbarHeight = conditionObj.showAdminbar() ? getFixedHeight(
          $('#wpadminbar')) : 0;
    };

    // Set fixed menu height.
    var setFixedMenuHeight = function() {
      data.fixedMenuHeight = getFixedHeight($(fixedtocOption.fixedMenu));
    };

    // Set fixed height. Include adminbar and fixed menu.
    var setFixedHeight = function() {
      if (fixedtocOption.fixedMenu) {
        data.fixedHeight = data.adminbarHeight + data.fixedMenuHeight;
      } else {
        data.fixedHeight = data.adminbarHeight;
      }
    };

    // Set offset top to window at fixed position.
    var setFixedOffsetTop = function() {
      data.fixedOffsetTop = data.fixedHeight + fixedtocOption.fixedOffsetY;
    };

    // Offset top of heading
    var setHeadingOffset = function() {
      data.headingOffset = data.fixedHeight + fixedtocOption.scrollOffset;
    };

    // Set heading top data
    var setHeadingsTop = function() {
      data.headingsTop = [];
      $.each($e.anchors, function() {
        var $heading = $($(this).attr('href'));
        var headingTop = $heading.length ? parseToInt(
            $heading.offset().top - data.headingOffset) : NaN;
        if (!isNaN(headingTop)) {
          data.headingsTop.push({
            headingTop: headingTop,
            anchorEle: $(this)
          });
        }
      });
    };

    // Set post rectangle data relative to the page.
    var setPostRect = function() {
      data.postRect = {};

      var postContentOffset = $e.postContent.offset();
      var postContentWidth = $e.postContent.outerWidth();
      var postContentHeight = $e.postContent.outerHeight();

      data.postRect.left = postContentOffset.left;
      data.postRect.top = postContentOffset.top;
      data.postRect.width = postContentWidth;
      data.postRect.right = data.postRect.left + data.postRect.width;
      data.postRect.bottom = postContentHeight + data.postRect.top;
      data.postRect.height = data.postRect.bottom - data.postRect.top;
    };

    // Set fixed TOC Y range.
    var setFtocRangeY = function() {
      data.ftocRangeY = {};

      if (conditionObj.supportInPost()) {
        data.ftocRangeY.top = data.inPostRangeY.bottom;
      } else {
        data.ftocRangeY.top = data.postRect.top - data.fixedHeight;
      }

      switch (fixedtocOption.disappearPoint) {
        case 'document-bottom':
          data.ftocRangeY.bottom = Math.round($e.document.height());
          break;
        default:
          data.ftocRangeY.bottom = data.postRect.bottom - data.window.height;
      }
      // data.ftocRangeY.bottom = data.postRect.bottom - data.window.height;
//			data.ftocRangeY.bottom = data.postRect.bottom;
    };

    // Set height of the container outer.
    var containerOuterHeight = (function() {
      var set = function() {
        var height;

        if (conditionObj.isAutoHeightInPost() ||
            fixedtocOption.contentsColexpInit) {
          $e.container.css('position', 'static');
          height = $e.containerOuter.outerHeight();
          $e.container.css('position', '');
        } else {
          height = fixedtocOption.contentsHeightInPost;
        }

        $e.containerOuter.css('height', height + 'px');

        data.containerOuterHeight = height;
      };

      var update = function() {
        if (!actionObj.location.inPost) {
          return;
        }

        if (conditionObj.isAutoHeightInPost()) {
          auto();
        } else {
          if ('collapse' == $e.contents.data('colexp')) {
            auto();
          } else {
            $e.containerOuter.css('height',
                fixedtocOption.contentsHeightInPost + 'px');
            $e.contents.css('height',
                fixedtocOption.contentsHeightInPost + 'px');
            listHeight.set(
                getContentsHeight(fixedtocOption.contentsHeightInPost));
            data.containerOuterHeight = $e.containerOuter.outerHeight();
//						$e.containerOuter.css('height', '');
//						$e.contents.css('height', '');
//						listHeight.unset();
          }
        }

        function auto() {
          $e.containerOuter.css('height', 'auto');
          $e.contents.css('height', 'auto');
          listHeight.setAuto();
          data.containerOuterHeight = $e.containerOuter.outerHeight();
//					$e.containerOuter.css('height', '');
//					$e.contents.css('height', '');
//					listHeight.unset();
        }
      };

      return {
        set: set,
        update: update
      };
    })();

    // Set Y range fot in post location.
    var setInPostRangeY = function() {
      data.inPostRangeY = {};

      data.inPostRangeY.top = 0;
      data.inPostRangeY.bottom = $e.containerOuter.offset().top +
          data.containerOuterHeight - data.fixedHeight;
    };

    // Set min viewport width in widget
    var setInWidgetMinWidth = function() {
      data.inWidgetMinWidth = data.postRect.width +
          $e.widgetContainer.outerWidth();
    };

    // Set fixed widget Y range.
    var setFixedWidgetRangeY = function() {
      data.fixedWidgetRangeY = {};

      data.fixedWidgetRangeY.top = $e.widgetContainer.offset().top -
          data.fixedHeight;
      data.fixedWidgetRangeY.bottom = data.ftocRangeY.bottom;
    };

    // Set fixed TOC rectangle data in widget.
    var ftocRectInWidget = (function() {
      var set = function() {
        data.ftocRectInWidget = {
          left: $e.widgetContainer.offset().left,
          top: data.fixedHeight,
          width: $e.widgetContainer.outerWidth(),
          height: getHeight()
        };
      };

      var getHeight = function() {
        var height;

        if ('collapse' == $e.contents.data('colexp')) {
          $e.contents.css('height', 'auto');
          height = $e.contents.outerHeight();
          $e.contents.css('height', '');
        } else {
          height = window.innerHeight - data.fixedHeight;
        }

        return height;
      };

      var updateOnResize = function() {
        set();
      };

      var updateHeight = function() {
        data.ftocRectInWidget.height = getHeight();
      };

      return {
        set: set,
        updateOnResize: updateOnResize,
        updateHeight: updateHeight
      };
    })();

    // Update data on window resize.
    var updateOnResize = function() {
      setWindowSize();
      setScrollTop();
      setAdminbarHeight();
      if (fixedtocOption.fixedMenu) {
        setFixedMenuHeight();
      }
      setFixedHeight();
      setFixedOffsetTop();
      if (conditionObj.supportInPost()) {
        containerOuterHeight.update();
        setInPostRangeY();
      }
      setHeadingOffset();
      setPostRect();
      setFtocRangeY();
      if (conditionObj.inWidget()) {
        setInWidgetMinWidth();
      }
      if (conditionObj.fixedWidget()) {
        setFixedWidgetRangeY();
        ftocRectInWidget.updateOnResize();
      }
      setHeadingsTop();

      consoleLog(data);
    };

    // Update data on window scroll.
    var prevFixedMenuHeight;
    var updateOnScroll = function() {
      setScrollTop();

      if (!fixedtocOption.fixedMenu) {
        return;
      }

      if (undefined === prevFixedMenuHeight) {
        prevFixedMenuHeight = data.fixedMenuHeight;
      }

      setFixedMenuHeight();

      if (prevFixedMenuHeight !== data.fixedMenuHeight) {
        setFixedHeight();
        setFixedOffsetTop();
        if (conditionObj.supportInPost()) {
          setInPostRangeY();
        }
        setHeadingOffset();
        setPostRect();
        setFtocRangeY();
        if (conditionObj.inWidget()) {
          setInWidgetMinWidth();
        }
        if (conditionObj.fixedWidget()) {
          setFixedWidgetRangeY();
          ftocRectInWidget.updateOnResize();
        }
        setHeadingsTop();

        prevFixedMenuHeight = data.fixedMenuHeight;

        consoleLog(data);
      }
    };

    // Update data on document height changing.
    var preDocumentHeight;
    var updateOnDocumentHeightChange = function() {
      setDocumentHeight();

      var curDocumentHeight = data.document.height;
      if (curDocumentHeight != preDocumentHeight) {
        fixedtoc.reload();

        preDocumentHeight = data.document.height;

//						consoleLog( 'Document Height: ' + data.document.height );
      }
    };

    // Return and set public API.
    return {
      data: data,

      ftocRectInWidget: ftocRectInWidget,

      // Create data on initial
      createOnInit: function() {
        setWindowSize();
        setScrollTop();
        setAdminbarHeight();
        if (fixedtocOption.fixedMenu) {
          setFixedMenuHeight();
        }
        setFixedHeight();
        setFixedOffsetTop();
        if (conditionObj.supportInPost()) {
          containerOuterHeight.set();
          setInPostRangeY();
        }
        setHeadingOffset();
        setPostRect();
        setFtocRangeY();
        if (conditionObj.inWidget()) {
          setInWidgetMinWidth();
        }
        if (conditionObj.fixedWidget()) {
          setFixedWidgetRangeY();
          ftocRectInWidget.set();
        }
        setHeadingsTop();

        consoleLog(this.data);
      },

      // Update data on resize window
      updateOnResize: updateOnResize,

      // Update data on scroll
      updateOnScroll: updateOnScroll,

      // Update data in post
      updateInPost: function() {
        containerOuterHeight.update();
        setInPostRangeY();
        setHeadingOffset();
        setHeadingsTop();
        setPostRect();
        setFtocRangeY();
      },

      // Update on document height change.
      updateOnDocumentHeightChange: updateOnDocumentHeightChange,

      // Update document height.
      setDocumentHeight: setDocumentHeight
    };

  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Location
   *
   * @since 3.0.0
   */
  var locationObj = (function() {
    // Display in widget.
    var inWidget = function() {
//			if (conditionObj.inWidget() && dataObj.data.window.width >= dataObj.data.inWidgetMinWidth) {
      return conditionObj.inWidget() && !conditionObj.isMobile();
    };

    // Fixed to widget.
    var fixedWidget = function() {
      if (!conditionObj.fixedWidget()) {
        return false;
      }

      if (!inWidget()) {
        return false;
      }

      return dataObj.data.fixedWidgetRangeY.top <= dataObj.data.scrollTop &&
          dataObj.data.fixedWidgetRangeY.bottom > dataObj.data.scrollTop;
    };

    // Display in post.
    var inPost = function() {
      if (!conditionObj.supportInPost()) {
        return false;
      }

      return dataObj.data.inPostRangeY.bottom > dataObj.data.scrollTop;
    };

    // Fixed to post
    var fixedToPost = function() {
      return dataObj.data.ftocRangeY.top <= dataObj.data.scrollTop &&
          dataObj.data.ftocRangeY.bottom > dataObj.data.scrollTop;
    };

    return {
      fixedWidget: fixedWidget,
      inWidget: inWidget,
      inPost: inPost,
      fixedToPost: fixedToPost
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Actions.
   *
   * @since 3.0.0
   */
  var actionObj = (function() {
    var location = {
      fixedWidget: false,
      inWidget: false,
      inPost: false,
      fixedToPost: false,
      hidden: false
    };

    var events = [
      'common',
      'hidden',
      'fixedToPost',
      'inPost',
      'inWidget',
      'fixedWidget'];

    // Register an action with event and handler.
    var register = function(event, handler) {
      if (-1 == $.inArray(event, events)) {
        consoleLog('Not support this event: ' + event);
        return;
      }

      if (undefined !== handler._construct) {
        $e.container.on('ftoc_' + event, handler._construct);
      }

      if ('common' != event && undefined !== handler._destruct) {
        $e.container.on('_ftoc_' + event, handler._destruct);
      }
    };

    // Active actions by location
    var activeByLocation = function(eventType) {
      if (locationObj.fixedWidget()) {
        if (!location.fixedWidget) {
          setLocation('fixedWidget');
          activeThe('fixedWidget');

          consoleLog(location);
        }
      } else if (locationObj.inWidget()) {
        if (!location.inWidget) {
          setLocation('inWidget');
          activeThe('inWidget');

          consoleLog(location);
        }
      } else if (locationObj.inPost()) {
        if (!location.inPost) {
          setLocation('inPost');
          activeThe('inPost');

          consoleLog(location);
        }
      } else if (locationObj.fixedToPost()) {
        if (!location.fixedToPost) {
          setLocation('fixedToPost');
          activeThe('fixedToPost');

          consoleLog(location);
        }
      } else {
        if (!location.hidden) {
          setLocation('hidden');
          activeThe('hidden');

          consoleLog(location);
        }
      }

      // Active the event
      function activeThe(event) {
        var len = events.length;
        var eventParam = {
          location: event,
          eventType: eventType
        };

        // Deactivate other events.
        for (var j = 1; j < len; j++) {
          if (event == events[j]) {
            continue;
          }

          $e.container.trigger('_ftoc_' + events[j], eventParam);
        }

        // Active the event.
        $e.container.trigger('ftoc_' + event, eventParam);
      }

      // Set location variable.
      function setLocation(event) {
        for (var i = 1, len = events.length; i < len; i++) {
          if (undefined !== event && event == events[i]) {
            location[event] = true;
          } else {
            location[events[i]] = false;
          }
        }
      }

    };

    // Public API
    return {
      // Location
      location: location,

      // Register custom event.
      register: register,

      // Update events on resize.
      updateOnResize: function() {
        activeByLocation('resize');
      },

      // Update events on scroll
      updateOnScroll: function() {
        activeByLocation('scroll');
      },

      // Initial events.
      init: function() {
        $e.container.trigger('ftoc_common');
        activeByLocation('init');
      }
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Set list height.
   *
   * @since 3.0.0
   */
  var listHeight = (function() {
    var set = function(h) {
      var contentsHeight;
      if (undefined !== h) {
        contentsHeight = h;
      } else {
        contentsHeight = $e.contents.height();
      }

      $e.list.css('height', (contentsHeight - $e.header.outerHeight()) + 'px');
    };

    var setAuto = function() {
      $e.list.css('height', 'auto');
    };

    var unset = function() {
      $e.list.css('height', '');
    };

    // Public API
    return {
      set: set,
      setAuto: setAuto,
      unset: unset
    };
  })();

  if (conditionObj.isColExpList()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Collapse/expand sub list.
     *
     * @since 3.0.0
     */
    var colExpSubList = (function() {
      // Constructor
      var _construct = function() {
        if (conditionObj.showColExpIcon() && conditionObj.isAccordionList()) {
          $e.colExpIcons.on('click', accordionHandler);
          $e.container.on('ftocAfterScrollToTarget', accordionHandler);
        } else if (conditionObj.showColExpIcon()) {
          $e.colExpIcons.on('click', toggleHandler);
          $e.container.on('ftocAfterScrollToTarget', toggleHandler);
        } else {
          $e.container.on('ftocAfterScrollToTarget', accordionHandler);
        }

        if (conditionObj.showColExpIcon()) {
          $e.colExpIcons.on('mousedown', preventDefaultEvt);
        }

        $e.container.on('ftocAfterTargetIndicated', function(evt, $anchor) {
          expandSubListOnIndicator($anchor);
        });

        consoleLog('Activated colExpSubList().');
      };

      // Toggle handler.
      var toggleHandler = function(evt, $anchor) {
        var $currentTarget = undefined === $anchor ? $(this) : $anchor;
        var $hasSubItem = $currentTarget.parent('.ftwp-has-sub');
        if (!$hasSubItem.length) {
          return;
        }

        if ($currentTarget.hasClass('ftwp-anchor')) {
          expand($hasSubItem, $currentTarget.prev('button'));
        } else {
          toggle($hasSubItem, $currentTarget);
        }
      };

      // Accordion handler.
      var accordionHandler = function(evt, $anchor) {
        var $currentTarget = undefined === $anchor ? $(this) : $anchor;
        var $item = $currentTarget.parent('.ftwp-item');
        if (!$item.length) {
          return;
        }

        if ($currentTarget.hasClass('ftwp-anchor')) {
          accordion($item, $currentTarget.prev('button'));
        } else {
          toggle($item, $currentTarget);
          collapseOther($item);
        }
      };

      // Toggle
      var toggle = function($hasSubItem, $icon) {
        if ($hasSubItem.hasClass('ftwp-collapse')) {
          expand($hasSubItem, $icon);
        } else if ($hasSubItem.hasClass('ftwp-expand')) {
          collapse($hasSubItem, $icon);
        }
      };

      // Accordion
      var accordion = function($item, $icon) {
        collapseOther($item);
        if ($item.hasClass('ftwp-has-sub')) {
          expand($item, $icon);
        }
      };

      // Collapse
      var collapse = function($hasSubItem, $icon) {
        $hasSubItem.removeClass('ftwp-expand').addClass('ftwp-collapse');
        if ($icon.length) {
          $icon.removeClass('ftwp-icon-expand').addClass('ftwp-icon-collapse');
        }
      };

      // Expand
      var expand = function($hasSubItem, $icon) {
        $hasSubItem.removeClass('ftwp-collapse').addClass('ftwp-expand');
        if ($icon.length) {
          $icon.removeClass('ftwp-icon-collapse').addClass('ftwp-icon-expand');
        }
      };

      // Collapse other
      var collapseOther = function($item) {
        $e.hasSubItems.each(function() {
          var $thisItem = $(this);
          if ($thisItem.get(0) == $item.get(0)) {
            return;
          }

          if ($thisItem.find($item).length) {
            return;
          }

          collapse($thisItem, $thisItem.children('button'));
        });
      };

      // Get icon element.
      // noinspection JSUnusedLocalSymbols
      var getIconEle = function($this) {
        var $icon;
        if ($this.hasClass('ftwp-anchor')) {
          $icon = $this.prev('button');
        } else if ('button' == $this.prop('tagName').toLowerCase()) {
          $icon = $this;
        }

        return $icon;
      };

      // Expand sub list on targetIndicator
      var expandSubListOnIndicator = function($anchor) {
        // Collapse other
        if (conditionObj.isAccordionList()) {
          collapseOther($anchor.parent('.ftwp-item'));
        }

        // Expand
        var $hasSubItems = $anchor.parents('.ftwp-has-sub');
        if ($hasSubItems) {
          $hasSubItems.each(function() {
            var $hasSubItem = $(this);
            var $icon = $hasSubItem.children('button');
            expand($hasSubItem, $icon);
          });
        }
      };

      // Public API
      return {
        _construct: _construct
      };
    })();
  }

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Scroll to target.
   *
   * @since 3.0.0
   */
  var scrollToTarget = (function() {
    // Constructor
    var _construct = function() {
      if (!conditionObj.isSmoothScroll()) {
        return;
      }

      $e.anchors.on('click', function(evt) {
        evt.preventDefault();
        var $anchor = $(evt.currentTarget);
        animateScroll($anchor);
      });

      $e.anchors.on('mousedown', preventDefaultEvt);

      consoleLog('Activated scrollToTarget().');
    };

    var animateScrolling = false;

    // Animated scroll
    var animateScroll = function($anchor) {
      var hash = $anchor.attr('href');
      var $targetObj = $(hash);
      // var $target = $targetObj.target;
      var index = $anchor.data('index');
      var headingsTop = dataObj.data.headingsTop[index];
      if (undefined === headingsTop) {
        return;
      }
      var top = headingsTop.headingTop;

      // Adding hash without scrolling by browser default behaviour.
      var headingID = hash.substr(1);
      $e.headings.removeClass('ftwp-heading-target');
      $targetObj.attr('id', '');
      window.location.hash = hash;
      $targetObj.attr('id', headingID);

      $('html, body').animate({scrollTop: top}, {
        duration: parseToInt(fixedtocOption.scrollDuration),
        start: function() {
          animateScrolling = true;
        }
      }).promise().then(function() {
        animateScrolling = false;

        // Fixing scroll to incorrectly position as the document height is changed.
        var curHeadingTop = dataObj.data.headingsTop[index].headingTop;
        if (top != curHeadingTop) {
          $('html, body').animate({scrollTop: curHeadingTop}, 100, function() {
            var curHeadingTop2 = dataObj.data.headingsTop[index].headingTop;
            if (curHeadingTop != curHeadingTop2) {
              $('html, body').animate({scrollTop: curHeadingTop2}, 1, function() {
              });
            }
          });
        }

//				consoleLog( 'Fixed scrolling to target!!!' );

        activeCurrent($anchor);
        $targetObj.addClass('ftwp-heading-target');

        // After scroll to heading target.
        $e.container.trigger('ftocAfterScrollToTarget', [$anchor, top]);
      });
    };

    // Defined activated element.
    var activedEle;

    // Active the current elements.
    var activeCurrent = function($anchor) {
      // Deactivate the previous elements.
      deactivatePrev();

      // Active anchor.
      activedEle = $anchor;
      $anchor.addClass('ftwp-active');
      if (actionObj.location.fixedToPost || actionObj.location.fixedWidget) {
        if (!$anchor.is(':focus') && !animateScrolling) {
          $anchor.trigger('focus');
        }
      }

      // Active icon
      if (conditionObj.showColExpIcon()) {
        var $icon = $anchor.prev();
        if ($icon.length) {
          $icon.addClass('ftwp-active');
        }
      }
    };

    // Deactivate the prev elements.
    var deactivatePrev = function() {
      if (activedEle) {
        activedEle.removeClass('ftwp-active').trigger('blur');
        if (conditionObj.showColExpIcon()) {
          var $icon = activedEle.prev();
          if ($icon.length) {
            $icon.removeClass('ftwp-active');
          }
        }
      }
    };

    // Deactivate all elements.
    var deactivateAll = function() {
      activedEle = undefined;
      $e.anchors.removeClass('ftwp-active').trigger('blur');
      if (conditionObj.showColExpIcon()) {
        $e.colExpIcons.removeClass('ftwp-active');
      }
    };

    // Public API
    return {
      _construct: _construct,
      activeCurrent: activeCurrent,
      deactivateAll: deactivateAll,
      deactivatePrev: deactivatePrev
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * target indicator.
   *
   * @since 3.0.0
   */
  var targetIndicator = (function() {
    // Constructor
    var _construct = function() {
      $e.window.on('ftocScroll', hint).on('ftocResize', hint);

      consoleLog('Actived targetIndicator().');
    };

    var start = function() {
      $e.window.on('ftocScroll', hint).on('ftocResize', hint);
    };

    var stop = function() {
      $e.window.off('ftocScroll', hint).off('ftocResize', hint);
    };

    // Present previous heading.
    var prevHeading;
    var preDocumentHeight;

    // Hint
    var hint = function() {
      var headingsTop = dataObj.data.headingsTop;
      var scrollTop = dataObj.data.scrollTop;

      if (headingsTop[0].headingTop > scrollTop ||
          dataObj.data.ftocRangeY.bottom < scrollTop) {
        if (undefined !== prevHeading) {
          scrollToTarget.deactivateAll();
        }

        prevHeading = undefined;
        return;
      }

      if (undefined !== preDocumentHeight && preDocumentHeight !=
          dataObj.data.document.height) {
        activeCurrentAnchor();

        consoleLog('Fixed target indicator!!');

        return;
      }
//			
      if (undefined !== prevHeading && prevHeading[0].headingTop <= scrollTop &&
          prevHeading[1].headingTop > scrollTop) {
        return;
      }

      activeCurrentAnchor();

      function activeCurrentAnchor() {
        $.each(headingsTop, function(i) {
          if (undefined === headingsTop[i + 1] && this.headingTop <=
              dataObj.data.ftocRangeY.bottom) {
            prevHeading = [this, headingsTop[i], i];
            preDocumentHeight = dataObj.data.document.height;

            // After target indicated.
            scrollToTarget.activeCurrent(this.anchorEle);

            $e.container.trigger('ftocAfterTargetIndicated',
                [this.anchorEle, scrollTop]);

            return false;
          }

          if (this.headingTop <= scrollTop && headingsTop[i + 1].headingTop >
              scrollTop) {
            prevHeading = [this, headingsTop[i + 1], i];
            if (undefined === dataObj.data.document) {
              dataObj.setDocumentHeight();
            }
            preDocumentHeight = dataObj.data.document.height;

            // After target indicated.
            scrollToTarget.activeCurrent(this.anchorEle);

            $e.container.trigger('ftocAfterTargetIndicated',
                [this.anchorEle, scrollTop]);

            return false;
          }
        });
      }
    };

    // Public API
    return {
      _construct: _construct,
      start: start,
      stop: stop
    };
  })();

  // noinspection JSUnusedLocalSymbols
  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Only scrolling the list prevent scrolling the window.
   *
   * @since 3.0.0
   */
  var noScrollWindow = (function() {
    // Constructor
    var _construct = function(evt, eventParam) {
      init();

      if ('fixedToPost' == eventParam.location) {
        $e.container.on('ftocAfterMaximize', on);
        $e.container.on('ftocAfterMinimize', off);
      }

      consoleLog('Actived noScrollWindow().');
    };

    // Destruct
    var _destruct = function() {
      off();
      $e.container.off('ftocAfterMaximize', on);
      $e.container.off('ftocAfterMinimize', off);

      consoleLog('Deactivated noScrollWindow().');
    };

    // Init
    var init = function() {
      on();
    };

    // On
    var on = function() {
      $e.list.on('scroll', start);
      $e.list.on('mouseleave', stop);
      $e.document.on('click', clickDocumentHandler);
      $e.window.on('scroll', stop);
    };

    // Off
    var off = function() {
      $e.body.removeClass('ftwp-no-scroll');
      $e.list.off('scroll', start);
      $e.list.off('mouseleave', stop);
      $e.document.off('click', clickDocumentHandler);
      $e.window.off('scroll', stop);
    };

    // Start
    var start = function() {
      $e.body.addClass('ftwp-no-scroll');
    };

    // Stop
    var stop = function() {
      if ($e.body.hasClass('ftwp-no-scroll')) {
        $e.list.off('scroll', start);
        $e.body.removeClass('ftwp-no-scroll');
        setTimeout(function() {
          $e.list.on('scroll', start);
        }, 100);
      }
    };

    // Click document handler.
    var clickDocumentHandler = function(evt) {
      if (!($.contains($e.list.get(0), evt.target))) {
        stop();
      }
    };

    // Public API
    return {
      _construct: _construct,
      _destruct: _destruct,
      on: on,
      off: off
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Hide TOC.
   *
   * @since 3.0.0
   */
  var hideToc = (function() {
    // Constructor
    var _construct = function() {
      $e.container.addClass('ftwp-hidden-state');

      consoleLog('Actived hideToc().');
    };

    // Destructor
    var _destruct = function() {
      $e.container.removeClass('ftwp-hidden-state');

      consoleLog('Deactivated hideToc().');
    };

    // Public API
    return {
      _construct: _construct,
      _destruct: _destruct
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Fixed TOC show in.
   *
   * @since 3.0.0
   */
  var ftocInOut = (function() {
    // Constructor
    var _construct = function() {
      init();

      // Set location
      location.set();
      $e.window.on('ftocResize', location.set);

      // Effect
      effect.in();
      $e.container.on('ftocAfterMinMax', effect.inOut);

      consoleLog('Actived ftocInOut().');
    };

    // Destructor
    var _destruct = function() {
      uninit();

      location.unset();
      $e.window.off('ftocResize', location.set);

      contentsHeight.unset();

      listHeight.unset();

      effect.out();
      $e.container.off('ftocAfterMinMax', effect.inOut);

      consoleLog('Deactivated ftocInOut().');
    };

    // Initialize
    var init = function() {
      $e.container.addClass('ftwp-fixed-to-post');

      if (!$e.container.parent().is($e.body)) {
        $e.container.appendTo($e.body);
      } // Append to body.

      $e.minIcon.addClass('ftwp-icon-minimize');

      // Make sure minimize the TOC on mobile
      if (conditionObj.isMobile()) {
        if ($e.container.hasClass('ftwp-maximize')) {
          $e.container.removeClass('ftwp-maximize').addClass('ftwp-minimize');
        }
      }
    };

    // Uninitialized
    var uninit = function() {
      $e.container.removeClass('ftwp-fixed-to-post');
      $e.minIcon.removeClass('ftwp-icon-minimize');
    };

    // Set location
    var location = (function() {
      var setPosition = function() {
        effect.setTransformOrigin(); // Set transform origin.

        if (conditionObj.isPositionAtFixed('left')) {
          var right = dataObj.data.window.width - dataObj.data.postRect.left +
              fixedtocOption.fixedOffsetX;
          var leftSpace = dataObj.data.postRect.left -
              fixedtocOption.fixedOffsetX;
          setLeftStyle($e.trigger, right, leftSpace);
          setLeftStyle($e.contents, right, leftSpace);
        } else {
          var left = dataObj.data.postRect.right + fixedtocOption.fixedOffsetX;
          var rightSpace = dataObj.data.window.width - left;
          setRightStyle($e.trigger, left, rightSpace);
          setRightStyle($e.contents, left, rightSpace);
        }

        resetTopStyle();

        // After set fixed location.
        contentsHeight.reset();
      };

      var isEdge = function(ele, space) {
        var eleWidth = ele.outerWidth();
        return space <= eleWidth;
      };

      var setLeftStyle = function(ele, right, space) {
        if (isEdge(ele, space)) {
          ele.css({
            left: '0px',
            right: 'auto'
          });

          effect.reverseTransformOrigin(ele); // reverse transform origin.
        } else {
          ele.css({
            right: right + 'px',
            left: 'auto'
          });
        }
      };

      var setRightStyle = function(ele, left, space) {
        if (isEdge(ele, space)) {
          ele.css({
            right: '0px',
            left: 'auto'
          });

          effect.reverseTransformOrigin(ele); // Reverse transform origin.
        } else {
          ele.css({
            left: left + 'px',
            right: 'auto'
          });
        }
      };

      var resetTopStyle = function() {
        if (conditionObj.isPositionAtFixed('top')) {
          $e.trigger.css('top', dataObj.data.fixedOffsetTop + 'px');
          $e.contents.css('top', dataObj.data.fixedOffsetTop + 'px');
        } else if (conditionObj.isPositionAtFixed('middle')) {
          $e.contents.css('top', dataObj.data.fixedHeight + 'px');
        } else {
          $e.trigger.css('top', '');
          $e.contents.css('top', '');
        }
      };

      var unsetPosition = function() {
        $e.trigger.css({
          left: '',
          right: '',
          top: ''
        });

        $e.contents.css({
          left: '',
          right: '',
          top: ''
        });

        effect.removeTransformOrigin(); // Remove transform origin.
      };

      return {
        set: setPosition,
        unset: unsetPosition
      };
    })();

    // Reset height of contents.
    var contentsHeight = (function() {
      // Reset
      var reset = function() {
        // Get contents original height.
        var originalHeight = getOriginal();

        // Reset to fit the window.
        var newHeight;
        if (conditionObj.isPositionAtFixed('middle')) {
          newHeight = dataObj.data.window.height - dataObj.data.fixedHeight;
        } else {
          newHeight = dataObj.data.window.height - dataObj.data.fixedOffsetTop;
        }

        var height;
        if (newHeight < originalHeight) {
          height = newHeight;
        } else {
          height = originalHeight;
        }

        $e.contents.css('height', height + 'px');

        // After reset contents height.
        var contentsHeight = getContentsHeight(height);
        listHeight.set(contentsHeight);
      };

      // Unset
      var unset = function() {
        $e.contents.css('height', '');
      };

      // Detect if is reset when top position.
      // noinspection JSUnusedLocalSymbols
      var isTopReset = function(originalHeight) {
        var top = dataObj.data.fixedOffsetTop;
        if (!top) {
          return false;
        }

        return (top + originalHeight) > dataObj.data.window.height;

      };

      // Detect if is reset when bottom position.
      // noinspection JSUnusedLocalSymbols
      var isBottomReset = function(originalHeight) {
        if (!conditionObj.isPositionAtFixed('bottom')) {
          return false;
        }

        var offsetBottom = fixedtocOption.fixedOffsetY;
        if (0 == offsetBottom) {
          return false;
        }

        return (offsetBottom + originalHeight) > dataObj.data.window.height;

      };

      // Get original contents height.
      var getOriginal = function() {
        var contentsHeight;
        if (conditionObj.isAutoHeightFixedToPost()) {
          if (conditionObj.isColExpList()) {
            contentsHeight = window.innerHeight;
          } else {
            listHeight.setAuto();
            $e.contents.css('height', 'auto');
            contentsHeight = $e.contents.outerHeight();
            unset();
            listHeight.unset();
          }
        } else {
          contentsHeight = fixedtocOption.contentsFixedHeight;
        }

        return contentsHeight;
      };

      // Public API
      return {
        reset: reset,
        unset: unset
      };
    })();

    // Animate in/out effect.
    var effect = (function() {
      var inCls = 'ftwp-animate-' + fixedtocOption.inOutEffect + '-in';
      var inOutCls = 'ftwp-animate-' + fixedtocOption.inOutEffect + '-inOut';

      // Show in
      var showIn = function() {
        $e.container.addClass(inCls);
      };

      // Show/hide in out
      var showInOut = function() {
        $e.container.removeClass(inCls + ' ' + inOutCls);
        void $e.container.offsetWidth;
        $e.container.addClass(inOutCls);
        setTimeout(function() {
          $e.container.removeClass(inOutCls);
        }, 1000);
      };

      // Hide out
      var hideOut = function() {
        $e.container.removeClass(inCls + ' ' + inOutCls);
      };

      // Set transform origin
      var transformCls, newTransformCls;
      var setTransformOrigin = function() {
        var re = fixedtocOption.fixedPosition.match(/(\w+)-(\w+)/i);
        if (re) {
          var horizontal = re[2];
          var vertical = re[1];
          if ('left' == horizontal) {
            horizontal = 'right';
          } else if ('right' == horizontal) {
            horizontal = 'left';
          }
          if ('middle' == vertical) {
            vertical = 'center';
          }
          transformCls = 'ftwp-transform-' + horizontal + '-' + vertical;
          $e.trigger.removeClass(newTransformCls).addClass(transformCls);
          $e.contents.removeClass(newTransformCls).addClass(transformCls);
        }
      };

      var reverseTransformOrigin = function(ele) {
        if (transformCls.match(/left/i)) {
          newTransformCls = transformCls.replace('left', 'right');
        } else {
          newTransformCls = transformCls.replace('right', 'left');
        }

        ele.removeClass(transformCls).addClass(newTransformCls);
      };

      var removeTransformOrigin = function() {
        $e.trigger.removeClass(transformCls + ' ' + newTransformCls);
        $e.contents.removeClass(transformCls + ' ' + newTransformCls);
      };

      // Public API
      return {
        inCls: inCls,
        in: showIn,
        inOut: showInOut,
        out: hideOut,
        setTransformOrigin: setTransformOrigin,
        reverseTransformOrigin: reverseTransformOrigin,
        removeTransformOrigin: removeTransformOrigin
      };
    })();

    // Public API
    return {
      _construct: _construct,
      _destruct: _destruct,
      effectInCls: effect.inCls
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Minimize/maximize the FTOC.
   *
   * @since 3.0.0
   */
  var minMaxFtoc = (function() {
    // Constructor
    var _construct = function() {
      var clickableElement = conditionObj.isClickableHeader()
          ? $e.header
          : $e.minIcon;

      $e.trigger.on('click', maximize);
      clickableElement.on('click', minimize);

      $e.trigger.on('mousedown', preventDefaultEvt);
      clickableElement.on('mousedown', preventDefaultEvt);

      if (conditionObj.isQuickMin()) {
        $e.document.on('click touchstart', quickMin);
      }

      if (conditionObj.isEscMin()) {
        $e.document.on('keyup', escMin);
      }

      if (conditionObj.isEnterMax()) {
        $e.document.on('keyup', enterMax);
      }

      consoleLog('Actived minMaxFtoc().');
    };

    // Destructor
    var _destruct = function() {
      var clickableElement = conditionObj.isClickableHeader()
          ? $e.header
          : $e.minIcon;

      $e.trigger.off('click', maximize);
      clickableElement.off('click', minimize);

      $e.trigger.off('mousedown', preventDefaultEvt);
      clickableElement.off('mousedown', preventDefaultEvt);

      if (conditionObj.isQuickMin()) {
        $e.document.off('click', quickMin);
      }

      if (conditionObj.isEscMin()) {
        $e.document.off('keyup', escMin);
      }

      if (conditionObj.isEnterMax()) {
        $e.document.off('keyup', enterMax);
      }

      consoleLog('Deactivated minMaxFtoc.');
    };

    // Maximize
    var maximize = function() {
      $e.container.removeClass('ftwp-minimize').addClass('ftwp-maximize');

      $e.container.trigger('ftocAfterMinMax');
      $e.container.trigger('ftocAfterMaximize');

      consoleLog('Maximized FTOC.');
    };

    // Minimize
    var minimize = function() {
      $e.container.removeClass('ftwp-maximize').addClass('ftwp-minimize');

      $e.container.trigger('ftocAfterMinMax');
      $e.container.trigger('ftocAfterMinimize');

      consoleLog('Minimized FTOC.');
    };

    // Click anywhere except the container.
    var quickMin = function(evt) {
      if ((evt.type == 'touchstart') && (dataObj.data.window.width > 768)) {
        return;
      }

      if ($e.container.hasClass('ftwp-maximize') &&
          !($.contains($e.container.get(0), evt.target))) {
        minimize();
      }
    };

    // Press the esc keyboard to minimize the container.
    var escMin = function(evt) {
      if ($e.container.hasClass('ftwp-maximize') && 27 == evt.keyCode) {
        minimize();
      }
    };

    // Press the enter keyboard to maximize the container.
    var enterMax = function(evt) {
      if ($e.container.hasClass('ftwp-minimize') && 13 == evt.keyCode) {
        maximize();
      }
    };

    // Detect if it is maximized.
    var isMax = function() {
      return !!$e.container.hasClass('ftwp-maximize');
    };

    var isMin = function() {
      return !!$e.container.hasClass('ftwp-minimize');
    };

    // Public API
    return {
      _construct: _construct,
      _destruct: _destruct,
      isMax: isMax,
      isMin: isMin,
      minimize: minimize
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Fade trigger button.
   *
   * @since 3.0.0
   */
  var fadeTrigger = (function() {
    var fadeTimeoutId;
    var fadeCls = 'ftwp-fade-trigger';
    var unfadeCls = 'ftwp-unfade-trigger';

    // Constructor
    var _construct = function() {
      if ($e.container.hasClass('ftwp-minimize')) {
        start();
      }

      $e.trigger.on('mouseenter', pause).on('mouseleave', restart);
      $e.container.on('ftocAfterMinimize', start).on('ftocAfterMaximize', stop);

      consoleLog('Actived fadeTrigger().');
    };

    // Destructor
    var _destruct = function() {
      stop();

      $e.trigger.off('mouseenter', mouseEnter);
      $e.trigger.off('mouseleave', mouseLeave);
      $e.container.off('ftocAfterMinimize', start);
      $e.container.off('ftocAfterMaximize', stop);

      consoleLog('Deactivated fadeTrigger().');
    };

    // Start
    var start = function() {
      if (undefined === fadeTimeoutId) {
        setTimeout(function() {
          $e.container.removeClass(ftocInOut.effectInCls);
        }, 500);

        fadeTimeoutId = setTimeout(function() {
          $e.trigger.addClass(fadeCls);
        }, fixedtocOption.fadeTriggerDuration);
      }
    };

    // Stop
    var stop = function() {
      if (undefined !== fadeTimeoutId) {
        clearTimeout(fadeTimeoutId);
        fadeTimeoutId = undefined;
        $e.trigger.removeClass(fadeCls + ' ' + unfadeCls);
      }
    };

    // Pause
    var pause = function() {
      if (undefined !== fadeTimeoutId) {
        clearTimeout(fadeTimeoutId);
        $e.trigger.removeClass(fadeCls).addClass(unfadeCls);
      }
    };

    // Restart
    var restart = function() {
      if (undefined !== fadeTimeoutId) {
        stop();
        start();
      }
    };

    // Mouse enter
    var mouseEnter = function() {
      pause();
    };

    // Mouse leave
    var mouseLeave = function() {
      restart();
    };

    // Public API
    return {
      _construct: _construct,
      _destruct: _destruct,
      stop: stop,
      start: start,
      restart: restart,
      mouseLeave: mouseLeave
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Blur background.
   *
   * @since 3.0.0
   */
  var blurBackground = (function() {
    // Start blur
    var start = function(ele) {
      if (conditionObj.isNotBlur()) {
        return;
      }

      if (ele && ele.length) {
        ele.removeClass('ftwp-unblur').addClass('ftwp-blur');
      }
    };

    // Stop blur
    var stop = function(ele) {
      if (ele && ele.length && ele.hasClass('ftwp-blur')) {
        ele.removeClass('ftwp-blur').addClass('ftwp-unblur');
        setTimeout(function() {
          ele.removeClass('ftwp-unblur');
        }, 500);
      }
    };

    // Clear blur
    var clear = function(ele) {
      if (ele && ele.length) {
        ele.removeClass('ftwp-blur ftwp-unblur');
      }
    };

    // Public API
    return {
      start: start,
      stop: stop,
      clear: clear
    };
  })();

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Blur body.
   *
   * @since 3.0.0
   */
  var blurBody = (function() {
    var $targets;

    // Constructor
    var _construct = function() {
      init();
      $e.window.on('ftocResize', initOnResize);
      $e.container.on('ftocAfterMaximize', afterMax);
      $e.container.on('ftocAfterMinimize', afterMin);
      $e.container.on('ftocAfterScrollToTarget', afterScrollToTarget);

      consoleLog('Actived blurBody().');
    };

    // Destructor
    var _destruct = function() {
      uninit();
      $e.window.off('ftocResize', initOnResize);
      $e.container.off('ftocAfterMaximize', afterMax);
      $e.container.off('ftocAfterMinimize', afterMin);
      $e.container.off('ftocAfterScrollToTarget', afterScrollToTarget);

      consoleLog('Deactivated blurBody().');
    };

    var init = function() {
      $targets = $e.container.siblings(':not("script, style")');

      if (minMaxFtoc.isMax() && isBlur()) {
        blurBackground.start($targets);
      }
    };

    var initOnResize = function() {
      if (minMaxFtoc.isMax() && isBlur()) {
        blurBackground.start($targets);
      } else {
        blurBackground.stop($targets);
      }
    };

    var afterMax = function() {
      if (isBlur()) {
        blurBackground.start($targets);
      }
    };

    var afterMin = function() {
      blurBackground.stop($targets);
    };

    var afterScrollToTarget = function(evt, $anchor) {
      if (isBlur() && $e.container.hasClass('ftwp-maximize')) {
        minMaxFtoc.minimize();
        blurBackground.stop($targets);
        $anchor.trigger('blur');
      }
    };

    var uninit = function() {
      blurBackground.clear($targets);
    };

    var isBlur = function() {
      return $e.window.width() * 0.6 <= $e.contents.outerWidth();
    };

    // Public API
    return {
      _construct: _construct,
      _destruct: _destruct
    };
  })();

  if (conditionObj.fixedWidget()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Blur widgets.
     *
     * @since 3.0.0
     */
    var blurWidgets = (function() {
      var $targets;

      // Constructor
      var _construct = function() {
        $targets = $e.widget.siblings('.widget');

        init();
        $e.contents.on('ftocAfterExpandContents', startBlur);
        $e.contents.on('ftocAfterCollapseContents', stopBlur);

        consoleLog('Actived blurWidgets().');
      };

      // Destructor
      var _destruct = function() {
        uninit();
        $e.contents.off('ftocAfterExpandContents', startBlur);
        $e.contents.off('ftocAfterCollapseContents', stopBlur);

        consoleLog('Deactivated blurWidgets().');
      };

      // Init
      var init = function() {
        if ('expand' == $e.contents.data('colexp')) {
          blurBackground.start($targets);
        }
      };

      // Start blur
      var startBlur = function() {
        blurBackground.start($targets);
      };

      // Stop blur
      var stopBlur = function() {
        blurBackground.stop($targets);
      };

      // Uninit
      var uninit = function() {
        blurBackground.clear($targets);
      };

      // Public API
      return {
        _construct: _construct,
        _destruct: _destruct
      };
    })();
  }

  if (conditionObj.inWidget() || conditionObj.supportInPost()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Collapse/expand contents.
     *
     * @since 3.0.0
     */
    var colExpContents = (function() {
      var funcExp, funcCol, firstInit = true;

      // Constructor
      var construct = function(expFunc, colFunc) {
        funcExp = expFunc;
        funcCol = colFunc;

        init();

        var clickableElement = conditionObj.isClickableHeader()
            ? $e.header
            : $e.minIcon;

        clickableElement.on('mousedown', preventDefaultEvt);
        clickableElement.on('click', toggle);
      };

      // Destructor
      var destruct = function() {
        uninit();

        var clickableElement = conditionObj.isClickableHeader()
            ? $e.header
            : $e.minIcon;

        clickableElement.off('mousedown', preventDefaultEvt);
        clickableElement.off('click', toggle);
      };

      // Init
      var init = function() {
        if (conditionObj.isMobile() && firstInit) {
          console.log(conditionObj.isColExpInitMobile());
          if (conditionObj.isColExpInitMobile()) {
            collapse(0, funcCol);
          } else {
            expand(0, funcExp);
          }

          controlObj.reload();
        } else if (isExpand()) {
          expand(0, funcExp);
        } else {
          collapse(0, funcCol);
        }

        firstInit = false;
      };

      // Uninit
      var uninit = function() {
        $e.list.show(0);
        $e.minIcon.removeClass('ftwp-icon-collapse ftwp-icon-expand');
      };

      // Toggle
      var toggle = function(evt) {
        if (isExpand()) {
          collapse(100, funcCol, evt);
        } else {
          expand(100, funcExp, evt);
        }
      };

      // Detect if it is expand state.
      var isExpand = function() {
        var colExpData = $e.contents.data('colexp');
        return 'expand' == colExpData || undefined === colExpData;
      };

      // Collapse
      var collapse = function(duration, func, evt) {
        $e.list.hide(duration, function() {
          $e.minIcon.removeClass('ftwp-icon-expand').
              addClass('ftwp-icon-collapse');
          if (undefined !== func) func(evt);
        });

        $e.contents.data('colexp', 'collapse');
        $e.contents.trigger('ftocAfterCollapseContents');

        consoleLog('Collapsed contents.');
      };

      // Expand
      var expand = function(duration, func, evt) {
        $e.list.show(duration, function() {
          $e.minIcon.removeClass('ftwp-icon-collapse').
              addClass('ftwp-icon-expand');
          if (undefined !== func) func(evt);
        });

        $e.contents.data('colexp', 'expand');
        $e.contents.trigger('ftocAfterExpandContents');

        consoleLog('Expanded contents.');
      };

      return {
        construct: construct,
        destruct: destruct
      };
    })();
  }

  if (conditionObj.inWidget()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Collapse/expand contents in widget.
     *
     * @since 3.0.0
     */
    var colExpConentsInWidget = (function() {
      // Constructor
      var _construct = function() {
        colExpContents.construct();

        consoleLog('Actived colExpConentsInWidget().');
      };

      // Destructor
      var _destruct = function() {
        colExpContents.destruct();

        consoleLog('Deactivated colExpConentsInWidget().');
      };

      // Public API
      return {
        _construct: _construct,
        _destruct: _destruct
      };
    })();
  }

  if (conditionObj.fixedWidget()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Collapse/expand contents in fixed widget.
     *
     * @since 3.0.0
     */
    var colExpConentsInFixedWidget = (function() {
      // Constructor
      var _construct = function() {
        colExpContents.construct(funcExp, funcCol);

        consoleLog('Actived colExpConentsInFixedWidget().');
      };

      // Destructor
      var _destruct = function() {
        colExpContents.destruct();

        consoleLog('Deactivated colExpConentsInFixedWidget().');
      };

      // Expend function.
      var funcExp = function() {
        dataObj.ftocRectInWidget.updateHeight();
        fixedInWidget.setFixed();
        //				listHeight.set();
      };

      // Collapse function.
      var funcCol = function() {
        dataObj.ftocRectInWidget.updateHeight();
        fixedInWidget.setFixed();
        //				listHeight.set();
      };

      // Public API
      return {
        _construct: _construct,
        _destruct: _destruct
      };
    })();
  }

  if (conditionObj.inWidget()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Display in widget.
     *
     * @since 3.0.0
     */
    var displayInWidget = (function() {
      // Constructor
      var _construct = function() {
        init();

        consoleLog('Actived displayInWidget().');
      };

      // Destruct
      var _destruct = function() {
        $e.contents.css('height', '');

        consoleLog('Deactivated displayInWidget().');
      };

      var init = function() {
        if (!$e.container.parent().is($e.widgetContainer)) {
          $e.container.appendTo($e.widgetContainer);
        }

        $e.contents.css('height', 'auto');
      };

      // Public API
      return {
        _construct: _construct,
        _destruct: _destruct,
        init: init
      };
    })();
  }

  if (conditionObj.fixedWidget()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Fixed in widget.
     *
     * @since 3.0.0
     */
    var fixedInWidget = (function() {
      // Constructor
      var _construct = function() {
        init();

        setFixed();
        $e.window.on('ftocResize', setFixed);

        consoleLog('Actived fixedInWidget().');
      };

      // Destruct
      var _destruct = function() {
        init();

        unsetFixed();
        $e.window.off('ftocResize', setFixed);

        listHeight.unset();

        consoleLog('Deactivated fixedInWidget().');
      };

      // Init
      var init = function() {
        displayInWidget.init();
      };

      // Uninit
      // var uninit = function() {
      //   displayInWidget.uninit();
      // };

      // Set fixed position.
      var setFixed = function() {
        $e.widget.addClass('ftwp-widget-fixed');

        var rect = dataObj.data.ftocRectInWidget;
        $e.contents.css({
          left: rect.left,
          top: rect.top,
          width: rect.width + 'px',
          height: rect.height + 'px'
        });

        // After set fixed position.
        var contentsHeight = getContentsHeight(rect.height);
        listHeight.set(contentsHeight);
      };

      // Unset fixed position
      var unsetFixed = function() {
        $e.widget.removeClass('ftwp-widget-fixed');

        $e.contents.css({
          left: '',
          top: '',
          width: '',
          height: ''
        });
      };

      // Public API
      return {
        _construct: _construct,
        _destruct: _destruct,
        setFixed: setFixed
      };
    })();
  }

  if (conditionObj.supportInPost()) {
    /** ---------------------------------------------------------------------------------------------------------------------------------------
     * Display in post
     *
     * @since 3.0.0
     */
    var displayInPost = (function() {
      // Constructor
      var _construct = function() {
        init();
        $e.window.on('ftocResize', setSize);

        consoleLog('Actived displayInPost().');
      };

      // Destructor
      var _destruct = function() {
        uninit();
        $e.window.off('ftocResize', setSize);

        // Keep the containerOuter height.
        if (conditionObj.isAutoHeightInPost()) {
          var height = $e.containerOuter.height();
          $e.containerOuter.css('height', height + 'px');
        }

        consoleLog('Deactivated displayInPost().');
      };

      // Init
      var init = function() {
        if (!$e.container.parent().is($e.containerOuter)) {
          $e.container.appendTo($e.containerOuter);
        }

        setSize();
      };

      // Uninit
      var uninit = function() {
        $e.contents.css('height', '');
        listHeight.unset();
      };

      // Set width and height
      var containerOuterWidth;
      var setSize = function() {
        // Width
        if (0 == fixedtocOption.contentsWidthInPost && conditionObj.isFloat()) {
          $e.containerOuter.css('width', '');
//					containerOuterWidth = $e.containerOuter.outerWidth() + 1;
          containerOuterWidth = $e.containerOuter.outerWidth();
          $e.containerOuter.css('width', containerOuterWidth + 'px');
        } else {
          $e.containerOuter.css('width', '');
          //					containerOuterWidth = undefined;
        }

        // Height
        if (conditionObj.isAutoHeightInPost()) {
          $e.containerOuter.css('height', 'auto');
          $e.contents.css('height', 'auto');
          listHeight.setAuto();
        } else {
          $e.containerOuter.css('height',
              dataObj.data.containerOuterHeight + 'px');
          $e.contents.css('height', dataObj.data.containerOuterHeight + 'px');

          var contentsHeight = getContentsHeight(
              dataObj.data.containerOuterHeight);
          listHeight.set(contentsHeight);
        }
      };

      // Public API
      return {
        _construct: _construct,
        _destruct: _destruct
      };
    })();

    /* Collapse/expand contents in post.
     *
     * @since 3.0.0
     */
    var colExpConentsInPost = (function() {
      // Constructor
      var _construct = function() {
        colExpContents.construct(funcExp, funcCol);

        consoleLog('Actived colExpConentsInFixedWidget().');
      };

      // Destructor
      var _destruct = function() {
        colExpContents.destruct();

        consoleLog('Deactivated colExpConentsInFixedWidget().');
      };

      // Expend function.
      var funcExp = function(evt) {
        funcCol(evt);
      };

      // Collapse function.
      var funcCol = function(evt) {
        if (undefined !== evt && 'click' == evt.type) {
          dataObj.updateInPost();
        }

//				$e.containerOuter.css('height', dataObj.data.containerOuterHeight + 'px');
//				$e.contents.css('height', dataObj.data.containerOuterHeight + 'px');
//				listHeight.set(getContentsHeight(dataObj.data.containerOuterHeight));
      };

      // Public API
      return {
        _construct: _construct,
        _destruct: _destruct
      };
    })();
  }

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Control the whole.
   *
   * @since 3.0.0
   */
  var controlObj = (function() {
    // Handler for 'ready' event
    var onReady = function() {
      // Set extra options.
      option.init();

      // Create elements.
      createElements();

      // Set index data to anchors.
      setAnchorIndex();

      // Trigger ftoc_ready event.
      $e.container.trigger('ftocReady');

      // Bind load event.
      onLoad();
      setTimeout(reload, 100);    // Force to refresh data if the page isn't still loaded.

      // Output fixedtocOption in console.
      consoleLog(fixedtocOption);
    };

    // Handler for 'load' event
    var onLoad = function() {
      // Create data.
      dataObj.createOnInit();

      // Register actions.
      if (conditionObj.isColExpList()) {
        actionObj.register('common', colExpSubList);
      }
      actionObj.register('common', scrollToTarget);
      actionObj.register('common', targetIndicator);

      actionObj.register('hidden', hideToc);

      if (conditionObj.fixedWidget()) {
        actionObj.register('fixedWidget', colExpConentsInFixedWidget);
        actionObj.register('fixedWidget', fixedInWidget);
        actionObj.register('fixedWidget', blurWidgets);
      }

      if (conditionObj.inWidget()) {
        actionObj.register('inWidget', displayInWidget);
        actionObj.register('inWidget', colExpConentsInWidget);
      }

      if (conditionObj.supportInPost()) {
        actionObj.register('inPost', displayInPost);
        actionObj.register('inPost', colExpConentsInPost);
      }

      actionObj.register('fixedToPost', ftocInOut);
      actionObj.register('fixedToPost', minMaxFtoc);
      actionObj.register('fixedToPost', fadeTrigger);
      actionObj.register('fixedToPost', blurBody);

      // Initial actions.
      actionObj.init();

      // Bind resize event.
      $e.window.on('resize', onResize);

      // Bind scroll event.
      $e.window.on('scroll', onScroll);
    };

    // Handler for 'resize' event.
    var onResize = function() {
      // Update data and actions on resize.
      dataObj.updateOnResize();
      actionObj.updateOnResize();
      $e.window.trigger('ftocResize');

      //			$( '#ftwp-header-title' ).text( dataObj.data.window.height );
    };

    // handler for 'scroll' event.
    // var prevWindowHeight;
    var onScroll = function() {
      // Update data and actions on scroll.
      dataObj.updateOnDocumentHeightChange();
      dataObj.updateOnScroll();

      actionObj.updateOnScroll();

      $e.window.trigger('ftocScroll');
    };

    // Reload
    var reload = function() {
      // Output fixedtocOption in console.
      consoleLog(fixedtocOption);

      dataObj.updateOnResize();
      actionObj.updateOnResize();
      $e.window.trigger('ftocResize');
    };

    // Public API
    return {
      option: option,
      onReady: onReady,
      reload: reload
    };
  })(); // End controlObj

  // Bind ready event.
  $(document).ready(controlObj.onReady);
  $(document).on('load', conditionObj.reload);

  /** ---------------------------------------------------------------------------------------------------------------------------------------
   * Public API.
   *
   * @since 3.0.0
   */
  return {
    option: controlObj.option,
    reload: controlObj.reload
  };

})(jQuery);