Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecation of dynamic commands #913

Open
6 tasks
AB1908 opened this issue Nov 11, 2022 · 51 comments
Open
6 tasks

Deprecation of dynamic commands #913

AB1908 opened this issue Nov 11, 2022 · 51 comments
Labels
enhancement New feature or request

Comments

@AB1908
Copy link
Collaborator

AB1908 commented Nov 11, 2022

Let's use this issue to discuss and track this. We'll need to create a migration guide and make info accessible. Here's a small task list I think we can start with:

  • Modal to indicate upcoming deprecation
  • Bold description at top of readme
  • Shoutout on Eleanor's roundup
  • Pages in docs
  • Migration guide itself
  • Notice if dynamic commands found in note?

I think this should cover most of our bases and drive awareness. The second part is actually compiling the ways other plugins help with this and the common use cases. We'll have to deal with edge cases by hand I imagine.

@AB1908 AB1908 added the enhancement New feature or request label Nov 11, 2022
@dleeftink
Copy link

I am using dynamic (javascript) commands quite extensively in combination with the Advanced URI plugin, to add notes from any browser environment > run an initial templater file in reading mode > trigger a dynamic command in the created file for data clean-up and moving the file to a folder based on metadata.

So for me, depreciating dynamic commands would be a workflow breaker.

@AB1908
Copy link
Collaborator Author

AB1908 commented Nov 11, 2022

Can you show me your template? I have a feeling we're talking about different things.

@dleeftink
Copy link

dleeftink commented Nov 11, 2022

Alright so this is a bit complex but here goes:

Bookmarklet

To my chrome bookmark bar, I've added a javascript:(function(){ ... })() bookmarklet that is synced across my devices. Upon triggering, it imports some npm packages (YAML, Citejs) and prepares an Advanced URI url command inside a plaintext payload.

Stepwise:

  1. The payload is prepared in a bookmarklet and send to Obsidian via Advanced URI (via the data={payload} and mode=append URL params)
  2. A new note is created in Obsidian from the payload using Advanced URI's
  3. The new note is opened in reading mode (via the viewmode=preview URL param)

In essence, the bookmarklet triggers and Advanced URI like so:

obsidian://advanced-uri?vault=lib&filepath=cite/${authorName}&data=${payload}&mode=append&viewmode=preview

with the ${payload} literal looking like this:

---
anno: ${add(inp.issued['date-parts'][0][0])}`
type: ${add(inp.type)}
cite: ${add(inp.id)}
full: ${add(apa.trim())}
item: ${add(inp.title)}
from: ${add(inp.author)}
 ---

### 

 <%+* window.location = "obsidian://advanced-uri?vault=lib&commandid=templater-obsidian%3Areplace-in-file-templater" %>
 <%_ tp.file.include("[[citation]]") %>`
 
~~~ bibx
${inp._graph[0].data}
~~~

Template file [[citation]]

As you can see, an Advanced URI is included in the payload as well! This is immediately triggered when the note opens in reading view, but only after another templater file (citation.md) is included on file creation.

  • This allows me to decouple the 'bookmarklet' stage from the 'templater formatting' stage, as the [[citation.md]] file can refer to all the metadata added in the first stage using tp.frontmatter, bypassing the caching problem as templater cannot refer to the frontmatter when inserted using Advanced URI
  • At least, templater could not access the frontmatter data in the past upon file creation, as a note needed to be 'saved' and indexed first before the frontmatter data was available, hence why the second Advanced URI is triggered a second time to replace the templater commands included from [[citation.md]]).

For completeness, the actual [[citation.md]] template looks like this:

 `i` 

__

<%_* let yml = tp.frontmatter; %>

#### view
[`v`] [[ 
<%- yml.cite -%>
 ]]  
[ apa :: <% yml.full %> ]

####
__

#### keys
- ...

###
__

### <% yml.item %>

__

...

### 
__

### `t`

__

#### refs
<%+* if(tp.file.title!="citation"){ window.location = "obsidian://advanced-uri?vault=lib&commandid=templater-obsidian%253Areplace-in-file-templater"} %>

<%_* window.location = "advanced-uri?vault=lib&searchregex=%2F%3C%25%5C%2B%5C*.*%25%3E%7Cfull%3A%20.*%2Fg&replace=" _%>

As you can see, the included [[citation.md]] template can even clean up after itself by running another dynamic command at the end that triggers an Advanced URI search and replace, leaving me with a static note with all data nicely formatted without leaving any trace of the dynamic templater commands.

@AB1908
Copy link
Collaborator Author

AB1908 commented Nov 11, 2022

