Hi there,
I would like to know if there’s a way to copy or export the results generated by an advanced query in Logseq. Any help or suggestions would be greatly appreciated!
Plugin “logseq-cuvee” seems only works for queries, not advanced query
I don’t have a solution for the generated results. But if you want to copy a raw list of contents (for returned blocks) or names (for returned pages), I can prepare some custom code.
Thank you so much! I really appreciate your offer for custom code. In fact, I need to copy the results of the following advanced query:
#+BEGIN_QUERY
{:title "🔙 All Backlinks in Current Page"
:inputs [:query-page]
:query [
:find (pull ?refpage [:block/name]) ; only care about block name
:in $ ?current
:where
[?b :block/refs ?refpage] ; blocks with references
(not [?refpage :block/journal-day]) ; not a journal
[?p :block/name ?current] ; get current page
[?b :block/page ?p] ; block must be on current page
]
:view (fn [res]
[:ul (for [row res]
[:li [:a {:href (str "#/page/" (:block/name row))} (:block/name row)]]
)])
}
#+END_QUERY
- Preparation:
- Add a macro inside file
config.edn
, insidemacros{}
:
:copyparent "<button class='kit eval' data-kit='copyquery'>► copy query results of parent block </button>"
- The code below requires having kits inside file
custom.js
. - Inside page
CopyQuery
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) { const name = query.includes("?current") && '"' + LS.get_current_page().name + '"' return (name ? LS.datascript_query(query, name) : 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.copyquery = Kits.addClickEventToButton.bind(null, function onCopyParentClicked(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") const out = [] res.forEach( (r)=>{ const name = r.name if (name) out.push(name) else out.push(r.content) }) navigator.clipboard.writeText(out.join("\n")) Msg.ofStatus("Copied " + res.length, "success") })
- This code makes plenty of assumptions, so:
- it will work with the specific query
- it will work with some other queries
- it will not work with many other queries
- To make it work for a specific query, provide it here to see what can be done.
- This code makes plenty of assumptions, so:
- Add a macro inside file
- Usage:
- Put the macro at a child block under the query:
- i.e.
{{copyparent}}
- i.e.
- Press the button. This should automatically:
- run the query of the parent block
- copy everything returned as text separated by new lines
- show a success message with the number of copied items
- Paste somewhere the content of the clipboard.
- Put the macro at a child block under the query:
Thank you so much for your help, it worked! You really saved the day.
I don’t have much of a coding background and I never knew that Logseq could be extended in this way. I’m thrilled and I definitely want to continue exploring more. I really appreciate your guidance.
I used GPT to modify the copyquery
code to make the results appear as links, which better suits my needs. Here’s the modified code:
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) {
const name = query.includes("?current") ? `"${LS.get_current_page().name}"` : null;
return LS.datascript_query(query, name).flat();
}
} else if (content.slice(queryWord - 2, queryWord) === "{{") {
const query = simpleQuery(content, queryWord);
if (query) return LS.custom_query(query);
}
}
logseq.kits.copyquery = Kits.addClickEventToButton.bind(null, async function onCopyParentClicked(e) {
const child = e.target.closest("div.ls-block");
const parent = child.parentElement.closest("div.ls-block");
const blockId = parent.getAttribute("blockid");
const block = await 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");
const out = [];
res.forEach((r) => {
const name = r.name;
if (name) out.push(`[[${name}]]`);
else out.push(r.content);
});
navigator.clipboard.writeText(out.join("\n"));
Msg.ofStatus("Copied " + res.length, "success");
});
Thanks again for all your help!
I installed Kits fine and the
{{evalparent}}
works fine. I added your button
{{copyparent}}
and, while it worked at first, stopped working completely even thought the evalparent buttons still works. I’m trying to use the code on a simple query since cuvee doesn’t work anymore. This is my query
{{query (and (property :year [[2025]]) (property :month) (property :status) (sort-by month asc) ))}}
Any assistance you can offer would be appreciated.
I’ve restarted logseq several times with no luck. I just need a CSV output.
I added the code from the previous reply to my config.js file but I’m not clear how to get the button on how to run the code. Any support would be appreciated.
What has changed in between?
- What is “cuvee”?
- I have made a change for advanced queries, please try the new code.
- If the problem remains, check the console for any errors (open with
Ctrl + Shift + i
).
Thanks for making the change. Where would I find the new code?
Cuvee is an old plugin that would have solved my problem but it doesn’t work anymore as it has not been updated in 3 years.
Nothing changed in between that I can determine. However, my graph is stored on OneDrive so I wonder if that has something to do with it.
The new code is exactly where the old code was (i.e. on the same post), as it has replaced it. Just update the code of page CopyQuery
with the one in the post (which is now the new code), restart Logseq, then try again.
The only potential problem concerning the storing place is if the content of a file is not the expected one. As long as the content is correct, it doesn’t matter where it is stored.
Thanks. I will try it out. I moved my graph off of onedrive into a local folder and the script seems to work their fine. This is so weird. I found that, when on onedrive, I don’t always see the Kits message when I launch logseq. I wonder if it’s possible to keep the config files in a local folder and point logseq there. Moving my graph out of onedrive isn’t ideal for me.
Great! It works now while on onedrive.
What would help, if possible, would be a message that confirms that the info was copied.
There is already a confirmation message, as seen at the last statement of the code. It appears for a few seconds at the top-right of the window.
Check file config.edn
for entries custom.css
and custom.js
Ok I will pay attention to the top for notifications then. Thanks for your help and I will check those files.
@mentaloid The query worked for me but only copied the properties and not the child blocks. My query result looks like this
and this is my query
{{query (and (property :year [[2025]]) (property :month) (property :status) (sort-by month asc) ))}}
Here is a basic implementation to include children:
- Add in the code this function:
function pushChildTree(child){ const block = LS.get_block(child[1]) this.push(block.content) block.children.forEach(pushChildTree, this) }
- Find and replace:
- these lines:
if (name) out.push(name) else out.push(r.content)
- with these lines:
if (name) { out.push(name) } else { out.push(r.content) LS.get_block(r.id).children.forEach(pushChildTree, out) }
- these lines:
- Restart Logseq.
This worked like a charm. Thanks