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?
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
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
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!
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.
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.
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();
}