Make custom macros that replace themselves with their value on first run

Create a relatively simple kit like this:

  • Add kits to file custom.js if not already there.
  • Pick names:
    • a name for the kit
      • in this example expandmacro
    • a name for each special macro (i.e. a macro that runs only the first time)
      • in this example mymacro
  • Use carefully the picked names to define each special macro inside macros{} of file config.edn
    • wrap the normal output of each macro within the div of the kit
    • in this example:
:mymacro "<div class='kit' data-kit='expandmacro'>use $1 here</div>"
  • Create a page with the kit’s name
    • in this example ExpandMacro
  • Put inside a javascript code-block with this code:
logseq.kits.setStatic(function expandmacro(div){
    const blockId = div.closest(".ls-block").getAttribute("blockid");
    const content = logseq.api.get_block(blockId).content;
    const macroStart = content.indexOf("{{" + div.closest(".macro").dataset.macroName);
    const macroEnd = content.indexOf("}}", macroStart) + 2;
    logseq.api.update_block(blockId, content.slice(0, macroStart) + div.innerHTML + content.slice(macroEnd));
});

Now whenever typing a special macro (e.g.: {{mymacro something}} ), it should get replaced (e.g. with use something here ).

Consider posting here your case of using this functionality, to inspire other users.

Hi,

I created custom.js and placed the js code in it as you instructed (" Put the following code in file custom.js (create inside folder logseq if missing):"). I haven’t bothered with the custom.css or the macro buttons because I will not use them, at least no plans atm.

I have reloaded Logseq and I haven’t seen any “kits ok” mssage. I see in the js code that it is the last command in the code and should appear when custom.js is loaded by logseq? Where should I look for it?

I can tell something didn’t work because I have created a macro:

 :time "<div class='kit' data-kit='expandmacro'> <%time%></div>"

which doesn’t get replaced with the time at the calling moment, but it always updates when i click that line and it enters edit mode and I leave edit mode (basically refreshing the macro);

What am I doing wrong? :-/

PS: I tried the example in your kits link:
image

I am not sure why nothing happens. This might be the same reason the macro isn’t replacing itself with the text …

You are the first user reporting an issue with kits. Here are some steps:

  • Ensure that custom.js is allowed to run.
  • Ensure that you have copied the whole code inside custom.js
    • Indeed css and other macros are optional.
      • You may want to temporarily add these too, to help with investigation.
  • But if you don’t get “kits ok” when Logseq starts, nothing is expected to work.
    • This is a pop-up at the top-right of the screen.
    • Indeed this is triggered by the last command.
    • To investigate why it doesn’t get executed:
      • open the console with Ctrl+ Shift + i
      • check for any errors there
        • preferably right after starting Logseq, to have a clean console
      • report back with any findings

Can this be because I have unpacked the exe to extract the insides so that I can use Logseq as a portable app without being forced to install it on a computer? I am using, basically, "Logseq-win-x64-0.8.9\Logseq-0.8.9-full.nupkg\lib\net45" so I don’t know what other tools am I missing that the .js would rely on to function as expected …
However, I don’t think there is anything else in the exe file that the .js functionality would depend on that isn’t in the net45 folder I extracted on a thumbdrive.
I see the following in Dev Tools > Console:

  • That message doesn’t seem relevant.
  • I tried to recreate your setup.
    • Various things are broken, it is like a minefield.
    • Yes, this is the cause of your troubles.
  • Here are a few things to try:
    • Update to a newer version of the exe.
    • For the general mess, in file custom.js replace this line:
      const content = block.content;
      
      with this line:
      const content = block.content.replace(/\n?.*::.*\n?/g, "\n").trim();
      
    • For the specific kit, in the page with the kit’s name (e.g. ExpandMacro), replace the code with the following:
      logseq.kits.setStatic(function expandmacro(div){
          const blockId = div.closest(".ls-block").getAttribute("blockid");
          const content = logseq.api.get_block(blockId).content;
          const divMacro = div.closest(".macro");
          const macroName = (divMacro) ? divMacro.dataset.macroName : (div.dataset.name || "");
          const macroStart = content.indexOf("{{" + macroName);
          const macroEnd = content.indexOf("}}", macroStart) + 2;
          logseq.api.update_block(blockId, content.slice(0, macroStart) + div.innerHTML + content.slice(macroEnd));
      });
      
    • Avoid multiple macros in the same block. Otherwise, try providing the name to the macro’s definition:
      :mymacro "<div class='kit' data-kit='expandmacro' data-name='mymacro'>use $1 here</div>"
      

Thanks @mentaloid for sticking with this. If Logseq would provide a PortableApp I wouldn’t have to do stuff like this (it’s quie annoying to do this every update - i have updated 0.9.17 to 0.9.17 on this ocasion ).

