Skip to content

Instantly share code, notes, and snippets.

@andrewgilmartin
Last active June 13, 2021 05:45
Show Gist options
  • Save andrewgilmartin/63e4f4686a61b78d588dd7f7b545c75d to your computer and use it in GitHub Desktop.
Save andrewgilmartin/63e4f4686a61b78d588dd7f7b545c75d to your computer and use it in GitHub Desktop.
A Google App Script to build a Table of TODOs from TODOs distributed in a Google Doc document.
/*
See https://www.calliopesounds.com/2019/10/adding-table-of-todos-to-google-document.html
This Google App Script script collects the text of all the TODO paragraphs
and organizes them under a Table of TODOs heading. The TODO paragraph is
normal-styled and starts with the string "TODO: ". The Table of TODOs is a
heading-styled paragraph and contains only the text "Table of TODOs". The
TODO texts are organized in to numbered list items under the heading. In
addition, the script adds a Bookmark to each TODO paragraph so that it can
be referenced from the numbered list item.
*/
function updateTableOfTodos() {
var document = DocumentApp.getActiveDocument();
var body = document.getBody();
// Find Table of TODOs remove exiting TODO list items
var tot = null;
for ( var c = body.getChild(0); c !== null; c = c.getNextSibling() ) {
// Find the Table of TODOs header
if ( c.getType() === DocumentApp.ElementType.PARAGRAPH ) {
var p = c.asParagraph();
if ( p.getHeading() !== DocumentApp.ParagraphHeading.NORMAL ) {
if ( p.getText().indexOf("Table of TODOs") === 0 ) {
// Found the Table of TODOs header
tot = c;
// Remove the Table of TODOs list items
for ( c = c.getNextSibling(); c !== null && c.getType() == DocumentApp.ElementType.LIST_ITEM; /* empty */ ) {
var t = c.getNextSibling();
c.removeFromParent();
c = t;
}
// Done
break;
}
}
}
}
if ( tot !== null ) {
// Map each bookmark position element index to the bookmark itself.
// We do this so we don't duplicate bookmarks unnecessarily.
// We use element index (and not element itself) as the index is the only stable reference to a document part
var p2b = {};
var bookmarks = document.getBookmarks();
for ( var i = 0; i < bookmarks.length; i++ ) {
var bookmark = bookmarks[i];
if ( bookmark.getPosition().getOffset() == 0 && bookmark.getPosition().getElement().getType() === DocumentApp.ElementType.PARAGRAPH ) {
p2b[ body.getChildIndex(bookmark.getPosition().getElement()) ] = bookmark;
}
};
var todos = [];
// Collect the TODO items
// These are normal-style paragraphs with leading "TODO: " text
for ( var c = body.getChild(0); c !== null; c = c.getNextSibling() ) {
if ( c.getType() === DocumentApp.ElementType.PARAGRAPH ) {
var p = c.asParagraph();
if ( p.getHeading() === DocumentApp.ParagraphHeading.NORMAL ) {
if ( p.getText().indexOf("TODO: ") == 0 ) {
var todo = p.getText().substring(6);
var bookmark = p2b[ body.getChildIndex(c) ] || null;
todos.push( { text: todo, element: p, bookmark: bookmark });
}
}
}
}
// Populate the Table of TODOs header list items
if ( todos.length > 0 ) {
var t = body.getChildIndex(tot);
for ( var i = 0; i < todos.length; i++ ) {
var todo = todos[i];
// add Table of TODOs item
var li = body.insertListItem(++t, todo.text + " ");
// add bookmark, if necessary
if ( ! todo.bookmark ) {
todo.bookmark = document.newPosition(todo.element,0).insertBookmark()
}
// add link to bookmark
var link = li.appendText(">>");
link.setLinkUrl("#bookmark="+todo.bookmark.getId());
}
}
}
}
function onOpen() {
var m = DocumentApp.getUi().createAddonMenu();
m.addItem("Update Table of TODOs", 'updateTableOfTodos');
m.addToUi();
}
// END
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment