chhoumann / quickadd

QuickAdd for Obsidian
https://quickadd.obsidian.guide
MIT License
1.53k stars 136 forks source link

[FEATURE REQUEST] Suggestions from existing field values for {{VALUE}} or suggester (API) ? #337

Open Elaws opened 1 year ago

Elaws commented 1 year ago

Problem description

Often, I use {{VALUE}} or the suggester (from QuickAdd API) to complete a field in a note (e.g. : type::). It would be really useful if {{VALUE}} or the suggester could automatically retrieve the possible values for the field (either from all the notes or a chosen subset of notes) and suggest them.

qaptoR commented 1 year ago

I support this 100%

I'm thinking of adding it myself as I see a few relatively simple solutions.

  1. add to the list of format variables {{FIELD:}} so that we can specify a field that is searched through.
  2. for every {{VALUE:}} variable, search the filesystem of fields with that name.

I prefer the idea of 1 because it improves performance by only incurring the cost of searching for a field when a user requests it explicitly.

MichaTarlton commented 1 year ago

In my mental model, this would replicate metadata inheritance. I would like QuickAdd to pull metadata from fields in the currently open note, so they could be reapplied in the created note. Expanding the value selection to some filterable subset of notes or fields would be a crazy powerful feature.

chhoumann commented 1 year ago

Yeah, this would be a fantastic feature to have. I especially like your suggestion about {{FIELD}}, @qaptoR, for the reasons you mentioned. Merging it with {{VALUE}} would be hard to maintain + inhibit performance unnecessarily.

Let's see. What if {{FIELD:Type}} looks for every occurrence of Type in metadata - both Dataview fields & YAML properties. Since both already have indexing, QA doesn't need to do that... so it's probably just O(1) lookup (very fast to do).

Then it's just how we get the subset. I imagine one could either specify

If there's only 1 potential value, then that's used without asking the user. Otherwise, use suggester to prompt user. If there are no values... an input prompt could be used to ask for a specific value.

Any thoughts?

@qaptoR the process for adding format syntax is quite involved at the moment. There are many things to be aware of. If you wish to save the headache of navigating this complexity, I'll be more than happy to add this feature tomorrow.

Elaws commented 1 year ago

@chhoumann :

Then it's just how we get the subset. I imagine one could either specify

  • a file path (for replicating properties of a specific file),
    • a folder path to get all files within as subset, or
    • a tag to get all files with this tag as subset.

Maybe more complex to implement, but I was also thinking about the following filters :

Files in PATH and not SUBPATH with TAG_1 and not TAG_2
qaptoR commented 1 year ago

@chhoumann I've already implemented it (to my own immediate specification, see below) I just made the pull request here: https://github.com/chhoumann/quickadd/pull/403

It's possible I've missed something, but I've tested it and it works.

The only hiccup I found was not realizing that if you search all frontmatter fields in all markdown files then it searches the templates too. So I have to do a check for frontmatter fields that are of type 'object' because the format syntax when read as frontmatter is an object.

My Specification: I'm not super familiar yet with the obsidian API, I've just been building small tools for linking together different workflows offered by different plugins. So it's possible there's a more performant way to get the values, as I didn't think about the dataview field:: syntax, or that the info might be cached in a more easy to access way.

Essentially, I just get all markdown files from the vault, and loop over them, checking their frontmatter for the \<variable> provided. It's definitely a brute force method, but I was more keen on getting the new syntax working than finding the optimal solution at this stage.

qaptoR commented 1 year ago

So, I just started using it, and it's great. But one major flaw that I'll probably fix, is that if there is no suggestion selectable (meaning no matches) then it won't allow you to accept the current input as the value.

This is definitely for safety, since the FuzzySuggestModal is a generic with type, and the input is always a string. But for string values it should be acceptable.

The problem I'm running into is that: if I want to set a field with a new entry I have to use {{VALUE}}. If I want to use an existing value (and get fuzzy suggest) then I can use {{FIELD:}}, but it can't be both

chhoumann commented 1 year ago

Thanks for your PR @qaptoR & your input @Elaws!

