Inline properties and reuse of property values, basic implementation

  • Inspiration
  • Usage
    • This uses a macro with the following syntax: {{p property, value}}
      • Should not use comma (,) anywhere else.
        • Though generally the comma can be omitted: {{p property value}}
      • Omitting the value reuses the existing one: {{p property}}
    • Typing something like this (check customization below):
      {{p title "The Gray Picture"}} is a {{p type phenomenon}} discovered by {{p scientist [[Person1]]}}, {{p scientist [[Person2]]}} and {{p scientist [[Person3]]}}. {{p title}} is very rare.

      Should produce this:

      Notice the last macro {{p title}} , which reuses the earlier value.
  • Preparation
    • Add a macro inside file config.edn , inside macros{} :
      :p "<span class='kit' data-kit='inlineprop' data-prop='$1' data-val='$2'>$1:: $2</span>"
    • The code below requires having kits inside file custom.js .
    • Inside page InlineProp in Logseq, put the following code in a javascript code-block:
      logseq.kits.setStatic(function inlineprop(span){
          const dataset = span.dataset
          var propname = dataset.prop
          if (propname === "$1") propname = ""
          var val = dataset.val
          var noVal = (val === "$2")
          const blockId = span.closest(".ls-block").getAttribute("blockid")
          const block = logseq.api.get_block(blockId)
          const props = block.propertiesTextValues
          var old = (props && props[propname])
          if (noVal && old === undefined) {
              const index = propname.indexOf(" ")
              if (index > -1) {
                  val = propname.slice(index + 1)
                  noVal = (val === "")
                  propname = propname.slice(0, index)
                  old = (props && props[propname])
          const linkTarget = dataset.linkTarget
          const isLink = val.startsWith("[[") && val.endsWith("]]")
          const needsLink = !noVal && (isLink || === "force")
          const plain = (isLink) ? val.slice(2, -2) : (noVal) ? old : val
          const fixval = (noVal) ? "" : val.replaceAll("[", "\\[").replaceAll("]", "\\]")
          const target = new RegExp("\\{\\{p\\s*" + propname + "\\s*,?\\s*" +
              fixval + "\\s*\\}\\}", "gimu")
          const text = (needsLink && linkTarget !== "prop") ? "[[" + plain + "]]" : plain
          logseq.api.update_block(blockId, block.content.replace(target, text))
          if (noVal || !propname || !plain) return
          const proptext = (needsLink && linkTarget !== "text") ? "[[" + plain + "]]" : plain
          if (old && old.split(", ").includes(proptext)) return
          logseq.api.upsert_block_property(blockId, propname, (old) ?
              old + ", " + proptext : proptext)
  • Customization of values in macro’s definition
    • Default behavior:
      • typing value produces:
        • block text: value
        • property value: value
      • typing [[value]] produces:
        • block text: [[value]]
        • property value: [[value]]
    • With attribute data-link-target='prop':
      • typing [[value]] produces:
        • block text: value
        • property value: [[value]]
    • With attribute data-link-target='text':
      • typing [[value]] produces:
        • block text: [[value]]
        • property value: value
    • With both attributes data-link='force' and data-link-target='prop':
      • typing either value or [[value]] produces:
        • block text: value
        • property value: [[value]]
    • With both attributes data-link='force' and data-link-target='text':
      • typing either value or [[value]] produces:
        • block text: [[value]]
        • property value: value
    • With attribute data-link='force' alone (meaning both):
      • typing either value or [[value]] produces:
        • block text: [[value]]
        • property value: [[value]]

@mentaloid, hello!

I’ve just created similar showcase with 🏛 Full House Templates plugin:

2023-12-01 01.13.08

Setup instructions are here.


This is awesome, after so much discussion about this need that Logseq implements this at the core level and support this natively, I am so enthusiastic about work that is being don at least in the direction of having this functionality via custom.js because it can target very specific needs and implement them with minimal code. I am tight on time these days but will definitely try it and give feedback.

It only now occurred to me that a nice-to-have, in addition to the functionality to auto-fill some properties, is to be able to maintain some sort of sync between the two. I would imagine it as a function that, when you enter Edit Mode, reads all properties and looks them up in the Title+Body of the Block and overlays some grayed out hints of the initial {{p <found match>}} and, if you modify the text inside, it updates the property as well.

This would be especially useful for those writing evergreen notes that often get updated. Maybe difficult to implement without a plugin… dunno, it’s just a “I-wish-that-was-available” idea.

  • No kit or plugin can cover such specialized requirements.
    • You need to code it yourself, whether in Javascript or in Synthesis.
  • Furthermore, I doubt that stuffing a single block with all that info is a good idea. Should rather use:
    • one block (or more) for inserting/editing/updating and holding the info (properties etc.)
    • another block (could be the parent) only for synthesizing and rendering that info

The block will behave just like you described atop of this post and, after Editing Mode is done, will retain the changes in both text and properties so not much stuffing in a single block. I was envisioning the “observer code” to be a part of the Edit Mode that can assist with it (checking the properties’ values inside the text and monitoring those for a change, then updating the other one depending on which one is modified by the user)…

But I reckon it’s a quite distant vision :slight_smile:

I have tested the code above (long overdue) but I get a weird one: I don’t get to type much after I do {{p abc... and it already parses and executes the code. Many times i get “undefined” as the result word in the block body due to this. Is there a way to “delay” executing the code so I can get to write the whole property and value?

You have reported this in other threads too, but nobody else has. Kits in particular don’t execute any code until a macro has finished rendering, thus never during typing. Therefore, something else causes it. As a guess, it may be caused by some customization that gets triggered every time a block’s content changes.

Ok, i will check custom.js for potential culprits. Thanks.

I have an issue with a particular situation:

this happened on {{p datetime, /today @ /current time}}

If I write such a block it will look like the following when I exit the Edit Mode and no property is created:

this happened on {{p datetime, [[Sunday, 05.05.2024]] @ 18:24 }}

I can achieve property creation if I enclose the “value” in quotes, but the property would be quoted:

this happened on "[[Sunday, 05.05.2024]] @ 18:24"
datetime:: "[[Sunday, 05.05.2024]] @ 18:24"

Is there a way to have the datetime property created without using the quotes?
I reckon that I do not understand how to use data-link and data-link-target or if they can help with this situation…

  • Macros don’t support some special characters.
    • That includes brackets and commas.
  • Attributes data-link and data-link-target are for adding brackets automatically.
    • But they do it to the whole value, not to part of it.
      • In theory, your property datetime:: should break into two properties:
        • date::
        • time::
  • I don’t see any easy workaround.
    • Should probably create a dedicated {{datetime}} macro.
      • That way would also avoid typing /today /current time
      • Synthesis allows for {{evalonce date and time}} (or a shorter form)
        • Adding the property is not yet covered in the tutorials.

In this particular situation it is not something I want to make into query-able properties, more of a “good-to-know” situation. In general you are right.

I believe it would be useful to allow any characters after the first comma and consider it the value of the property without needing " ". Dunno if it’s possible programmatically though…

I have noticed that some properties get their value replaced while using {{p property, value}} while others get the value in the inline call added to the existing value, with a comma after the last value.

While I found it weird I was actually looking for replacing a status:: property with the “new” value from the “inline” call so I made some experiments to see what’s what (I noticed before that for my due-by:: property, calling it from the inline macro would replace the date in the value with the new date while the status:: property got the new status added to the list).

I learned that property names that have a dash (“-”) in their name get the value replaced while the properties that are named without any dash get their property added to the list.

While I very much like the idea that for some properties the value gets added while for other it gets replaced, it might not be an intended feature…

Even if it’s not intended I’d like to keep the feature as it’s helpful, albeit modifying the names of the properties to benefit from it is a quirky way of doing it.

One other feature that would be mostly helpful for me is to be able to modify a property from the inline editing of the block but its value to be hidden from (not written to) the body of the block. This makes sense when you want to write everything down without going with arrow keys or with the mouse cursor at the right property and remove and replace the value (it saves time to just write it while you’re at it but it would also make no sense to have that value appear in the text). Could this feature be added to your “inline properties” module?


  • The strange behavior you describe may have to do with passing a value with comma in it.
    • But I cannot tell, unless you provide an actual block to test.
    • The intended behavior of this macro is to auto-generate the properties the first time of writing the block’s content, not to update them afterwards.
  • As stated both in the title and here:
    • Therefore, I don’t see any of your advanced features getting implemented by this macro.
    • For advanced functionality, should look at Synthesis.
      • The respective tutorial is not yet prepared.

Ok, here you go: a usecase for inline properties

worked-on:: {{today}} 
* →

My workflow for introducing data in the journal is via Custom Commands, leaving the cursor at the most important section. In the above case, the cursor would be placed right after “#To” so I can add right away the (Action) Tag and Description of the Task: #ToCall John and invite him and wife for dinner ...

By using “inline properties” I would modify the Custom Command to be:

🔳{{p status, #To }}
worked-on:: {{today}} 
* →

and place the cursor after #To like before so I can update right away the status property, then hit Fn+Right (on my keyboard) to skip the closing curly brackets and go on with writing the task content:

#ToCall }} John by {{p due-by, /date }} and invite him and wife for dinner to discuss the {{p context, [[trip]]}} this summer

The block would become:

🔳 #ToCall John by [[Friday, 10.05.2024]] and invite him and wife for dinner to discuss the [[trip]] this summer
status:: #ToCall
due-by::  [[Friday, 10.05.2024]]
worked-on:: [[Thursday, 09.05.2024]]
context:: [[trip]]
* →

Now after I would have called John I would come back and replace “:white_square_button:” (by double-clicking on it so it gets selected and then calling another Custom Command - <.) with :ballot_box_with_check:. The “Checked” Custom Command would look like:

{{p status, #Done}} ☑️

This is where it gets crooked. Now, the status property would be status:: #ToCall, #Done and I only want it to be #Done. So, having noticed that the due-by value gets replaced every time if it has been there before, I changed the status property name to current-status -so it gets a dash- and presto, now the final status will look like: status:: #Done .

But because the {{p }} macro didn’t just update the property but also wrote the value to the Block Title, The Block will look now like this:

#Done ☑️ #ToCall John by [[Friday, 10.05.2024]] and invite him and wife for dinner to discuss the [[trip]] this summer
status:: #Done
due-by::  [[Friday, 10.05.2024]]
worked-on:: [[Thursday, 09.05.2024]]
context:: [[trip]]
* →

Or, I don’t want to see the #Done hashtag in the Block’s Title line. If the code would allow a parameter to prevent writing anything to the block then I could also add, at the end of the Title some other properties:

[...] to discuss the {{p context, [[trip]]}} this summer {{p priority, Urgent, 0}}

, where the third variable ($3), “0”, means no text in the block’s body.

I don’t know how can this be achieved best, maybe a different macro altogether. And I know very little from my usage has been this kit’s purpose, but it works for me and I am ok with it (if only there would be a way to also have the option to write nothing to the block’s text while updating/creating the property);

  • It turns out that internally:
    • due-by gets converted to dueBy
    • current-status gets converted to currentStatus
    • etc.
  • This conversion doesn’t seem justified, but results in the noticed behavior.
  • It is better to use “a different macro altogether” for each scenario:
    • Could either use a different kit or pass the extra info as data-attributes (like data-link).
    • Instead of typing: {{p priority, Urgent, 0}}
      • to type e.g.: {{p0 priority, Urgent}}
    • Instead of replacing the macro with a value: block.content.replace(target, text)
      • to replace it with an empty string: block.content.replace(target, "")
    • Instead of appending to the old value: (old) ? old + ", " + proptext : proptext
      • to ignore the old value with plain: proptext
1 Like