I’ve started converting my queries to v2. But as I lack coding knowledge (I did a lot of reading through the docs, trial and error and looking at the forums for v1) I’m having quite a bit of trouble making them work for v2. Maybe you can help me out a bit.
The first v1 query is a query to display my tasks for today:
task
where page != "Grocery List" and tags = "next" or deadline = "{{today}}" order by done
render [[Library/Personal/Query/TaskTags]]
and this is how it is rendering: * [{{state}}] {{name}}{{#each tags}} _[[📌 {{.}}|#{{.}}]]_{{/each}} | [[{{ref}}]]
This is what I got so far for v2:
${template.each(query[[
from index.tag "task"
where page != "Grocery List" and deadline = "${date.today}"
order by done]],
template.new[==[
* [${state}] ${name} ${tags} | [[${ref}]]
]==]
)}
A few issues I’m having so far.
I can’t include only one page instead of exclude a page. For example where page = "Tasks" doesn’t work.
It is ignoring the where deadline = "${date.today}" clause completely.
I haven’t found a way to render my query with the tags of the task being displayed as clickable tags looking like #clickableTaskTag that take me to the tags page of that certain tag.
I have a query that queries all my tags in my space and creates a simple display of clickable tags that take me to my tags page and includes the number of uses of that tag in my space. So it looks like this: #onetag(3) #anothertag(9) and so on.
This is what my query looked in v1:
template
{{#each {tag select name where parent != "builtin" order by name}}}
{{#let @tag_name = name}}
_[[📌 {{name}}|#{{name}}({{count({tag where name = @tag_name})}})]]_ {{/let}}
{{/each}}
Last one. Will Houston started helping me in the chat. I wanted to create a space-lua script that excludes certain pages of displaying the ToC. I was planning to create a page Library/My/GlobalSpaceLua where I’d put this in. I want to exclude the index and the Tasks page as an example. This is what he sent me:
function widgets.toc(options)
-- the rest of the code before this
local pageName = editor.getCurrentPage()
local parsedMarkdown = markdown.parseMarkdown(text)
-- code to exit if the pageName matches what you're looking for
-- the rest of the code
end
This was a lot but I hope I can get some help. I think it would give me a good starting point to further adapt stuff from it. Thank you.
custom = custom or {}
function custom.concatenateTags(tags)
local result = {}
for _, tag in ipairs(tags) do
-- Format each tag as [[tag:tag_name|🗂️ tag_name]]
local formattedTag = string.format("[[tag:%s|🗂️ %s]]", tag, tag)
table.insert(result, formattedTag)
end
return table.concat(result, " ")
end
Continuing with point 3, you can find the current widgets code in [[Library/Std/Widgets]] in your space’s meta pages. For the full text below, I copied everything except the event.listen call. Possible solution:
widgets = widgets or {}
local tocSchema = {
type = "object",
properties = {
minHeaders = { type = "number" },
}
}
function widgets.toc(options)
options = options or {}
local validationResult = jsonschema.validateObject(tocSchema, options)
if validationResult then
error(validationResult)
end
options.minHeaders = options.minHeaders or 3
local text = editor.getText()
local pageName = editor.getCurrentPage()
local parsedMarkdown = markdown.parseMarkdown(text)
-- Custom code to break out on index and Tasks
local ignorePages = {"index", "Tasks"}
if table.includes(ignorePages, pageName) then
return widget.new {}
end
-- End of custom code
-- Collect all headers
local headers = {}
for topLevelChild in parsedMarkdown.children do
if topLevelChild.type then
local headerLevel = string.match(topLevelChild.type, "^ATXHeading(%d+)")
if headerLevel then
local text = ""
table.remove(topLevelChild.children, 1)
for child in topLevelChild.children do
text = text .. markdown.renderParseTree(child)
end
if text != "" then
table.insert(headers, {
name = text,
pos = topLevelChild.from,
level = headerLevel
})
end
end
end
end
if options.minHeaders and options.minHeaders > #headers then
return widget.new{}
end
-- Find min level
local minLevel = 6
for _, header in ipairs(headers) do
if header.level < minLevel then
minLevel = header.level
end
end
-- Build up markdown
local md = (options.header or "# Table of Contents") .. "\n"
for _, header in ipairs(headers) do
if not(options.maxHeader and header.level > options.maxHeader or
options.minLevel and header.level < options.minLevel) then
md = md .. string.rep(" ", (header.level - minLevel) * 2) +
"* [[" .. pageName .. "@" .. header.pos .. "|" .. header.name .. "]]\n"
end
end
return widget.new {
markdown = md
}
end
The tocSchema needed to be copied because it was used in the function widgets.toc. The relevant section is surrounded by comments:
-- Custom code to break out on index and Tasks
local ignorePages = {"index", "Tasks"}
if table.includes(ignorePages, pageName) then
return widget.new {}
end
-- End of custom code
Returning widget.new {} will cause nothing to be rendered for the ToC on the “index” or “Tasks” pages. I tested on my local index, and it seems to be working.
Here’s a first attempt at that conversion. I went ahead and added a condition to avoid tags that start with “meta” because I don’t think parent != "builtin" is going to do anything:
${template.each(
query[[
from t = index.tag "tag"
where not t.name:match("^meta") and t.parent != "builtin"
order by t.name
select {
name = t.name,
count = #query[[from index.tag "tag" where name == t.name]]
}
]],
template.new [==[
* [[tag:${name}|${name}]] (${count})
]==]
)}
I used explicit variable binding to bind each item to t with t = index.tag "tag". That was so I could reference the current object’s name easier when selecting name and count.
t.name:match("^meta") is a shortcut in Lua that does some magic I don’t understand. It is calling string.match on t.name looking for meta at the start of the tag name.
count = #query[[from index.tag "tag" where name == t.name]] is calling another query, looking for the current tag’s occurrences. Prefixing a table with # gives you the number of items in that table.
Thank you Vlad. That function works great and was exactly what I was looking for. I modified it a bit to display a hashtag before the tag. Works like a charm.
Ah I’m glad you are explaining it a bit to me, so I can better wrap my head around it and learn a bit from it for future explorations I appreciate it. I now understood what you were saying in the chat and your solution works great.
What does the ^ do in t.name:match("^meta")?
Also if I want to display this query without line breaks between the tags. How would I go about it in the template part? I already removed *
I tried adding == to where deadline == "${date.today}". But when I do that the whole query just disappears when I leave the code and doesn’t even display an error. I’m using the following format for my tasks with deadlines btw: - [ ] Important Task #urgent 📅 2025-05-19
Is that maybe a problem? EDIT: I figured out my problem with the deadline. So I had to input the following string: deadline == os.date('%Y-%m-%d')
With the part in the ( ) it worked.
I’m glad to help! The caret at the beginning of a Lua pattern (and most regular expressions) matches the start of the string passed in. So “^meta” would match “meta” or “meta/templates” but not “a/meta/tag”.
To get the tags on the same line, remove the line breaks around the template text, like [==[ [[tag:${name}|${name}]] (${count}) ]==]. I think that will work, at least.
Ah great I didn’t know the line breaks format how the query is displayed.
I have one last interesting query that I’m not figuring out yet.
Basically I want to query all my upcoming undone tasks and display them with their weekday of the deadline date at the front. What I have so far is this:
${template.each(query[[
from index.tag "task"
where page == "Tasks" and deadline >= date.today('%Y-%m-%d') and deadline != nil and done == false
order by deadline]],
template.new[==[
* [${state}] ${deadline} ${name} ${custom.concatenateTags(_.tags)} | [[${ref}]]
]==]
)}
This displays fine as: * [ ] 2025-05-20 This is a Task #task | Ref
But I’d like for the weekday to be displayed either in front of the date of the deadline or as a header before all tasks with a certain deadline.
So either: * [ ] Tuesday 2025-05-20 This is a Task #task | Ref
or ## Tuesday * [ ] 2025-05-20 This is a Task #task | Ref ## Wednesday * [ ] 2025-05-21 Next day's Task #task | Ref * [ ] 2025-05-21 Another Task #task | Ref
I have found the string for weekday ${os.date("%A")} but adding this will just add the current weekday to every task and not associate it to the deadline. So basically I want something where the weekday function checks for the deadline date and associates the correct weekday.
First I’m assuming, that the deadline attribute in your tasks is always formatted correctly, so that we can skip format validation.
If you’re using the syntax (- [ ] task with deadline 📅 2025-05-20) it will be, but by using explicit attribute definitions you could also set the deadline to some nonsense (- [ ] task with deadline [deadline: 'This is not a date']).
If you want to keep the whole thing inside your inline lua expression, the following (admittedly pretty bulky) piece of code should do it:
But since you’re already using an externally defined lua function for the tag concatenation, you could add this function (with format validation!) to the same space-lua block:
function custom.dayOfDate(date)
local year, month, day = date:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
if not (year and month and day) then
-- failed to parse date
return os.date("%A")
else
return os.date("%A", os.time({year = year, month = month, day = day}))
end
end
Thanks this worked nicely and is what I was after. I don’t fully grasp what is exactly going on in the first two lines of the function though. Is it trying to find the deadline date string and then give it the os.date("%A") calculated by the year, month, day?
Pretty much
In lua, functions (like string.match) can return multiple values at once, wich can then be assigned to multiple values at once. In this case, string.match returns a value for every regex group capture. The date:match(...) bit is just a shorthand way of writing string.match(date, ...) (As far as I understand it, this is part of lua oop and works for all string methods where the first parameter is a string and passed as a variable).
os.time takes a table with a key-value pair for every date component and returns a timestamp number (I think its the unix timestamp), wich can then be passed to os.date to produce a formatted date.
any hint howto show only tasks from special folder?
use template from above and it shows me on fresh 2.0 install tasks from
Library/Std/Query Templates
which I would like to hide..
With
where not t.name:match(“^meta”) and t.parent != “builtin”
it ignores nothing,.. again see all open tasks.
thanks.
I would also prefer the sb1.0 old queries are written as a wiki or a hint inside this forum because it was so much work to make all the 1.0 queries run fine,. and now rebuild it.
Sure!
If you only want to show tasks from a specific ‘folder’ or path, you could use string.startsWith in your query:
query [[from index.tag 'task' where page:startsWith('Path/To/My/Folder')]]
If, however, you just want to filter out tasks on meta pages, you should check for tasks without ‘meta’ in their itags property:
query [[from index.tag 'task' where not table.includes(itags, 'meta')]]
Let me know if that helped ;D
About your last take: I can totally understand your point. To someone comfortable with lua, the new query syntax is way more powerfull and flexible than the legacy one, but at the same time harder to understand and use to others. Maybe you could open up a new topic with the exact idea you’ve got in mind?
I don’t think that just giving one to one translations for specific queries from legacy to lua syntax would be a good idea, but maybe some general guidelines for common actions (like filtering out objects from meta pages) could work!
Weirdly enough the script worked for a few days and when I just went to Reload: System I got the following error on my queries that have the custom.dayOfDate function you provided included:
Lua error: error evaluating “custom.dayOfDate(deadline)”: Attempting to call nil as function (Origin: Template@824)
This is what the query looks like
${template.each(query[[
from index.tag "task"
where page == "Tasks" and deadline > date.today('%Y-%m-%d') and deadline != nil and done == false
order by deadline
limit 6]],
template.new[==[
* [${state}] **${custom.dayOfDate(deadline)}** _${name}_ ${custom.concatenateTags(_.tags)} | [[${ref}]]
]==]
)}
huh, seems like your custom.dayOfDate function isn’t found when the template is evaluated. “Attempting to call nil as function” means in this context, that the custom table exists, but the specific function isn’t contained in it. Did you maybe rename the function or overwrite the custom table with a custom = {} statement after the function definition?
So I have all my custom scripts in one note in my personal library called Library/Personal/Space Lua Scripts tagged with #meta
There I have the script you shared and many others separated by headers. Is that not good practice? Should I have separate notes for each script?
It’s this btw:
function custom.dayOfDate(date)
local year, month, day = date:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
if not (year and month and day) then
-- failed to parse date
return os.date("%A")
else
return os.date("%A", os.time({year = year, month = month, day = day}))
end
end