Merging selected record using JavaScript

Hello community. How can I merge the currently selected records using Javascript ?

My attempt didn’t work:

(() => {
    "use strict";

    const 
        appDT = Application("DEVONthink 3");

    return appDT
    .merge({records: appDT.selectedRecords()})
})();

The script doesn’t specify a destination group, therefore it might have worked but the result is not where it’s expected.

Experimentally, this variant also appears to fail:

(i.e. no error message, but also no merged record created in the current group)

(() => {
    "use strict";

    const appDT = Application("DEVONthink 3");

    return (
        appDT.merge({
            records: appDT.selection(),
            in: appDT.currentGroup()
        }),
        appDT.currentGroup.name()
    );
})();
1 Like

Perhaps some typing glitch in the OSA declarations ?

This osascript -l AppleScript translation seems to work:

tell application "DEVONthink 3"
    
    merge records (selected records) in current group
    
    return name of current group
end tell
1 Like

The problem is that the records field of the parameter object is always an empty array:

app.merge({records:[], in:app.databases.byId(6).parents.byId(2147483645)})
		--> null

is what Script Editor says, regardless of what I put into the records array (I even tried to pass an NSArray unsuccessfully, but at least that was not empty :wink: ). We’ve talked about that before in the context of summarizeHighlightsOf, where the first parameter is also empty (Script Summarize Highlights from Javascript - #2 by chrillek).

This thread petered out inconclusively. Since there are apps out there which can work with arrays of data in JXA and AppleScript lists map just fine to JavaScript Arrays in other situations, I still assume that there’s a subtle glitch in DT’s approach here. @cgrunenberg?

3 Likes

During the testing DEVONthink didn’t receive any records at all, hard to tell where exactly the JXA is broken. Maybe JXA just doesn’t like the name (records).

It’s not a reserved word in JavaScript. Nor is it a global variable in JXA, afaik. And putting the properties (records, in) in quotes first change a thing.
I suppose you could test that by inventing a method internally which receives a list parameter named foo or whatever.

As mentioned before, GraphicConverter has a changeResolution method that accepts a to parameter, which is a list. Apple’s Mail has performMailActionWithMessages, which also takes an array as parameter, this time unnamed.

1 Like

Thank you all for the effort. I’ll be glad if a solution appears since the main logic of my script is written in JS, and easier to just use the merge method there.

That mapping looks a little unusual – in that context I would normally expect to see a method named .mergeRecords defined for the app in the JS name-space.

mergeRecords is not defined.

I compared the sdef of DT (which I must have somehow generated using a tool, don’t remember how) to that of GraphicConverter. In the case of DT, the type of the parameter is simply list, whereas GC uses type="integer" list="yes". I don’t know if the difference matters or if it’s only the result of the automatic sdef generation.

Also, DT has obviously no problem at all to return a list of records from a method call (cf. classify, selectedRecords etc.). Which makes me wonder why it wouldn’t work with a list as input parameter.

@cgrunenberg: It is not about the parameter type “record”. See the following script using cells and a sheet:

(() => {
  const appDT = Application("DEVONthink 3");
  const rec = appDT.createRecordWith({type: "sheet", columns:['A','B','C'], 
        'name': "newSheet"});
  appDT.addRow(rec,{cells: ['1', '2','3']});
})();

The AppleEvents related to that:

app = Application("DEVONthink 3")
app.createRecordWith({"type":"sheet", "columns":["A", "B", "C"], "name":"newSheet"})
app.addRow(app.databases.byId(1).contents.byId(676287), {cells:[]})

As you can clearly see, the columns parameter in the call to createRecord is passed correctly (and also the parameter name is quoted!), whereas in the call to addRow, the cells parameter is suddenly empty, and it is not quoted.

I’m aware that JXA is messy and buggy, but is it really that buggy that it sends empty arrays only in some cases? I know, in the first case you pass in a record (in the AS sense, not in DT’s sense), in the second one it is a named parameter. But from the JXA point of view, there’s no frigging difference at all: Both times, I pass in an object whose property names are just the names used in DT’s API.

Unrelated: What exactly would be the “specifier” to pass in as the first parameter to addRow? I know that the record returned by createRecordWith does not work, but what does work? Didn’t even see an AppleScript example in the forum.

1 Like

This is a completely different command and only supported by the think window class, see e.g. Add n records to sheet.scpt.

It still removes the content of the parameter

The typical pattern, in other application libraries, for cases in which the first (named) parameter is required, and may be followed by options, would be:

.mergeRecords(recs, {in: someLocation})

i.e. to

  • reserve the key:value dictionary for subsequent optionals, and
  • pass the required parameter directly.

I wonder if the OSA interface expectations are getting thwarted here by the attempt to pass everything in a dictionary ?

(I haven’t seen it attempted elsewhere, and the required parameter seems to be getting lost)

2 Likes

I would expect the same.

I just noticed that move command expects a list of records, and also doesn’t work:

(Assuming a group named New Group at root location)

(() => {
    "use strict";

    const appDT = Application("DEVONthink 3");

    // move
    // record: Record : The record or a list of records to move.
    // to: Record : The destination group which doesn't have to be in the same database.
    // [from: Record] : The source group. Only applicable if record and destination group are in the same database
    // → Record

    return (
        appDT.move({
            record: appDT.selection(),
            to: appDT.getRecordAt("New Group")
        })
    );
})();

However, moving a single record (instead of a list) works:

(() => {
    "use strict";

    const appDT = Application("DEVONthink 3");

    // move
    // record: Record : The record or a list of records to move.
    // to: Record : The destination group which doesn't have to be in the same database.
    // [from: Record] : The source group. Only applicable if record and destination group are in the same database
    // → Record

    return (
        appDT.move({
            record: appDT.selection()[0],
            to: appDT.getRecordAt("New Group")
        })
    );
})();

There seems to be an issue with lists in DevonThink JXA implementation.

Interestingly, looking at scripting dictionary definition, one would expect a direct (labelled) parameter for move, but the correct invocation is using a dictionary of options.

As a side note, looking at for addReadingList definition, the method signature and the actual invocation match in this case. Parameters are passed as a dictionary of options.

(() => {
    "use strict";

    const appDT = Application("DEVONthink 3");

    // addReadingList
    // [record: Record] : The record. Only documents are supported.
    // [title: Text] : The title of the webpage.
    // [url: Text] : The URL of the webpage.
    // → boolean

    return (
        appDT.addReadingList({
            record: appDT.selection()[0]
        })
    );
})();
1 Like

It’s not possible to revise the script suite without breaking existing scripts but we’ll definitely keep this issue in mind for future upgrades.

Thank you, @cgrunenberg. I appreciate your reply and understand that, but I hope we can use merge JXA method in the future.

For the record: the node module nodeautomation https://github.com/hhas/nodeautomation works ok with DT’s merge method:

const {app, con, its, k, File, CommandError} = require('nodeautomation');

const dt = app('DEVONthink 3');
const sel = dt.selectedRecords();
if (sel.length > 1) {
  const result = dt.merge({records: sel});
}

creates a new record as a merger of the selected ones. nodeautomation is quite low-level, if I understand it correctly, in that it’s working directly with AppleEvents.

3 Likes

Thank you for the interesting info! That’s actually another hint that this might be an issue of JXA but not of DEVONthink.

Although it might be possible to work around this by revising the script suite but this is not planned for minor maintenance releases. More likely in a future upgrade.

1 Like