Set link type automatically based on container of destination notes?

I have been scratching my head a lot lately, trying to figure out a way to automatically set link type for links to notes in a particular container.

For context, I have one large container called “Zettels” for all my knowledge notes (zettels) and another container called “Sources” with a note for each source cited in the larger container. I then use ziplinks to link from the knowledge notes to the cited sources and currently set the link type manually to “reference” to separate these links from all the links between the knowledge notes.

So, is there a way to automatically set the link type based on the destination container? I cannot figure out a query and action code for that, but I am really a beginner when it comes to agents, rules and edicts.

Thanks on beforehand!

This is going to be a little tricky, and I have a plane to catch. (Doesn’t that phrase take you back to the good old days?)

Here’s a quick outline of one way I might do this.

  1. Make an agent that finds notes inside(theDestinationContainer).
  2. The agent’s onAdd action then examines the links to each note:
    2.1 eachLink(x,original) {..modify the link type...}

Note that you need to examine the links to the original note — not the alias.


Further to the above:

For clarity, No. But , @eastgate offers a working solution albeit not in the way you phrase the question.

Part of your unintentional miss-assumption here is as to what the ‘zip’ method of text link creation does. The method is already Byzantine in the complexity of options (IOW, only easy once you figure them out).

Also, re the above solution, the ability to change a link type via action code to v9.5.0 but I’m not aware of any code sample (yet!) showing how to do this.

Thanks @eastgate and @mwra ! I am still a bit confused if I can create a workaround or not. The agent query to select all notes in the container is simple, now when @eastgate told me how to do it (i.e. inside(Sources) ). I am then dabbling with the action code and cannot get it to work. Perhaps it cannot be done. Fully disclosing my ignorance, I am trying something like this:

eachLink(x){if(x.type == "*untitled"){x.type = "reference";}};

The links to the sources are *untitled so I thought I could select the links needing a new link type like that. Not working.

Is it possible to update the links coming in to the notes in the container using some action code?

I think you want:

eachLink(x){if(x["type"] == "*untitled"){x["type"]  = "reference";}};

Note the type is a Dictionary key and not a dot operator.
See my notes here

The eachLink() operator examines each link for the current note, either inbound or outbound; prototype links are excluded. The local, user-named, variable loopVar argument is bound to a dictionary-type object of per-link properties, and is used in the {} enclosed action code.The Dictionary includes:

  • source ($Path of source object)
  • destination ($Path of destination object)
  • type
  • class
  • title
  • target
  • url
  • comment
  • anchor (for text links, the links’ anchor text within $Text)
1 Like

