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