Skip to content

Instantly share code, notes, and snippets.

@wecsam
Created July 30, 2026 00:37
Show Gist options
  • Select an option

  • Save wecsam/e3a05e2b23157863028181171fd7049d to your computer and use it in GitHub Desktop.

Select an option

Save wecsam/e3a05e2b23157863028181171fd7049d to your computer and use it in GitHub Desktop.
A Google Slides extension that adds numbers to a slideshow of pictures and shuffles the pictures
/**
* Creates a custom menu in Google Slides when the presentation is opened.
*
* @returns {void}
*/
function onOpen() {
SlidesApp.getUi()
.createMenu('Picture Tools')
.addItem('Select and Shuffle Pictures', 'shufflePictureSlides')
.addItem('Auto-Number Picture Slides', 'autoNumberSlides')
.addToUi();
}
/**
* Starts from the current slide and adds speaker notes and a styled numbering shape
* to every slide until the end of the presentation.
*
* @returns {void}
*/
function autoNumberSlides() {
const presentation = SlidesApp.getActivePresentation();
const ui = SlidesApp.getUi();
const selection = presentation.getSelection();
// Ensure a slide is selected
if (!selection) {
ui.alert('Please select a slide to start from.');
return;
}
const currentPage = selection.getCurrentPage();
if (!currentPage) {
ui.alert('Please select a specific slide in the left sidebar to start from.');
return;
}
const slides = presentation.getSlides();
let startingIndex = -1;
// Find the array index of the currently selected slide
for (let i = 0; i < slides.length; i++) {
if (slides[i].getObjectId() === currentPage.getObjectId()) {
startingIndex = i;
break;
}
}
if (startingIndex === -1) {
ui.alert('Could not determine the current slide.');
return;
}
let picNumber = 101;
for (let i = startingIndex; i < slides.length; i++) {
const slide = slides[i];
updateSpeakerNotes(slide, picNumber);
addPictureNumberShape(slide, picNumber);
picNumber++;
}
}
/**
* Updates the speaker notes of a slide to include the picture number.
*
* @param {GoogleAppsScript.Slides.Slide} slide - The slide to update.
* @param {number} picNumber - The picture number to append.
* @returns {void}
*/
function updateSpeakerNotes(slide, picNumber) {
const notesShape = slide.getNotesPage().getSpeakerNotesShape();
let currentNotes = notesShape.getText().asString().trim();
// Remove any existing "Pic: X" tags so we don't duplicate them
currentNotes = currentNotes.replace(/Pic:\s*\d+/gi, '').trim();
const newNote = `Pic: ${picNumber}`;
const finalNotes = currentNotes.length > 0 ? currentNotes + '\n\n' + newNote : newNote;
notesShape.getText().setText(finalNotes);
}
/**
* Inserts a styled rounded rectangle with the picture number onto a slide.
*
* @param {GoogleAppsScript.Slides.Slide} slide - The slide to update.
* @param {number} picNumber - The number to display in the shape.
* @returns {void}
*/
function addPictureNumberShape(slide, picNumber) {
const presentation = SlidesApp.getActivePresentation();
const pageHeight = presentation.getPageHeight();
// Shape dimensions: 2.5 inches (180pt) by 1.5 inches (108pt)
const shapeWidth = 180;
const shapeHeight = 108;
const margin = 15;
const leftPos = margin;
const topPos = pageHeight - shapeHeight - margin;
// Insert Rounded Rectangle
const shape = slide.insertShape(
SlidesApp.ShapeType.ROUND_RECTANGLE,
leftPos,
topPos,
shapeWidth,
shapeHeight
);
// Format the Shape
// "Red berry" hex color is typically #980000
shape.getFill().setSolidFill('#980000');
shape.getBorder().getLineFill().setSolidFill('#000000'); // Black border
shape.getBorder().setWeight(4); // 4px border
// Add and Format Text
const textRange = shape.getText();
textRange.setText(picNumber.toString());
const textStyle = textRange.getTextStyle();
textStyle.setForegroundColor('#FFFFFF'); // White text
textStyle.setFontFamily('Arial');
textStyle.setFontSize(72);
textStyle.setBold(false);
// Center the text horizontally and vertically
textRange.getParagraphStyle().setParagraphAlignment(SlidesApp.ParagraphAlignment.CENTER);
shape.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE);
}
/**
* Main orchestrator function: gets input, processes slides, and shuffles them.
*
* @returns {void}
*/
function shufflePictureSlides() {
const presentation = SlidesApp.getActivePresentation();
const ui = SlidesApp.getUi();
// 1. Get user input directly as a Set for O(1) lookups
const selectedNumbers = getUserInput(ui);
if (!selectedNumbers || selectedNumbers.size === 0) {
return; // Exit silently if canceled or empty
}
// 2. Build a map of picNumber -> original slide index
const picSlideMap = getPictureSlideMap(presentation);
if (picSlideMap.size === 0) {
ui.alert('No picture slides found with "Pic: X" tags in the speaker notes.');
return;
}
// 3. Compute the starting index for the picture slides
const indexOfFirstPictureSlide = Math.min(...Array.from(picSlideMap.values()));
// 4. Segregate and toggle visibility
const slides = presentation.getSlides();
const selectedSlides = [];
const unselectedSlides = [];
picSlideMap.forEach((slideIndex, picNumber) => {
const slide = slides[slideIndex];
if (selectedNumbers.has(picNumber)) {
slide.setSkipped(false);
selectedSlides.push(slide);
} else {
slide.setSkipped(true);
unselectedSlides.push(slide);
}
});
if (selectedSlides.length === 0) {
ui.alert('None of the entered numbers matched the available picture slides.');
return;
}
// 5. Shuffle the selected slides only
shuffleArray(selectedSlides);
// 6. Combine arrays: selected (shuffled) come before unselected (hidden)
const orderedSlides = [...selectedSlides, ...unselectedSlides];
// 7. Move all picture slides to their new sequential positions
reorderSlides(orderedSlides, indexOfFirstPictureSlide);
}
/**
* Prompts the user and returns a Set of parsed numbers.
*
* @param {GoogleAppsScript.Base.Ui} ui - The Google Slides UI object used to prompt the user.
* @returns {Set<number>|null} A Set containing the parsed picture numbers, or null if the user canceled the prompt.
*/
function getUserInput(ui) {
const response = ui.prompt(
'Select Pictures',
'Enter the picture numbers you want to show (separated by spaces, commas, or semicolons):',
ui.ButtonSet.OK_CANCEL
);
if (response.getSelectedButton() !== ui.Button.OK) {
return null;
}
const parsedNumbers = response.getResponseText()
.split(/[\s,;]+/g)
.map(s => parseInt(s, 10))
.filter(n => !isNaN(n));
return new Set(parsedNumbers);
}
/**
* Scans the presentation and returns a Map linking picture numbers to their slide indices.
*
* @param {GoogleAppsScript.Slides.Presentation} presentation - The active Google Slides presentation.
* @returns {Map<number, number>} A Map where the key is the picture number (extracted from speaker notes) and the value is the slide's original index.
*/
function getPictureSlideMap(presentation) {
const slides = presentation.getSlides();
const picMap = new Map();
for (let i = 0; i < slides.length; i++) {
const notes = slides[i].getNotesPage().getSpeakerNotesShape().getText().asString().trim();
const match = notes.match(/^Pic:\s*(\d+)/i);
if (match) {
const picNumber = parseInt(match[1], 10);
picMap.set(picNumber, i);
}
}
return picMap;
}
/**
* Moves an array of slides to a sequential block starting at a target index.
*
* @param {GoogleAppsScript.Slides.Slide[]} slidesToMove - An array of Slide objects to be moved.
* @param {number} startIndex - The target index where the first slide in the array should be placed.
* @returns {void}
*/
function reorderSlides(slidesToMove, startIndex) {
for (let j = 0; j < slidesToMove.length; j++) {
slidesToMove[j].move(startIndex + j);
}
}
/**
* Helper function to randomize an array in place (Fisher-Yates shuffle).
*
* @param {Array<any>} array - The array to shuffle. The array is modified in place.
* @returns {void}
*/
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment