Skip to content

Instantly share code, notes, and snippets.

@dcooney
Created October 18, 2021 13:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcooney/525f47b25133d820990b16de4b7c2e2e to your computer and use it in GitHub Desktop.
Save dcooney/525f47b25133d820990b16de4b7c2e2e to your computer and use it in GitHub Desktop.
EZ TOC JS
jQuery( function( $ ) {
/**
* @typedef ezTOC
* @type {Object} ezTOC
* @property {string} affixSelector
* @property {string} scroll_offset
* @property {string} smooth_scroll
* @property {string} visibility_hide_by_default
*/
if ( typeof ezTOC != 'undefined' ) {
/**
* Init EZ TOC.
*/
function ezTOCInit() {
var affix = $( '.ez-toc-widget-container.ez-toc-affix' );
if ( 0 !== affix.length ) {
/**
* The smooth scroll offset needs to be taken into account when defining the offset_top property.
* @link https://github.com/shazahm1/Easy-Table-of-Contents/issues/19
*
* @type {number}
*/
var affixOffset = 30;
// check offset setting
if ( typeof ezTOC.scroll_offset != 'undefined' ) {
affixOffset = parseInt( ezTOC.scroll_offset );
}
$( ezTOC.affixSelector ).stick_in_parent( {
inner_scrolling: false,
offset_top: affixOffset
} )
}
$.fn.shrinkTOCWidth = function() {
$( this ).css( {
width: 'auto',
display: 'table'
});
if ( /MSIE 7\./.test( navigator.userAgent ) )
$( this ).css( 'width', '' );
};
var smoothScroll = parseInt( ezTOC.smooth_scroll );
if ( 1 === smoothScroll ) {
$( 'a.ez-toc-link' ).on( 'click', function(e) {
e.preventDefault();
var self = $( this );
var target = '';
var hostname = self.prop( 'hostname' );
var pathname = self.prop( 'pathname' );
var qs = self.prop( 'search' );
var hash = self.prop( 'hash' );
// ie strips out the preceding / from pathname
if ( pathname.length > 0 ) {
if ( pathname.charAt( 0 ) !== '/' ) {
pathname = '/' + pathname;
}
}
if ( ( window.location.hostname === hostname ) &&
( window.location.pathname === pathname ) &&
( window.location.search === qs ) &&
( hash !== '' )
) {
// var id = decodeURIComponent( hash.replace( '#', '' ) );
target = '[id="' + hash.replace( '#', '' ) + '"]';
// verify it exists
if ( $( target ).length === 0 ) {
console.log( 'ezTOC scrollTarget Not Found: ' + target );
target = '';
}
// check offset setting
if ( typeof ezTOC.scroll_offset != 'undefined' ) {
var offset = -1 * ezTOC.scroll_offset;
} else {
var adminbar = $( '#wpadminbar' );
if ( adminbar.length > 0 ) {
if ( adminbar.is( ':visible' ) )
offset = -30; // admin bar exists, give it the default
else
offset = 0; // there is an admin bar but it's hidden, so no offset!
} else {
// no admin bar, so no offset!
offset = 0;
}
}
if ( target ) {
$.smoothScroll( {
scrollTarget: target,
offset: offset,
beforeScroll: deactivateSetActiveEzTocListElement,
afterScroll: function() { setActiveEzTocListElement(); activateSetActiveEzTocListElement(); }
} );
}
}
} );
}
if ( typeof ezTOC.visibility_hide_by_default != 'undefined' ) {
var toc = $( 'ul.ez-toc-list' );
var toggle = $( 'a.ez-toc-toggle' );
var invert = ezTOC.visibility_hide_by_default;
toggle.css( 'display', 'inline' );
if ( Cookies ) {
Cookies.get( 'ezTOC_hidetoc' ) == 1 ? toggle.data( 'visible', false ) : toggle.data( 'visible', true );
} else {
toggle.data( 'visible', true );
}
if ( invert ) {
toggle.data( 'visible', false )
}
if ( ! toggle.data( 'visible' ) ) {
toc.hide();
}
toggle.on( 'click', function( event ) {
event.preventDefault();
if ( $( this ).data( 'visible' ) ) {
$( this ).data( 'visible', false );
if ( Cookies ) {
if ( invert )
Cookies.set( 'ezTOC_hidetoc', null, { path: '/' } );
else
Cookies.set( 'ezTOC_hidetoc', '1', { expires: 30, path: '/' } );
}
toc.hide( 'fast' );
} else {
$( this ).data( 'visible', true );
if ( Cookies ) {
if ( invert )
Cookies.set( 'ezTOC_hidetoc', '1', { expires: 30, path: '/' } );
else
Cookies.set( 'ezTOC_hidetoc', null, { path: '/' } );
}
toc.show( 'fast' );
}
} );
}
// ======================================
// Set active heading in ez-toc-widget list
// ======================================
var headings = $( 'span.ez-toc-section' ).toArray();
var headingToListElementLinkMap = getHeadingToListElementLinkMap( headings );
var listElementLinks = $.map( headingToListElementLinkMap, function ( value, key ) {
return value
} );
var scrollOffset = getScrollOffset();
activateSetActiveEzTocListElement();
function setActiveEzTocListElement() {
var activeHeading = getActiveHeading( scrollOffset, headings );
if ( activeHeading ) {
var activeListElementLink = headingToListElementLinkMap[ activeHeading.id ];
removeStyleFromNonActiveListElement( activeListElementLink, listElementLinks );
setStyleForActiveListElementElement( activeListElementLink );
}
}
function activateSetActiveEzTocListElement() {
if ( headings.length > 0 && $('.ez-toc-widget-container').length) {
$( window ).on( 'load resize scroll', setActiveEzTocListElement );
}
}
function deactivateSetActiveEzTocListElement() {
$( window ).off( 'load resize scroll', setActiveEzTocListElement );
}
function getEzTocListElementLinkByHeading( heading ) {
return $( '.ez-toc-widget-container .ez-toc-list a[href="#' + $( heading ).attr( 'id' ) + '"]' );
}
function getHeadingToListElementLinkMap( headings ) {
return headings.reduce( function ( map, heading ) {
map[ heading.id ] = getEzTocListElementLinkByHeading( heading );
return map;
}, {} );
}
function getScrollOffset() {
var scrollOffset = 5; // so if smooth offset is off, the correct title is set as active
if ( typeof ezTOC.smooth_scroll != 'undefined' && parseInt( ezTOC.smooth_scroll ) === 1 ) {
scrollOffset = ( typeof ezTOC.scroll_offset != 'undefined' ) ? parseInt( ezTOC.scroll_offset ) : 30;
}
var adminbar = $( '#wpadminbar' );
if ( adminbar.length ) {
scrollOffset += adminbar.height();
}
return scrollOffset;
}
function getActiveHeading( topOffset, headings ) {
var scrollTop = $( window ).scrollTop();
var relevantOffset = scrollTop + topOffset + 1;
var activeHeading = headings[ 0 ];
var closestHeadingAboveOffset = relevantOffset - $( activeHeading ).offset().top;
headings.forEach( function ( section ) {
var topOffset = relevantOffset - $( section ).offset().top;
if ( topOffset > 0 && topOffset < closestHeadingAboveOffset ) {
closestHeadingAboveOffset = topOffset;
activeHeading = section;
}
} );
return activeHeading;
}
function removeStyleFromNonActiveListElement( activeListElementLink, listElementLinks ) {
listElementLinks.forEach( function ( listElementLink ) {
if ( activeListElementLink !== listElementLink && listElementLink.parent().hasClass( 'active' ) ) {
listElementLink.parent().removeClass( 'active' );
}
} );
}
function correctActiveListElementBackgroundColorHeight( activeListElement ) {
var listElementHeight = getListElementHeightWithoutUlChildren( activeListElement );
addListElementBackgroundColorHeightStyleToHead( listElementHeight );
}
function getListElementHeightWithoutUlChildren( listElement ) {
var $listElement = $( listElement );
var content = $listElement.html();
// Adding list item with class '.active' to get the real height.
// When adding a class to an existing element and using jQuery(..).height() directly afterwards,
// the height is the 'old' height. The height might change due to text-wraps when setting the text-weight bold for example
// When adding a new item, the height is calculated correctly.
// But only when it might be visible (so display:none; is not possible...)
// But because it get's directly removed afterwards it never will be rendered by the browser
// (at least in my tests in FF, Chrome, IE11 and Edge)
$listElement.parent().append( '<li id="ez-toc-height-test" class="active">' + content + '</li>' );
var listItem = $( '#ez-toc-height-test' );
var height = listItem.height();
listItem.remove();
return height - $listElement.children( 'ul' ).first().height();
}
function addListElementBackgroundColorHeightStyleToHead( listElementHeight ) {
// Remove existing
$( '#ez-toc-active-height' ).remove();
// jQuery(..).css(..) doesn't work, because ::before is a pseudo element and not part of the DOM
// Workaround is to add it to head
$( '<style id="ez-toc-active-height">' +
'.ez-toc-widget-container ul.ez-toc-list li.active::before {' +
// 'line-heigh:' + listElementHeight + 'px; ' +
'height:' + listElementHeight + 'px;' +
'} </style>' )
.appendTo( 'head' );
}
function setStyleForActiveListElementElement( activeListElementLink ) {
var activeListElement = activeListElementLink.parent();
if ( !activeListElement.hasClass( 'active' ) ) {
activeListElement.addClass( 'active' );
}
correctActiveListElementBackgroundColorHeight( activeListElement );
}
}
/**
* Attach global init handler to ezTOC window object.
*/
ezTOC.init = function(){
ezTOCInit();
}
// Start EZ TOC on page load.
ezTOCInit();
}
});
jQuery((function(e){if("undefined"!=typeof ezTOC){function t(){if(0!==e(".ez-toc-widget-container.ez-toc-affix").length){var t=30;void 0!==ezTOC.scroll_offset&&(t=parseInt(ezTOC.scroll_offset)),e(ezTOC.affixSelector).stick_in_parent({inner_scrolling:!1,offset_top:t})}if(e.fn.shrinkTOCWidth=function(){e(this).css({width:"auto",display:"table"}),/MSIE 7\./.test(navigator.userAgent)&&e(this).css("width","")},1===parseInt(ezTOC.smooth_scroll)&&e("a.ez-toc-link").on("click",(function(t){t.preventDefault();var i=e(this),o="",n=i.prop("hostname"),a=i.prop("pathname"),s=i.prop("search"),r=i.prop("hash");if(a.length>0&&"/"!==a.charAt(0)&&(a="/"+a),window.location.hostname===n&&window.location.pathname===a&&window.location.search===s&&""!==r){if(o='[id="'+r.replace("#","")+'"]',0===e(o).length&&(o=""),void 0!==ezTOC.scroll_offset)var c=-1*ezTOC.scroll_offset;else{var d=e("#wpadminbar");c=d.length>0&&d.is(":visible")?-30:0}o&&e.smoothScroll({scrollTarget:o,offset:c,beforeScroll:h,afterScroll:function(){l(),f()}})}})),void 0!==ezTOC.visibility_hide_by_default){var i=e("ul.ez-toc-list"),o=e("a.ez-toc-toggle"),n=ezTOC.visibility_hide_by_default;o.css("display","inline"),Cookies&&1==Cookies.get("ezTOC_hidetoc")?o.data("visible",!1):o.data("visible",!0),n&&o.data("visible",!1),o.data("visible")||i.hide(),o.on("click",(function(t){t.preventDefault(),e(this).data("visible")?(e(this).data("visible",!1),Cookies&&(n?Cookies.set("ezTOC_hidetoc",null,{path:"/"}):Cookies.set("ezTOC_hidetoc","1",{expires:30,path:"/"})),i.hide("fast")):(e(this).data("visible",!0),Cookies&&(n?Cookies.set("ezTOC_hidetoc","1",{expires:30,path:"/"}):Cookies.set("ezTOC_hidetoc",null,{path:"/"})),i.show("fast"))}))}var a=e("span.ez-toc-section").toArray(),s=function(t){return t.reduce((function(t,i){return t[i.id]=function(t){return e('.ez-toc-widget-container .ez-toc-list a[href="#'+e(t).attr("id")+'"]')}(i),t}),{})}(a),r=e.map(s,(function(e,t){return e})),c=function(){var t=5;void 0!==ezTOC.smooth_scroll&&1===parseInt(ezTOC.smooth_scroll)&&(t=void 0!==ezTOC.scroll_offset?parseInt(ezTOC.scroll_offset):30);var i=e("#wpadminbar");i.length&&(t+=i.height());return t}();function l(){var t=function(t,i){var o=e(window).scrollTop()+t+1,n=i[0],a=o-e(n).offset().top;return i.forEach((function(t){var i=o-e(t).offset().top;i>0&&i<a&&(a=i,n=t)})),n}(c,a);if(t){var i=s[t.id];!function(e,t){t.forEach((function(t){e!==t&&t.parent().hasClass("active")&&t.parent().removeClass("active")}))}(i,r),function(t){var i=t.parent();i.hasClass("active")||i.addClass("active");!function(t){!function(t){e("#ez-toc-active-height").remove(),e('<style id="ez-toc-active-height">.ez-toc-widget-container ul.ez-toc-list li.active::before {height:'+t+"px;} </style>").appendTo("head")}(function(t){var i=e(t),o=i.html();i.parent().append('<li id="ez-toc-height-test" class="active">'+o+"</li>");var n=e("#ez-toc-height-test"),a=n.height();return n.remove(),a-i.children("ul").first().height()}(t))}(i)}(i)}}function f(){a.length>0&&e(".ez-toc-widget-container").length&&e(window).on("load resize scroll",l)}function h(){e(window).off("load resize scroll",l)}f()}ezTOC.init=function(){t()},t()}}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment