Last active
October 14, 2019 10:49
-
-
Save gjroelofs/facee5638e30a581053c51a79839afb3 to your computer and use it in GitHub Desktop.
Javascript injection hack to create a TOC for Coda, also see: https://gist.github.com/gjroelofs/8cd6d0908ac72783ec97bbf7e0c91ef0 for CSS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Table of Contents **/ | |
var AUTOGENERATED_TABLE_OF_CONTENTS = true; | |
var TOC_ID_PREFIX = "auto_toc_"; | |
var canvasSection = ".document--root--MUOgORi0"; | |
var codeH1 = ".kr-h1"; | |
var codeH2 = ".kr-h2"; | |
var codeH3 = ".kr-h3"; | |
var codeTable = ".grid_title--title--2kGaCBGJ"; | |
var targettedCodes = [codeH1, codeH2, codeTable]; | |
// Setup Last() on Array | |
if (!Array.prototype.last){ | |
Array.prototype.last = function(){ | |
return this[this.length - 1]; | |
}; | |
}; | |
function hasAttribute(target, attr){ | |
// For some browsers, `attr` is undefined; for others, | |
// `attr` is false. Check for both. | |
var attr = target.attr(attr); | |
return typeof attr !== typeof undefined && attr !== false; | |
} | |
/** Updates the TOC, and assigns a listener to the element to update TOC upon changes. */ | |
function assignTOCChangeListener(){ | |
createOrUpdateTOC(); | |
// Ensure we can respond to any further changes to this target | |
// (Arrive only triggers once per element.) | |
$(this).attrchange(function(attrName){ | |
if( attrName != 'class') return; | |
var clzz = $(this).attr('class'); | |
// If the element is not anything we target any longer, we need to check whether we need to clean up. | |
if(!targettedCodes.some(code => clzz.includes(code))){ | |
// If we set the ID for TOC, clean it up | |
var id = $(this).attr('id'); | |
if(hasAttribute($(this), 'id') && $(this).attr('id').includes(TOC_ID_PREFIX)){ | |
$(this).removeAttr('id'); | |
} | |
} | |
createOrUpdateTOC(); | |
}); | |
} | |
/** Sets or returns the ID for the given element so it can be referenced using the TOC. */ | |
function setIDForAnchor(target, index){ | |
// If no ID is set (or it is auto-generated by us), create one and assign. | |
if(!hasAttribute($(this),'id') || $(this).attr('id').includes(TOC_ID_PREFIX)){ | |
keyID = TOC_ID_PREFIX + index; | |
target.attr("id", keyID); | |
} | |
return keyID; | |
} | |
/** returns the header number (1-n) for headers 1-3. */ | |
function getHeaderNumber(target){ | |
if(target.is(codeH1)){ | |
return 1; | |
} else if(target.is(codeH2)){ | |
return 2; | |
} else if(target.is(codeH3)){ | |
return 3; | |
} | |
} | |
/** Creates or updates the TOC element that outlines all headers & tables in the section. */ | |
function createOrUpdateTOC(){ | |
var $element = $('#toc-full'); | |
if($element.length == 0){ | |
var tocParent = $('<div id="toc" class="toc" data-is-transparent-container="true" data-transparent-container-forward-to-first-child="true"><center>TOC</center></div>').appendTo(canvasSection); | |
$element = $('<div id="toc-full"></div>').appendTo(tocParent); | |
} | |
$element.empty(); | |
$('<p style="margin-block-start: 0em;">Headings:</p>').appendTo($element); | |
var headerLists = [null]; | |
var lastListItems = [$element]; | |
var headerIndices = [0]; | |
var headerListTypes = { 1: "I", 2:"i", 3:"a" }; | |
var index = 0; | |
$([codeH1, codeH2, codeH3].join(", ")).each(function() { | |
// Check if the ID is set, if so - use that, if not, use index. | |
var keyID = setIDForAnchor($(this), index); | |
var li = $("<li><a href='#" + keyID + "'>" + $(this).text() + "</a></li>"); | |
// Check if we need to create new targets for a list. | |
var headerNumber = getHeaderNumber($(this)); | |
if(headerNumber != headerIndices.last()){ | |
// First check if we need to pop, afterwards we could still need to move up. | |
while(headerNumber < headerIndices.last()){ | |
// Pop upwards so we use the list above us. | |
headerLists.pop(); | |
headerIndices.pop(); | |
lastListItems.pop(); | |
// Allow fallthrough, because after pop we could also be in a header above. | |
} | |
// If we are in a higher header, insert a new list for it. | |
if(headerNumber > headerIndices.last()){ | |
// Create new list, attach to the current parent list target and push onto the stack. | |
var newHeaderList = $('<ol type="'+ headerListTypes[headerNumber] +'"></ol>'); | |
newHeaderList.appendTo(lastListItems.last()); | |
headerLists.push(newHeaderList) | |
headerIndices.push(headerNumber); | |
// Push onto the stack. | |
lastListItems.push(li); | |
} | |
} | |
// Attach the item to the current list and replace the stack reference. | |
li.appendTo(headerLists.last()); | |
lastListItems[lastListItems.length - 1] = li; | |
index++; | |
}); | |
$('<p>Tables:</p>').appendTo($element); | |
headerList = $('<ol type="A"></ol>'); | |
headerList.appendTo($element); | |
$(codeTable).each(function() { | |
// Check if the ID is set, if so - use that, if not, use index. | |
var keyID = setIDForAnchor($(this), index); | |
var li = "<li><a href='#" + keyID + "'>" + $(this).text() + "</a></li>"; | |
$(li).appendTo(headerList); | |
index++; | |
}); | |
} | |
var tocOptions = { | |
fireOnAttributesModification: true, | |
onceOnly: true, | |
existing: true | |
}; | |
if(AUTOGENERATED_TABLE_OF_CONTENTS){ | |
var tocTargets = [codeH1, codeH2, codeH3, codeTable].join(", "); | |
$(document).arrive(tocTargets, tocOptions, assignTOCChangeListener); | |
$(document).leave(tocTargets, createOrUpdateTOC); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment