<file_sep>import throttle from 'lodash/throttle'
import debounce from 'lodash/debounce'
import * as viewport from "../utilities/viewport";

export class Video {
  constructor() {
    this.$placeholders = $('.lazy-video-placeholder');

    if ( !this.$placeholders.length ) return;

    this.initialize();

    this.removePersistedVideos();

    document.addEventListener('turbo:before-cache', () => {
      this.turnOffEventListeners();
    }, { once: true });
  }

  removePersistedVideos() {
    document.querySelectorAll('.turbo-persist-video').forEach((video) => {
      video.pause();
      video.src = '';
      video.remove();
      video = null;
    });
  }

  turnOffEventListeners() {
    this.$videos.each((index, video) => {
      if ( video.classList.contains('turbo-persist-video') ) return;

      video.pause();
      video.src = '';
      video.remove();
      video = null;
    });
    this.$videos = null;
    this.$placeholders.removeClass('lazy-video-initialized');
    App.$window.off('resize.lazyLoadVideo scroll.lazyLoadVideo');
  }

  initialize() {
    if ( !this.$placeholders.length ) return;

    this.$placeholders.each(function() {
      var $placeholder = $(this);

      if ( $placeholder.hasClass('lazy-video-initialized') ) return;

      var videoHTML = $placeholder.attr('data-video-tag-html');
      var $video = $(videoHTML);

      if ( $placeholder.hasClass('desktop-video') && App.breakpoint.isMobile() ) return;
      if ( $placeholder.hasClass('mobile-video') && !App.breakpoint.isMobile() ) return;

      var video = $video[0];
      var srcFull = $video.attr('data-src');
      var srcMobile = $video.attr('data-src-mobile');
      var src;

      if ( App.breakpoint.isMobile() && srcMobile ) {
        src = srcMobile;
      } else {
        src = srcFull;
      }

      $video.addClass('lazy-video').attr('src', src);
      $placeholder.addClass('lazy-video-initialized').hide().after($video);

      $video.one('loadedmetadata', function() {
        $video.closest('.video-jumpfix').addClass('has-loadedmetadata');
      });
    });

    this.$videos = $('.lazy-video');

    var checkForVisibility = () => {
      if ( !this.$videos || !this.$videos.length ) return;

      this.$videos.each((index, element) => {
        var $video = $(element);
        var video = $video[0];
        var autoplay = $video.attr('autoplay');
        var isInHero = $video.closest('#hero-viewport-container').length;
        var isVisible = false;

        if ( !autoplay || autoplay == 'false' ) {
          $video.data('lazy-load-video-autoplay-disabled', true);
          return;
        }

        if ( isInHero ) {
          if ( App.scrollTop < App.windowHeight / 2 ) {
            isVisible = true;
          }
        } else if ( viewport.isVisible(video) ) {
          if ( $video.data('has-been-paused-by-user') ) return;
          if ( $video.data('hasFinishedPlaying') == true && ( !loop || loop == 'false' ) ) return;

          isVisible = true;
        }

        if ( isVisible && !$video.is(':visible') ) {
          isVisible = false;
        }

        if ( isVisible ) {
          var playPromise = video.play();

          if ( playPromise !== undefined ) {
            playPromise.then(function() {
              // Automatic playback started
            }).catch(function(error) {
              // Auto-play was prevented
              console.warn('Error playing video', error);
            });
          }
        } else {
          video.pause();
        }
      });
    };

    App.$window.on('resize.lazyLoadVideo', debounce(checkForVisibility, 500));
    App.$window.on('scroll.lazyLoadVideo', throttle(checkForVisibility, 500));
    checkForVisibility();

    var $disabledVideos = this.$placeholders.filter(function() {
      return $(this).data('lazy-load-video-autoplay-disabled') == true;
    });

    if ( this.$placeholders.length == $disabledVideos.length ) {
      this.turnOffEventListeners();
    }
  }
}

// Export an init function that looks for and instantiates the module on pageload
export default (() => {
  App.pageLoad.push(() => {
    new Video();
  });

  $(function() {
    App.$document.on('click.lazyLoadVideo', '.lazy-video', function() {
      var $video = $(this);

      if ( $video[0].paused ) {
        $video.data('has-been-paused-by-user', false);
        $video[0].play();
      } else {
        $video.data('has-been-paused-by-user', true);
        $video[0].pause();
      }
    });
  });
})();

App.$document.on('click.lazyLoadVideo', '.video-controls__control', function() {
  var $control = $(this);
  var $scope = $control.closest('[data-video-scope]');
  var $audioOn = $scope.find('.video-controls__audio-on');
  var $audioOff = $scope.find('.video-controls__audio-off');
  var $video = $scope.find('video').filter(':visible');

  if ( $video[0].muted ) {
    $video[0].muted = false;
    $audioOn.hide();
    $audioOff.show();
  } else {
    $video[0].muted = true;
    $audioOn.show();
    $audioOff.hide();
  }
});
<file_sep>In a new Rails app running Forest:

```
rm package.json
cd app/assets
git clone <EMAIL>:dylanfisher/forest-assets.git
mv forest-assets/package.json ../../
rm -rf javascripts
rm -rf ../javascript
rm -rf stylesheets
rm -rf config
rm -rf forest-assets/.git
rm forest-assets/.gitignore
rm forest-assets/README.md
mv forest-assets/* ./
mkdir ../javascript
mv javascripts/* ../javascript
rmdir javascripts
rmdir forest-assets
cd ../../
```
<file_sep>// External links

App.isExternalLink = function($el) {
  var documentHost = document.location.href.split('/')[2];
  var internalLinkExceptions = ['mailto:', 'tel:', 'sms:', 'javascript:'];
  var href = $el.attr('href');
  var link = $el.get(0).href;
  var linkHost = link.split('/')[2];
  var internalLinkMatches = $.map(internalLinkExceptions, function(d) {
    if ( href ) {
      return href.indexOf(d) != -1;
    } else {
      return true;
    }
  });

  return linkHost != documentHost && ( $.inArray(true, internalLinkMatches) === -1 );
};

App.pageLoad.push(function() {
  $('a').each(function() {
    var $link = $(this);

    if ( App.isExternalLink($link) ) {
      var target = $link.attr('target');

      if ( !target ) {
        target = '_blank';
      }

      $link.attr('target', '_blank')
           .addClass('external-link');
    }
  });
});
<file_sep>// Smooth scroll

import { gsap } from "gsap";
import { ScrollToPlugin } from "gsap/ScrollToPlugin";

gsap.registerPlugin(ScrollToPlugin);

App.pageLoad.push(function() {
  if ( location.hash.length > 1 ) {
    var hash = location.hash.substr(1);

    var $el = $('[name="' + hash + '"], #' + hash).first();

    if ( $el.attr('data-default-jump-link') != undefined ) return;

    // carousel modules can mess up spacing of page, so wait a second before scrolling
    setTimeout(function() {
      if ( $el.length ) App.scrollTo($el);
    }, 250);
  }
});

$(document).on('click', 'a[href*="#"]:not([href="#"])', function(e) {
  // If you want to disable the call to `e.preventDefault()` you can add a ` data-default-jump-link` attribute to the link.
  // This is good if e.g. you want to enable the default history behavior for jump links.
  // <a href="#jump-link" data-default-jump-link>the hash in this jump link get's added to the current URL as usual</a>

  var $target = $(e.target);

  if ( location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'' ) && location.hostname == this.hostname && location.search == this.search) {
    var target = $(this.hash);
    target = target.length ? target : $('[name=' + this.hash.slice(1) +']');

    if ( target.length ) {
      var offset = parseInt( $target.attr('data-smooth-scroll-offset') );
      App.scrollTo(target, offset);

      if ( $(this).attr('data-default-jump-link') == undefined ) {
        e.preventDefault();
      }
    }
  }
});

App.scrollTo = function(targetOrPosition, offset, callback) {
  var smoothScrollOffset = (offset || offset == 0) ? offset : 0;
  var duration = 1.5;
  var scrollTop;

  if ( isNaN(targetOrPosition) ) {
    if ( !targetOrPosition.length ) {
      console.warn('Can\'t find target to scroll to.', targetOrPosition);
      return;
    } else {
      scrollTop = targetOrPosition.offset().top - smoothScrollOffset;
    }
  } else {
    scrollTop = targetOrPosition;
  }

  gsap.to(window, {
    duration: duration,
    scrollTo: scrollTop,
    ease: 'power4.out',
    onComplete: function() {
      if ( callback ) callback();
    }
  });
};