The good:

  • I now see the “kits ok” message at Logseq startup;

The bad:

  • I have only two macros in my config.edn:
 :macros {
 :page-date "<% current page %>"
 :time "<div class='kit' data-kit='expandmacro'> <%time%></div>"
 }

, but when I trigger the Macro with:

- time is {{time}}

It is not replaced with the time but rather functions the old way (same as :time "<%time%>" macro would), by updating this time every time i refresh (click on it to see the “{{time}}” text) :face_with_diagonal_mouth:
image

Edit: it looks like the

<div class='kit' data-kit='expandmacro'> <%time%></div>

definition isn’t applied somehow, maybe the expandmacro function in expandmacro.md is not reached/applied… (is there a js command I can add to this function to show something on screen or whatever just so I can see if it’s actually run? - I tried with adding console.log("Expanding macro: " + macroName); to the function but I get nothing at the Logseq Dev Tools Console…)

  • Now that kits are ok, you can use {{togglemsg}} to get a button for toggling some potentially useful messages.
    • Enabling those messages in your case, should probably say that expandmacro is missing, because its codeblock was not found.
      image
    • So it falls back to the old/repeating behavior.
  • Ensure that Logseq page ExpandMacro is present and contains the needed codeblock.
    • If the page seems empty, re-indexing may be needed.
  • You can further debug in the console, checking that logseq.kits contains logseq.kits.expandmacro

yeah, that is what I get:

The weird thing is that I have the markdown file under /pages (tried even copy/pasting it into the /logseq folder and I get a warning that I already have a page with that name so Logseq clearly finds/sees it). It contains your updated function:

logseq.kits.setStatic(function expandmacro(div){
    const blockId = div.closest(".ls-block").getAttribute("blockid");
    const content = logseq.api.get_block(blockId).content;
    const divMacro = div.closest(".macro");
    const macroName = (divMacro) ? divMacro.dataset.macroName : (div.dataset.name || "");
    const macroStart = content.indexOf("{{" + macroName);
    const macroEnd = content.indexOf("}}", macroStart) + 2;
    logseq.api.update_block(blockId, content.slice(0, macroStart) + div.innerHTML + content.slice(macroEnd));
});

I see nothing in the Dev Console when I click-in/out of the macro line.

Where should I see “that logseq.kits contains logseq.kits.expandmacro” ?

Hmm… copied that function inside custom.js and all works ok :face_with_diagonal_mouth:

It somehow finds the /pages/expandmacro.md because, if I remove that file, I get a different first error message: instead of ran 0 codeblock(s) I get page not found:

ok, I created the expandmacro.md outside Logseq and had the dash for the block missing. I opened the page from Logseq and it added the dash (created the block containing the function) but I am sill having the same error.

This is what I get after I open the expandmacro page in logseq and I switch to the scratchpad page where I run my tests:

EDIT:

Ok, I solved the mystery :man_facepalming:

I omitted using the code-block for the javascript function… :roll_eyes:
Once I added it (without the “\” obviously):

