Manual Sort Not Preserved After Syncing

I started using DT for about a month now. I spent it migrating & curating my life-long databases for work and fun. Few days ago, I upgraded my MacBook and synced all 8 databases via iCloudKit. To my surprise, all the manual sort I have done the entire month is now gone. Manual sorting is not only essential for my content, it is content itself. There is information and messaging that can learned when seeing how content is sorted e.g. order and relationship. In other words, I can claim the DT sync is loosing content!

Almost everything I write in DT is order dependent. For example, book chapters, runbooks, playbooks, procedure manuals, etc. I have seen numerous posts reporting the same issue.

I tried pre-creating a database on the new Mac with the same name as the imported one (via sync), set it to ‘unsorted’. Sync warns me that it is going to replace it, but it does not import a thing. It does not look like it works this way.

I have not see an option in the import dialog to persevere sort order. Only option to where to save the imported database.

I tried to manually insert numbers in doc titles & sort by name, but quickly I realized this effort is futile. Maintaining manual numbering for thousands of docs is impossible.

I have also seen references to some scripts that can be used for automated numbering. I browsed through all of them, but I was not able to see any. There are probably ones for pdf’s.

Am I hitting a dead-end here? Has anyone discovered a clever workaround to maintain manual sort after a sync? Is there a way to auto number docs titles to maintain a user-defined order?

I have also seen some support experts sharing that such feature might be coming but no promises!

I hope DT engineers read this and help resolve this essential omission from the product feature list.

Thanks

Why is this impossible?
I don’t have a manual sort requirement but if I did I could maintain a manual numbering system.
The trick is to allow enough digits to allow for future insertions;
something like nnn.nnn.nnn

I don’t use sync. However, I guess it’s perfectly possible to use a custom metadata field to maintain the manual numbering system. I use a custom metadata field (of type “Date”) to sort my journal entries and it works very well. However, you’re probably faced then with a similar problem to that created by renaming entries with a leading number: the need to renumber possibly many entries when you wish to insert one within the order.

Nevertheless, some form of custom metadata may be worth considering.

Stephen

Sorting is a local attribute. It is not synced between machines.

If you use custom metadata, that is relatively simple to overcome; say I have ten records, numbered 1, 2, 3 thru 10. I now want to move record 7 to be between 2 and 3; to do that I would change 7 to say 2.5 - hey presto, the order is as required, and I’ve only had to change one record.

Databases are identified by ID rather than name; you can’t precreate a database and sync into it from another database.

Unlikely; DT doesn’t offer such a function, and I don’t think it can be done with a script (@pete31, @BLUEFROG: is there any way to pick up the order of documents displayed using AS?)

That has me wondering whether DT is the ideal tool for what you are doing. I love DT and use it many times a day; it has transformed how I work. I don’t, however, use it for writing (for which I use Scrivener).

This is just an idea, perhaps others can expand on it:

If you select (i.e. mark) the documents you have manually sorted and select Create Table of Contents from the context menu, a document is created which preserves the order and places a number in front of each record name/ID. It should be possible to write a script which copies that number to custom metadata for each document - structurally something along the lines of:

get number from TOC
get record ID from TOC
replace custom metadata SortOrder for record record ID with number from TOC

That way you could always manually trigger a renumber; and you could always sort by the custom metadata column. If you don’t script yourself, one of the aforementioned gurus might assist :slight_smile:

1 Like

Better to use more characters if this is a text field (like title)
something like 001, 002, 003 thru 010

Getting the selection should get the records in the current UI order.

nice one @pete31; that’s simple then.

@cnewtonne the following script will add an ascending count to custom metadata (as is, it will use an integer called Sort, identifier sort, creating that entry if it doesn’t already exist), starting from 1, to the selected records in the order in which they are displayed. It excludes groups (but not smart groups). It overwrites a count previously attributed to a selected record. It will not do anything to any record which is not selected.

You can add scripts such as this one to the toolbar; so that might be a solution for you - manually sort your records, select them, run the script, and from then on use the column of the same name (“Sort”) to sort your records.

Various adaptations are possible, e.g. a dialog which asks for the number to start from, or offers to delete sort data from the selected records.

property cmetadata : "sort" -- custom meta data identifier

