Skip to content

Instantly share code, notes, and snippets.

@idStar
Created December 29, 2023 16:53
Show Gist options
  • Save idStar/76882ef0387c1f247161b624e91a4a8d to your computer and use it in GitHub Desktop.
Save idStar/76882ef0387c1f247161b624e91a4a8d to your computer and use it in GitHub Desktop.
// Heading Level Numbering for Google Docs
// Created by Sohail Ahmed
// Created On October 3, 2023
// This series of functions will install an 'Auto Numbering' menu with a couple of options in it.
// The most important is 'Update Heading Numbers', which will generate/update outline numbering.
function onOpen() {
updateMenu();
}
function updateMenu() {
var ui = DocumentApp.getUi();
var numberLevel1 = isNumberLevel1();
var menuItemText = numberLevel1 ? 'Hide Level 1 Numbers' : 'Show Level 1 Numbers';
ui.createMenu('Auto Numbering')
.addItem('Update Heading Numbers', 'initiateHeadingUpdate')
.addItem(menuItemText, 'toggleNumberLevel1')
.addToUi();
}
function isNumberLevel1() {
return PropertiesService.getScriptProperties().getProperty('numberLevel1') === 'true';
}
function toggleNumberLevel1() {
var numberLevel1 = isNumberLevel1();
PropertiesService.getScriptProperties().setProperty('numberLevel1', !numberLevel1);
updateMenu();
}
function initiateHeadingUpdate() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var bookmark;
try {
// Create a temporary bookmark at the current cursor position.
var selection = doc.getSelection();
if (selection) {
var rangeElements = selection.getRangeElements();
if (rangeElements.length > 0) {
var position = rangeElements[0].getStartOffset();
var element = rangeElements[0].getElement();
if (doc.createBookmark) {
bookmark = doc.createBookmark(element, position);
} else {
console.warn('doc.createBookmark method not available.');
}
}
}
} catch (e) {
console.error("Bookmark creation failed: ", e);
}
// Update the headings
updateHeadings();
// Open the sidebar, which forces Google Docs to navigate to the bookmark's position.
DocumentApp.getUi().showSidebar(HtmlService.createHtmlOutput("<script>window.onload = function() { google.script.run.closeSidebar(); }</script>"));
try {
// Remove the bookmark.
if (bookmark) {
bookmark.remove();
}
} catch (e) {
console.error("Bookmark removal failed: ", e);
}
}
function closeSidebar() {
DocumentApp.getUi().closeDialog();
}
function updateHeadings() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var paragraphs = body.getParagraphs();
var numberLevel1 = isNumberLevel1();
var currentLevelNumbers = [0, 0, 0, 0, 0, 0]; // supports up to 6 levels of headings
for (var i = 0; i < paragraphs.length; i++) {
var paragraph = paragraphs[i];
var heading = paragraph.getHeading();
// Skip empty paragraphs
if (!paragraph.getText().trim()) {
continue;
}
// If it's a heading (not NORMAL or TITLE or SUBTITLE)
if (heading !== DocumentApp.ParagraphHeading.NORMAL &&
heading !== DocumentApp.ParagraphHeading.TITLE &&
heading !== DocumentApp.ParagraphHeading.SUBTITLE) {
var level;
switch(heading) {
case DocumentApp.ParagraphHeading.HEADING1:
level = 1;
break;
case DocumentApp.ParagraphHeading.HEADING2:
level = 2;
break;
case DocumentApp.ParagraphHeading.HEADING3:
level = 3;
break;
case DocumentApp.ParagraphHeading.HEADING4:
level = 4;
break;
case DocumentApp.ParagraphHeading.HEADING5:
level = 5;
break;
default:
level = 0;
}
// Increment the current level number and reset all sub-levels to 0
currentLevelNumbers[level - 1]++;
for (var j = level; j < currentLevelNumbers.length; j++) {
currentLevelNumbers[j] = 0;
}
var numbering = currentLevelNumbers.slice(0, level).join('.');
if (level !== 1 || (level === 1 && numberLevel1)) {
// Set the updated text for all levels when numberLevel1 is true,
// or for levels other than 1 when numberLevel1 is false
var text = paragraph.getText();
text = text.replace(/^\d+(\.\d+)*(\s*\.\s*)?\s*/, '');
paragraph.setText(numbering + ' ' + text.trim());
} else if (level === 1 && !numberLevel1) {
// Remove numbering from Level 1 headings when numberLevel1 is false
var text = paragraph.getText();
text = text.replace(/^\d+(\.\d+)*(\s*\.\s*)?\s*/, '');
paragraph.setText(text.trim());
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment