How do you sort Tags by number of Items?

I have created a tag called ‘Customer’ with a number of tags for each customer nested within it (e.g. ‘John Doe’). They are all at the same level ‘within’ this customer tag. What I wish to do is sort the tags within this ‘Customer’ tag by the number of items assigned to each one, in descending order. Items are of different file sizes so the ‘by Size’ option, in View > Sort, doesn’t satisfy my requirement.

Is there a straightforward solution to my problem?

1 Like

Welcome @ari
Sorry but this is currently not possible at this time. Perhaps it will be in the future.

Is there a less straightforward solution? A different way I can structure it, so that I may sort by number of items?

At this time, no. The item count isn’t a sortable column, and other attributes, like size or word count, wouldn’t be reliable.

Development would have to assess the potential here.

Alright. I believe it would be a useful feature to add, for my workflow. Evidently, though, it hasn’t been a pressing issue for the community.

Thanks for taking the time to respond.

1 Like

You’re welcome. No promises, but the request has noted. :slight_smile:

1 Like

Hi @ari, if you create a smart group (kind:tag) and attach this triggered script to it (in the info inspector) you can sort your tags with the comment column.

-- Make tags sortable by child count (via finder comment)

-- Assuming you don't use the finder comments of tags (as they will be overwritten) you could attach this triggered script to a smart group that searches for "kind:tag". When you click the smart group it updates the comments.

on triggered(theRecord)
	tell application id "DNtp"
		try
			set theTagGroup to tag group of current database
			
			repeat with thisTag in theTagGroup
				my walkTags(thisTag)
			end repeat
			
		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
end triggered

on walkTags(thisTag)
	tell application id "DNtp"
		try
			if type of thisTag = group then
				set theChildCount to (count of (children of thisTag whose type ≠ group)) as string
				set comment of thisTag to theChildCount
			end if
			
			set theChildren to children of thisTag whose type = group as list
			repeat with thisChild in theChildren
				my walkTags(thisChild)
			end repeat
			
		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
end walkTags

Hey @pete31, Thanks A lot! I got your solution to work. I only have to adjust my tag hierarchy structure.

How I have it, I include an additional tag on the same level as the items (where the script does its count), with suffixes such as “_paid”. While the items of the “suffixed tags” aren’t included in the child count of the script, Devonthink includes the total count of items, which includes the nested items in those tags. The sort is correct on the first level but Devonthink including the total count of all items nested in that tag, makes it look like it isn’t sorted properly.

This looks strange to anyone who doesn’t understand what is happening in the smart group. But i’m just nitpicking, your solution is functionally sound at the moment, even though Devonthink adds that weird visual quirk. Thanks again.

Try this (I’m quite sure this is not the right way to do it but maybe it’s ok)

-- Sort Tags by child count (via finder comment)

on triggered(theRecord)
	tell application id "DNtp"
		try
			set theTagGroup to tag group of current database
			
			repeat with thisTag in theTagGroup
				my walkTags(thisTag)
				
				set theComments to {}
				
				set theGroups to ((children of thisTag) whose type = group)
				repeat with thisGroup in theGroups
					set end of theComments to comment of thisGroup as string
				end repeat
				
				set addAll to 0
				repeat with thisComment in theComments
					set addAll to addAll + (thisComment as integer)
				end repeat
				set addAll to addAll + (count of (children of thisTag whose type ≠ group))
				
				set comment of thisTag to addAll as string
				
			end repeat
			
		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
end triggered

on walkTags(thisTag)
	tell application id "DNtp"
		try
			if type of thisTag = group then
				set theChildCount to (count of (children of thisTag)) as string
				set comment of thisTag to theChildCount
			end if
			
			set theChildren to children of thisTag whose type = group as list
			repeat with thisChild in theChildren
				my walkTags(thisChild)
			end repeat
			
		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
end walkTags

I will try it. Thanks again @pete31.

Hey @pete31 is there anyway I can get the script to only include the filtered tags of the smart group I attach it to? Currently it appears to affect all the tags in my database. I want to run the script on the findings of a smart group

You could try to extract the tags from the smart rules search predicates (which is a string), make it a list, and then use this instead of “tag group of current database” in

set theTagGroup to tag group of current database

Hi, I second that request :slight_smile:

Hello! Just checking: is there still no way to sort tags or documents by number of items? It seems like a fundamental functionality… but I am not an expert.

It would be wonderful if there could be a column option created for “Item Count”. We could then sort easily and even disable the option to “Display number of items inside groups” (which I feel clutters the sidebar, but that’s just my opinion) but still easily see the item counts.

I would mark this as a feature request but I am not sure how in a response. Thanks for all that you do!

It’s a rare request but we’ll consider this for future releases.

1 Like

I would like to third this request, even though the thread is two years old. Here is my use case.

I have a database containing roughly 5,000 PDF articles created by 2,000 authors. Each imported PDF is tagged by author name. I would like to have a view in DT of authors sorted by number of articles written, which equates to number of items per tag sorted in descending order.