With the latest update, https://github.com/chhoumann/quickadd/releases/tag/0.11.7, {{FIELD}} has been added as a new feature. I'm considering this release a beta-test of the {{FIELD}} feature, and have asked for feedback in the release notes: image

I've also gone ahead and fixed the issue that @qaptoR mentioned: Supports when there are no fields like the one you specify in {{FIELD}} in your vault. Will just ask you to input a value manually. Similar to {{VALUE}}.

I want to more towards what @Elaws suggested, allowing for a more sophisticated language to query for these. I'm strongly considering using dataview for this, as it is familiar syntax to many Obsidian users. Need to consider it a bit more, though...

Anyway, hoping to get some more feedback in here. Looking to add dataview support (inline properties) to this soon, so leaving this issue up + for further consideration & feedback.

MichaTarlton commented 1 year ago

Forgive my ignorance, but are there defined capabilities and directions for the {{FIELD}} feature as of current?

From my quick study and testing, adding {{FIELD:fieldname}} to a template, will search all frontmatter or DV fields with that name, and then display variables from those fields in a suggester during quick add. It does not display unique variables, so if multiple notes have the same field variable, e.g. FIELD: uniquename, I would see multiple entries for uniquename in the suggester for each note it appears in, across the vault. Am I missing any additional functionality?

Also, am I missing a way to define or filter the fieldname input? My ideal use case would be for quickadd to take FIELD: fieldname from the active file. I see that adding a query ability is in the cards but, it feels as if there is some "capture from active file" feature in QA that I keep missing.

chhoumann commented 1 year ago

No ignorance on your part, @MichaTarlton. You raise some very valid concerns.

I have added documentation for the feature. I'm including it here as a point for discussion:

Suggest the values of FIELDNAME anywhere {{FIELD:FIELDNAME}} is used. Fields are YAML fields, and the values represent any value this field has in your vault. If there exists no such field or value, you are instead prompted to enter one. This is currently in beta, and the syntax can change—leave your thoughts here.

Dataview fields are not currently supported. I'm still figuring how the integration should be managed. As it stands, it will look for any possible value contained in the specified YAML field (case-sensitive!), across your vault.

And for your last question: what would you expect to happen if

One of the things I'm pondering is how to encode these things in a query/with format syntax.

However, some things I think make sense:

Which I'll be implementing now.

MichaTarlton commented 1 year ago

Thank you for putting my mind to rest.