Confirmed. The following code works-I’ve used aTbRef-style naming/notation as I’ll add the code to my article as example code:

   if(aLink["type"] == "*untitled"){
      aLink["type"]  = "reference";

If the link type to be applied does not exist, it is created and applied to the altered links.

Here is my test document: linktype-change.tbx (149 KB)

Note for @eastgate: currently a new link type added via this method is not immediately added to list of defined link types, as seen in the Links Inspector. I was testing in an unsaved new TBX. I had to save/close/reopen before the Inspector showed the new link type. Minor but confusing for the new occasional user.

1 Like

My article on eachLink(loopVar[,scope]){actions} is now updated to include the above code example to show how to change a link type.

1 Like

Don’t forget that you likely want eachLink(original){} , since you’re interested in links to the original note and not the agent’s alias.

1 Like

Yes, my error.

So whilst the above operates on the current note, as @eastgate correctly notes, in an agent context the current note would be an alias and we need the context to be the original of the alias.

For that we need the optional second ‘scope’ argument for eachLink(loopVar, Scope){}. The example becomes:

   if(aLink["type"] == "*untitled"){
      aLink["type"]  = "reference";

So we male a new test, where “Test note 2” which has 2 links one of which has the link type ‘example’. so, in our test only one of the two link’s types should get altered.

[Edit: the original image that the note name wrongly spelt.]

The agent query:

$Path=="/Test note 2"

and the action is as above.

Thanks @mwra ! Fantastic! This is exactly what I try to accomplish.

However, it still does not work when I am using your correct code with or without @eastgate 's reminder of specifying links to the original note:

eachLink(aLink.original){ if(aLink["type"] == "*untitled"){ aLink["type"] = "reference"; } };

It struck me that I have not managed to get my employer to pay for the last couple of updates, so I am still running on TB 9.2.1. Could that be the problem now when the code is tried and tested?

Here is the updated demo TBX: linktype-change.tbx (148.7 KB)

I have also extended the example in eachLink(loopVar[,scope]){actions}.

This is because you are randomly applying dot notation.

eachLink() has two arguments:

  • loopVar. Mandatory. This is the user chosen name for the in-loop reference variable to the link being worked upon.
  • scope. Optional. This is the desired evaluation contreext. In our case here, the original of an alias.

Furthermore, where an action code operator takes more than one argument, there are comma-delimited, e.g. operatorName(aa,bb). White space before/after the comma is ignored, some some may prefer to write this as operatorName(aa, bb).

In loop, the properies of the link being evaluated are accessed as keys to a Dictionary-type data object. Thus if our loopVar is set as ‘aLink’ to access the ‘title’ property of the link we call is as aLink["title"]. A quoted string value inside square brackets and typed directly after the loopVar name (with no white space in between the two).

†. That’s not me being snarky. I simply can’t see how, given the available documentation, you would assume the use of dot notation in the way you have used it. So, I presume you just guessed (as do we all sometimes!). Anyway, If there is something you read that caused to to think otherwise, please let me know as likely that can be improved. You might find it useful to read a whole new section I wrote for the new aTbRef 9.5 on Action Operator Arguments.

The dot notation is due to the combination of my mistake and ignorance. It is not from anywhere in the documentation or in the kind assistance provided by you and @eastgate. Simply my own fault. Sorry!

Thank you for your thorough explanation of how the action code works! It still does not work for me, but I will go through everything carefully again this evening to see if I can locate where I am going wrong. I will also try again to get funds for updating to TB 9.5.

No worries. Tinderbox’s action code has grown over the years from a limited macro system to something more complex. With is comes a degree of inconsistency.

Barring authoring errors of mine, the current aTbref is probably the most fully described and cross-referenced resource (disclaimer: I am the (sole) author).

The dot notation allows easier/clearer writing of code, so:


instead of (pseudo code as-some operator in next example don’t exist):


In fact, I’m already showing an ambiguity for the learner. In full verbose form, the first example should read:


But Tinderbox is clever enough to realise there are no arguments for those operators except the chained source. IOW, .sort() knows to sort $MyList, the previously chained object. Thus .unique()acts on the _sorted_ version of $MyList, as it is chined to.sort()` and not directly to $MyList.

As to brackets:

  • normal brackets (BrE) or parentheses (BrE and AmE), i.e. ( and ):
    • indicate arguments at the end of operators (including functions
    • indicate order of evaluation in both queries and actions. The process is as in spreadsheets, so that X = (A + (B * C)) means B and C are multiplied before being added to a. otherwise, as code is normally parsed left to right the app might add A and B before multiplying the result by C. sometimes the difference matters and brackets help with this.
  • square brackets (BrE and AmE) or brackets (AmE), i.e. [ and ] indicate an array, or as Tinderbox only has one-dimensional arrays, list position:
    ** by value (zero-based), $MyList[2] gets the value of third (as zero-based) item in my list.
    ** by key name (for key:value pairs in dictionary data type), `$MyDictionary[“apple”] with match the key “apple” and from the “apple:fruit” key:pair will return the value “fruit”.
  • curly brackets (BrE and AmE) or braces (BrE and AmE) , i.e. { and }, indicate enclosed sections for code such as occur with contritions code (i.e.if(){}else{}) or for loops, .each(){}). Such enclosures mainly effects the scope of back-reference and loop variables—the details of which can be read up on in aTbref.

dot-chaining is only used for operators. confusingly (see my point above about long term, and changes/exceptions)) they are used by the links() operator to delimit the operator’s arguments, so links(arg1.arg2.arg3.arg4) and not links(arg1,arg2,arg3,arg4).

I’m sure there are more exceptions, but I hope the general pointers help.

†. BrE (British, or international, English) and AmE (American (USA) English) can have slightly different naming of things (as they do spellings: colour/color, etc.), which is why I’ve also included the actual characters too.

1 Like

Thanks for the quick run through! I really need to learn this. Thanks also for aTbref! Without it and this forum, I would not have managed to do much at all.

I have now checked everything and I do not understand why I cannot get it to work. I have replicated the setup in this TB document, but with just a few notes:
Problem_link_type_change_TB.tbx (107.2 KB)

I am using your working action code, but with the query suggested by @eastgate (i.e. inside(Sources) ) that selects all notes in the Source-container. I cannot figure out what I do wrong. I have included one ziplink from a note in the Zettels-container to a note in the Sources-container (my actual target), and one ziplink between two notes in the Sources-container (just to test), but the agent does not change the link type from *untitled to reference as expected. I have gone through everything so many times… With my untrained eyes…

Thanks for the demo, but we’re missing essential info. You originally asked:

So, we need to define:

  • which notes in which we will test the links
  • the criteria to test for the link. I think this will be the links destination property… IOW, in terms of earlier code testing if(aLink["destination"]==????){...
  • what link type we set for what destination.
    • Is several destinations are being tested for and they each have a discrete desired link type, you could store the info in a dictionary were each key:value pair is in the form link-destination:linktype-to-apply

Answer those and we cam make a solution, and one you can apply to different files if needed.

1 Like

Certainly! Sorry for being too vague.

  • It is the notes in the container called Sources receiving the links that I would like to test. Simply because it is easier to identify them with a query (i.e. inside(Sources)) than the notes in the other container from which the links originate. If there are better ways to select the links to these notes, I am more than happy to follow your helpful lead.
  • Again, fully disclosing my ignorance; If the destination property is a property of the notes the links point to, that would open up a way to select the links pointing to notes inside the Sources-container. Is that what you mean?
  • These links are ziplinks so the default link type is *untitled, which I would like to set to a user-defined link type called reference. If it is possible to select the links based on their destination container, it may perhaps not be necessary to include the default link type in the code. Hence, making the code more robust by also updating the link type of links made by other means than ziplinks and having other default link types. When getting this to work, your hint to the possibility of automatically assigning discrete link types to different destination containers opens up a lot of opportunities (although I will most likely end up on aTbref and on this forum again before I manage to use a dictionary :blush:).

Is this enough explanation, or have I missed important details?

I cannot thank you enough for helping me with this. It is sincerely appreciated.

1 Like

Note, there is no such ltype of link as a zip link. What you refer to is a text link which is created using the ‘zip’ keyboard based linking method. What you make and how it is made are two disconnect things, thus talking about ‘ziplinks’ is thus confusing. 'Text link ’ is the term you are looking for.

1 Like

Thanks. Be aware the method suggested above only works on outbound notes so,

Also there is no way to query only for notes that have link(s) whose destination (actually a path) contains a certain folder.

The best you can do is, I think:

  • make an agent that collects all notes with outbound basic or text links
  • for each alias, iterate (li.e. loop through) all the links and if the link type is untitled, then:
    • check the link’s destination path,
    • if that path includes the container Sources (which must be case-sensitively a unique title within the entire TBX)
    • change the links link type to Reference

No, the destination describes the path of the note to which the link points. Action code can only access link data for a note’s outbound links. The later is a limit desire cannot work around. So, your solution has to work from the context of notes with outbound links.

No! They are text links of type “*untitled”, as explained above

You can’t select links this way. You can only look at a note’s links, cycle trough and for each one check the path stored as the `destination, etc., as described above.

To confirm, blending your desired outcome and possible avenues for code, we need to check all note’s outbound links and for any that are ‘untitled’ and the the destination path includes the ‘Sources’ container, finally we set a link type references.

So I think we can get the outcome you want, albeit via a method you didn’t envisage.

If so, then we’ll need to amend you document as it has no notes with outbound links that meet the criteria, Having at least one note meeting that criteria is essential for testing the solution. For instance we add a note “Note 4” with an untitled link to “Source 3”. This meets the criteria:

  • a note with an outbound untitled link
  • the link points to a note with `/Sources/ in its path (and thus the link’s destination path).

For proper testing, we also want a note with an untitled link that. Doesn’t point to a note in Sources as we don’t want that to be changed as it only meets the criteria partially.

Edge case, if a note links to a source note by any link type other than “*untitled” or “reference” will not be affected by the above.

1 Like

Thanks for clarifying the naming convention in TB! I picked up the name “ziplink” from an earlier thread, but will try to unlearn that and use “text link” from now on.