Is it possible to add a block ref in Markdown format " [Refferenced-Block-Title]( ((Block-Ref)) ) " by JavaScript Macro?

I would like to have the Block Ref functionality of Logseq a bit differently, namely, when I do the Paste of the Block Ref, Logseq to create a Markdown Link for me instead of just the ((block-ref)).

I am wondering if it’s possible to use js so that I could have a macro like this:

{{ref ((656455c3-18f1-4d8c-b1ca-165ba05bdb72)) )

and it would turn it into:

[<Title of the referred block (or max -ex- 100 characters>]( ((656455c3-18f1-4d8c-b1ca-165ba05bdb72)) )

Thanks.

Can use the following kit:

  • Add a macro inside file config.edn , inside macros{} :
    :ref "<span class='kit' data-kit='blockref' data-ref='$1'>[ref]( $1 )</span>"
    
  • Create a page with the kit’s name
    • in this case BlockRef
  • Put inside a javascript code-block with this code:
    logseq.kits.setStatic(function blockref(span){
        const uuid = span.dataset.ref.slice(2, -2)
        const block = logseq.api.get_block(uuid)
        if (!block) return
    
        var title = block.content.slice(0, 64)
        const index = title.indexOf("\n")
        if (index > -1) title = title.slice(0, index)
    
        const blockId = span.closest(".ls-block").getAttribute("blockid")
        const sourceContent = logseq.api.get_block(blockId).content
        const ref = span.dataset.ref
        const target = "{{ref " + ref + "}}"
        const markdown = "[" + title + "]( " + ref + " )"
        logseq.api.update_block(blockId, sourceContent.replace(target, markdown))
    })
    
  • Now when typing something like {{ref pasted-ref}}
    • one space only
    • check target in the code
  • If the ref’s block is found, it will be replaced with [block-title]( pasted-ref )
    • check markdown in the code
    • for the desired title’s maximum length, adjust number 64 in the code
2 Likes

Thank you @mentaloid, it works great. I actually ended up using:

const markdown = title + "[Link]( " + ref + " )"

instead of

const markdown = "[" + title + "]( " + ref + " )"

so I can retain Title formatting which is not possible inside a Link.

Question: I assume it’s not possible to send the javascript function a parameter from the macro, say the length I need so I can maybe call it with {{ref <pasted-ref> 1000}} if I want to have the full length of the block title…

  • It is possible to pass multiple parameters, if you define them:
    • find part data-ref='$1'
    • add desired parameters, e.g. data-ref='$1' data-length='$2'
    • call it with comma in between parameters, e.g. {{ref <pasted-ref>, 1000}}
  • Using a passed parameter is more complicated:
    • read it:
      • find first-usage line, e.g. var title = block.content.slice(0, 64)
      • above that line:
        • put the passed parameter into a variable, e.g. const length = span.dataset.length
        • check if it is passed, e.g. const hasLength = (length !== '$2')
      • fix the usage line for both passed and default value, e.g. slice(0, (hasLength) ? length : 64)
    • replace it if passed:
      • find line const target = "{{ref " + ref + "}}"
      • change it to const target = "{{ref " + ref + (hasLength ? ", " + length : "") + "}}"

Ok, i have implemented the changes and it works, but I would need it to return the full block title if no $2 parameter is specified and $2 characters from the title if it is specified in the function’s call from the macro.

I can’t wrap my head around the code to write it myself.

Ok, I ended up with this:

logseq.kits.setStatic(function blockref(span){
    const uuid = span.dataset.ref.slice(2, -2)
    const block = logseq.api.get_block(uuid)
    if (!block) return

  	const length = span.dataset.length
    const hasLength = length !== 'undefined' && length !== '$2';
    var title = block.content.slice(0, (hasLength) ? length : block.content.length);
    const index = title.indexOf("\n")
    if (index > -1) title = title.slice(0, index)

    const blockId = span.closest(".ls-block").getAttribute("blockid")
    const sourceContent = logseq.api.get_block(blockId).content
    const ref = span.dataset.ref
    const target = "{{ref " + ref + (hasLength ? ", " + length : "") + "}}"
    const markdown = title + "[Link]( " + ref + " )"
    logseq.api.update_block(blockId, sourceContent.replace(target, markdown))
})

… seems to work … :-/

1 Like

If

const block = logseq.api.get_block(uuid)

returns the whole block text, is there an API call that can return the filename on disk the block is found in?

Can return the name of the page the block is in (from which the filename is formed) with this call: logseq.api.get_page(block.page.id).originalName

Hmm, you are right and it works as expected but I assumed I would get the name of the file on disk. Right now I am getting:

[Link]( ((8060aba0-f9a3-49b3-863e-c72125044feb)) "Friday, 01.12.2023")

while, in fact, I need to get:

[Link]( ((8060aba0-f9a3-49b3-863e-c72125044feb)) "2023_12_01.md")

I assume this would require another javascript function just to translate the current preferred date format into the format Logseq names the journal files on disk …

Generally the filesystem is hidden/protected (remember Can we restrict Simple Queries for Blocks to Pages only?). To reveal it, should look at drastic solutions (e.g. File explorer from within Logseq, which you haven’t attempted so far).

I am needing this as a MetaData only so just translating some internal data (like the mentioned (block.page.id).originalName) on the actial creation date of the containing file so I can “re-compose” the actual journal filename would suffice for me.

But I agree this could potentially be too complex. If the containing file is a Logseq Page (as in /pages/*) it would get the correct name I suppose, but for journal files (the ones in /journals/*) I would need to process the current page name (or maybe the actual existing block metadata of the creation of the file) and extract from it the day, month, year and recompose it into yyyy_mm_dd.md.

ok, got it working for both pages and journals. Although journals naming is configurable from config.edn and I could have make them match, I like the files naming to contain no spaces, commas or dots and the Logseq Page Name should be something easy on the eyes, like “Saturday, 02.12.2023”.

So, based on my specific situation, where the filename for journals on disk is 2023_12_03.md, I needed to split the Logseq Page Name by the space, split by dot the remaining string, reverse it and replace dots with underscores and add an .md termination. That is for filenames that contain a date so they mostlikely are journal files while page files just need the extension added to the Page Name string.

I ended up with this code:

const inputString = logseq.api.get_page(block.page.id).originalName
const pageName = inputString.split(" ")[1].match(/^\d{2}\.\d{2}\.\d{4}$/) ? inputString.split(" ")[1].split(".").reverse().join("_") + ".md" : inputString + ".md";

Now I get markdown Links in the desired form:

# for journals:
[Link](((6568d3a4-6874-4782-a70c-3f42de833d03)) "2023_11_25.md")
# for pages:
[Link](((656a5e14-afe9-4032-92af-287da1ceb7bd)) "ScratchPad.md")

I’ll put the whole code here so this thread feels finished:

logseq.kits.setStatic(function blockref(span){
    const uuid = span.dataset.ref.slice(2, -2)
    const block = logseq.api.get_block(uuid)
    const inputString = logseq.api.get_page(block.page.id).originalName
    const pageName = inputString.split(" ")[1].match(/^\d{2}\.\d{2}\.\d{4}$/) ? inputString.split(" ")[1].split(".").reverse().join("_") + ".md" : inputString + ".md";
    if (!block) return

  	const length = span.dataset.length
    const hasLength = length !== 'undefined' && length !== '$2';
    var title = block.content.slice(0, (hasLength) ? length : block.content.length);
    const index = title.indexOf("\n")
    if (index > -1) title = title.slice(0, index)

    const blockId = span.closest(".ls-block").getAttribute("blockid")
    const sourceContent = logseq.api.get_block(blockId).content
    const ref = span.dataset.ref
    const target = "{{ref" + ref + (hasLength ? ", " + length : "") + " }}"
    const markdown = title + "[Link]( " + ref + " \"" + pageName + "\")"
    logseq.api.update_block(blockId, sourceContent.replace(target, markdown))
})

I’m pretty much done with this one, thanks for the help.

1 Like

Not done yet, it seems :slight_smile: … I need to solve the “one space only” issue and I seem to be unable to reliably solve by myself:
I need this line:

const target = "{{ref " + ref + (hasLength ? ", " + length : "") + "}}"

to somehow allow no space or one space before closing double curly brackets:

This is because the SEARCH mechanism in Logseq will introduce a space after the found BlockRef-ID, while, if the BlockRef-ID is in the clipboard, invoking the macro with {{}} will have me always enter an extra space after the ID if I alter the const line to allow for the extra space:

const target = "{{ref " + ref + (hasLength ? ", " + length : "") + " }}"

Update Edit: solved it at last :hot_face::

  • so instead of:
const target = "{{ref " + ref + (hasLength ? ", " + length : "") + "}}"
  • I have:
const target = "{{ref" + sourceContent.split("{{ref")[1].split("}}")[0] + "}}"

, so I don’t “construct” the “target”, rather I extract it from the larger string by using the “{{ref” and “}}” as .split delimiters.

PS: I am sorry I kinda polluted the post with Javascript rookie questions but this function to have every Logseq BlockRef-ID as a correct syntax of Markdown Link was a request for me since one year ago when I started with Logseq and was seeing criptic IDs in my journal markdown files with no idea of what those were about. Having the Title (as in [Title](Link "Attribute") markdown syntax) would at least give a hint of what that “link” was about.