One click solution to convert 'alias' to '[[Alias|alias]]'

I already saw at least 2 threads where this was discussed. There is a solution…but…

Due to limitations it is not easy to achieve this without javascript (which is what I am suggesting someone should help us out with – not the code, that is not that hard, but the exact workflow: how to target text selection and write back to the file). Simply because the regex flavours used throughout Obsidian plugins do not support the lowercase and uppercase switches \l, \L, \u and \U (not even regex101.com and Notepad++ does but e.g. Sublime Text can handle them).


Setup

What we need to do is install the plugins Commander, Apply Patterns and Code Editor Shortcuts.

In Apply Patterns, add a pattern for e.g. LinkifywithAlias (which can act as the subsequent name for the command as well):
In the pattern, we use one rule:
Match: (.+)
Replacement: [[$1|$1]]

Then we add another pattern for RemoveWikilinks. Again a single rule:
Match: \[\[(.*)\]\]
Replacement: $1

  • In the Patterns section, we can leave every box unchecked when creating both patterns as we use the created command one a single selection.

Still in Apply Patterns, we scroll down to the Commands section (you will find it) and and fill out the boxes with the names we specified above (it’s okay to name the command the same as the pattern and then you point the command at the pattern, so just copy and paste the names in the second box).
You tick the boxes for selection for both commands.

In order to activate these two patterns, you need to disable and re-enable the Apply Patterns plugin (faster then restarting Obsidian).

The rest is done in Commander. Go to the Macro tab and fill it out like so:

  • I think it’s all straightforward. You can omit the Delay parts – as you can see I only used them for placeholders, in case I need to add some value (150-250ms is enough), but I didn’t need to. The commands are nicely run one after the other like this.
    Again, one step can be ommited here; see second post about it. (I already made the screenshot and was too lazy to customize it for publishing.)

The command of this macro (LinkifyWithAlias Upp&Low) you can add to the Editing Toolbar with whatever icon you want or assign a hotkey to it.
I like to use Editing Toolbar in a submenu like this:

Due to the limitation mentioned above and how this makeshift macro works, running the command for a single word will work. You can see what happens if you select two or more words.

  • If someone likes to tinker, they might be able to make it work for more than one word. The tricky part will be the correct way of Transforming the Case. The regex patterns side is easier as you can add multiple rules in the pattern and only the match will be replaced.

So running it on a selected word like dad will exchange dad with [[Dad|dad]], which is I think what most people were looking for. (I didn’t mean Stan and Pan, ha-ha.)

BTW: The last command, Add internal link is an internal Obsidian command, so no need to install anything there.

P.S. On the plus side, this works on mobile as well, so you can put the command on the Mobile Toolbar.

2 Likes

If someone is thinking why the need to add the Wikilinks if we are to remove them in the next step (and we add them back in the last step), well, there is an explanation:
I had these patterns before and just joined them up like so only recently. We could just as well omit the second pattern and use the replacement $1|$1 without the double square brackets in the first.

  • This is why in the second pattern I have .* and not the same .+; because I can use the second command separately (I have that set for global, multiline, if someone is interested).

But for the sake of teaching how (to have a feel for regex replacements), this is better: one extra building block, one extra command to use for other cases.

What I would like is to be able to use inline javascript for text selection. I’m pretty sure someone can point the way.

