Populate Template Placeholders Script Automation?

Thanks - yes looking at the Taskpaper option that looks like exactly the sort of thing I was after. Thanks!

A taskpaper option that works with JS would be top-notch as it’d be cross-platform. Shortcuts etc are unfortunately not…

Cross platform templates, with fields etc, are the one thing I really want to see automated.

I modified the example script on the Omni automation website that imports a Taskpaper file to do a find/replace (prompting the user) on variables in the file. My convention is to enclose the variables as follows: «var».

It’s just a few extra lines of code. A nice side benefit, aside from being cross-platform, is the Javascript is much faster than the old AppleScript that served the same purpose.

/*{
"author": "JBB",
"targets": ["omnifocus"],
"type": "action",
"identifier": "com.jbb.omnifocus.com.template",
"version": "1.0",
"description": "Add Template Using TaskPaper Format",
"label": "Add Template…",
"mediumLabel": "Add Template Using TaskPaper Format",
"paletteLabel": "Add Template",
}*/
(() => {
var action = new PlugIn.Action(function(selection, sender) {
	var folder = selection.folders[0]
	var folderName = folder.name
	
	var picker = new FilePicker()
	picker.folders = false
	picker.multiple = false
	var aFileType = new FileType("com.taskpaper.text")
	picker.types = [aFileType]
	var pickerPromise = picker.show()
	
	pickerPromise.then(function(urlsArray) {
		var fileURL = urlsArray[0]
		fileURL.fetch(function(data) {
			var taskPaperText = data.toString()
			var macros = taskPaperText.match(/«.*?»/g)
			if (macros != null && macros.length > 0) {
				const distinct = (value, index, self) => { return self.indexOf(value) === index }
				macros = macros.filter(distinct).map(function(x){return x.replace(/[«»]/g, '')})

				var inputForm = new Form()
				for (var i = 0; i < macros.length; i++) {
					var formInput = new Form.Field.String("var" + String(i), macros[i], "")
					inputForm.addField(formInput)
				}
				
				var formPromise = inputForm.show("Enter values for parameters:", "OK")   
	
				inputForm.validate = function(formObject) {
					return true
				}
	
				formPromise.then(function(formObject) {
					for (var i = 0; i < macros.length; i++) {
						var x = formObject.values["var" + String(i)]
						taskPaperText = taskPaperText.replaceAll('«' + macros[i] + '»', x)
					}

					taskPaperText = encodeURIComponent(taskPaperText)
					folderName = encodeURIComponent(folderName)
					var urlStr = "omnifocus:///paste?target=/folder/" + folderName +  "&content=" + taskPaperText
					URL.fromString(urlStr).open()					
				});				
			}
			else {
				taskPaperText = encodeURIComponent(taskPaperText)
				folderName = encodeURIComponent(folderName)
				var urlStr = "omnifocus:///paste?target=/folder/" + folderName +  "&content=" + taskPaperText
				URL.fromString(urlStr).open()
			}
		})
	})
});

action.validate = function(selection, sender) {
	return (selection.folders.length === 1)
};

return action;

})();

if you copy this script don’t forget the small part below the script…

2 Likes

Also a work-in-progress, but had this post saved and thought it might be worth linking my weekend project here in case it is useful to anyone else:

There are a lot of things I’d still like to add in for my own use, but I believe this more or less replicates the functionality in Curt Clifton’s original AppleScript.

3 Likes

Thanks, Kaitlin! I look forward to checking this out.

Does your plug-in handle date math (i.e. relative defer/due date calculations) the way that Curt Clifton’s AppleScript does? If not, please consider this a feature request. 😃

Hi Tim!

I haven’t used Curt’s script myself for some time, but I believe it does based on his description.

I haven’t tested extensively yet, obviously, so please let me know if anything doesn’t work as expected!

(I would actually like to add an option to specify due dates without setting the due date on the template itself, ultimately.)

Thanks for your reply, @kaitlin. I’ll be sure to check out your plug-in sometime soon…and will let you know if anything doesn’t work as expected.

(I would actually like to add an option to specify due dates without setting the due date on the template itself, ultimately.)

Having an option to specify due dates without including due dates in the template would be very helpful. As it stands, when creating templates, I use defer/dues that are far into the future (e.g. Jan 31, 2100).

For those interested, I altered @JBB’s script above to work without the need for a folder selection and to place the new project at top-level in the document tree.