I think DV uses case-sensitive field querying, so some people might actually be using case uniqueness (though in my situation it's just a pain in my ass).

To your Qs, shooting from the hip I would expect:

  1. Insertion of the undefined entry that usually shows up in such cases. But a prompt which searches across the vault, sounds better.
  2. Same as above.
  3. Use the empty value. But this would probably be a highly personal preference. I could see where I may want it to trigger a prompt instead.
chhoumann commented 1 year ago

Thank you for the input!

Luckily, I hadn't removed the case sensitivity yet. I was under the impression that it didn't, but I'm glad to hear that my assumption might be wrong before I acted on it. Will wait for further input / more info on that matter

I've gone ahead and made QA only consider unique values. No need to put every value in the suggester. That is included in 0.11.

I'm also considering what to do instead of undefined across the board. It is really just is because the implementation details spill into the plugin. The value is undefined in JavaScript, so if none is given, it'll just get put in our notes (or an error). However, I'd like to make the UX a bit more clear as to when undefined could occur, and give options for recovery or alternatives. Perhaps a setting that could customize this behavior for users? Although I don't think adding settings for everything necessarily makes the plugin any simpler. Anyway, that's a bit besides the scope of this discussion.

MichaTarlton commented 1 year ago

Probably should digress this into another thread, but what I've seen some template using plugins do is let you define the "no data" output. Though with this I can see users wanting flexible case-specific "undefined-s". Maybe a user chosen "undefined" and then one which can be passed to templater for the hyper-specific stuff?

Elaws commented 1 year ago

Dataview fields are not currently supported. I'm still figuring how the integration should be managed.

Looking forward to trying it ! I will test this new feature then : for now, I only use dataview fields, because there were some drawbacks with YAML fields back then (maybe this was fixed, I should investigate).

Even if some suggested values are found, is there still a possibility to manually add a new value ? If not, it would be very useful for all notes where field values are not fixed once and for all.

chhoumann commented 1 year ago

Completely agree, writing values manually even with suggested values is a great idea.

Just added this functionality. Will be in next release. Obsidian_ZJWyUxpX1w

hydraInsurgent commented 1 year ago

Hi guys, I have a small suggestion that will increase the usability of this feature tremendously, I recently started using the QuickAdd plugin. I am fairly new to this so please excuse me if this is the wrong place to suggest.

My suggestion is to get the list of auto-suggested values for fields from another plugin called MetaData Menu. It's not mandatory that the user needs to use this plugin but can be presented as a choice for better suggestions.

In case you are not aware of this plugin what it does is it lets define fields inside a file class and there we can define the fields and field type for every metadata field, there will be different suggestions eg boolean, select from the list, multi-select as an array. Metadata menu suggestions are highly configurable as it integrates Dataview to narrow down the selected options as the user wants.

Let me know your thoughts on this. I am willing to put some effort myself to get this moving but I am looking for some guidance as I do not have any experience with plugins. Will appreciate any help you can give me.

chhoumann commented 1 year ago

Hi @hydraInsurgent

Thank you for your thoughts! I've only had sparse interaction with MetaData Menu. This is because I've developed MetaEdit, so I haven't had the need for MDM. MetaEdit has a similar(?) feature to the fileclass system MDM has going on. Although, I haven't really looked much at it, so I could be totally wrong.

The difficulty in supporting other plugins is that we have to keep the interactions updated. For example, QuickAdd currently hooks into Natural Language Dates & Templater. Templater doesn't have an API, so I've had to manually find the appropriate functions and parameters to use. Since they don't have an 'official' API, those functions and parameters can change at any time, making maintenance of QuickAdd harder. The plugin could break due to an update in any of the plugins it depends on.

DataView on the other hand does have an API, which is what makes it easy for plugin developers to use in their own plugins. And why it would be easier to support for the {{FIELD}} feature.

Please note that this is not me saying no to your suggestion, just thinking out loud. I think it could make sense if

But obviously, there could be (are) things I'm not seeing. Welcoming any thoughts :)

chhoumann commented 1 year ago

Enhancement coming in the next release: {{FIELD:fieldName}} now shows Enter value for fieldName in suggester to differentiate between prompts.

This comes from a conversation on Discord regarding the feature. It can be hard to differentiate between the suggesters when a template has multiple {{FIELD}}s.

image

chhoumann commented 1 year ago

Note: should prioritize somehow filtering template values out, so users don't see values from templates here.

Open question: Should this be opt in? Opt out? Or just default? Something else?

MichaTarlton commented 1 year ago

I'd say opt in, though perhaps depends on how one defines where the templates are stored.

anvimea commented 1 year ago

FWIW, if you use:

tag:
- x
- y
- z

x, y, z don't show up as options for {{FIELD:tag}}.

Elaws commented 1 year ago

@anvimea : Are you using YAML metadata format (enclosing with ---) ?

Have there been some progress concerning support for dataview fields (field::) ?

Thanks !

anvimea commented 1 year ago

@anvimea : Are you using YAML metadata format (enclosing with ---) ?

Have there been some progress concerning support for dataview fields (field::) ?

Thanks !

Yep, using YAML metadata format, and I just tested it again - still doesn't work.

modymp commented 1 year ago

greetings!

(this could be the same as what @anvimea has described)

Recently I was testing the {FIELD} option and I noticed that the comma separated values of the key is not listed as separate items, instead, the whole line is considered

--- 
xy-key: va1,val2,va3 
---

with {{FIELD:xy-key}}, the suggester prompts 'va1,val2,va3' and not as va1 + val2 + va3

I suppose this is the current behavior. If yes, can this be added as an option.

thanks!

anvimea commented 1 year ago

greetings!

(this could be the same as what @anvimea has described)

Recently I was testing the {FIELD} option and I noticed that the comma separated values of the key is not listed as separate items, instead, the whole line is considered

--- 
xy-key: va1,val2,va3 
---

with {{FIELD:xy-key}}, the suggester prompts 'va1,val2,va3' and not as va1 + val2 + va3

I suppose this is the current behavior. If yes, can this be added as an option.

thanks!

Have also noticed this behavior!

mundorfd commented 1 year ago

After invoking a new note from a template, I hope to populate the YAML front matter with content I select from a drop-down list. Is this possible? As an example, I would like a "context" field in my Person template that would include options for me to select from "Colleague" "Friend" "Family" etc. when I create a note.

On Discord, someone suggested the use of {{FIELD:<FIELDNAME>}} to get this done.

I am also trying to get it to create a title using values from a prompt and templater. I am likely doing this wrong, but I have not been able to get it to work with either a Capture or Template QuickAdd. Here's the template I am using:

---
<%* let title = tp.file.title; 
    if (title.startsWith('Untitled')) { 
        title = await tp.system.prompt('Name (Last, First)');
        await tp.file.rename(`${title}`);
    }
-%>
title: <% `${title}` %>
context: {{FIELD:context}}
---
# <% `${title}` %>

## Contact Information

[Email](mailto:)
ONID: 

Any suggestions would be greatly appreciated.

NaturallyAsh commented 1 year ago

After invoking a new note from a template, I hope to populate the YAML front matter with content I select from a drop-down list. Is this possible? As an example, I would like a "context" field in my Person template that would include options for me to select from "Colleague" "Friend" "Family" etc. when I create a note.

On Discord, someone suggested the use of {{FIELD:<FIELDNAME>}} to get this done.

I am also trying to get it to create a title using values from a prompt and templater. I am likely doing this wrong, but I have not been able to get it to work with either a Capture or Template QuickAdd. Here's the template I am using:

---
<%* let title = tp.file.title; 
  if (title.startsWith('Untitled')) { 
      title = await tp.system.prompt('Name (Last, First)');
      await tp.file.rename(`${title}`);
  }
-%>
title: <% `${title}` %>
context: {{FIELD:context}}
---
# <% `${title}` %>

## Contact Information

[Email](mailto:)
ONID: 

Any suggestions would be greatly appreciated.

Hey @mundorfd I stumbled on your comment since I was trying to figure out something similar. I figured out a solution that works for me and executes both templater and quickadd. You might find useful.

Regarding your title problem, the following worked (I needed to include the templater tR variable to output the result):

---
<%*
  let title = tp.file.title
  if (title.startsWith("Untitled")) {
    title = await tp.system.prompt("Title");
    await tp.file.rename(title);
  } 
  tR += ""
%>
---
# <%* tR += title %>

Regarding your context field, I wanted something similar but for my tags field. I believe the templater suggester should work for you context field:

---
tags: [<% tp.system.suggester(["Friend contact, Acquaintance contact", "Colleague contact", "Business contact", "Conference contact", "Family contact"], ["contact/friend", "contact/acquaintance", "contact/colleague", "contact/business", "contact/conference", "contact/family"]) %>]
company: "{{VALUE:Company}}"
aliases: [<% tp.file.title.replace(/^(\w+)\s(\w)\w+$/,'$1 $2.') %>]
---
mundorfd commented 1 year ago

Thank you @NaturallyAsh - I will give this a try!

mundorfd commented 1 year ago

Borrowing from @NaturallyAsh's help, here is what I have now as my template (Thank you! works exactly as I had hoped):

---
<%*
  let title = tp.file.title
  if (title.startsWith("Untitled")) {
    title = await tp.system.prompt("Title");
    await tp.file.rename(title);
  } 
  tR += ""
%>
context: <% tp.system.suggester(["Ecampus Colleague", "Instructor", "Intern", "Friend", "Family"], ["Ecampus Colleague", "Instructor", "Intern", "Friend", "Family"]) %>
---

# <%* tR += title %>

I have added some code for DataView to show me all files linked to the person.

## Linked files

```dataview
LIST FROM [[]]
SORT title ASC
OmniNaut commented 1 year ago

I noticed while using Capture and using this code:

That the suggester will show last, while I stated to be shown first. So the order it is shown now is: 1 Amount 2 Unit 3 Ingredient

Is this a bug or am I doing something wrong?

image

qaptoR commented 1 year ago

@OmniNaut, I can't speak for the developer of this plugin, but I did contribute the initial code for this feature implementation, so unless something has changed and I'm corrected this is the result of the order in which each of the regex substitutions types are implemented in the code. This is because code executes top to bottom and something has to be executed first, last etc. ie. First replace all VALUE, then replace all FIELD, etc.

It would be a more complex solution to queue each individual substitution and then have them happen in the order they're written. And since the order you enter the data doesn't change the final result it would be more work for a feature that adds marginal value to the overall functionality of the plugin. that said, marginal value here is relative to each individual.

The reason that you are noticing it though, is that the FIELD syntax is new, and requires it's own regex to make substitutions, whereas before there was only VALUE substitutions, so they did just happen in the order they were written as you've said in your comment simply because the regex query returns the matches in the order they're found, which is in string order (or the order they're written).

So to answer your question, no it is not a bug, and no you are not doing something wrong, it is working just as expected. Granted, it should possibly be documented.

Just as an example of how complex the solution would be, this is an algorithm to solve the issue off the top of my head:

run a pass to get all matches for VALUE into var A
run a pass to get all matches for FIELD into var B
# where A and B are array types

loop while A and B are not size 0:
if position of A[0] is < B[0] then 
C.push(A.pop_front(): A_Funcref) else 
C.push(B.pop_front(): B_Funcref)
# where C is an ordered dictionary type 

for key, value in C: value.call(key)

except now imagine that there is eventually a new substitution NEWSUBA and NEWSUBB, so that now building C becomes more and more complicated whenever a new type is added, since you can only compare two values at a time.

Versus the code as it stands now:

run a pass to substitute all VALUE
run a pass to substitute all FIELD
run a pass to substitute all NEWSUBA
... etc.

BUT, that is just my off the cuff perspective, and maybe there is someone who doesn't think it would be so difficult to maintain that feature and would like to implement it themselves, or possibly another solution using regex or javascript features that I'm not aware of that could result in a much simpler solution. For example, there may be a library that can merge and sort arrays together to build C.

Anyway, I hope this helps contribute to the conversation. I don't want to shut down anyone's ideas, only illustrate the picture as I currently see it.

OmniNaut commented 1 year ago

@qaptoR Thank you for your detailed response.

I was thinking as a quick solution, splitting up the capture into two separate ones. Then combining them in one Macro. But I'm struggling with getting the second capture to continue at the desired point after the first capture. Any suggestions?

ReidWeb commented 1 year ago

Thank you for implementing this fantastic feature.

I would love features as below, but understand it may take time to consider & implement them

Inline field support

It's been noted before, but just to ask for it personally - inline field support would be great. So existing values in documents will show up

**Project**:: [[Project_X]]

Presently I have had to move/replicate that field to my frontmatter

---
project: [[Project X]]
---

> **Project**:: [[Project_X]]

Comment on VALUE/FIELD

I use the value of certain captured fields to drive my file names, e.g.

template.md

---
<%* 
let formattedFileName = tp.file.title
formattedFileName = formattedFileName.replace(/_/g,' ')
-%>
title: formattedFileName
---
Body 

then in quick add I have title configured as

{{DATE}}-{{VALUE:title}}

This creates a file 2023-05-29-My_Test_File

with content

---
title: My Test File
---

I would love to be able to add a 'comment' that reminds me to Snake_Case my file names, or can be used to remind me of something else e.g.

title: {{VALUE:title#Snake cased name to give the file}}

Which could show up as

image

Realistically, I have plenty more use cases for this, such as adding a description to the template fields I have for 'Person' records.

Elaws commented 1 year ago

Inline field support would be amazing indeed.

danielo515 commented 1 year ago

Let's see. What if {{FIELD:Type}} looks for every occurrence of Type in metadata - both Dataview fields & YAML properties.

+1 for this. I was really expecting this behaviour already. For now, I will just duplicate the value in both places

thoresson commented 1 year ago

I wonder if I mistunderstand how {{FIELD:FIELDNAME}} is supposed to work. This is how I use it in my first template – but when I run it, QA claims that there are no existing values for Typ even though there is. But they are all wikilinks. Could that be the culprit?

CleanShot 2023-08-05 at 10 00 24@2x
danielo515 commented 1 year ago

Make sure to put them in the front matter, or they will not work

El sáb, 5 ago 2023, 10:02, thoresson @.***> escribió:

I wonder if I mistunderstand how {{FIELD:FIELDNAME}} is supposed to work. This is how I use it in my first template – but when I run it, QA claims that there are no existing values for Typ even though there is. But they are all wikilinks. Could that be the culprit? [image: CleanShot 2023-08-05 at 10 00 @.*** https://user-images.githubusercontent.com/6024521/258571636-9541bb56-c725-409f-af19-ae3e1051c775.png

— Reply to this email directly, view it on GitHub https://github.com/chhoumann/quickadd/issues/337#issuecomment-1666432874, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARKJWMSKLWZBL2D2EH2X3LXTX42HANCNFSM6AAAAAASMCPJTM . You are receiving this because you commented.Message ID: @.***>

thoresson commented 1 year ago
CleanShot 2023-08-06 at 15 29 05@2x

This is what I have. An existing note to the left, where you can see that Område is one of the existing values for Typ. To the right, the frontmatter of the template. But, as the input box shows, the existing values aren't picked up på QA. I'm more inclined to think I'm doing something wrong, rather than this being a bug. But no matter what I try, the end result is the same – no existing values to chose from.

chrisjterrell commented 10 months ago

I would like to have the field check the page to see if the property exists, and if the property exists on the page, then the value would be passed. Otherwise, a property modal would be provided for manual selection.

Quick Add String possibility as "Pagecheck" as an optional function variable. {{FIELD:fieldname, pagecheck:true (optional)}}

My example

Page YAML

--- 
Priority1: Product123
--- 

Quick Add String

Outcome - Not modal popups, just the string for the Capture

JosefTaylor commented 8 months ago

I'm having some struggles and wanted to understand if I'm doing the intended thing: my template is:

---
aliases: <% tp.user.split_name("{{VALUE}}") %>
pronouns: {{FIELD:pronouns}}
company: {{FIELD:company}}
created: {{DATE}}
role: {{FIELD:role}}
location: {{FIELD:location}}
---

company is a link to another file, i.e. [[<company>]]. I'm having an issue because Obsidian wants quotes around the whole link, i.e. "[[<company>]]", but this template yields [[<company>]], which Obsidian doesn't recognize as a link in frontmatter. So I tried including quotes in the template:

[...]
company: "{{FIELD:company}}"
[...]

This works, but has the unfortunate effect of including the template string itself in the drop-down menu (i.e. {{FIELD:company}}). If I click it, Obsidian crashes. What am I doing wrong?

EDIT: per @ReidWeb below, this is a bug, not really part of this feature request. I've submitted at #644. I suspect this is similar to #455. I am going to try a workaround in which I use a very simple .js to wrap the field in quotes, so that QuickAdd doesn't get confused.

ReidWeb commented 8 months ago

@JosefTaylor your comment relates more to the creation of links from quickadd inputs than to this issue. You should probably create a separate issue.


FWIW: I've never found a way to successfully create an obsidian link in the frontmatter of a document based on quickadd input - what i've seen replicates what you're seeing . What I do to establish that relationship is something like so (using your example)

---
aliases: <% tp.user.split_name("{{VALUE}}") %>
pronouns: {{FIELD:pronouns}}
company: {{FIELD:company}}
created: {{DATE}}
role: {{FIELD:role}}
location: {{FIELD:location}}
---

> **Company:**  [[{{FIELD:company}}]]

Which creates a link in the body rather than the metadata, realise this doesn't entirely achieve the desired effect

hbock-42 commented 6 months ago

Thx, excellent feature !

It work perfectly for one-line front matter field, however, it seems that if you are using multi-choice front-matter (like tags) it does not work.

author: Robert

in the case above, next time I use {{FIELD:author}} I have Robert as a suggestion.

However, if my front matter is like this:

author: 
   - Robert
   - Bob

Next time I use {{FIELD:author}}, I have no suggestion

WhiskeyJack96 commented 3 weeks ago

FYI I opened https://github.com/chhoumann/quickadd/pull/740 (which I think will fix the above issue) I haven't tested yet since attempting to build locally isnt working (on main without changes) but if someone else has that working it might help you!