Haha, so sorry it was a bit complicated! The point was to show a range of examples.
But you do make a good point that it would really help to have a UI. I've added one to the version below.

I did it fast, so hopefully no bugs! Note: it only does one numbering scheme at a time, but that will be fine for most people. To use it without the UI, just configure `numberings` and set `settings.showUI: false`.
- Mark
/**
* @File Apply Numbering.js
*
* Searches document for "numberings" which are configured
* in the script, then sorts the results and applies numbering.
*
* To use, either configure script to show UI for figuring ONE
* numbering scheme, or set `settings.showUI` to false if you
* want to configure multiple numberings at once.
*
* Notes:
*
* - `preferHorizontal` means wether to prefer horizontal sorting
* over vertical sorting.
*
* - `padWithZeros` is the count of how many digits you want, for example
* the number 24 with padWithZeros of 4 gives '0024'.
*
* - `onlyFirstNumberInParagraph` means whether to stop after applying
* numbering to the first found number in a paragraph. For example,
* if your heading was "12. The 2024 Economy", we would want it to
* leave "2024" untouched, so onlyFirstNumberInParagraph = true,
* which is the default. However, for footnote-type numbering, you
* would set it to false because a paragraph could have several.
*
* - change the values in `settings.defaultFindGrepOptions` to configure
* the find grep options.
*
* @7111211 m1b
* @version 2024-09-17
* @discussion https://community.adobe.com/t5/indesign-discussions/how-do-i-automatically-set-numbering-for-paragraph-styles-in-multiple-unconnected-text-boxes/m-p/14860638
*/
function main() {
// EDIT YOUR CONFIGURATION BELOW
var settings = {
// text frames that are within 3 pt are considered aligned
defaultTolerance: 5,
// whether horizontal sorting should win against vertical sorting
defaultPreferHorizontal: false,
// default find grep options
defaultFindGrepOptions: {
includeLockedLayersForFind: false,
includeLockedStoriesForFind: false,
includeHiddenLayers: false,
includeFootnotes: true,
includeMasterPages: false,
searchBackwards: false,
},
// whether to show the UI, but if so,
// only the first numbering will execute
showUI: true,
};
/* --------------------------------- *
* Example numberings *
* --------------------------------- *
* Note: if using the UI, then only *
* the first example will be active. *
* --------------------------------- */
var numberings = [
{
findGrepPreferences: {
findWhat: '\\b\\d+\\b',
appliedParagraphStyle: 'Numbered Heading',
},
padWithZeros: 3,
preferHorizontal: true,
},
{
findGrepPreferences: {
findWhat: '\\b\\d+\\b',
appliedParagraphStyle: 'Numbered Figure',
},
preferHorizontal: true,
},
{
// this numbering example is for "footnote" style
// numbering, where we want to number every instance
// even if it's in the same paragraph
findGrepPreferences: {
findWhat: '\\d+\\b',
appliedCharacterStyle: 'Numbered Symbol',
},
onlyFirstNumberInParagraph: false,
preferHorizontal: true,
},
];
// END OF CONFIGURATION
if (0 === app.documents)
return alert('Please open a document and try again.');
var doc = settings.doc = app.activeDocument;
settings.numberings = numberings;
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
if (settings.showUI) {
var result = ui(settings);
if (2 === result)
// user cancelled
return;
if (undefined != settings.cleanup) {
settings.cleanup();
delete settings.cleanup;
}
}
// perform each numbering
for (var i = 0, numbering, found, found; i < settings.numberings.length; i++) {
numbering = settings.numberings[i];
if (!numbering.findGrepPreferences)
continue;
app.findGrepPreferences = NothingEnum.NOTHING;
app.findGrepPreferences.properties = numbering.findGrepPreferences;
app.findChangeGrepOptions.properties = numbering.findChangeGrepOptions || settings.defaultFindGrepOptions;
// find the numbers
found = doc.findGrep();
// remove illegal numbers
if (false !== numbering.onlyFirstNumberInParagraph)
found = getFirstFoundInParagraph(found);
// sort
found.sort(getSorter({
tolerance: numbering.tolerance || settings.defaultTolerance,
preferHorizontal: numbering.preferHorizontal || settings.defaultPreferHorizontal,
}));
// apply numbering
for (var len = found.length, j = len - 1, n = len; j >= 0; j--, n--) {
found[j].contents = numbering.padWithZeros
? ('00000000' + n).slice(-numbering.padWithZeros)
: String(n);
}
}
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Apply Numbering');
/**
* Returns a sorting function suitable
* for sorting numberable text.
*
* Numberables with x or y values that are
* within `tolerance` pts are considered aligned.
*
* @7111211 m1b
* @version 2024-09-15
* @9397041 {Number} tolerance - the x or y distance in pts.
* @Returns {Function}
*/
function getSorter(options) {
options = options || {};
return (function (tolerance, preferHorizontal) {
return function sortTextByFramePosition(a, b) {
// compare same story index
if (a.parentStory.id === b.parentStory.id)
return a.index - b.index;
// compare page index
if (a.parentTextFrames[0].parentPage.documentOffset !== b.parentTextFrames[0].parentPage.documentOffset)
return a.parentTextFrames[0].parentPage.documentOffset - b.parentTextFrames[0].parentPage.documentOffset;
// compare top left of text frame
var ab = a.parentTextFrames[0].geometricBounds,
bb = b.parentTextFrames[0].geometricBounds,
dx = Math.abs(ab[1] - bb[1]),
dy = Math.abs(ab[0] - bb[0]),
result;
if (!preferHorizontal) {
if (dy > tolerance)
result = ab[0] - bb[0];
if (!result && dx > tolerance)
result = ab[1] - bb[1]
}
else {
if (dx > tolerance)
result = ab[1] - bb[1];
if (!result && dy > tolerance)
result = ab[0] - bb[0]
}
return result || 0;
};
})(options.tolerance || 0, true === options.preferHorizontal);
};
/**
* Returns an array of only the first text
* instance of `found` in each paragraph.
* Note: assumes that `found` is in the
* usual findGrep order.
* @7111211 m1b
* @version 2024-09-15
* @9397041 {Array<Text>} found - the text to filter.
* @Returns {Array<Text>}
*/
function getFirstFoundInParagraph(found) {
var firstOnly = [];
for (var j = 0, para = undefined; j < found.length; j++) {
if (found[j].paragraphs[0] !== para)
firstOnly.push(found[j])
para = found[j].paragraphs[0];
}
return firstOnly;
};
/**
* Shows UI for Apply Numbering script,
* allowing for only one numbering scheme.
* @9397041 {Object} settings - the settings to adjust.
* @Returns {1|2} - result code
*/
function ui(settings) {
// the UI only handles one numbering scheme
if (settings.numberings.length > 1)
settings.numberings.splice(1, settings.numberings.length - 1)
// try to load numbering from document
var key = 'm1b_applyNumbering',
savedValues = settings.doc.extractLabel(key);
if (savedValues && savedValues.length > 0)
settings.numberings[0] = reviveNumbering(savedValues);
var w = new Window("dialog", 'Apply Numbering', undefined, { closeButton: false }),
// numberings
numberingsGroup = w.add("group {orientation:'column', alignment:['fill','top'], margins: [0,0,0,0] }"),
numberingUI = numberingUI(numberingsGroup, 0, settings.numberings[0]),
bottomUI = w.add("group {orientation:'row', alignment:['fill','top'], margins: [0,0,0,0] }"),
extraControls = bottomUI.add("group {orientation:'row', alignChildren: ['left','center'] alignment:['fill','center'] }"),
saveInDocCheckBox = extraControls.add("CheckBox { text: 'Save these settings in document', alignment:['left','bottom'] }"),
buttons = bottomUI.add("group {orientation:'row', alignment:['right','top'], alignChildren:'right' }"),
cancelButton = buttons.add('button', undefined, 'Cancel', { name: 'cancel' }),
applyButton = buttons.add('button', undefined, 'Apply', { name: 'ok' });
// event handling
applyButton.onClick = apply;
w.center();
return w.show();
/**
* Executes the numbering by closing
* dialog with return code 1.
*/
function apply() {
// update numbering
settings.numberings = [numberingUI.getNumbering()];
if (saveInDocCheckBox.value) {
settings.cleanup = function () {
// save values in document
settings.doc.insertLabel(key, serializeNumbering(settings.numberings[0]));
};
}
// finished with dialog
w.close(1);
};
/**
* Returns a default numbering.
* @Returns {Object}
*/
function defaultNumbering() {
return {
findGrepPreferences: { findWhat: '\\b\\d+\\b', appliedParagraphStyle: 'X' },
preferHorizontal: false,
padWithZeros: 0,
onlyFirstNumberInParagraph: true,
};
};
/**
* Creates the UI for a numbering.
* @9397041 {SUI Group|SUI Window} container - the location of the new numbering group.
* @9397041 {Number} numIndex - the index to the numberings array.
* @9397041 {Object} numbering - the numbering object.
* @Returns {SUI Group?}
*/
function numberingUI(container, numIndex, numbering) {
var styleMenu,
targets = ['Character Style', 'Paragraph Style'],
suppressTriggers = false;
var group = container.add("group {orientation:'column', margins:[5,5,5,0] }"),
panel = group.add("panel {orientation:'row', alignChildren: ['left','center'], alignment:['fill','top'], margins: [5,5,5,0] }"),
column1 = panel.add("group {orientation:'column', alignment:['fill','top'], margins: [20,20,20,20] }"),
row1 = column1.add("group {orientation:'row', alignChildren: ['left','center'] alignment:['fill','top'] }"),
row2 = column1.add("group {orientation:'row', alignChildren: ['left','center'] alignment:['fill','top'] }"),
findWhatLabel = row1.add('statictext', undefined, 'Find Numbers Grep:'),
findWhatField = row1.add('edittext'),
inLabel = row1.add('statictext { text:" in ", alignment:["left","center"], preferredSize:[25,-1] }'),
targetMenu = row1.add('dropDownList { preferredSize:[120,-1] }'),
styleMenu = row1.add("Dropdownlist {alignment:['left','center']}"),
// checkboxes
onlyFirstNumberCheckbox = row2.add("CheckBox { text: 'Only First Number In Paragraph' }"),
// radio button
sortPreferenceGroup = row2.add('group {orientation:"row", alignment:["fill","center"], alignChildren:["left","center"], margins:[25,2,0,0] }'),
marginGroup = sortPreferenceGroup.add('group {orientation:"row", alignment:["center","center"], margins:[0,0,0,5]}'),
sortPreferenceLabel = marginGroup.add('statictext { text:"Sorting Preference", alignment:["left","center"] }'),
preferHorizontalRadio = sortPreferenceGroup.add('radiobutton {text:"Horizontal"}'),
preferVerticalRadio = sortPreferenceGroup.add('radiobutton {text:"Vertical"}'),
padWithZerosGroup = row2.add("group {orientation:'row', alignChildren: ['right','center'] alignment:['fill','center'], margins:[0,0,0,4] }"),
padWithZerosLabel = padWithZerosGroup.add('statictext', undefined, 'Pad With Zeros:'),
padWithZerosField = padWithZerosGroup.add('edittext {preferredSize: [30,-1]}');
group.index = numIndex;
// add targets
for (var i = 0; i < targets.length; i++)
targetMenu.add('item', targets[i]);
// set sizes
var labelWidth = 120,
fieldWidth = 200;
findWhatLabel.preferredSize.width = labelWidth;
findWhatField.preferredSize.width = fieldWidth;
targetMenu.preferredSize.width = labelWidth;
styleMenu.preferredSize.width = fieldWidth;
targetMenu.onChange = function (ev) { updateTarget(this.selection.index) };
group.update = updateNumberingUI;
group.getNumbering = getNumbering;
/**
* Returns a numbering object derived from the UI.
* @Returns {Object}
*/
function getNumbering() {
var numbering = {
findGrepPreferences: {
findWhat: findWhatField.text,
},
padWithZeros: Number(padWithZerosField.text),
preferHorizontal: true === preferHorizontalRadio.value,
onlyFirstNumberInParagraph: true == onlyFirstNumberCheckbox.value,
};
if (isNaN(numbering.padWithZeros))
numbering.padWithZeros = 0;
// add the applied style name
var findGrepKey = ['appliedCharacterStyle', 'appliedParagraphStyle'][targetMenu.selection.index];
numbering.findGrepPreferences[findGrepKey] = styleMenu.selection.text;
return numbering;
};
/**
* Update UI from `numbering` object.
*/
function updateNumberingUI() {
findWhatField.text = numbering.findGrepPreferences.findWhat;
preferHorizontalRadio.value = true === numbering.preferHorizontal;
preferVerticalRadio.value = true !== numbering.preferHorizontal;
padWithZerosField.text = String(numbering.padWithZeros || 0);
onlyFirstNumberCheckbox.value = false !== numbering.onlyFirstNumberInParagraph;
};
if (numbering.findGrepPreferences.appliedCharacterStyle)
updateTarget(0);
else
updateTarget(1);
/**
* Populates the target menu.
* @9397041 {0|1} targetType - character styles or paragraph styles.
*/
function updateTarget(targetType) {
if (suppressTriggers)
return;
else
suppressTriggers = true;
var things,
targetProp;
if (0 === targetType) {
// target: character styles
things = settings.doc.allCharacterStyles
targetProp = 'appliedCharacterStyle';
}
else {
// target: paragraph styles
things = settings.doc.allParagraphStyles
targetProp = 'appliedParagraphStyle';
}
var list = getThingsWithGetter(things, excludeBracketItems);
if (0 === list.length)
throw new Error('Could not find ' + targetProp + '.');
var value = numbering.findGrepPreferences[targetProp] || list[0];
// clear the existing menu
styleMenu.removeAll();
for (var i = 0; i < list.length; i++)
styleMenu.add('item', list[i]);
targetMenu.selection = targetType;
// set menu index
var menuIndex;
for (var i = 0; i < list.length; i++) if (value === list[i]) { menuIndex = i; break; };
styleMenu.selection = menuIndex || 0;
suppressTriggers = false;
};
// updateTarget();
group.update();
return group;
};
};
/**
* Returns source string from a stringified RegExp.
* eg, '/^Z.*$/gi' -> '^Z.*$'
* @9397041 {String/RegExp} obj - the object to parse.
* @Returns {?String}
*/
function regexSource(obj) {
var parts = String(obj).match(/^\/(.*)\/([gimsuy]*)$/) || {};
if (parts[1])
return parts[1];
}
/**
* Returns an array of things by applying `getter` function.
*
* Example getter:
* function (thing, index) {
* if (/^[/.test(thing.name))
* return thing;
* }
*
* @7111211 m1b
* @version 2024-09-16
* @9397041 {Array|Collection} things - the things to look through.
* @9397041 {Function} getter - a function that returns a thing to get.
* @Returns {Array<*>} - the things found.
*/
function getThingsWithGetter(things, getter) {
var found = [];
for (var i = 0, thing; i < things.length; i++) {
thing = getter(things[i], i);
if (thing)
found.push(thing);
}
return found;
};
/**
* Filter to exclude item's whose name
* starts with square bracket, eg. '[None]'.
* @9397041 {*} thing - a thing with a `name`.
* @Returns {*?}
*/
function excludeBracketItems(thing) {
if (!/^\[/.test(thing.name))
return thing.name;
};
function serializeNumbering(numbering) {
var targetProperty = numbering.findGrepPreferences.appliedCharacterStyle ? 'appliedCharacterStyle' : 'appliedParagraphStyle',
targetType = numbering.findGrepPreferences.appliedCharacterStyle ? 0 : 1,
styleName = numbering.findGrepPreferences[targetProperty],
findWhat = numbering.findGrepPreferences.findWhat,
preferHorizontal = true === numbering.preferHorizontal ? 1 : 0,
padWithZeros = numbering.padWithZeros || 0,
onlyFirstNumber = true === numbering.onlyFirstNumberInParagraph ? 1 : 0;
return [
targetType,
styleName,
findWhat,
padWithZeros,
preferHorizontal,
onlyFirstNumber,
].join('###');
};
function reviveNumbering(serialized) {
var values = serialized.split('###'),
numbering = {
findGrepPreferences: {
findWhat: values[2],
},
padWithZeros: Number(values[3]),
preferHorizontal: '1' === values[4],
onlyFirstNumberInParagraph: '1' === values[5],
};
if (isNaN(numbering.padWithZeros))
numbering.padWithZeros = 0;
// add the applied style name
var findGrepKey = ['appliedCharacterStyle', 'appliedParagraphStyle'][Number(values[0])];
numbering.findGrepPreferences[findGrepKey] = values[1];
return numbering;
};