Set link type automatically based on container of destination notes?

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.

I know I sound pedantic, but I see the confusion ‘ziplinks’ causes those learning the app, thus the explanations. :slight_smile:

1 Like

I see. I have learned two things over the couple of years I have used TB. First, that there is almost always a solution for TB to do what I want. Second, that the solution rarely is what I have envisaged :blush:. Thanks a million for not only telling me what’s wrong, but also for explaining why it is wrong so I may actually learn.

So, the solution would be to:

  1. An agent query that selects all notes with an outgoing *untitled link in the entire document. I can select all notes with outbound links with the query $OutboundLinkCount > 0 but it would be unnecessarily cumbersome to select all link types. What query selects only *untitled links?

  2. Action code that loops through all selected links, selects the ones with a destination containing /Sources/ in their paths and assigns the link type referencesto them. I think I know how to select link with a particular destination note:
    eachLink(aLink,original){ if(aLink["destination"] == "Source 1"){ aLink["type"] = "reference"; } };

But how do I select all links with destinations containing the particular container name Sources in their paths?

Would it be possible to design a query that only selects notes with outgoing *untitled links to notes containing /Sources/ in their paths? And then run the action code originally suggested:
eachLink(aLink,original){ if(aLink["type"] == "*untitled" { aLink["type"] = "reference"; } };

However, I simply don’t know what such a query would look like…

1 Like

I added some notes to your document and reset the agent query to:


For ease of testing, the above matches any note with 1 or Text links ($PlainLinkCount), for as we know know all links created via the ‘zip’ method are, in Tinderbox terms, Text links. This query ensure you don’t you won’t match notes with only Basic type links.

The agent action we came up with earlier is:

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

Can you see the problem? If I have an untiled link from Note 4 to Note I, this incorrectly resets the link type as it fails to check the destination is in /Sources. We need to be more specific, we need to test not only the the link type but also that the destination includes the path /Sources and that anchor is not empty (in which case the link must be a basic link , as only Text links have anchor text).

The action becomes:

   if(aLink["type"] == "*untitled"&aLink["destination"].contains("/Sources")&aLink["anchor"]!=""){
      aLink["type"] = "reference";

In my test there are the following links:

  • Source 1. ‘untitled’ text link to ‘Note 3’: should be unaffected.
  • Source 2. ‘untitled’ text link to ‘Source 3’: should change to ‘reference’.
  • Source 3. ‘untitled’ basic link to ‘source 2’: should be unaffected.
  • Note 1. ‘untitled’ text link to ‘Source 1’: should change to ‘reference’.
  • Note 2. ‘disagree’ text link to ‘Note 3’: should be unaffected.
  • Note 3. ‘disagree’ text link to ‘Note 2’: should be unaffected.
  • Note 4. ‘untitled’ text link to ‘Source 2’: should change to ‘reference’.
  • Note 5. ‘untitled’ text link to ‘note 2’: should be unaffected.
  • Note 6. ‘untitled’ basic link to ‘source 2’: should be unaffected.

Here is my test file in two versions:

Look at both, and check their links against the results I listed above: remember use Browse Links with the original note selected and not the alias in the agent. Then, in the ‘before’ version, turn the agent query on. To to this, in the Query Inspector, open the Priority pop-up menu bottom left, change the selection from ‘Off’ to ‘Normal’. Allow a few moments for the agent to run, then check the links and note any changes.

1 Like

Thanks @mwra ! You have not only helped me creating the functionality I aspired, but also provided a pedagogical crash course in TB agents and automation that I am sure will also benefit others on this forum (at least other relative newbies). Simply fantastic!

However, I suspect there is something fishy with my TB installation or version (9.2.1). Nothing happens when I open your “before”-file, set the agents query priority to normal and let the agent run. I tested if there is something wrong with agent actions in general by just letting an agent select a note ($Name == "Note 4") and turn it red ($Color = "red";) and that worked like normal. If it would have been my own TB document that is acting out, I would have been certain it was something I have done or not done, but I cannot understand why your tested document fail to execute on my computer and TB version. Any ideas?

Thanks again!

Too kind. In truth, most of it I’d never actually done before and so it took a few missteps to get right. I’m as aware as any that there’s a gap between reading a description and understanding it and another from understanding conceptually to putting into action. That’s my impetus for posting my test file (hardly a demo as there are no instruction in there, etc.), plus the fact that seeing the process or at least before/after states is it gives confidence that re-using the concept should work when ported to personal projects.

Expanding for later reader, as much as for now, this shows the structural limitations to auto-interrogating links. Importantly, regardless of the context you are interested in, link data is only accessible from the link source’s note. So fine if the context is looking at outbound links; not so fine if looking at inbound links because each must be interrogated outside the context in their source note. This is better explained thus:

I’ve used an adornment ‘Context of Interest’ to simulate the part of the document where we are interested in the links. We can address most of the links in that context by looking at notes ‘in’ the area, i.e. note A and note D. But, to look at all the links in the area of interest, we must also look at notes outside the contest—note B and Note E as they link into the context area.

I hope that helps helps illustrate how we address working on 'links into ‘Context of Interest’. The context could be a whole map, an outline container, an arbitrary section of the map (the example I show here), etc.

Other action code tools can help is collecting the necessary—and minimum—number of note’s whose links will process:

continuing in a new post…

1 Like

We’ve also learned that we can use $PlainLinkCount (Basic links out from a note) and $TextLinkCount (Text links out from a note) to filter notes with either or both Basic and Text links. Thus if only using the ‘zip’ method for links of interest you would only want notes where $TextLinkCount >0. But what if some notes have both basic and test links?

Then the filtering has to happen within use of eachLink(). For each link processed, the operator offers a Dictionary of properties (sometimes referred to a ‘facets’) some of which my be empty. In terms of what they are/do:

  • start/end:
    • source. The $Path of source object, though it is also the note whose links we are processing so it is essentially already known. Read/write.
    • destination The $Path of destination object. Read/write.
  • type. The links’ link type. Read/write.
  • anchor (only for text links, the links’ anchor text within $Text). Read-only.
  • comment (internal link comment, otherwise accessed via Browse Links—the blank box the the bottom of the dialog). Read-only.
  • Info only for Web links (i.e. out of Tinderbox to external URLs—local or on the Web). Used for HTML mark-up when exported:
    • title. For HTML link’s <title> value. Read-only.
    • target. For HTML’s <target> value. Read-only.
    • url. For HTML link’s <href> value. Read-only.
    • class. For HTML link’s <class> value. Read-only.

By checking anchor, we can filter Basic from Text links as it will be empty only for Basic Links.

By checking destination, we can filter for inks going to a specific note, container, or branch of the document outline.

By checking type, we can check the link’s link type. This always has a value; the default is *untitled which in pop-up lists is ‘untitled’ and sows in a default map as a link with no link label (though beware, users can turn off the screen labels on other link types via the Links Inspector.

In summary: “some assembly required”, so to speak, but you can to a lot once you’ve figured out what to test and he code to do that. But questions like that are a key role for this forum.

A lesson I’d offer for starters, from this topic, is don’t be overly visual in your framing of the problem. By all means start with what you see—especially if using the map. Then consider where the information you see is actually stored. For instance you see links as lines between notes, but to interrogate a links’ data the only context you can do so is the links source note. But what if the link comes from a different map—where is the source. One additional bit of info can be obtained from link stubs. The inbound link stub (inbound from notes outside the map) is drawn above a note: click the number at the top of the arrow to show the source(s) of these link(s). Once you have an idea of where the notes are that you need to work with, you can start to figure out a query to find them (or make an agent) and then work on the links themselves.

HTH :slight_smile:

1 Like

In 8.x they were called ziplinks. Due to confusion, they are no longer called that. “Links” has far too many different meanings.

1 Like

This is a great functionality that I am sure will be useful for many! All thanks to @mwra, and Tinderbox 9.5!

When implementing it in my personal knowledge management system (Zettelkästen), I remembered @mwra 's earlier advice in this thread about making sure the name of the destination container remains unique in the entire document.

Since there it a significant probability that I will come up with names containing Sources in the string in the future, I changed the name of the container with my sources to zSources. This is perhaps obvious for experienced users, but I thought it was worth emphasising in this thread anyway.

1 Like

It is a good idea, and one that tends to only be obvious once you know. Simple prefixes are a simple stylistic device—even if only used when needed—to help avoid title ($Name) collisions. I tend to prefix all my (user) prototypes with a ‘p’ (‘pNote’) and other prefix styles are available according to personal taste.

Something like ‘zSources’ might look ugly to some eyes, but I see it as enlightened self-interest. If it makes something unambiguous something that otherwise is not, that is a gain and less worry about query/action mis-steps.

1 Like