const TARGET_HEADING_LEVEL = 3;
const SCRIPT_NAME = 'Custom Script';
const SCRIPT_ERROR_MENU_NAME = `${SCRIPT_NAME} (Configuration Error)`;
function onOpen() {
try {
if (!isValidHeadingLevel(TARGET_HEADING_LEVEL)) {
Logger.log(`onOpen: TARGET_HEADING_LEVEL (${TARGET_HEADING_LEVEL}) is invalid. Displaying error menu.`);
showConfigurationErrorMenu("Invalid setting (integer 1-6)");
return;
}
DocumentApp.getUi()
.createMenu(SCRIPT_NAME)
.addItem(`Insert Page Break Before "Heading ${TARGET_HEADING_LEVEL}"`, 'insertPageBreakBeforeHeading')
.addToUi();
Logger.log(`onOpen: Menu added successfully (Target: Heading ${TARGET_HEADING_LEVEL}).`);
} catch (e) {
Logger.log(`onOpen: Error while adding menu: ${e}\n${e.stack}`);
showConfigurationErrorMenu("Error adding menu");
}
}
function showConfigurationErrorMenu(reason) {
try {
DocumentApp.getUi()
.createMenu(SCRIPT_ERROR_MENU_NAME)
.addItem(`Check Settings (${reason})`, 'showConfigurationError')
.addToUi();
} catch (e) {
Logger.log(`showConfigurationErrorMenu: Further error while displaying error menu: ${e}`);
}
}
function showConfigurationError() {
let currentSetting = "Undefined or inaccessible";
try {
currentSetting = (typeof TARGET_HEADING_LEVEL !== 'undefined') ? String(TARGET_HEADING_LEVEL) : "Undefined";
} catch(e) { }
const message = `There is a problem with the script's settings.\n\n`
+ `Current setting value: ${currentSetting}\n\n`
+ `Please open the script editor, check and correct the "TARGET_HEADING_LEVEL" value (an integer from 1 to 6) at the top of the file, and **save the file**.\n\n`
+ `(e.g., const TARGET_HEADING_LEVEL = 3;)`
DocumentApp.getUi().alert("Script Configuration Error", message, DocumentApp.getUi().ButtonSet.OK);
}
function isValidHeadingLevel(level) {
return typeof level === 'number' && Number.isInteger(level) && level >= 1 && level <= 6;
}
function getHeadingConstant(level) {
if (!isValidHeadingLevel(level)) {
Logger.log(`getHeadingConstant: Invalid level: ${level}`);
return null;
}
const headingMap = {
1: DocumentApp.ParagraphHeading.HEADING1,
2: DocumentApp.ParagraphHeading.HEADING2,
3: DocumentApp.ParagraphHeading.HEADING3,
4: DocumentApp.ParagraphHeading.HEADING4,
5: DocumentApp.ParagraphHeading.HEADING5,
6: DocumentApp.ParagraphHeading.HEADING6,
};
return headingMap[level] || null;
}
function getHeadingEnumName(enumValue) {
if (enumValue === null || typeof enumValue === 'undefined') {
return 'NULL_OR_UNDEFINED';
}
if (!this.headingEnumNameCache) {
this.headingEnumNameCache = {};
for (const key in DocumentApp.ParagraphHeading) {
if (Object.prototype.hasOwnProperty.call(DocumentApp.ParagraphHeading, key)) {
this.headingEnumNameCache[DocumentApp.ParagraphHeading[key]] = key;
}
}
}
return this.headingEnumNameCache[enumValue] || `UNKNOWN_STYLE (${String(enumValue)})`;
}
function insertPageBreakBeforeHeading() {
const startTime = new Date();
let doc;
try {
doc = DocumentApp.getActiveDocument();
if (!doc) throw new Error("Could not get the active document.");
doc.getName();
} catch (e) {
handleExecutionError("Document Access Error", e);
return;
}
if (!isValidHeadingLevel(TARGET_HEADING_LEVEL)) {
Logger.log(`insertPageBreakBeforeHeading: Invalid setting (${TARGET_HEADING_LEVEL}).`);
showConfigurationError();
return;
}
const targetHeadingConstant = getHeadingConstant(TARGET_HEADING_LEVEL);
if (!targetHeadingConstant) {
Logger.log(`insertPageBreakBeforeHeading: Failed to get heading constant (level: ${TARGET_HEADING_LEVEL}).`);
DocumentApp.getUi().alert(`Internal script error: Could not get the heading style constant for the setting (${TARGET_HEADING_LEVEL}).`);
return;
}
const targetHeadingName = getHeadingEnumName(targetHeadingConstant);
Logger.log(`--- Starting Page Break Insertion Process ---`);
Logger.log(`Document: ${doc.getName()}, Target Heading: ${TARGET_HEADING_LEVEL} (${targetHeadingName})`);
const body = doc.getBody();
const numChildren = body.getNumChildren();
let pageBreakInsertedCount = 0;
let foundTargetHeadings = 0;
Logger.log(`Number of elements: ${numChildren}. Traversing in reverse order...`);
for (let i = numChildren - 1; i >= 0; i--) {
const element = body.getChild(i);
const elementType = element.getType();
let headingStyle = null;
try {
if (elementType === DocumentApp.ElementType.PARAGRAPH) {
headingStyle = element.asParagraph().getHeading();
} else if (elementType === DocumentApp.ElementType.LIST_ITEM) {
headingStyle = element.asListItem().getHeading();
} else {
continue;
}
if (headingStyle === targetHeadingConstant) {
foundTargetHeadings++;
if (i > 0 && body.getChild(i - 1).getType() !== DocumentApp.ElementType.PAGE_BREAK) {
try {
body.insertPageBreak(i);
pageBreakInsertedCount++;
} catch (insertError) {
Logger.log(`! Error while inserting page break at Index ${i}: ${insertError}`);
}
} else if (i > 0) {
} else {
}
}
} catch (elementError) {
Logger.log(`! Error processing Index ${i} (Type: ${elementType}): ${elementError}. Skipping.`);
}
}
const endTime = new Date();
const duration = (endTime.getTime() - startTime.getTime()) / 1000;
Logger.log(`--- Traversal Complete ---`);
Logger.log(`Processing time: ${duration.toFixed(2)} seconds`);
Logger.log(`Detected target headings (${targetHeadingName}): ${foundTargetHeadings}`);
Logger.log(`Inserted page breaks: ${pageBreakInsertedCount}`);
const resultMessage = buildResultMessage(pageBreakInsertedCount, foundTargetHeadings, targetHeadingName, duration);
DocumentApp.getUi().alert("Processing Complete", resultMessage, DocumentApp.getUi().ButtonSet.OK);
Logger.log(`--- Page Break Insertion Process Complete ---`);
}
function handleExecutionError(context, error) {
Logger.log(`Runtime error (${context}): ${error}\n${error.stack}`);
let userMessage = `An error occurred while running the script.\n\nContext: ${context}`;
if (error.message) {
if (error.message.includes("Authorization required")) {
userMessage = "The necessary permissions to run the script have not been approved.\n\nPlease reload the document or run the script again, and grant permission on the authorization screen.";
} else {
userMessage += `\nDetails: ${error.message}`;
}
}
DocumentApp.getUi().alert("Script Error", userMessage, DocumentApp.getUi().ButtonSet.OK);
}
function buildResultMessage(insertedCount, foundCount, headingName, duration) {
const headingLevelName = `"${headingName}" style (Heading ${TARGET_HEADING_LEVEL})`;
if (insertedCount > 0) {
return `Inserted page breaks before ${insertedCount} instances of ${headingLevelName}.\n\nProcessing time: ${duration.toFixed(2)} seconds`;
} else if (foundCount > 0) {
return `${headingLevelName} was found in ${foundCount} places, but no page breaks were inserted.\n(This may be because the heading is at the beginning of the document or a page break already exists.)\n\nProcessing time: ${duration.toFixed(2)} seconds`;
} else {
return `No elements with the ${headingLevelName} style applied were found.\n\nPlease check the following:\n`
+ `1. Is the setting value (${TARGET_HEADING_LEVEL}) correct?\n`
+ `2. Has the correct heading style been applied to the target text from [Format] > [Paragraph styles]?\n`
+ `3. Have the script permissions been approved?`;
}
}