Skip to content

Instantly share code, notes, and snippets.

@gjroelofs
Last active October 14, 2019 10:49
Show Gist options
  • Save gjroelofs/facee5638e30a581053c51a79839afb3 to your computer and use it in GitHub Desktop.
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
/** 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