Links Relink Subfolders
Script for Adobe InDesign
Search a folder and all subfolders below it for missing links, and update all.
To rename links, see related script Links GREP Rename.
For files renamed outside of InDesign, see related script Links GREP Relink.
- Replace links located in subfolders
- Specify folder to search for links
- Alerts user when multiple files the same name
How-to Video
How to use the script
The interface is a single section with button to select a folder and input to enter folder names to ignore. The path to the folder selected appears beside the button, which defaults to the Links folder where the document is located. Click the Folder button to select another location. In the input Ignore, leave blank to include all subfolders, or enter a folder name or names to ignore. Links in folders of the name or names entered are ignored. If multiple folder names, separate each with a comma. Click the OK button to begin. Links found are relinked and updated in the open InDesign document.
Because subfolders could allow files of the same name to exist, only in different folders, the script checks the file list for duplicates. For any links that have duplicate files the same name, the link is not updated, and the condition is reported in a log file at the location of the InDesign document. An alert appears on screen with the option to open the log file, which is saved as plain text format. Examine the duplicate files listed in the log file to determine which file is the correct link, and then relink it manually.
Source code
(download button below)
/*
Links Relink Subfolders
Copyright 2022 William Campbell
All Rights Reserved
https://www.marspremedia.com/contact
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
(function () {
var title = "Links Relink Subfolders";
if (!/indesign/i.test(app.name)) {
alert("Script for InDesign", title, false);
return;
}
// Script variables.
var count;
var doc;
var doneMessage;
var error; // Error
var folderLinks; // Folder
var log;
var progress;
// Reusable UI variables.
var g; // group
var p; // panel
var w; // window
// Permanent UI variables.
var btnCancel;
var btnFolderLinks;
var btnOk;
var inpIgnore;
var txtFolderLinks;
// LANGUAGE EXTENSIONS
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (x) {
for (var i = 0; i < this.length; i++) {
if (this[i] == x) {
return i;
}
}
return -1;
};
}
// SETUP
// Script requires open document.
if (!app.documents.length) {
alert("Open a document", title, false);
return;
}
doc = app.activeDocument;
if (!doc.saved) {
alert("Save document first", title, false);
return;
}
// LOG
log = {
entries: [],
file: null,
// Default write log to user desktop.
// Preferably set 'log.path' to a meaningful
// location in later code once location exists.
path: Folder.desktop,
add: function (message) {
this.entries.push(message);
},
write: function () {
var contents;
var d;
var fileName;
var padZero = function (v) {
return ("0" + v).slice(-2);
};
if (!this.entries.length) {
// No log entries to report.
this.file = null;
return;
}
contents = this.entries.join("\r") + "\r";
// Create file name.
d = new Date();
fileName =
title +
" " +
"Log" +
" " +
d.getFullYear() +
"-" +
padZero(d.getMonth() + 1) +
"-" +
padZero(d.getDate()) +
"-" +
padZero(d.getHours()) +
padZero(d.getMinutes()) +
padZero(String(d.getSeconds()).substr(0, 2)) +
".txt";
// Open and write log file.
this.file = new File(this.path + "/" + fileName);
this.file.encoding = "UTF-8";
try {
if (!this.file.open("w")) {
throw new Error("Failed to open log file.");
}
if (!this.file.write(contents)) {
throw new Error("Failed to write log file.");
}
} catch (e) {
this.file = null;
throw e;
} finally {
this.file.close();
}
// Log successfully written.
// log.file == true (not null) indicates a log was written.
}
};
// CREATE PROGRESS WINDOW
progress = new Window("palette", "Progress", undefined, {
"closeButton": false
});
progress.t = progress.add("statictext");
progress.t.preferredSize = [450, -1];
progress.b = progress.add("progressbar");
progress.b.preferredSize = [450, -1];
progress.display = function (message) {
message && (this.t.text = message);
this.show();
this.update();
};
progress.increment = function () {
this.b.value++;
};
progress.set = function (steps) {
this.b.value = 0;
this.b.minvalue = 0;
this.b.maxvalue = steps;
};
// CREATE USER INTERFACE
w = new Window("dialog", title);
w.alignChildren = "fill";
p = w.add("panel", undefined, "Search for links in folder");
p.alignChildren = "left";
p.margins = [24, 24, 24, 18];
g = p.add("group");
btnFolderLinks = g.add("button", undefined, "Folder...");
txtFolderLinks = g.add("statictext", undefined, "", {
truncate: "middle"
});
txtFolderLinks.preferredSize = [320, -1];
g = p.add("group");
g.margins = [0, 12, 0, 0];
g.add("statictext", undefined, "Ignore:");
inpIgnore = g.add("edittext", undefined, "");
inpIgnore.preferredSize = [250, -1];
// Action Buttons
g = w.add("group");
g.alignment = "center";
btnOk = g.add("button", undefined, "OK");
btnCancel = g.add("button", undefined, "Cancel");
// Panel Copyright
p = w.add("panel");
p.add("statictext", undefined, "Copyright 2022 William Campbell");
// SET DEFAULTS
folderLinks = new Folder(doc.fullName.path + "/Links");
if (folderLinks.exists) {
txtFolderLinks.text = Folder.decode(folderLinks.fullName);
} else {
folderLinks = null;
}
// UI ELEMENT EVENT HANDLERS
btnFolderLinks.onClick = function () {
var f = Folder.selectDialog("Select links folder", txtFolderLinks.text);
if (f) {
txtFolderLinks.text = Folder.decode(f.fullName);
}
};
btnOk.onClick = function () {
folderLinks = new Folder(txtFolderLinks.text);
if (!(folderLinks && folderLinks.exists)) {
alert("Select links folder", " ", false);
return;
}
w.close(1);
};
btnCancel.onClick = function () {
w.close(0);
};
// DISPLAY THE DIALOG
if (w.show() == 1) {
doneMessage = "";
try {
app.doScript(process, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, title);
doneMessage = count + " links relinked";
} catch (e) {
error = error || e;
doneMessage = "An error has occurred.\nLine " + error.line + ": " + error.message;
}
progress.close();
try {
log.path = doc.fullName.path;
log.write();
} catch (e) {
alert("Error writing log:\n" + e.message, title, true);
}
if (log.file) {
if (
confirm(doneMessage +
"\nAlerts reported. See log for details:\n" +
File.decode(log.file.fullName) +
"\n\nOpen log?", false, title)
) {
log.file.execute();
}
} else {
doneMessage && alert(doneMessage, title, error);
}
}
//====================================================================
// END PROGRAM EXECUTION, BEGIN FUNCTIONS
//====================================================================
function getFiles(folder, subfolders, extensions, ignore) {
// folder = folder object, not folder name.
// subfolders = true to include subfolders.
// extensions = string, extensions to include.
// ignore = folder names to ignore.
// Combine multiple extensions with RegExp OR i.e. jpg|psd|tif
// extensions case-insensitive.
// extensions undefined = any.
// Ignores hidden files and folders.
var f;
var files;
var i;
var pattern;
var results = [];
if (extensions) {
pattern = new RegExp("\." + extensions + "$", "i");
} else {
// Any extension.
pattern = new RegExp(".*");
}
files = folder.getFiles();
for (i = 0; i < files.length; i++) {
f = files[i];
if (!f.hidden) {
if (f instanceof Folder && subfolders) {
if (ignore && ignore.indexOf(f.name) > -1) {
// Skip if folder in list to ignore.
continue;
}
// Recursive (function calls itself).
results = results.concat(getFiles(f, subfolders, extensions));
} else if (f instanceof File && pattern.test(f.name)) {
results.push(f);
}
}
}
return results;
}
function process() {
var files = [];
var fileDupes = [];
var fileNames = [];
var i;
var ignore;
var ii;
var iii;
var link;
var name;
progress.display("Initializing...");
try {
ignore = inpIgnore.text.split(",");
files = getFiles(folderLinks, true, null, ignore);
// Make array of file names.
for (i = 0; i < files.length; i++) {
fileNames[i] = File.decode(files[i].name).toLowerCase();
}
// Make array of duplicates.
for (i = 0; i < fileNames.length; i++) {
fileDupes[i] = [];
for (ii = 0; ii < fileNames.length; ii++) {
if (i != ii && fileNames[i] == fileNames[ii]) {
fileDupes[i].push(ii);
}
}
}
progress.set(doc.links.length);
count = 0;
// Find the links...
for (i = 0; i < doc.links.length; i++) {
link = doc.links[i];
name = link.name.toLowerCase();
progress.display(link.name);
for (ii = 0; ii < fileNames.length; ii++) {
if (fileNames[ii] == name) {
if (fileDupes[ii].length) {
// There are duplicates of this link.
// Log it, and don't relink.
log.add("Multiple files the same name. Manually update the link to the desired file.");
log.add(" " + File.decode(files[ii].fullName));
for (iii = 0; iii < fileDupes[ii].length; iii++) {
log.add(" " + File.decode(files[fileDupes[ii][iii]].fullName));
}
break;
}
// Match. Relink and update.
link.relink(files[ii]);
link.update();
count++;
break;
}
}
progress.increment();
}
} catch (e) {
error = e;
throw e;
}
}
})();
Links Relink Subfolders
For help installing scripts, see How to Install and Use Scripts in Adobe Creative Cloud Applications.
Custom solutions based on any script, or completely new ideas, are possible at reasonable cost. Contact William for more information.
IMPORTANT: by downloading any of the scripts on this page you agree that the software is provided without any warranty, express or implied. USE AT YOUR OWN RISK. Always make backups of important data.
IMPORTANT: fees paid for products purchased from this site, or for programming custom solutions, are the purchase of a non-exclusive license to use the software and do not grant the purchaser any degree of ownership of the software. Author of the intellectual property and copyright holder William Campbell retains 100% ownership of all code used in all products and custom solutions.
IMPORTANT: scripts are developed for the latest Adobe Creative Cloud applications. Many scripts work in CC 2018 and later, even some as far back as CS6, but may not perform as expected, or run at all, when used in versions prior to 2018. Photoshop features Select Subject and Preserve Details 2.0 definitely fail prior to CC 2018 (version 19) as the features do not exist in earlier versions. For best results use the latest versions of Adobe Creative Cloud applications.