tell application id "DNtp"
	try
		set theRecords to selected records whose type is not group
		if theRecords is equal to {} then error "No records (or only groups) selected"
		show progress indicator "Generating Sort Order" cancel button 1 steps count of theRecords
		set n to 1
		repeat with theRecord in theRecords
			if cancelled progress then error number -128
			step progress indicator (name of theRecord) as string
			add custom meta data n for cmetadata to theRecord
			set n to n + 1
		end repeat
	on error error_message number error_number
		hide progress indicator
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
	end try
	hide progress indicator
end tell

As always, I recommend testing this script on a test database and ensuring it does both what you expect and what you want before using it - at your own risk - in a productive environment.

Custom metadata is only available in DT3 Pro or Server, not in the vanilla edition.

1 Like

Another nice little snippet of code to tuck away in my DT AppleScript database: thanks! :grinning:

Stephen

1 Like

Thanks for the great replies. I purchased the standard edition which I believe you described as ‘vanilla’ edition. I do not see the option for custom data ‘preferences / data tab’. Hard, at this point, to justify the extra cost. I’ll keep the code for if/when I upgrade in the future.

Yeah, sorry, not trying to be rude. As you rightly point out, the Standard edition does not offer custom metadata. @pete31 sent me a complex piece of script (well, it’s complex for me, anyway) which he had lying around and which might be useful in your case. He gave it to me with a “I’m pushed for time, see what you can do with this”-disclaimer. I will look into it for you, and publish it once I have done so, assuming it does what Pete thinks he remembers it does :smiley:

1 Like

@pete31 I couldn’t figure out why you decided to use RegEx in the script you sent to me; the routine threw an error, which I couldn’t figure out either. So I just dumped the whole routine and simplified it. I’m mildly worried, as I’m sure there was some reason you were using RegEx, but it does seem to work.

@cnewtonne Pete really knows what he is doing; I don’t. I have tested the following script on my test database, and it does what it advertises, namely it enumerates the contents of a group based on the order of the items in that group at the time of enumeration. If an item is already enumerated, it will however simply add another enumeration (depending on how my coffee tastes, I may expand it to automatically remove any enumeration before adding a new one). It has a remove function, too. You can define your own delimiter. I strongly recommend you try this on some test data (try, for example, duplicating some data to a database you call test, close all other databases, and play). I have tested some 10 times, and the script has done what I expect it to. It would confer unexpected results if you have file names which already contain the delimiter (so if your records were typically called “c.| newtonne’s special file 1” for example; then you should select a different delimiter). WARNING: changes made by a script CANNOT BE UNDONE.

-- Task:	Counter Prefix hinzufügen oder entfernen
-- Note:	Dieses Skript fügt jedem Child eine Nummerierungszahl (basierend aus "Unsortiert") und den angegebenen Delimiter hinzu oder entfernt beides

property theDelimiter : ".| "

tell application id "DNtp"
	try
		set theGroups to selected records whose type = group
		if theGroups = {} then error "Please select a group"
		
		set chooseFromListItems to {"Add", "Add if more than 1 Child", "Remove"}
		set theChoice to choose from list chooseFromListItems with prompt "Enumeration:" default items (item 1 of chooseFromListItems) with title "" without multiple selections allowed
		if theChoice is false then return
		
		if item 1 of theChoice = item 1 of chooseFromListItems then -- Counter Prefix hinzufügen 
			
			repeat with thisGroup in theGroups
				set theChildren to children of thisGroup
				my addCounterPrefix(theChildren, 0)
			end repeat
			
		else if item 1 of theChoice = item 2 of chooseFromListItems then -- Counter Prefix nur hinzufügen wenn es mehr als ein child gibt
			
			repeat with thisGroup in theGroups
				set theChildren to children of thisGroup
				if (count theChildren) > 1 then
					my addCounterPrefixIfMoreThanOneChild(theChildren, 0, true)
				else
					my addCounterPrefixIfMoreThanOneChild(theChildren, 0, false)
				end if
			end repeat
			
		else if item 1 of theChoice = item 3 of chooseFromListItems then -- Counter Prefix entfernen
			repeat with thisGroup in theGroups
				set theChildren to children of thisGroup
				my removeCounterPrefix(theChildren)
			end repeat
		end if
		
	on error error_message number error_number
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
	end try
end tell