/*{
"author": "JBB",
"targets": ["omnifocus"],
"type": "action",
"identifier": "com.jbb.omnifocus.com.template",
"version": "1.0",
"description": "Add Template Using TaskPaper Format",
"label": "Add Template…",
"mediumLabel": "Add Template Using TaskPaper Format",
"paletteLabel": "Add Template",
}*/
(() => {
var action = new PlugIn.Action(function(selection, sender) {
    var picker = new FilePicker()
    picker.folders = false
    picker.multiple = false
    var pickerPromise = picker.show()

    pickerPromise.then(function(urlsArray) {
        var fileURL = urlsArray[0]
        fileURL.fetch(function(data) {
            var taskPaperText = data.toString()
            var macros = taskPaperText.match(/«.*?»/g)
            if (macros != null && macros.length > 0) {
                const distinct = (value, index, self) => { return self.indexOf(value) === index }
                macros = macros.filter(distinct).map(function(x){return x.replace(/[«»]/g, '')})

                var inputForm = new Form()
                for (var i = 0; i < macros.length; i++) {
                    var formInput = new Form.Field.String("var" + String(i), macros[i], "")
                    inputForm.addField(formInput)
                }

                var formPromise = inputForm.show("Enter values for parameters:", "OK")

                inputForm.validate = function(formObject) {
                    return true
                }

                formPromise.then(function(formObject) {
                    for (var i = 0; i < macros.length; i++) {
                        var x = formObject.values["var" + String(i)]
                        taskPaperText = taskPaperText.replaceAll('«' + macros[i] + '»', x)
                    }

                    taskPaperText = encodeURIComponent(taskPaperText)
                    var urlStr = "omnifocus:///paste?target=projects&content=" + taskPaperText
                    URL.fromString(urlStr).open()
                });
            }
            else {
                taskPaperText = encodeURIComponent(taskPaperText)
                var urlStr = "omnifocus:///paste?target=projects&content=" + taskPaperText
                URL.fromString(urlStr).open()
            }
        })
    })
});

return action;
})();

EDIT: One note. To make this work on iOS, the taskpaper template files need to be .txt files and not .taskpaper. iOS does something weird there and doesn’t let you select them.

3 Likes

I remember having a related issue in the past, It seemed to matter if you have an application registered to handle the extension. I can’t actually remember what I was doing but when I uninstalled an app that handled taskpaper files, unrelated shortcuts that handled the files broke.

1 Like

Greetings all! Excellent concepts and ideas.

Just a heads-up that the FileType class has been renamed to the TypeIdentifier class, but old terms should continue to work.
https://omni-automation.com/shared/filetypes.html

var picker = new FilePicker()
picker.folders = false
picker.multiple = false
var aType = new TypeIdentifier("com.taskpaper.text")
picker.types = [aType, TypeIdentifier.plainText]
var pickerPromise = picker.show()

pickerPromise.then(urlArray => {
	var fileURL = urlArray[0]
	fileURL.fetch(data => {
		var importedText = data.toString()
		
		var openTag = "«"
		var closeTag = "»"
		var expression = new RegExp(openTag + ".*?" + closeTag, "g")
		var placeholders = importedText.match(expression)
		
		if (placeholders != null && placeholders.length > 0){
			var placeholders = Array.from(new Set(placeholders))
			
			console.log(placeholders)
		
		} else {
			console.error("Imported text contains no placeholders.")
		}
	})
})

is the only thing you removed the folder select? (cause I’m looking to add that and can then take @JBB’s original :-) )

I believe so. I had also altered the file type piece so that I had more control of the selection process.

1 Like

Ah ok, I’ll muddle along some more then ;-)
Folder select is more challenging than expected, but a good exercise to get to know the syntax.

Tested this out today.

  1. Is it possible to allow invoking the script even if something is selected?
  2. Curt had things set up to keep the Templates folder hidden so you can keep the defer/due dates set on items in the templates, without them affecting other Perspectives throughout the app (by dropping the Templates folder, but leaving the Projects within it alone) - could be a bug, but it works. I modified the script to replace: let templateFolder = foldersMatching("Templates")[0]; with let templateFolder = Folder.byIdentifier('xxxxxx);

I had made a few patches to Curt’s script. I’ll have to eventually update this one, I’ll submit the changes via GitHub. Thanks for the start on this!

Hi @jon123!

  1. Definitely possible! In the createFromTemplate.js file, just change return selection.tasks.length == 0 && selection.projects.length == 0; (line 41) to return true and this should achieve what you want.

  2. My approach to this was to set defer/due dates waaaay into the future so they didn’t show in Forecast etc and then they are included in a Hidden folder which includes a few other ‘projects’ and is already hidden from my perspectives anyway. I prefer this because it’s easier to go in and make changes to the templates when they are available than when they are dropped (which is my main motivation for keeping them in OmniFocus rather than somewhere external e.g. in TaskPaper format). I actually want to update the script to allow the due/defer dates to be specified in the notes rather than on the tasks themselves to get around exactly this problem - but with a 9-week-old in the house it might not be anytime soon! 😂

1 Like

Thanks Kaitlin! I agree on your approach for point #2, I’m going to switch over to that!

Is there a way to automate the defer date so that it is 2w prior to the template’s start date? I tried putting «start date»-2w in the Project’s defer date but that didn’t seem to work.

Hi @rubinjm,

I’m not 100% sure I follow what you’re trying to achieve, sorry! Perhaps you could clarify a little further?

The dates are all shifted relatively. So if you want, for example, a defer date that is two weeks before the due date (which you then set using the prompts) then you would set an (arbitrary, probably far in the future) due date on the template, and a defer date two weeks before that.

If you want the projects to have a particular defer date, would you not just set that directly as the defer date in the project? If there is no due date that will be used.

Sorry if I’m being obtuse and missing something obvious!

I don’t think that the defer dat can be set to before the start date of the project via scripting.

I remember trying to do something like this with Editorial and Taskpaper for a project to conduct an audit with the Audit itself at the centre of the project with the prep before and wrap up afterwards, but in the end I had to base it all off the end date of the project rather than the date of the audit.