Right now I can indeed see the number of items per tag because the count is kinda overlaid on top of the tag name field, right-justified. But clicking on that column only sorts by tag name, not count.

Thanks for any updates on this topic.

Aside: PDFs contain an author meta data field. That might be a better approach to store the author name than proliferating the tags.

In any case, a script could create a sheet (or any type of record, really) that contains the required data.

(() => {
  const app = Application("DEVONthink 3");
  const databaseName = "XXX";
  const database = app.databases[databaseName];
  const tagsGroup = database.tagsGroup;
  const tags = tagsGroup.children();
  tags.sort((a,b) => {
     return a.children.length - b.children.length;
  })
  const tabSheet = app.createRecordWith({name: "Tags sorted", type: "sheet", columns: ["tags", "count"]});
  const cells = [];
  tags.forEach(t => cells.push([t.name(), t.children.length]));
  tabSheet.cells = cells;
  })()

This one (written in JavaScript) creates a script for the tags of database XXX (change that to whatever your database is named). It writes them by ascending number of items into a new record named “Tags sorted” in the global inbox.
If you want to sort by descending number or items, invert the sort test like so:
return b.children.length - a.children.length;
To make the sheet more useful, you could add a third column to it containing the tab’s item link.
The code does not consider hierarchical tags.

Thank you. I will try this.

PS: I actually have the author already as a Custom Metadata field. I used the “Add Tags from Document” batch tool to convert this field into a tag. Are you implying that DT may not like dealing with 2,000+ tags for all my authors? If I just kept the author as a CM field, would there be a simple way to allow viewing/grouping the articles by author, similar to the existing tags view, but not requiring the “overhead” of tags?

I meant the PDF meta data, not CM. PDFs have fields for author, publisher, creation date etc.
I find adding data to a file that can also be stored inside it not very practical. That’s all. If you prefer using tags, go for it.

I actually have a JavaScript program sort-of working to take my CM data and edit the PDF fields of author, title, etc. In fact, I think you provided the original code for it a while back here and here.

I tried running this script against my entire database of 5000 PDFs and it didn’t work very well in bulk (I ended up having to rebuild my database to see any of the changes). But doing a few at a time seems to work.

/* DEVONthink JavaScript to update General Conference PDF file properties
   PDF Author = Custom metadata Speaker (ID 'mdspeaker')
   PDF Title = Custom metadata Title (ID 'mdtitle')
   PDF Subject = Custom metadata Conference (ID 'mdconference') formatted as "<Year> <Month> General Conference" E.g. "1971 April General Conference"
   PDF Copyright = <month> <year>
   Nathan Ellsworth - August 2024
   
   Extensive help taken from chrillek and cgrunenberg from DEVONthink forums:
   
   https://discourse.devontechnologies.com/t/javascript-get-custom-metadata-to-rename-file/68445/24
   https://discourse.devontechnologies.com/t/tag-name-into-custom-metadata/70050/3
   https://discourse.devontechnologies.com/t/custom-metadata-import/77900/7
   https://discourse.devontechnologies.com/t/custom-metadata-import/77900
*/
ObjC.import('PDFKit'); 

function performsmartrule(records) {
   const app = Application("DEVONthink 3");
   app.includeStandardAdditions=true;

   records.forEach (r => {

		const m = r.customMetaData();
  		const conf = m['mdconference'];
  		const spk  = m['mdspeaker'];
		const title = m['mdtitle'];
		const conf_year = conf.split(" ")[1]
		const conf_month = conf.split(" ")[0]
		const copyright = conf_month + " " + conf_year 
		const subject = copyright + " General Conference"
  		/* app.displayDialog(conf); */
  		/* const conf = app.getCustomMetadata({for:"mdconference", from:r, defaultValue:""}); */
  		/* convert record's path to NSURL object */
  		const docURL = $.NSURL.fileURLWithPath($(r.path()));
  		/* app.displayDialog(r.path()); */
  		/* load the PDF document from this URL */
  		const PDFDoc = $.PDFDocument.alloc.initWithURL(docURL);
  		/* get the current PDF attributes as a MUTABLE dictionary.
     		other dictionaries can't be modified! */
  		const PDFAttributes = $.NSMutableDictionary.dictionaryWithDictionary(PDFDoc.documentAttributes);
  		/* Set the PDF properties */
  		PDFAttributes.setObjectForKey(subject, $("Subject"));
  		PDFAttributes.setObjectForKey(spk, $("Author"));
  		PDFAttributes.setObjectForKey(title, $("Title"));
		PDFAttributes.setObjectForKey(copyright, $("Copyright"));
  		/* Update the PDF attributes */
  		PDFDoc.documentAttributes = $(PDFAttributes); 
  		/* Write the PDF doc back to the URL */
  		const result = PDFDoc.writeToURL(docURL);

	})
}

(() => {
if (currentAppID() === "DNtp") return;
const app = Application("DEVONthink 3");
performsmartrule(app.selectedRecords());
})()

function currentAppID() {
  const p = Application.currentApplication().properties();
  return Application(p.name).id();
}
1 Like