on addCounterPrefix(theChildren, theCount)
	try
		tell application id "DNtp"
			repeat with thisChild in theChildren
				set theCount to theCount + 1
				set thisChildName to name of thisChild
				set name of thisChild to ((theCount as string) & theDelimiter & thisChildName) as string
				my addCounterPrefix(children of thisChild, 0)
			end repeat
		end tell
	on error error_message number error_number
		activate
		display alert "Error: Handler \"addCounterPrefix\"" message error_message as warning
		error number -128
	end try
end addCounterPrefix

on addCounterPrefixIfMoreThanOneChild(theChildren, theCount, moreThanOneChild)
	try
		tell application id "DNtp"
			repeat with thisChild in theChildren
				if moreThanOneChild = true then
					set theCount to theCount + 1
					set thisChildName to name of thisChild
					set name of thisChild to ((theCount as string) & theDelimiter & thisChildName) as string
				end if
				set theseChildren to children of thisChild
				if (count theseChildren) > 1 then
					my addCounterPrefixIfMoreThanOneChild(theseChildren, 0, true)
				else
					my addCounterPrefixIfMoreThanOneChild(theseChildren, 0, false)
				end if
			end repeat
		end tell
	on error error_message number error_number
		activate
		display alert "Error: Handler \"addCounterPrefixIfMoreThanOneChild\"" message error_message as warning
		error number -128
	end try
end addCounterPrefixIfMoreThanOneChild

on removeCounterPrefix(theChildren)
	try
		tell application id "DNtp"
			repeat with thisChild in theChildren
				set thisChildName to name of thisChild
				if thisChildName contains theDelimiter then
					set thePos to offset of theDelimiter in thisChildName
					set thePos to thePos + (length of theDelimiter)
					set thisChildName_withoutPrefix to texts thePos thru -1 of thisChildName
					set name of thisChild to thisChildName_withoutPrefix
				end if
				my removeCounterPrefix(children of thisChild)
			end repeat
		end tell
	on error error_message number error_number
		activate
		display alert "Error: Handler \"removeCounterPrefix\"" message error_message as warning
		error number -128
	end try
end removeCounterPrefix

Again, any thanks go to Pete; the above is his effort, not mine; I’ve only chucked out bits I don’t understand and added simpler bits which I think I do understand.

PS the usual routine on this forum is that @chrillek will now post an alternative written in JavaScript; it will be 10 times shorter but contain more brackets than an average human requires in the course of their life. It’s all fun and games here :slight_smile:

PPS the other thing I might add is a counter and the option to stop the script whilst it is running.

2 Likes

It was written in a hurry so pretty sure I just used regex because that’s what came to mind first.

offset is better :+1:

1 Like

You, my friend, must be the only person on the planet who could write set theEscapePattern to "\\,|\\!|\\?|\\.|\\(|\\)|\\[|\\]|\\{|\\}|\\*|\\\\|\\^|\\+|\\<|\\>|\\||\\$|\\=" in a hurry :smiley:

Thanks so much for your input here in this forum :slight_smile:

2 Likes

Automation is magic!

I applied the script to a test database of 6500 items. it numbered all of them in about 4 secs. I would love to see this feature become native to DT. Initially, I’m sure there are ramifications to adding this feature that requires further engineering. But, the fact that a group of talented people put something up in a short time and how fast script updated 6500 items appears to be a low hanging feature to add.

Also, there isn’t a visual indicator for ‘in progress’ to stop user from interacting with the tree to make changes while numbering is in progress. A ‘done’ dialogue at a minimum so user knows when to move on and make changes to the tree.

I could not discern the difference between ‘add’ & ‘add if more than one child’. I applied both and they appeared to be doing same thing.

I will run script on the my old Mac and reimport everything back to the new Mac.

Enjoy your day!

I had no time to rise to the challenge yesterday. I’ll have a look at it today.

As to the regular expression you quoted in your other post: while I agree that it is an amazing feat to not lose track of all the \ and | signs, I’d rather use a character class […] to specify the characters to exclude. It is easier to read and understand and also faster to check by the regex engine.
Also, it has the advantage of using brackets, which are superior to \ and |

1 Like

I have now included a progress indicator (which will appear in the usual location at the bottom left of the DT window in the sidebar); it also allows you to cancel the process.