More adventurous users with time on their hands can go about targeting the leading characters and deal with them separately.
For example in a command called “AliasforTitle&Heading_lowercase”, I have rules doing just that, but you need to go into the data.json file of Apply Patterns and do some copy+pasting to make rules fast for all A-Z cases (must add diacritic characters separately, should you need them, of course). So, for example (I copied this from my json file):

      "name": "AliasforTitle&Heading_lowercase",
      "rules": [
        {
          "from": "(\\[\\[)(.*?)(#)([A])(.*?)(\\]\\])",
          "to": "$1$2$3$4$5|a$5$6",
          "caseInsensitive": false,
          "global": true,
          "multiline": true,
          "sticky": false,
          "disabled": false
        },
...
        {
          "from": "(\\[\\[)(.*?)(#)([Z])(.*?)(\\]\\])",
          "to": "$1$2$3$4$5|z$5$6",
          "caseInsensitive": false,
          "global": true,
          "multiline": true,
          "sticky": false,
          "disabled": false
        }
  • You can see that one can target a word starter letter and then change that in the replacement. One can go about it this way. Downside is that your data.json will grow in size (but that doesn’t make it work slower, of course) and this is not good computing practice.
    Don’t worry about extra \ characters here, it is how AP saves the rules into the file. So don’t copy rules from here now if you don’t know what you’re doing.

As I said, both solutions are bad computing practice so anyone with a neater solution are welcome to chime in.

For reference, Obsidian is case-insensitive for internal links so you can actually just use [[alias]] directly without any issues.

That being said, I like to have tidy notes with the correct note links, so I do it with the [[Alias|alias]] style like you have there.

Here’s my Templater template which I have assigned to a hotkey. It works like this:

No text selected:

If you have no text selected before hitting the hotkey, it will insert [[]] and put the cursor in the middle so you can immediately start typing an internal link.

Alias text selected:

If you have some text selected, it will:

  1. Look for a note title matching the same text, regardless of case. If found, it will insert a link in the format [[Alias|alias]]. This means it will happily find a note titled “ALiaS” and create [[ALiaS|alias]]. Equally the case of your alias text doesn’t matter - it will still find the note.

Obsidian_3EtBHzLsJk

Of course if the title matches, it won’t use an alias:

Obsidian_eitkRKsIoN

  1. If there are multiple matching notes (that is, you have multiple notes with that same exact title), it will give you a list so that you can pick the one you want, then it will add the note in the format [[path/to/Alias|alias]].

Obsidian_n2djz8qjtT

  1. If there are no notes found, it will add a link in the format [[|alias]], and put the cursor before the pipe so you can start typing the name of the note you want to link to.

The template - just put it your templates folder and assign it to a hotkey with Templater:

<%*
const editor = app.workspace.activeLeaf.view.editor

// Move cursor after inserting the link text
function moveCursorBack (distance) {
  const cursor = editor.getCursor()
  cursor.ch -= (distance)
  editor.setCursor(cursor)
}

// The text selected before running the template
const alias = editor.getSelection() || ''
if (!alias) {
  // No alias text provided
  editor.replaceSelection(`[[]]`)
  moveCursorBack(2)
} else {
  // Alias text was provided, search for matching notes
  const notes = app.vault.getFiles().filter(f => f.basename.toLowerCase() === alias.toLowerCase())
  if (notes.length === 1) {
    // Exactly one matching note found, direct the link to that note
    let link = alias
    if (notes[0].basename !== alias) {
      // As the title and alias are not an exact match, add the alias to the link
      link = `${notes[0].basename}|${alias}`
    }
    editor.replaceSelection(`[[${link}]]`)
  } else if (notes.length >= 2) {
    // Multiple notes found, let the user choose which note to link
    editor.replaceSelection(`[[${alias}|${alias}]]`)
    moveCursorBack(alias.length + 3)
  } else {
    // No matching note found, let the user type any note
    editor.replaceSelection(`[[|${alias}]]`)
    moveCursorBack(alias.length + 3)
  }
}
%>

Works on mobile too, just assign it to a button on your mobile toolbar.

3 Likes

I had an idea you’d be the one taking the challenge. You’re only too kind – to include GIFs and whatnot.

Gotta say it works but it takes some getting used to: you see, my one always creates the word pair regardless of whether there was an existing note or not.

I agree it’s good practice to follow case sensitivity because we need compatibility with other apps and cater to people reading our ramblings once they’re published online.

On the technical side, as far as I can see it’s pure JavaScript so it will work with other plugins with inline script functionality, right?

Cheers

1 Like

If you want that, just change the “no note found part” from this:

// No matching note found, let the user type any note
editor.replaceSelection(`[[|${alias}]]`)
moveCursorBack(alias.length + 3)

To this:

// No matching note found, create the alias for a new note
const noteName = alias // Use your regex here or perform whatever transformations you like
editor.replaceSelection(`[[${noteName}|${alias}]]`)

Of course you can just leave it like I had it and simply type your new non-existing note name with the exact upper/lowercase you want.

Exactly. The reason I do most things in Templater is because I don’t want to install more plugins, and it happily executes any arbitrary Javascript I give it. The script above doesn’t use any Templater-specific functions, it will work with any plugin that can execute Javascript.

2 Likes

I have a separate template I run to create new notes and insert the link. It creates notes with my pre-filled default content for particular note types and/or creates the note in the right folder. I detailed it here:

It would be a simple matter to add the functionality of “If someone ran the New Note script and already had text selected, then use that as the alias for the new note”.

1 Like

I decided on this:

const noteName = alias.charAt(0).toUpperCase() + alias.slice(1).toLowerCase();

True. I did get rid of some plugins with overlapping functions myself.

So I’m going to use editor.replaceSelection all the time now. Fantastic.


Ta very much in the name of all those who partake. :slight_smile:

3 Likes