Render frontmatter as styled cards in public share view
Split search query tokens on word boundaries for accurate inverted-index matching
This commit is contained in:
parent
dc9684e56c
commit
7c4f2964eb
@ -2866,6 +2866,25 @@ async def public_share_view(token: str):
|
||||
post = parse_markdown_file(raw)
|
||||
html = _render_markdown(post.content, share["vault"], file_path)
|
||||
title = post.metadata.get("title", file_path.stem)
|
||||
|
||||
# Build frontmatter section HTML
|
||||
fm_html = ""
|
||||
if post.metadata:
|
||||
fm_items = []
|
||||
skip_keys = {"title", "titre"}
|
||||
for k, v in post.metadata.items():
|
||||
if k in skip_keys:
|
||||
continue
|
||||
if isinstance(v, list):
|
||||
v = ", ".join(str(x) for x in v)
|
||||
elif isinstance(v, bool):
|
||||
v = "✓" if v else "✗"
|
||||
elif v is None:
|
||||
v = "—"
|
||||
fm_items.append(f'<div class="fm-row"><span class="fm-key">{k}</span><span class="fm-val">{v}</span></div>')
|
||||
if fm_items:
|
||||
fm_html = f'<div class="fm-section"><div class="fm-header">Frontmatter</div><div class="fm-body">{"".join(fm_items)}</div></div>'
|
||||
|
||||
return HTMLResponse(f"""<!DOCTYPE html><html lang="fr" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{title} — ObsiGate Share</title>
|
||||
<style>
|
||||
@ -2890,6 +2909,12 @@ body{{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:
|
||||
.content code{{font-size:0.9em;background:var(--bg-card);padding:1px 4px;border-radius:3px}}
|
||||
.content pre code{{background:none;padding:0}}
|
||||
.content a{{color:var(--accent)}}.content img{{max-width:100%;border-radius:6px}}
|
||||
.fm-section{{background:var(--bg-card);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:20px}}
|
||||
.fm-header{{font-weight:600;font-size:0.8rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px}}
|
||||
.fm-body{{display:grid;grid-template-columns:1fr 2fr;gap:4px 12px;font-size:0.85rem}}
|
||||
.fm-row{{display:contents}}
|
||||
.fm-key{{color:var(--accent);font-weight:500}}
|
||||
.fm-val{{color:var(--text);word-break:break-word}}
|
||||
.content blockquote{{border-left:3px solid var(--accent);padding-left:16px;color:var(--text-muted);margin:12px 0}}
|
||||
.content table{{border-collapse:collapse;width:100%;margin:12px 0}}
|
||||
.content th,.content td{{border:1px solid var(--border);padding:8px 12px;text-align:left}}
|
||||
@ -2917,7 +2942,7 @@ body{{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:
|
||||
PDF
|
||||
</button>
|
||||
</div>
|
||||
<div class="content" id="content">{html}</div>
|
||||
<div class="content" id="content">{fm_html}{html}</div>
|
||||
<script>
|
||||
function toggleTheme(){{var t=document.documentElement;var isDark=t.dataset.theme==="dark";t.dataset.theme=isDark?"light":"dark";document.getElementById("theme-icon-dark").style.display=isDark?"none":"";document.getElementById("theme-icon-light").style.display=isDark?"":"none";localStorage.setItem("obsigate-share-theme",t.dataset.theme)}}
|
||||
(function(){{var s=localStorage.getItem("obsigate-share-theme");if(!s)s="dark";document.documentElement.dataset.theme=s;var isDark=s==="dark";document.getElementById("theme-icon-dark").style.display=isDark?"":"none";document.getElementById("theme-icon-light").style.display=isDark?"none":""}})();
|
||||
|
||||
@ -762,6 +762,7 @@ def _split_query_tokens(raw: str) -> List[str]:
|
||||
def _passes_search_filters(
|
||||
file_info: dict,
|
||||
query_terms: List[str],
|
||||
query_terms_raw: List[str],
|
||||
raw_query: str,
|
||||
case_sensitive: bool,
|
||||
whole_word: bool,
|
||||
@ -774,6 +775,7 @@ def _passes_search_filters(
|
||||
content = file_info.get("content", "")
|
||||
path = file_info.get("path", "")
|
||||
search_text = f"{title} {content}"
|
||||
search_text_norm = normalize_text(search_text)
|
||||
|
||||
# --- Regex mode ---
|
||||
if regex and raw_query:
|
||||
@ -786,20 +788,20 @@ def _passes_search_filters(
|
||||
if not pattern.search(search_text):
|
||||
return False
|
||||
except re.error:
|
||||
return False # invalid regex
|
||||
return False
|
||||
return _passes_path_filters(path, include_paths, exclude_paths)
|
||||
|
||||
# --- Case-sensitive ---
|
||||
if case_sensitive and query_terms:
|
||||
for term in query_terms:
|
||||
# --- Case-sensitive (use raw, non-normalized terms) ---
|
||||
if case_sensitive and query_terms_raw:
|
||||
for term in query_terms_raw:
|
||||
if term not in search_text:
|
||||
return False
|
||||
|
||||
# --- Whole-word ---
|
||||
# --- Whole-word (use normalized text + normalized terms) ---
|
||||
if whole_word and query_terms:
|
||||
for term in query_terms:
|
||||
pattern = re.compile(rf"\b{re.escape(term)}\b", re.IGNORECASE)
|
||||
if not pattern.search(search_text):
|
||||
if not pattern.search(search_text_norm):
|
||||
return False
|
||||
|
||||
# --- Path filters (glob-like) ---
|
||||
@ -870,8 +872,12 @@ def advanced_search(
|
||||
# Vault filter — parsed vault: overrides parameter
|
||||
effective_vault = parsed["vault"] or vault_filter
|
||||
|
||||
# Normalize free-text terms
|
||||
query_terms = [normalize_text(t) for t in parsed["terms"] if t.strip()]
|
||||
# Tokenize free-text terms (splits on non-word chars like dots)
|
||||
# "192.168" → ["192", "168"] for proper inverted index matching
|
||||
query_terms_raw = [t for t in parsed["terms"] if t.strip()]
|
||||
query_terms = []
|
||||
for t in query_terms_raw:
|
||||
query_terms.extend(tokenize(t))
|
||||
has_terms = len(query_terms) > 0
|
||||
|
||||
if not has_terms and not all_tags and not parsed["title"] and not parsed["path"] and not parsed["ext"]:
|
||||
@ -991,7 +997,7 @@ def advanced_search(
|
||||
if score > 0:
|
||||
# --- Post-filters: case-sensitive, whole-word, regex, path filters ---
|
||||
if not _passes_search_filters(
|
||||
file_info, query_terms, query,
|
||||
file_info, query_terms, query_terms_raw, " ".join(query_terms_raw) if query_terms_raw else query,
|
||||
case_sensitive, whole_word, regex, include_paths, exclude_paths
|
||||
):
|
||||
continue
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user