You now also have the option to receive a (self-dismissing) dialog once the process has finished.

I have added a further option, namely “Remove then add”; that’s useful if you have changed the order of records and want them renumbered. I have also added a routine which will check to see whether any records have already been enumerated before applying an enumeration when you select either “Add” or “Add if more than 1 Child”; that avoids cascading enumeration (but does double the time required for the script to run).

If a record (i.e., group) only has one child (i.e., contains only one record) then the “add if more than one child” option will do nothing to that child (the idea being that there is not much point in enumerating a single record).

-- Task:	Counter Prefix hinzufügen oder entfernen
-- Note:	Dieses Skript fügt jedem Child eine Nummerierungszahl (basierend aus "Unsortiert") und den angegebenen Delimiter hinzu oder entfernt beides
-- by Pete31, adapted by Blanc

property theDelimiter : ".| " -- use a unique delimiter which is not otherwise used in file names
property showFinish : 0 -- set to 1 to display self-dismissing dialogue after completion, otherwise set to 0

tell application id "DNtp"
	try
		set theGroups to selected records whose type = group
		if theGroups = {} then error "Please select a group"
		
		set chooseFromListItems to {"Add", "Add if more than 1 Child", "Remove then Add", "Remove"}
		set theChoice to choose from list chooseFromListItems with prompt "Enumeration:" default items (item 1 of chooseFromListItems) with title "" without multiple selections allowed
		if theChoice is false then return
		if item 1 of theChoice = item 1 of chooseFromListItems then -- Counter Prefix hinzufügen 
			
			show progress indicator "Checking Enumeration" cancel button 1
			repeat with thisGroup in theGroups
				if cancelled progress then error number -128
				step progress indicator (name of thisGroup) as string
				set theChildren to children of thisGroup
				set theCheck to my CheckPrefix(theChildren, theDelimiter)
				if theCheck = 1 then
					set theResponse to display alert "Found previously enumerated records; remove first?" buttons {"Cancel", "Yes"} default button "Cancel"
					if button returned of theResponse is "Yes" then
						hide progress indicator
						delay 0.5
						show progress indicator "Removing Enumeration" cancel button 1
						repeat with thisGroup in theGroups
							if cancelled progress then error number -128
							step progress indicator (name of thisGroup) as string
							set theChildren to children of thisGroup
							my removeCounterPrefix(theChildren)
						end repeat
					else
						error number -128
					end if
				end if
			end repeat
			hide progress indicator
			delay 0.5
			
			show progress indicator "Enumerating Records" cancel button 1
			repeat with thisGroup in theGroups
				if cancelled progress then error number -128
				step progress indicator (name of thisGroup) as string
				set theChildren to children of thisGroup
				my addCounterPrefix(theChildren, 0)
			end repeat
			
		else if item 1 of theChoice = item 2 of chooseFromListItems then -- Counter Prefix nur hinzufügen wenn es mehr als ein child gibt
			
			show progress indicator "Checking Enumeration" cancel button 1
			repeat with thisGroup in theGroups
				if cancelled progress then error number -128
				step progress indicator (name of thisGroup) as string
				set theChildren to children of thisGroup
				set theCheck to my CheckPrefix(theChildren, theDelimiter)
				if theCheck = 1 then
					set theResponse to display alert "Found previously enumerated records; remove first?" buttons {"Cancel", "Yes"} default button "Cancel"
					if button returned of theResponse is "Yes" then
						repeat with thisGroup in theGroups
							if cancelled progress then error number -128
							step progress indicator (name of thisGroup) as string
							set theChildren to children of thisGroup
							my removeCounterPrefix(theChildren)
						end repeat
					else
						error number -128
					end if
				end if
			end repeat
			hide progress indicator
			delay 0.5
			
			show progress indicator "Enumerating Records" cancel button 1
			repeat with thisGroup in theGroups
				if cancelled progress then error number -128
				step progress indicator (name of thisGroup) as string
				set theChildren to children of thisGroup
				if (count theChildren) > 1 then
					my addCounterPrefixIfMoreThanOneChild(theChildren, 0, true)
				else
					my addCounterPrefixIfMoreThanOneChild(theChildren, 0, false)
				end if
			end repeat
			
		else if item 1 of theChoice = item 3 of chooseFromListItems then -- Counter Prefix entfernen dann hinzufügen
			show progress indicator "Removing Enumeration" cancel button 1
			repeat with thisGroup in theGroups
				if cancelled progress then error number -128
				step progress indicator (name of thisGroup) as string
				set theChildren to children of thisGroup
				my removeCounterPrefix(theChildren)
			end repeat
			hide progress indicator
			delay 0.5
			show progress indicator "Enumerating Records" cancel button 1
			repeat with thisGroup in theGroups
				if cancelled progress then error number -128
				step progress indicator (name of thisGroup) as string
				set theChildren to children of thisGroup
				my addCounterPrefix(theChildren, 0)
			end repeat
			
		else if item 1 of theChoice = item 4 of chooseFromListItems then -- Counter Prefix entfernen
			show progress indicator "Removing Enumeration" cancel button 1
			repeat with thisGroup in theGroups
				if cancelled progress then error number -128
				step progress indicator (name of thisGroup) as string
				set theChildren to children of thisGroup
				my removeCounterPrefix(theChildren)
			end repeat
		end if
		
	on error error_message number error_number
		hide progress indicator
		set showFinish to 0
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
	end try
	hide progress indicator
	if showFinish = 1 then display dialog "Finished Enumerating Records" with title "Enumerator Script" buttons "OK" giving up after 30
