mod eval;
mod parse;
use crate::graph::PageStore;
use crate::parser::PageId;
use lazy_static::lazy_static;
use regex::Regex;
pub use parse::QueryExpr;
lazy_static! {
/// Matches <div class="query-fallback"><code>...</code><div class="query-note">This query uses advanced features. View in Logseq for live results.</div></div> inline queries
static ref QUERY_INLINE_RE: Regex = Regex::new(
r"(?s)\{\{query\s+(.*?)\}\}"
).unwrap();
/// Matches #+BEGIN_QUERY ... #+END_QUERY blocks
static ref QUERY_BLOCK_RE: Regex = Regex::new(
r"(?sm)^\s*#\+BEGIN_QUERY\s*\n(.*?)\s*#\+END_QUERY"
).unwrap();
}
/// Resolve all query blocks in markdown content, returning HTML with results.
pub fn resolve_queries(markdown: &str, store: &PageStore) -> String {
// Fast path: skip if no query patterns
if !markdown.contains("{{query") && !markdown.contains("#+BEGIN_QUERY") {
return markdown.to_string();
}
let mut result = markdown.to_string();
// Resolve inline <div class="query-fallback"><code>...</code><div class="query-note">This query uses advanced features. View in Logseq for live results.</div></div> blocks
result = QUERY_INLINE_RE
.replace_all(&result, |caps: ®ex::Captures| {
let query_text = caps[1].trim();
match parse::parse_query(query_text) {
Some(expr) => {
let page_ids = eval::evaluate(&expr, store);
render_query_results(query_text, &page_ids, store)
}
None => render_query_fallback(query_text),
}
})
.to_string();
// Resolve #+BEGIN_QUERY ... #+END_QUERY blocks
result = QUERY_BLOCK_RE
.replace_all(&result, |caps: ®ex::Captures| {
let query_body = caps[1].trim();
render_query_fallback(query_body)
})
.to_string();
result
}
/// Render query results as an HTML list of linked page titles.
fn render_query_results(query_text: &str, page_ids: &[PageId], store: &PageStore) -> String {
if page_ids.is_empty() {
return format!(
"<div class=\"query-results\">\
<div class=\"query-results-header\">Query: <code>{}</code></div>\
<p><em>No results</em></p></div>",
html_escape(query_text)
);
}
let mut items = String::new();
for id in page_ids {
if let Some(page) = store.pages.get(id) {
items.push_str(&format!(
"<li><a href=\"/{}\" class=\"internal-link\">{}</a></li>\n",
id, html_escape(&page.meta.title)
));
}
}
format!(
"<div class=\"query-results\">\
<div class=\"query-results-header\">Query: <code>{}</code> ({} results)</div>\
<ul>\n{}</ul></div>",
html_escape(query_text),
page_ids.len(),
items
)
}
/// Render an unrecognized query as a styled fallback.
fn render_query_fallback(query_text: &str) -> String {
format!(
"<div class=\"query-fallback\">\
<code>{}</code>\
<div class=\"query-note\">This query uses advanced features. View in Logseq for live results.</div>\
</div>",
html_escape(query_text)
)
}
fn html_escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
render/src/query/mod.rs
ฯ 0.0%