Last active
June 13, 2021 05:45
-
-
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.
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
/* | |
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