end tell

on addCounterPrefix(theChildren, theCount)
	try
		tell application id "DNtp"
			repeat with thisChild in theChildren
				if cancelled progress then error number -128
				step progress indicator (name of thisChild) as string
				set theCount to theCount + 1
				set thisChildName to name of thisChild
				set name of thisChild to ((theCount as string) & theDelimiter & thisChildName) as string
				my addCounterPrefix(children of thisChild, 0)
			end repeat
		end tell
	on error error_message number error_number
		tell application id "DNtp" to hide progress indicator
		activate
		display alert "Error: Handler \"addCounterPrefix\"" message error_message as warning
		error number -128
	end try
end addCounterPrefix

on CheckPrefix(theChildren, theDelimiter)
	try
		tell application id "DNtp"
			repeat with thisChild in theChildren
				if cancelled progress then error number -128
				step progress indicator (name of thisChild) as string
				set thisChildName to name of thisChild
				if thisChildName contains theDelimiter then
					return 1
					exit repeat
				end if
				my CheckPrefix(children of thisChild, theDelimiter)
			end repeat
			return 0
		end tell
	on error error_message number error_number
		tell application id "DNtp" to hide progress indicator
		activate
		display alert "Error: Handler \"CheckPrefix\"" message error_message as warning
		error number -128
	end try
end CheckPrefix

on addCounterPrefixIfMoreThanOneChild(theChildren, theCount, moreThanOneChild)
	try
		tell application id "DNtp"
			repeat with thisChild in theChildren
				if cancelled progress then error number -128
				step progress indicator (name of thisChild) as string
				if moreThanOneChild = true then
					set theCount to theCount + 1
					set thisChildName to name of thisChild
					set name of thisChild to ((theCount as string) & theDelimiter & thisChildName) as string
				end if
				set theseChildren to children of thisChild
				if (count theseChildren) > 1 then
					my addCounterPrefixIfMoreThanOneChild(theseChildren, 0, true)
				else
					my addCounterPrefixIfMoreThanOneChild(theseChildren, 0, false)
				end if
			end repeat
		end tell
	on error error_message number error_number
		tell application id "DNtp" to hide progress indicator
		activate
		display alert "Error: Handler \"addCounterPrefixIfMoreThanOneChild\"" message error_message as warning
		error number -128
	end try
end addCounterPrefixIfMoreThanOneChild

on removeCounterPrefix(theChildren)
	try
		tell application id "DNtp"
			repeat with thisChild in theChildren
				if cancelled progress then error number -128
				step progress indicator (name of thisChild) as string
				set thisChildName to name of thisChild
				if thisChildName contains theDelimiter then
					set thePos to offset of theDelimiter in thisChildName
					set thePos to thePos + (length of theDelimiter)
					set thisChildName_withoutPrefix to texts thePos thru -1 of thisChildName
					set name of thisChild to thisChildName_withoutPrefix
				end if
				my removeCounterPrefix(children of thisChild)
			end repeat
		end tell
	on error error_message number error_number
		tell application id "DNtp" to hide progress indicator
		activate
		display alert "Error: Handler \"removeCounterPrefix\"" message error_message as warning
		error number -128
	end try