You're talking specifically about execution commands and not dynamic commands as defined in the docs. These are indicated by <%+. These are the ones that need to be deprecated since they're fairly confusing to use, don't gel well with templater's mental model, and frankly are better done by other plugins. Sorry for not clarifying that earlier.

@dleeftink
Copy link

So the <%+* ... %> formats remains in use? I.e. dynamic execution/javascript templates?

@AB1908
Copy link
Collaborator Author

AB1908 commented Nov 11, 2022

Minor correction, <%* but essentially yes. You needn't worry for this one. I guess this brings up the point that we need to communicate who will be affected.

@dleeftink
Copy link

This removal of the 'dynamic' evaluation of js would break my workflow, e.g.

<%* return 1 %> does not evaluate on reading mode while <%+* return 1 %> does. The latter is needed in my use case.

@dleeftink
Copy link

In fact, I love dynamic commands; elsewhere I use them to add per note eventListeners that detach themselves after unloading said notes. This allows custom hooks that are entirely encapsulated in the notes themselves, and forgo the need to write plugins for extending note interactivity .

@AB1908
Copy link
Collaborator Author

AB1908 commented Nov 12, 2022

That sounds more like execution commands but I'll need to think about this some more meanwhile.

@dleeftink
Copy link

If the docs are anything to go by, yes, I do mean dynamic commands, whether of the 'templater' (<%+ %>) or 'javascript/execution' (<%+* %>) variety.

For instance, in case of attaching per note hooks, I add a dynamic YAML field to each note like so:

---
cssclass: taskline spacious
notehook: <%+ tp.user.details({app,open:false}) %>
---

Which runs a user script called details.js when opening a note in reading mode, and detaches itself after the note is closed. In this case, the script checks for an option parameter called open, and opens/closes any <details> element in the note based on the provided condition (true/false).

@AB1908
Copy link
Collaborator Author

AB1908 commented Nov 12, 2022

This can be done via other means, Templater has an option for setting up hooks into obsidian events, though I haven't used it myself. Perhaps it would be sensible to move it to your own plugin given that you're one of the few folks doing this. As always, you're also free to maintain your own fork (like I do for a few other plugins).

@pauby
Copy link

pauby commented Nov 16, 2022

The second part is actually compiling the ways other plugins help with this and the common use cases.
... and frankly are better done by other plugins.

I'm fairly new to Obsidian, so my workflow is easy enough to change (as I don't really have much of one at this stage). But I appreciate I'm in the minority here.

However, I do use modification date: <%+ tp.file.last_modified_date() %> in the YAML of my notes. This has stopped working entirely and results in it being replaced by NaN. This doesn't match my understanding of 'deprecated', but would suggest 'removed' instead. I could be wrong, and it's not working for other reasons, so I'm not making judgements. It did work recently. Unsure what update stopped it.

Anyway, I'm interested in suggestions of plugins that will give similar functionality to dynamic commands?

@AB1908
Copy link
Collaborator Author

AB1908 commented Nov 16, 2022

Dataview is the main one. You can use the inline query `=this.file.mtime`

@AB1908
Copy link
Collaborator Author

AB1908 commented Nov 16, 2022

As for breakage, see #910

@pauby
Copy link

pauby commented Nov 16, 2022

Dataview is the main one. You can use the inline query `=this.file.mtime`

Doesn't appear to work in the frontmatter. But this isn't the issue to discuss that in! Thanks for pointing me in that direction. I already have dataview, but haven't used it much. Will investigate further 😄

@red-co
Copy link

red-co commented Nov 20, 2022

Will dynamic commands be replaced by new commands like writing multiple times? (It feels more convenient to import and export data)

@Zachatoo
Copy link
Collaborator

Zachatoo commented Dec 1, 2022

Will dynamic commands be replaced by new commands like writing multiple times? (It feels more convenient to import and export data)

@red-co What do you mean by writing multiple times? Regardless, should probably be a separate issue if that's a feature you're looking for, as it wouldn't be related to deprecating dynamic commands.

@muya
Copy link

muya commented Feb 23, 2023

Dataview is the main one. You can use the inline query `=this.file.mtime`

Doesn't appear to work in the frontmatter. But this isn't the issue to discuss that in! Thanks for pointing me in that direction. I already have dataview, but haven't used it much. Will investigate further 😄

@pauby By any chance, have you found a way to make the DataView query work in frontmatter as a replacement to something like:

---
modified_on: '<%+ tp.file.last_modified_date("YYYY-MM-DD HH:mm:ss Z") %>'
---

I've searched to no avail.