\```javascript
logseq.kits.setStatic(function expandmacro(div){
    const blockId = div.closest(".ls-block").getAttribute("blockid");
    const content = logseq.api.get_block(blockId).content;
    const divMacro = div.closest(".macro");
    const macroName = (divMacro) ? divMacro.dataset.macroName : (div.dataset.name || "");
    const macroStart = content.indexOf("{{" + macroName);
    const macroEnd = content.indexOf("}}", macroStart) + 2;
    logseq.api.update_block(blockId, content.slice(0, macroStart) + div.innerHTML + content.slice(macroEnd));
});
\```

It all works as expected.

Thanks for sticking with me. :bowing_man:

This is exactly what I was thinking, to have macros that are not dynamic but static -replacing themselves at runtime is something Logseq Maros shoudl have as a parameter or something. I wonder why nobody has posted any use cases … :thinking:

Man, it’s no easy task to get uploads less than 4MB in here. I wanted for a quite some long time to showcase my need for macros that replace themselves, so here is my use case:

  • Logseq Dynamic Variables are available in Templates, Queries and Macros. They are not available to Custom Commands nor they are available to Journal Blocks/Pages. I wanted to make them available to daily usage in Blocks as well as in Block Templates created with Custom Commands;
  • Macros are the obvious solution to be able to insert Dynamic Variables in Blocks. But there is one fat problem. Macros are Dynamic themselves and they update every time the page is loaded. So I was stuck until this solution -luckily- became available; :bowing_man: @Mentaloid
  • Now I can have Macros that “replace themselves with their result”. Awesome. So I created a few Macros that access Dynamic Variables:
 :macros {
 ;; make Dynamic Variables accessible by running [custom macros that replace themselves with their value on first run](https://discuss.logseq.com/t/make-custom-macros-that-replace-themselves-with-their-value-on-first-run/20967/1)  
 :current-page "<div class='kit' data-kit='expandmacro'><% current page %></div>"
 :time "<div class='kit' data-kit='expandmacro'><%time%></div>"
 :today "<div class='kit' data-kit='expandmacro'><%today%></div>"
}
  • Finally, I added the folowing Macro to be able to access the natural language syntax for dates, ex: <%next Tuesday%>:
 :var "<div class='kit' data-kit='expandmacro'><%$1%></div>"
  • I can write, then:
{{var next tuesday}}

and it gets replaced with the appropriate date just as I would do by using the /date Command and then selecting, with mouse, the day I am interested in.

Here is a screen recording showcasing it (you might want to right click > “open image in new Tab” for larger viewpoint)…

ConEmu_n4Zv6SJXeU-compressed

Now, to be able to pre-fill some properties in Block Templates (yes, these are Templates for a single Block that I can easily access by typing “<template_name” instead of going the route → “/templ”{ENTER} + Template_Name{Enter}, which would ruin my Quick Capture experience):

ConEmu_k9arEftMrH-compressed

2 Likes

What’s the advantage of this compared to dynamic variables in templates?

I use a lot of Single-Block-“Templates” for every input i do in Logseq. I rarely do multi-block templates (the daily journal template is one of those, for example). I totally dislike to have to use /template {Enter}, then template name {Enter} and would rather do <template-name{Enter}. I also design my “single-block-teplates” (actually Custom Commands) to leave the cursor exactly where I feel it’s the most logical for quick capture of data (something Templates can’t do). And, because Custom Commands can’t use Dynamic Variables, I had to somehow make them work via Macros. But Normal Macros won’t do, as they are dynamic and change their result at every refresh. So @mentaloid 's solution is gold to my specific use case.

2 Likes

I’ve been looking for this since literally today and found today. This seems like it should become an official feature of Logseq. Thanks for the discussion here, very clear.

My usage is just writing complex templated, long winded latex equations for category theory. You can use the Katex macros but it requires custom.js. Going this far I found this post, which only required a little more and made the argument clear how it could be done. I just did this.

:var "<div class='kit' data-kit='expandmacro'>\u0024\u0024 $1 \u0024\u0024</div>"

Thank again guys.

1 Like

@mentaloid Is there a way to circumvent macros not being parsed inside Named Links?

If I do Ctrl+L I get “[]()” and the cursor is placed inside the square brackets, where I write my name for the link, then I go in between the round brackets and I call my macro “{{var next monday}}”. When I exit the Edit Mode for the Block I get nothing, the “{{…}}” is not parsed.

PS: I would extensively use this when writing days of week in my language: [Luni]([[Monday, 01.04.2024]]) but it’s impossible atm. I was wondering if you can see a solution for that by using Macros (it can be done by doing [Luni]( /date) and picking a date from the popup )…

  • No, nested calls are parsed as plain text, thus not called.
  • To achieve what you want, should have the link produced by the macro, by either:
    • performing all string manipulations inside Javascript’s codeblock
    • using Synthesis’ macro evalonce

Does evalonce replace expandmacro?

…ok I have read Tutorial: Initial steps and ended up here:Synthesis lab - Natural-language programming … reading

Super intrigued and holding my breath when reading:

Synthesis is (among other things):

a game-changer, that explodes the possibilities.
especially for simple users
“forget” datalog, clojurescript, javascript, VBA etc.

…and on:Comparison of Synthesis to other programming languages

The programming language that comes closer to Synthesis is pseudocode.

  • not well-defined, but Synthesis can resemble most of its variations

and

Is that relevant with AI around?

Short answer: AI makes Synthesis even more relevant.
Long answer: [Comparison of Synthesis to modern AI] (topic under construction)

It’s quite late here … can’t wait to learn more about this… especially if there are any practical examples around …

  • expandmacro is a specialized kit
    • has its own Logseq page
    • does a single job
    • its macro accepts a single parameter
    • can be customized only through javascript
  • evalonce is one of Synthesis’ macros
    • supports unlimited complexity
      • accepts nested natural language expressions
      • calls external definitions recursively
    • Synthesis is a general purpose kit
      • it is made up of multiple Logseq pages

I understand that and like the concept very mch. I was simply asking if configuring Synthesis would render expandmacro superseeded and I should use further evalonce instead of it, as it does the same thing and more.

Regarding Synthesis, can we maybe have a new thread under Synthesis - Logseq to gather practical usecases so it’s easier to grasp for people like me (non-programmers)?