end removeCounterPrefix

Ok, I took the bait and re-wrote the whole thingy in JavaScript (see below - though I used the version from about 1h ago, which had no progress indicator and no show finish thingy. Those should be trivial to add, but I won’t bother).

The script tries mostly to follow the logic of the original version. One important deviation is the usage of an object to associate the choices with the corresponding functions. I’m sure one can do that in AppleScript, too (well, I’m not sure at all, just wanted to be nice ;-). This permits for a much compacter code and eliminates the tedious if-else part with its mostly identical code (DRY!). Adding the new “Remove and then add” function would be nearly trivial with this setup.

Also, I didn’t like the logic in the original “removePrefix” part: My version simply runs replace with a regular expression on the name. If there is no counter prefix, there’s nothing to replace, so no harm done. In addition, I choose a very strict regular expression that checks for a number followed by the delimiter (actually a separator) at the beginning of the name. The original code seemed to be happy about a delimiter somewhere, not necessarily following a number – I found that a bit risquée.

Version Lines of code Characters
AppleScript 102 3950
JavaScript 67 2307

So the JavaScript variant is not 10 times shorter, but about one third (in both LOCs and characters). And it has the added benefit to

For even more brackets, curly braces and parenthesis, you might want to head over to

(() => {
  const app = Application("DEVONthink 3");
  app.includeStandardAdditions = true;

  const delimiter = '.| ';
  const prefixRE = /^\d+\.\| /;
  const groups = app.selectedRecords.whose({_match: [ObjectSpecifier().type, "group"]})();
  if (groups.length === 0) {
    app.displayAlert("Please select a group.",
	    {as: "critical",  buttons: ["Understood"],   cancelButton: "Understood"});
	return;
  }
  
  /* Use an object to assign functions to choices instead of an if-else switch */
  const functionDistributor = {
    "Add": function (c, counter, flag) { addCounterPrefix(c,counter)},
	"Add if more than 1 Child": function(c,counter, flag) { addCounterPrefixIfMoreThanOneChild(c,counter)},
	"Remove": function(c,counter, flag) { removeCounterPrefix(c)}
	};

  /* Get user's choice (note the [0] at the end to get the first and only string from the array of answers) */	
  const choice = app.chooseFromList(Object.keys(functionDistributor), 
    {withPrompt: "Enumeration:", defaultItems: [Object.keys(functionDistributor)[0]]})[0];
	
  /* Call the function associated with "choice" on all groups */
  
  groups.forEach(g => functionDistributor[choice](g.children(),0, g.children().length > 0));

  function addCounterPrefix(children, count) {
    try {
      children.forEach(c => {
        c.name = `${++count}${delimiter}${c.name()}`;
        addCounterPrefix(c.children(),0);
      })
    } catch (Error) {
      showError(Error.message, "addCounterPrefix");
    }
  }
  
  function addCounterPrefixIfMoreThanOneChild(children, count, flag) {
    try {
      children.forEach(c => {
        if (flag) {
          c.name = `${++count}${delimiter}${c.name()}`;
        }
        addCounterPrefixIfMoreThanOneChild(c.children(), 0, c.children().length);
      })
    } catch (Error) {
      showError(Error.message, "addCounterPrefixIfMoreThanOneChild");
    }
  }
  
  function removeCounterPrefix(children) {
    try {
      children.forEach(c => {
        c.name = c.name().replace(prefixRE,"");
        removeCounterPrefix(c.children());
      })
    } catch (Error) {
      showError(Error.message, "removeCounterPrefix");
    }
  }  
  function showError(text, functionName) {
    app.displayAlert(`Error: Handler "${functionName}"`, {
      message: text, as: warning});
  }
})()
1 Like

I love it, thanks - I’m going to save both these scripts; together they may go some way towards helping me understand at least the basics of JXA. And I’ll be sure to head over to your webpage too.

You underestimate our sheer brilliance :smiley:

As I mentioned, you can simply add the script to your toolbar - that way, it’s there whenever you want it; and of course, you can further adapt it to meet your needs. I’ve learned to love DTs flexibility which is offered - in part - by intense AppleScript integration.

2 Likes