@divinites
Copy link

@AB1908 I am looking for the same thing. Dataview does not work in the frontmatter.

@pauby
Copy link

pauby commented Feb 24, 2023

@muya I didn't get a solution that workled using dataview. If that's what you need, the rest of this isn't for you.

I eventually transitioned to Linter which has some good options for working with YAML frontmatter including the option to add a 'modified' field that is automatically updated.

Before I found that I did find another plugin that isn't in the Obsidian Community Plugins list and all it did was add a created: and modified: frontmatter on save. It was simple and just worked but had no configuration (if you needed it). I can dig that plugin name out if you need it. Once I found Linter I moved to that.

@pauby
Copy link

pauby commented Feb 27, 2023

@muya I found the other plugin - Update time on edit.

@Dercraker
Copy link

Hello, I'm looking for a system to link my notes within a single folder. My idea was to use dynamic commands to automate the next Link. Could someone tell me how to proceed?

@Zachatoo
Copy link
Collaborator

Zachatoo commented Sep 3, 2023

Look into Dataview, it's perfect for that use case.

@Dercraker
Copy link

Look into Dataview, it's perfect for that use case.

How can dataview allow me to generate the next link? it is possible to execute script ?

@Zachatoo
Copy link
Collaborator

Zachatoo commented Sep 8, 2023

Here's the "view" code. Paste this into a .js file in a folder where you'll put all your Dataview Views. I only had to change how you're getting the contents of the current file, how to render the result, and remove the function declaration wrapping the code.

const WPM = 255
const content = await dv.io.load(dv.current().file.path) // Dataview's way to get contents of current file
const words = content.trim().replace(/[^\w\s]/g, "").split(/\s+/).filter((x) => x !== "")
const readTime = Math.ceil(words.length / WPM)
const totalHours = (readTime / 60)
const readHours = Math.floor(totalHours)
const totalMinutes = (totalHours - readHours) * 60
const readMinutes = Math.round(totalMinutes)

if (readMinutes == 0) {
	dv.span(`Less than a minute`) // Dataview's way to show content in a span
	return
}

if (readHours < 1) {
	dv.span(`${readMinutes} minute${readMinutes == 1 ? "" : "s"}`)
	return
}

if (readHours != 0) {
	dv.span(`${readHours} hour${readHours == 1 ? "" : "s"} and ${readMinutes} minute${readMinutes == 1 ? "" : "s"}`)
	return
}

Then you can use it in a markdown file like this. Replace the path to the view with your path, excluding the extension.

```dataviewjs
dv.view("Dataview Views/read-time")
```

More information about Dataview views in the docs here.

@AB1908
Copy link
Collaborator Author

AB1908 commented Sep 8, 2023

What a champ. Thanks for getting to these Zach.

@dashinja
Copy link

dashinja commented Sep 9, 2023

Here's the "view" code. Paste this into a .js file in a folder where you'll put all your Dataview Views. I only had to change how you're getting the contents of the current file, how to render the result, and remove the function declaration wrapping the code.

const WPM = 255
const content = await dv.io.load(dv.current().file.path) // Dataview's way to get contents of current file
const words = content.trim().replace(/[^\w\s]/g, "").split(/\s+/).filter((x) => x !== "")
const readTime = Math.ceil(words.length / WPM)
const totalHours = (readTime / 60)
const readHours = Math.floor(totalHours)
const totalMinutes = (totalHours - readHours) * 60
const readMinutes = Math.round(totalMinutes)

if (readMinutes == 0) {
	dv.span(`Less than a minute`) // Dataview's way to show content in a span
	return
}

if (readHours < 1) {
	dv.span(`${readMinutes} minute${readMinutes == 1 ? "" : "s"}`)
	return
}

if (readHours != 0) {
	dv.span(`${readHours} hour${readHours == 1 ? "" : "s"} and ${readMinutes} minute${readMinutes == 1 ? "" : "s"}`)
	return
}

Then you can use it in a markdown file like this. Replace the path to the view with your path, excluding the extension.

