- Preparation:
- Add a macro inside file
config.edn
, insidemacros{}
:
:deleteparent "<button class='kit eval' data-kit='massdelete'>► !!! DELETE !!! query results of parent block !!!</button>"
- The code below requires having kits inside file
custom.js
. - Inside page
MassDelete
in Logseq, put the following code in a javascript code-block:const LS = logseq.api const Module = logseq.Module const Kits = Module.Kits const Msg = Module.Msg function advancedQuery(content, queryWord){ const queryStart = content.indexOf("[", queryWord + 5) if (queryStart < 0) return var queryEnd = queryStart + 1 var n = 1 while (n > 0) { const close = content.indexOf("]", queryEnd) if (close < 0) return const open = content.indexOf("[", queryEnd) if (close < open || open < 0) { queryEnd = close + 1 n -= 1 continue } queryEnd = open + 1 n += 1 } return content.slice(queryStart, queryEnd) } function simpleQuery(content, queryWord){ const queryStart = queryWord + 6 const queryEnd = content.indexOf("}}", queryStart) return content.slice(queryStart, queryEnd) } function getBlocks(content, queryWord){ if (queryWord < 0) return if (content[queryWord - 1] === ":") { const query = advancedQuery(content, queryWord) if (query) return LS.datascript_query(query).flat() } else if (content.slice(queryWord - 2, queryWord) === "{{") { const query = simpleQuery(content, queryWord) if (query) return LS.custom_query(query) } } logseq.kits.massdelete = Kits.addClickEventToButton.bind(null, function onDeleteParentClicked(e){ const child = e.target.closest("div.ls-block") const parent = child.parentElement.closest("div.ls-block") const blockId = parent.getAttribute("blockid") const block = logseq.api.get_block(blockId) const content = block.content.replace(/\n?.*[:][:].*\n?/g, "\n").trim() const queryWord = content.indexOf("query") const res = getBlocks(content, queryWord) if (!res) return Msg.ofStatus("Missing query", "warning") res.forEach( (r)=>{ const name = r.name if (name) LS.delete_page(name) else LS.remove_block(r.uuid) }) Msg.ofStatus("Deleted " + res.length, "success") })
- Add a macro inside file
- Usage:
- Create a query that returns the items for deletion:
- either simple or advanced
- either blocks or pages
- keep it simple, as this is not tested with every possible query
- Check the results to make sure that all of them are meant for deletion.
- This code deletes information, so use with caution !
- Ensure that you have a backup, just in case.
- This code deletes information, so use with caution !
- Put the macro at a child block under the query:
- i.e.
{{deleteparent}}
- i.e.
- Press the button. This should automatically:
- try reading the query from the parent block
- the parser is basic, so:
- avoid special strings
- don’t use
:inputs
(read the following posts)
- the parser is basic, so:
- run the query again
- delete without confirmation everything returned one-by-one
- this is important, because it may be undoable
- show a success message with the number of deleted items
- try reading the query from the parent block
- Create a query that returns the items for deletion:
Since updating a query to this format Switching a query’s scope between graph-level and page-level with a single argument, an instance of the button as a child under the query no longer works. A sample of the errors in the console:
Uncaught $APP.$cljs$core$ExceptionInfo$$ {message: 'Too few inputs passed, expected: [$ ?inputpage ?q], got: 1', data: $…P.$cljs$core$PersistentArrayMap$$, cause: null, name: 'Error', description: undefined, …}
If you have any ideas on how to work around this, I’d be grateful for your help!
- I don’t understand that statement. What format do you talk about?
- If a query fails, the button fails too.
- Given the error message, I would try replacing the inputs with constants.
By format, I meant the type of query I posted in that thread (which you helped me with), which accepts :current-page
as an argument—but in this case, the argument for using the current page is false
, which means that the query returns global results, not current-page results.
Here’s the query in question:
#+BEGIN_QUERY
{:title [:h3 "☑️ DONE"]
:query [:find (pull ?b [*])
:in $ ?inputpage ?q
:where
[?b :block/page ?p]
(or
(not [(= ?q true)])
(and [(= ?q true)]
[?p :block/name ?inputpage]))
[?b :block/marker ?marker]
[(contains? #{"DONE"} ?marker)]]
:result-transform (fn [result]
(sort-by (fn [h] (get h :block/priority "Z")) (map (fn [m] (assoc m :block/collapsed? true)) result)))
:inputs [:current-page false] ; last argument (true|false): to query all pages, use false. to query current page only, use true.
:table-view? false
:breadcrumb-show? false
:group-by-page? false
:collapsed? false}
#+END_QUERY
The query doesn’t fail, only the button.
I can do that, but I’ll try to find another way around it so I can continue using the query with current inputs.
FYI, the previous version of the query (no inputs) works with the button:
#+BEGIN_QUERY
{:title [:h3 "☑️ DONE"]
:query [:find (pull ?b [*])
:where
[?b :block/marker ?marker]
[(contains? #{"DONE"} ?marker)]]
:result-transform (fn [result]
(sort-by (fn [h] (get h :block/priority "Z")) (map (fn [m] (assoc m :block/collapsed? true)) result)))
:table-view? false
:breadcrumb-show? false
:group-by-page? false
:collapsed? false}
#+END_QUERY
Still would be cool to find a working solution for the query that uses inputs, however!
I reviewed the code. As stated in the description:
Among other things, it doesn’t parse the :inputs
. Even if it was parsing them, it wouldn’t be able to replace special values like :current-page
. Therefore, this code doesn’t support inputs at all. Since no inputs are read, no inputs are passed either, so the query complains as per the error message.
If you really want to mess with passing inputs, should:
- go to
function onDeleteParentClicked(e)
- implement a way to store the desired inputs into variables
- e.g. by reading them from
e.target
- This is the only hard part.
- e.g. by reading them from
- find
getBlocks(content, queryWord)
and pass the inputs as arguments- e.g.
getBlocks(content, queryWord, ...inputs)
- e.g.
- implement a way to store the desired inputs into variables
- go to
function getBlocks(content, queryWord)
- add the extra parameters
- e.g.
function getBlocks(content, queryWord, ...inputs)
- e.g.
- find
LS.datascript_query(query)
and pass the inputs as arguments- e.g.
LS.datascript_query(query, ...inputs)
- e.g.
- add the extra parameters
If the inputs are accepted and the query succeeds, the button will also succeed.
Thank you so much for these pointers. Although my brain kind of wants to take on the challenge, I think I’d be just creating work for myself that isn’t really necessary.
I really appreciate you listing out how it could be done, and hope that someone finds it useful, if not me at some future time. It’s amazing what you’ve done with Kits.
P.S., appreciate you editing my incorrect link in the previous post for clarity