```dataviewjs
dv.view("Dataview Views/read-time")

More information about Dataview views in the docs [here](https://blacksmithgu.github.io/obsidian-dataview/api/code-reference/#dvviewpath-input).

Thank you @Zachatoo .
It does work in non-frontmatter areas.
I've tried to get rid of the newer 'properties' thing so it will always work where I want to put it but alas.
Even so, at least there is a replacement in for the deprecation of dynamic commands.

I appreciate you.

@Zachatoo
Copy link
Collaborator

It does work in non-frontmatter areas.
I've tried to get rid of the newer 'properties' thing so it will always work where I want to put it but alas.

I don't think Dataview would work in frontmatter before properties was introduced either, Obsidian would parse it as the Dataview string instead of the result of the Dataview execution. Though the same thing happens for Templater dynamic commands.

Either way, having dynamic values in frontmatter hasn't totally been solved, unless it's created/updated values, then the Linter plugin has solved that.

@pauby
Copy link

pauby commented Sep 17, 2023

having dynamic values in frontmatter hasn't totally been solved

This is precisely why I'm still on 1.15.3 of Templater and can't move until it is resolved. I create a lot of tags based on information obtained dynamically when the page is created.

@AB1908
Copy link
Collaborator Author

AB1908 commented Sep 18, 2023

That's not creation though, is it? It's only something you see when viewing, it doesn't actually exist.

@pauby
Copy link

pauby commented Sep 18, 2023

@AB1908 I'm unsure if that's aimed at me? What is created, exists, and is part of the frontmatter when the note is created.

@AB1908
Copy link
Collaborator Author

AB1908 commented Sep 18, 2023

It's not a dynamic command then, can you explain what trouble you're facing?

@pauby
Copy link

pauby commented Sep 18, 2023

The issue I'm having is that dynamic commands have been deprecated and I use them. That's why I said I'm sticking with 1.15.3 as they are not deprecated in that version.

I always update plugins as soon as they become available. I did this with 1.16.0 and it broke my code. I investigated the issue and found that dynamic commands had been deprecated. I downgraded to 1.15.3 and everything started working again.

I didn't want to hijack this thread. I only posted my comment as a temporary alternative to rewriting. I'm just sitting and watching. I appreciate you're trying to help but I'll go back to lurking just now as the issue is clear. Once I have the time to look at alternatives to my code, I'll update it and upgrade to 1.16+

@AB1908
Copy link
Collaborator Author

AB1908 commented Sep 18, 2023

Just to point out, it technically hasn't been deprecated, I think there's just been some sort of regression but as a whole, we've only been discussing deprecating it since it's kinda hard to maintain. It has some CodeMirror stuff if I remember correctly and ain't nobody wanna touch that.

@pauby
Copy link

pauby commented Sep 18, 2023

Then I've misunderstood. Apologies. I suppose the result is the same (it's not working).

If it's a regression, and 1.16.0 was released 10 months ago, is there a particular reason it's not been fixed by now? Or is it just lack of maintainer time?

@Zachatoo
Copy link
Collaborator

@pauby Since we're now talking about a feature regression in 1.16.0 and not deprecating dynamic commands, can we shift this conversation elsewhere? You can make an issue or discussion and post your template there and someone can help figure out why your template isn't working, and either make a bug fix or make a suggestion to your template to get it working again.

I think the biggest change in 1.16.0 was shifting to using a different templating engine (rusty_engine), which likely broke some templates. Would not be related to deprecating dynamic commands.

@pauby
Copy link

pauby commented Sep 18, 2023

@Zachatoo I'm answering questions that were asked of me. I didn't really intend to contribute here.

But, point taken. I'll go back to lurking.

@Zachatoo
Copy link
Collaborator

@pauby The point I was intending to make was that I'd love to help get your template working on 1.16.0, just not in this thread so that we're not sending notifications to people who don't care about your specific template. I apologize if any other messaging came across in my message. I appreciate your contributions, even if it wasn't your intention.

@MarioRicalde
Copy link

The issue with dynamic commands that makes it confusing is that they get cached and won't get updated until X or Y happens. Correct?

How about we provide with a "Replace All Tags in Documents", leaving the original command likew:

Score: %%<%+ tp.frontmatter["score"] %>%%**5**

Where the surrounding **,** can be replaced for other strings? That way the command can be run any time, inline and would work with Obsidian Publish without magic.

@AB1908
Copy link
Collaborator Author

AB1908 commented Oct 5, 2023

Okay now that we have an active maintainer, it's time to start planning for this. Zach, can we start by making a cosmetic version bump with a modal that asks users to recheck their templates to stop using dynamic commands if possible?

Let's also create some sort of discussion thread on both GitHub and Discord to see if we can get folks to migrate to other solutions that might be appropriate. I think giving a 1-3 month headstart should be enough and there will always be older versions to fall back on. Do you think there are any other important fixes that should make it in before we start working on the deprecation?

We also need to clarify a few other goals but let's start with these I guess. And sorry to roleplay as a PM lolol.

@AB1908
Copy link
Collaborator Author

AB1908 commented Oct 5, 2023

I'm also thinking if there's a way to cleanly decouple this and hide it behind a feature flag for people that can't find replacements. They can choose to opt in but we'll keep it disabled by default. Thoughts?

@MarioRicalde
Copy link

MarioRicalde commented Oct 5, 2023

The issue with dynamic commands that makes it confusing is that they get cached and won't get updated until X or Y happens. Correct?

How about we provide with a "Replace All Tags in Documents", leaving the original command likew:

Score: %%<%+ tp.frontmatter["score"] %>%%**5**

Where the surrounding **,** can be replaced for other strings? That way the command can be run any time, inline and would work with Obsidian Publish without magic.

I implemented the functionality above with dataview, I have an open PR that allows to do it with some extra code on part of the user for maximum customization.

We also need to clarify a few other goals but let's start with these I guess. And sorry to roleplay as a PM lolol.
You are funny.

I'm also thinking if there's a way to cleanly decouple this and hide it behind a feature flag for people that can't find replacements. They can choose to opt in but we'll keep it disabled by default. Thoughts?

How about disabling the feature for new installs only. That way you can discourage the use of the feature while teaching them as to why it's off (in case it's someone using an old guide, or active user installing on another device).

If the reinstall scenario for active users can be mitigated, that'd be much better.

@AB1908
Copy link
Collaborator Author

AB1908 commented Oct 5, 2023

We'll need to investigate how much work that would.

@Zachatoo
Copy link
Collaborator

Zachatoo commented Oct 6, 2023

Zach, can we start by making a cosmetic version bump with a modal that asks users to recheck their templates to stop using dynamic commands if possible?

I can get a modal together this weekend. I think it would be wise to have a migration guide prepared first. Do you have any interest in taking that on or would you like me to work on that AB? Not saying a migration guide has to be done that fast, we can take our time on this. I'm definitely interested in at least contributing to the guide.

The issue with dynamic commands that makes it confusing is that they get cached and won't get updated until X or Y happens. Correct?

In my mind there's two main issues:

  1. The bugs piling up for it that no one wants to take the time to fix. I don't expect I'll ever have time to address them, there are other more pressing bugs/features that I want to address for the foreseeable future.
  2. There are existing plugins like Dataview and Linter that can accomplish most of what dynamic commands currently provides.

It's high cost and low benefit to maintain this feature.

I'm also thinking if there's a way to cleanly decouple this and hide it behind a feature flag for people that can't find replacements. They can choose to opt in but we'll keep it disabled by default. Thoughts?

I think leaving the feature in, but having it under a flag would be a good compromise for those that absolutely need dynamic commands, knowing that it will be considered a legacy feature and could break at any time.

@AB1908
Copy link
Collaborator Author

AB1908 commented Oct 6, 2023

Thanks Zach, I will try to get started on a migration guide. Apologies in advance if progress is slow on it, drowning in office work at the moment.

@MAUGUS2
Copy link

MAUGUS2 commented Apr 11, 2024

I've been trying to create a dynamic Obsidian note template that generates a link to the previous day's note using the Templater plugin. My goal is to have the note title reflect the date of "Yesterday" dynamically, but I'm encountering some inconsistencies with the date variables and their output. Here are the details of what I've tried:

[[🏡 HOME/DailyNotes/<%+ tp.date.now("YYYY") %>/<%+ tp.date.now("MM") %>/<%+ tp.date.now("YYYY-MM-DD", -1)%> | Yesterday]] 

This line should return a note titled with the date of "Yesterday", e.g., "YYYY-MM-DD", but it's not working as expected.

 [[🏡 HOME/DailyNotes/<% tp.date.now("YYYY") %>/<%+ tp.date.now("MM") %>/<%+ tp.date.now("YYYY-MM-DD", -1)%> | Yesterday]] 

In this case, I get a note titled "NaN", which indicates that the date is not being processed correctly.

I've explored various approaches, including attempting to utilize dynamic JAVA to trigger a non-dynamic Templater script that generates the link for yesterday's note. Unfortunately, I haven't been able to resolve this issue. If anyone has encountered a similar problem and found a solution, I would greatly appreciate your insight.

Peace.

@Zachatoo
Copy link
Collaborator

@M-AU-O The Dataview plugin can be used for this instead of using dynamic commands in Templater. Here is your code converted to use a Dataview inline query. Note that yyyy and dd are lowercase due to Dataview using Luxon instead of Moment tokens.

`="[[🏡 HOME/DailyNotes/" + dateformat(date(now), "yyyy/MM/yyyy-MM-dd") + "|Yesterday]]"`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests