secondbrain-py 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brain/__init__.py +0 -0
- brain/__main__.py +18 -0
- brain/_capture_command.py +445 -0
- brain/_compose.py +52 -0
- brain/activity.py +206 -0
- brain/ask.py +631 -0
- brain/audio.py +591 -0
- brain/backfill/__init__.py +12 -0
- brain/backfill/search_extras.py +141 -0
- brain/backfill/source_rows.py +101 -0
- brain/bin/__init__.py +1 -0
- brain/bin/_launcher.py +107 -0
- brain/bin/down.py +8 -0
- brain/bin/launchd.py +268 -0
- brain/bin/monitor.py +570 -0
- brain/bin/rebuild.py +8 -0
- brain/bin/status.py +8 -0
- brain/bin/up.py +8 -0
- brain/brief.py +272 -0
- brain/capture.py +49 -0
- brain/chat.py +293 -0
- brain/cli.py +9760 -0
- brain/cli_claude.py +81 -0
- brain/cli_connect.py +285 -0
- brain/cli_demo.py +266 -0
- brain/config.py +1949 -0
- brain/connect.py +925 -0
- brain/db.py +540 -0
- brain/demo/__init__.py +452 -0
- brain/demo/corpus/manifest.json +403 -0
- brain/demo/embedder.py +74 -0
- brain/durations.py +84 -0
- brain/edit_session.py +156 -0
- brain/editor.py +67 -0
- brain/elicit/__init__.py +16 -0
- brain/elicit/detectors.py +250 -0
- brain/elicit/drafter.py +70 -0
- brain/elicit/queue.py +220 -0
- brain/elicit/schema.py +48 -0
- brain/elicit/session.py +445 -0
- brain/embedding_targets.py +54 -0
- brain/embeddings.py +424 -0
- brain/enrichment.py +808 -0
- brain/errors.py +357 -0
- brain/eval/__init__.py +129 -0
- brain/eval/answer_eval.py +281 -0
- brain/eval/baseline.py +265 -0
- brain/eval/concept_extraction.py +378 -0
- brain/eval/corpus.py +152 -0
- brain/eval/errors.py +19 -0
- brain/eval/graph_baseline.py +226 -0
- brain/eval/graph_retrieval.py +202 -0
- brain/eval/graph_runner.py +319 -0
- brain/eval/metrics.py +101 -0
- brain/eval/runner.py +223 -0
- brain/format.py +783 -0
- brain/gaps.py +390 -0
- brain/graph_rag/__init__.py +94 -0
- brain/graph_rag/_retrieval_common.py +113 -0
- brain/graph_rag/aggregates.py +303 -0
- brain/graph_rag/aliases/__init__.py +583 -0
- brain/graph_rag/backends/__init__.py +10 -0
- brain/graph_rag/backends/_age_helpers.py +473 -0
- brain/graph_rag/backends/age.py +782 -0
- brain/graph_rag/backends/base.py +272 -0
- brain/graph_rag/build.py +344 -0
- brain/graph_rag/communities.py +644 -0
- brain/graph_rag/communities_summary.py +437 -0
- brain/graph_rag/concepts.py +202 -0
- brain/graph_rag/cooccur.py +193 -0
- brain/graph_rag/cross_type.py +312 -0
- brain/graph_rag/extract.py +885 -0
- brain/graph_rag/fuse.py +371 -0
- brain/graph_rag/global_.py +412 -0
- brain/graph_rag/grouping.py +372 -0
- brain/graph_rag/person_resolver.py +167 -0
- brain/graph_rag/reconcile.py +792 -0
- brain/graph_rag/relational.py +353 -0
- brain/graph_rag/retrieve.py +526 -0
- brain/graph_rag/router.py +288 -0
- brain/graph_rag/schema.py +320 -0
- brain/graph_rag/sync.py +237 -0
- brain/graph_rag/tenancy.py +43 -0
- brain/graph_rag/themes.py +501 -0
- brain/graph_rag/weighting.py +202 -0
- brain/ingest/__init__.py +1926 -0
- brain/ingest/chunker.py +249 -0
- brain/ingest/docx.py +40 -0
- brain/ingest/gmail.py +621 -0
- brain/ingest/markdown.py +37 -0
- brain/ingest/pdf.py +61 -0
- brain/ingest/stdin.py +22 -0
- brain/ingest/sub_tokens.py +91 -0
- brain/ingest/text.py +16 -0
- brain/interactions.py +205 -0
- brain/maintenance.py +355 -0
- brain/mcp_server.py +3405 -0
- brain/migrations/001_init.sql +43 -0
- brain/migrations/002_qwen3_embedding.sql +17 -0
- brain/migrations/003_vault_model.sql +41 -0
- brain/migrations/004_relax_content_hash_uniqueness.sql +18 -0
- brain/migrations/005_derived_links.sql +67 -0
- brain/migrations/006_dedup_file_by_source_path.sql +25 -0
- brain/migrations/007_email_thread_and_draft.sql +15 -0
- brain/migrations/008_gmail_thread_unique.sql +11 -0
- brain/migrations/009_chunks_weighted_tsv.sql +28 -0
- brain/migrations/010_interactions.sql +30 -0
- brain/migrations/011_documents_summary.sql +23 -0
- brain/migrations/012_graphrag.sql +171 -0
- brain/migrations/013_graphrag_communities.sql +125 -0
- brain/migrations/014_graphrag_community_summary_hash.sql +33 -0
- brain/migrations/015_interactions_graph_targets.sql +89 -0
- brain/migrations/016_index_hygiene.sql +61 -0
- brain/migrations/017_elicit.sql +30 -0
- brain/migrations/018_review_gap_signal_kinds.sql +40 -0
- brain/migrations/019_search_queries.sql +35 -0
- brain/migrations/020_link_suggestions.sql +40 -0
- brain/migrations/021_timeline_doc_date.sql +34 -0
- brain/migrations/022_link_suggestions_undirected.sql +84 -0
- brain/migrations/023_search_queries_fts_count.sql +28 -0
- brain/quartz_overrides/__init__.py +8 -0
- brain/quartz_overrides/quartz/bootstrap-cli.mjs +65 -0
- brain/quartz_overrides/quartz/build.ts +568 -0
- brain/quartz_overrides/quartz/cli/args.js +152 -0
- brain/quartz_overrides/quartz/cli/build_partial_handler.js +544 -0
- brain/quartz_overrides/quartz/cli/handlers.js +636 -0
- brain/quartz_overrides/quartz/components/CommandPalette.tsx +172 -0
- brain/quartz_overrides/quartz/components/Explorer.tsx +198 -0
- brain/quartz_overrides/quartz/components/Footer.tsx +27 -0
- brain/quartz_overrides/quartz/components/Graph.tsx +468 -0
- brain/quartz_overrides/quartz/components/PageTitle.tsx +72 -0
- brain/quartz_overrides/quartz/components/RelatedDocs.tsx +38 -0
- brain/quartz_overrides/quartz/components/Search.tsx +161 -0
- brain/quartz_overrides/quartz/components/SummaryLede.tsx +72 -0
- brain/quartz_overrides/quartz/components/index.ts +92 -0
- brain/quartz_overrides/quartz/components/pages/TagContent.tsx +272 -0
- brain/quartz_overrides/quartz/components/scripts/commandPalette.inline.ts +665 -0
- brain/quartz_overrides/quartz/components/scripts/explorer.inline.ts +768 -0
- brain/quartz_overrides/quartz/components/scripts/graph.inline.ts +2302 -0
- brain/quartz_overrides/quartz/components/scripts/relatedDocs.inline.ts +163 -0
- brain/quartz_overrides/quartz/components/scripts/search.inline.ts +1011 -0
- brain/quartz_overrides/quartz/plugins/emitters/contentIndex.ts +546 -0
- brain/quartz_overrides/quartz/plugins/transformers/codeCopy.ts +94 -0
- brain/quartz_overrides/quartz/plugins/transformers/derivedFenceMark.ts +302 -0
- brain/quartz_overrides/quartz/plugins/transformers/emailThread.ts +148 -0
- brain/quartz_overrides/quartz/plugins/transformers/emptyDoorFilter.ts +213 -0
- brain/quartz_overrides/quartz/plugins/transformers/index.ts +114 -0
- brain/quartz_overrides/quartz/plugins/transformers/linkKindMark.ts +205 -0
- brain/quartz_overrides/quartz/plugins/transformers/linkSourceTag.ts +104 -0
- brain/quartz_overrides/quartz/plugins/transformers/relativeDate.ts +100 -0
- brain/quartz_overrides/quartz/plugins/transformers/reloadSignal.ts +131 -0
- brain/quartz_overrides/quartz/processors/parse.ts +371 -0
- brain/quartz_overrides/quartz/processors/parser_cache.ts +78 -0
- brain/quartz_overrides/quartz/static/brain-logo-dark.png +0 -0
- brain/quartz_overrides/quartz/static/brain-logo-light.png +0 -0
- brain/quartz_overrides/quartz/static/codeCopy.js +196 -0
- brain/quartz_overrides/quartz/static/emailThread.js +334 -0
- brain/quartz_overrides/quartz/static/favicon.ico +0 -0
- brain/quartz_overrides/quartz/static/icon.png +0 -0
- brain/quartz_overrides/quartz/static/linkSourceTag.js +104 -0
- brain/quartz_overrides/quartz/static/relativeDate.js +142 -0
- brain/quartz_overrides/quartz/static/reload.js +168 -0
- brain/quartz_overrides/quartz/styles/brain/_article.scss +252 -0
- brain/quartz_overrides/quartz/styles/brain/_atmosphere.scss +113 -0
- brain/quartz_overrides/quartz/styles/brain/_callouts.scss +180 -0
- brain/quartz_overrides/quartz/styles/brain/_cmdk.scss +7 -0
- brain/quartz_overrides/quartz/styles/brain/_code.scss +208 -0
- brain/quartz_overrides/quartz/styles/brain/_command_palette.scss +369 -0
- brain/quartz_overrides/quartz/styles/brain/_email_thread.scss +228 -0
- brain/quartz_overrides/quartz/styles/brain/_explorer.scss +142 -0
- brain/quartz_overrides/quartz/styles/brain/_home.scss +182 -0
- brain/quartz_overrides/quartz/styles/brain/_links.scss +322 -0
- brain/quartz_overrides/quartz/styles/brain/_marginalia.scss +117 -0
- brain/quartz_overrides/quartz/styles/brain/_motion.scss +175 -0
- brain/quartz_overrides/quartz/styles/brain/_people_hub.scss +100 -0
- brain/quartz_overrides/quartz/styles/brain/_related_docs.scss +137 -0
- brain/quartz_overrides/quartz/styles/brain/_search.scss +252 -0
- brain/quartz_overrides/quartz/styles/brain/_sidebar.scss +468 -0
- brain/quartz_overrides/quartz/styles/brain/_summary_lede.scss +56 -0
- brain/quartz_overrides/quartz/styles/brain/_surface.scss +43 -0
- brain/quartz_overrides/quartz/styles/brain/_tag_content.scss +118 -0
- brain/quartz_overrides/quartz/styles/brain/_tokens.scss +197 -0
- brain/quartz_overrides/quartz/styles/brain/_typography.scss +92 -0
- brain/quartz_overrides/quartz/styles/custom.scss +89 -0
- brain/quartz_overrides/quartz/styles/graph.scss +505 -0
- brain/quartz_overrides/quartz/util/ctx.ts +92 -0
- brain/quartz_overrides/quartz/util/fastpath_manifest.ts +608 -0
- brain/quartz_overrides/quartz/util/path.ts +358 -0
- brain/quartz_overrides/quartz/util/sourceIcons.ts +55 -0
- brain/quartz_overrides/quartz.config.ts +270 -0
- brain/quartz_overrides/quartz.layout.ts +314 -0
- brain/queries.py +1188 -0
- brain/rank_fusion.py +8 -0
- brain/resurface.py +210 -0
- brain/review/__init__.py +26 -0
- brain/review/emit.py +27 -0
- brain/review/queries.py +436 -0
- brain/review/render.py +196 -0
- brain/review/scans.py +355 -0
- brain/review/weekly.py +413 -0
- brain/search.py +704 -0
- brain/set_similarity.py +15 -0
- brain/setup.py +1205 -0
- brain/tags.py +56 -0
- brain/templates/Caddyfile.j2 +9 -0
- brain/templates/__init__.py +1 -0
- brain/templates/bin/__init__.py +1 -0
- brain/templates/bin/_brain-brief-fg.sh +25 -0
- brain/templates/bin/_brain-build-fg.sh +53 -0
- brain/templates/bin/_brain-watcher-fg.sh +65 -0
- brain/templates/bin/brain-down.sh +89 -0
- brain/templates/bin/brain-status.sh +83 -0
- brain/templates/bin/brain-up.sh +221 -0
- brain/templates/docker/age/Dockerfile +79 -0
- brain/templates/docker-compose.stock.yml.j2 +26 -0
- brain/templates/docker-compose.yml.j2 +34 -0
- brain/templates/env.example +190 -0
- brain/templates/launchd/__init__.py +1 -0
- brain/templates/launchd/com.brain.brief.plist.j2 +45 -0
- brain/templates/launchd/com.brain.build.plist.j2 +46 -0
- brain/templates/launchd/com.brain.watcher.plist.j2 +46 -0
- brain/templates/skill/SKILL.md +63 -0
- brain/templates/skill/__init__.py +1 -0
- brain/timeline.py +834 -0
- brain/todo.py +124 -0
- brain/uninstall.py +185 -0
- brain/vault/__init__.py +115 -0
- brain/vault/_atomic.py +25 -0
- brain/vault/daily_index.py +228 -0
- brain/vault/derived_links/__init__.py +50 -0
- brain/vault/derived_links/directory.py +683 -0
- brain/vault/derived_links/fence.py +408 -0
- brain/vault/derived_links/gws.py +64 -0
- brain/vault/derived_links/participants.py +143 -0
- brain/vault/derived_links/pass_runner.py +362 -0
- brain/vault/derived_links/rules.py +137 -0
- brain/vault/export.py +683 -0
- brain/vault/frontmatter.py +165 -0
- brain/vault/graph.py +620 -0
- brain/vault/graph_format.py +388 -0
- brain/vault/link_rewrite.py +235 -0
- brain/vault/links.py +260 -0
- brain/vault/note_builder.py +211 -0
- brain/vault/paths.py +55 -0
- brain/vault/quartz_overlay.py +236 -0
- brain/vault/rename.py +591 -0
- brain/vault/resolver.py +304 -0
- brain/vault/slug.py +127 -0
- brain/vault/sync.py +1513 -0
- brain/vault/sync_summaries.py +264 -0
- brain/vault/templates.py +145 -0
- brain/vault/watch.py +1052 -0
- brain/wiki/__init__.py +6 -0
- brain/wiki/_github_slugger.py +76 -0
- brain/wiki/_person_name.py +314 -0
- brain/wiki/build_homepage.py +541 -0
- brain/wiki/build_partial.py +273 -0
- brain/wiki/build_people.py +934 -0
- brain/wiki/build_related.py +758 -0
- brain/wiki/build_swap.py +585 -0
- brain/wiki/build_watcher.py +975 -0
- brain/wiki/edit_classifier.py +215 -0
- brain/wiki/errors.py +10 -0
- brain/wiki/fastpath_manifest.py +475 -0
- brain/wiki/fastpath_state.py +174 -0
- brain/wiki/install.py +296 -0
- brain/wiki/slug.py +111 -0
- secondbrain_py-0.2.1.dist-info/METADATA +195 -0
- secondbrain_py-0.2.1.dist-info/RECORD +273 -0
- secondbrain_py-0.2.1.dist-info/WHEEL +5 -0
- secondbrain_py-0.2.1.dist-info/entry_points.txt +11 -0
- secondbrain_py-0.2.1.dist-info/licenses/LICENSE +21 -0
- secondbrain_py-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Brain Search component — Phase 3.2 of the Wiki UX Overhaul.
|
|
2
|
+
//
|
|
3
|
+
// This file is a TEMPLATE. It is installed at
|
|
4
|
+
// `<vault>/.quartz/quartz/components/Search.tsx` by `brain vault
|
|
5
|
+
// render --overlay`, OVERWRITING the stock Quartz `Search.tsx` so the
|
|
6
|
+
// component barrel (`components/index.ts`) re-exports the brain
|
|
7
|
+
// override instead of upstream. It does NOT compile or run from the
|
|
8
|
+
// brain repo itself — esbuild-loader inside the cloned Quartz
|
|
9
|
+
// workspace bundles `./scripts/search.inline` into a string that this
|
|
10
|
+
// component publishes via `Search.afterDOMLoaded`. The string is then
|
|
11
|
+
// injected at `</body>` time by Quartz's `renderPage.tsx`.
|
|
12
|
+
//
|
|
13
|
+
// Why this override exists: stock Quartz renders search results as
|
|
14
|
+
// `<h3>title</h3><p>snippet</p>` and offers no source filter. The brain
|
|
15
|
+
// corpus mixes ingested transcripts (krisp / slack / gmail) with
|
|
16
|
+
// authored notes (manual / vault), so a search popover that doesn't
|
|
17
|
+
// surface "where did this come from" makes results visually
|
|
18
|
+
// indistinguishable. P3.2 adds:
|
|
19
|
+
//
|
|
20
|
+
// * Source-icon prefix per result row — a one-glance visual key for
|
|
21
|
+
// gmail / krisp / slack / manual / vault.
|
|
22
|
+
// * Source filter chips above the input — toggle to constrain the
|
|
23
|
+
// visible results. State persists in `localStorage` under
|
|
24
|
+
// `brain.search.activeSources` so a user's preferred slice
|
|
25
|
+
// survives SPA navigation and full reloads.
|
|
26
|
+
// * Lazy preview pane — when a result is selected (hover / arrow),
|
|
27
|
+
// fetch `static/contentBodies/<slug>.json` (P3.1's split body file)
|
|
28
|
+
// and render the full content. P3.1 left only a 240-char snippet
|
|
29
|
+
// in `contentIndex.json`; without lazy fetching the preview pane
|
|
30
|
+
// would be permanently truncated.
|
|
31
|
+
// * Date column — short ISO date or "Nd ago" for the past week.
|
|
32
|
+
//
|
|
33
|
+
// The component layer (this file) only ships the markup + class hooks.
|
|
34
|
+
// All dynamic behaviour — chip state, lazy fetching, fuzzy match — is
|
|
35
|
+
// owned by the inline script at `./scripts/search.inline.ts`.
|
|
36
|
+
//
|
|
37
|
+
// brain: source-icon mapping. Pinned here at module scope so the same
|
|
38
|
+
// glyph table renders both inline (`renderResultRow` constant
|
|
39
|
+
// references it) and in the SSR'd chip rail (the script reads it at
|
|
40
|
+
// boot via the embedded JSON below). Unknown sources fall back to the
|
|
41
|
+
// vault glyph — better than rendering an empty span.
|
|
42
|
+
//
|
|
43
|
+
// Coordination point: if a future ingest source is added (e.g. brain
|
|
44
|
+
// grows a `notion` extractor), append it ONCE in
|
|
45
|
+
// `quartz_overrides/quartz/util/sourceIcons.ts` (the canonical
|
|
46
|
+
// SOURCE_ICONS table). This component, the inline search script, and
|
|
47
|
+
// the TagContent override all import from that one util — so the new
|
|
48
|
+
// glyph cascades to all three surfaces. Also append it to
|
|
49
|
+
// `_search.scss`'s chip palette (via the chip data attribute) AND to
|
|
50
|
+
// `commandPalette.inline.ts`'s `kindIcon()` helper if Cmd-K is rebuilt
|
|
51
|
+
// to share the same icon vocabulary.
|
|
52
|
+
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
|
53
|
+
// @ts-ignore — esbuild-loader rewrites this to a bundled string.
|
|
54
|
+
import script from "./scripts/search.inline"
|
|
55
|
+
import { classNames } from "../util/lang"
|
|
56
|
+
import { i18n } from "../i18n"
|
|
57
|
+
import { SOURCE_ICONS, SOURCE_CHIP_ORDER } from "../util/sourceIcons"
|
|
58
|
+
|
|
59
|
+
export interface SearchOptions {
|
|
60
|
+
enablePreview: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const defaultOptions: SearchOptions = {
|
|
64
|
+
enablePreview: true,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// brain (P3.6 fix-4): source-icon table consolidated. Previously this
|
|
68
|
+
// file declared its own `SOURCE_ICONS` constant duplicated against
|
|
69
|
+
// `util/sourceIcons.ts` and `search.inline.ts`. All three sites now
|
|
70
|
+
// share the canonical util — adding a new source is a one-line change
|
|
71
|
+
// in `util/sourceIcons.ts`. The chip rail still serializes the table
|
|
72
|
+
// into `data-brain-source-icons` so the inline script reads it back at
|
|
73
|
+
// boot via `readSourceIcons` (defense in depth: even if the import
|
|
74
|
+
// chain breaks at runtime, the SSR'd attribute keeps the chip glyphs
|
|
75
|
+
// rendering).
|
|
76
|
+
|
|
77
|
+
// brain: ordered list of chip values rendered above the search input.
|
|
78
|
+
// Order is deterministic and pinned (vs. derived from the index) so
|
|
79
|
+
// the chip rail looks identical even when the loaded corpus is missing
|
|
80
|
+
// one source. Matches the `chipVocabularies.source` order in
|
|
81
|
+
// `graph.inline.ts` so the two filter rails read as siblings. Sourced
|
|
82
|
+
// from the shared `SOURCE_CHIP_ORDER` so the order stays in lock-step
|
|
83
|
+
// with the canonical util.
|
|
84
|
+
const CHIP_VALUES: ReadonlyArray<keyof typeof SOURCE_ICONS> = SOURCE_CHIP_ORDER
|
|
85
|
+
|
|
86
|
+
export default ((userOpts?: Partial<SearchOptions>) => {
|
|
87
|
+
const Search: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
|
88
|
+
const opts = { ...defaultOptions, ...userOpts }
|
|
89
|
+
const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder
|
|
90
|
+
// brain: serialize the icon table once into a data attribute so the
|
|
91
|
+
// inline script can rebuild the chip glyphs without a duplicate
|
|
92
|
+
// hard-coded copy. JSON.stringify keeps the table escapable through
|
|
93
|
+
// the HTML attribute boundary.
|
|
94
|
+
const iconsJson = JSON.stringify(SOURCE_ICONS)
|
|
95
|
+
return (
|
|
96
|
+
<div class={classNames(displayClass, "search")}>
|
|
97
|
+
<button class="search-button">
|
|
98
|
+
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7">
|
|
99
|
+
<title>Search</title>
|
|
100
|
+
<g class="search-path" fill="none">
|
|
101
|
+
<path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4" />
|
|
102
|
+
<circle cx="8" cy="8" r="7" />
|
|
103
|
+
</g>
|
|
104
|
+
</svg>
|
|
105
|
+
<p>{i18n(cfg.locale).components.search.title}</p>
|
|
106
|
+
</button>
|
|
107
|
+
<div class="search-container">
|
|
108
|
+
<div class="search-space">
|
|
109
|
+
{/* brain: chip rail above the input. The script attaches
|
|
110
|
+
click handlers and reads the icon table from the
|
|
111
|
+
`data-brain-source-icons` attribute on the rail's root.
|
|
112
|
+
`data-active="true"` is reflected by the script when
|
|
113
|
+
a chip is in the active set; CSS keys hover/focus
|
|
114
|
+
styling off the same attribute. */}
|
|
115
|
+
<div
|
|
116
|
+
class="brain-search-chips"
|
|
117
|
+
role="group"
|
|
118
|
+
aria-label="Filter by source"
|
|
119
|
+
data-brain-source-icons={iconsJson}
|
|
120
|
+
>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="brain-search-chip brain-search-chip-all"
|
|
124
|
+
data-brain-source="__all__"
|
|
125
|
+
data-active="true"
|
|
126
|
+
>
|
|
127
|
+
All
|
|
128
|
+
</button>
|
|
129
|
+
{CHIP_VALUES.map((value) => (
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
class="brain-search-chip"
|
|
133
|
+
data-brain-source={value}
|
|
134
|
+
data-active="true"
|
|
135
|
+
>
|
|
136
|
+
<span class="brain-search-chip-icon" aria-hidden="true">
|
|
137
|
+
{SOURCE_ICONS[value]}
|
|
138
|
+
</span>
|
|
139
|
+
<span class="brain-search-chip-label">{value}</span>
|
|
140
|
+
</button>
|
|
141
|
+
))}
|
|
142
|
+
</div>
|
|
143
|
+
<input
|
|
144
|
+
autocomplete="off"
|
|
145
|
+
class="search-bar"
|
|
146
|
+
name="search"
|
|
147
|
+
type="text"
|
|
148
|
+
aria-label={searchPlaceholder}
|
|
149
|
+
placeholder={searchPlaceholder}
|
|
150
|
+
/>
|
|
151
|
+
<div class="search-layout" data-preview={opts.enablePreview}></div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Search.afterDOMLoaded = script
|
|
159
|
+
|
|
160
|
+
return Search
|
|
161
|
+
}) satisfies QuartzComponentConstructor
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Brain summary-lede component — wave Q2-SUMMARY-WIKI.
|
|
2
|
+
//
|
|
3
|
+
// This file is a TEMPLATE. It is installed at
|
|
4
|
+
// `<vault>/.quartz/quartz/components/SummaryLede.tsx` by `brain vault
|
|
5
|
+
// render --overlay`. It does NOT compile or run from the brain repo
|
|
6
|
+
// itself; the imports below resolve against the dependencies Quartz
|
|
7
|
+
// pulls into the cloned workspace via `npm install`, not against any
|
|
8
|
+
// package brain ships.
|
|
9
|
+
//
|
|
10
|
+
// Tested against Quartz v4.5.x (April 2026). The component shape
|
|
11
|
+
// mirrors stock Quartz components — see `Backlinks.tsx` (also a
|
|
12
|
+
// brain-overlayed component) for the canonical pattern. If a future
|
|
13
|
+
// Quartz version restructures `QuartzComponent` / `QuartzComponentProps`,
|
|
14
|
+
// pull the latest reference component from
|
|
15
|
+
// https://github.com/jackyzha0/quartz/blob/v4/quartz/components/
|
|
16
|
+
// and re-apply the brain tweaks below.
|
|
17
|
+
//
|
|
18
|
+
// Strategy — render `fileData.frontmatter.summary` as an inline TL;DR
|
|
19
|
+
// above the article body. The Q1-D ``OllamaEnricher`` writes
|
|
20
|
+
// ``documents.summary``; the Q2 vault export pipeline plumbs that
|
|
21
|
+
// value into ``summary:`` frontmatter on the mirror file. This
|
|
22
|
+
// component reads that frontmatter key and renders an `<aside>` block
|
|
23
|
+
// with a soft "AI summary" eyebrow.
|
|
24
|
+
//
|
|
25
|
+
// When the frontmatter doesn't carry a ``summary`` (vault-tier notes
|
|
26
|
+
// the enricher hasn't touched, or short docs below the
|
|
27
|
+
// ``BRAIN_ENRICH_MIN_TOKENS`` threshold), the component renders
|
|
28
|
+
// `null` — no empty aside, no visual leak.
|
|
29
|
+
//
|
|
30
|
+
// Pin: this component is registered in `quartz.layout.ts` under
|
|
31
|
+
// `defaultContentPageLayout.beforeBody`, immediately after
|
|
32
|
+
// `Component.TagList()` and before `Component.Breadcrumbs()`. That
|
|
33
|
+
// places the lede above the article body but below the title /
|
|
34
|
+
// content meta strip, which is the natural place for a TL;DR.
|
|
35
|
+
//
|
|
36
|
+
// Responsibility (CLAUDE.md rule 8): this file owns the Preact
|
|
37
|
+
// component wrapper. Visual rules live in
|
|
38
|
+
// `../styles/brain/_summary_lede.scss`. No inline script — the lede
|
|
39
|
+
// is a static SSR'd block with no interactive behavior.
|
|
40
|
+
|
|
41
|
+
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
|
42
|
+
import { classNames } from "../util/lang"
|
|
43
|
+
|
|
44
|
+
const SummaryLede: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
|
|
45
|
+
// brain: defensive type-narrowing — Quartz's `fileData.frontmatter`
|
|
46
|
+
// is typed as `Record<string, unknown>` because authors can write
|
|
47
|
+
// arbitrary YAML. Only a string `summary` is renderable; a list,
|
|
48
|
+
// object, or null falls through to `null` (no aside).
|
|
49
|
+
const raw = fileData.frontmatter?.summary
|
|
50
|
+
if (typeof raw !== "string") {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
const summary = raw.trim()
|
|
54
|
+
if (summary.length === 0) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<aside class={classNames(displayClass, "brain-summary-lede")} aria-label="AI summary">
|
|
60
|
+
<span class="brain-summary-lede-eyebrow" aria-hidden="true">
|
|
61
|
+
AI summary
|
|
62
|
+
</span>
|
|
63
|
+
<p class="brain-summary-lede-body">{summary}</p>
|
|
64
|
+
</aside>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// brain: no component-css here — visuals live in the global
|
|
69
|
+
// `_summary_lede.scss`, which is `@use`-imported via `custom.scss`. That
|
|
70
|
+
// ensures the styles ship with EVERY page in the build.
|
|
71
|
+
|
|
72
|
+
export default (() => SummaryLede) satisfies QuartzComponentConstructor
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Brain wiki — components barrel re-export with brain extensions wired in.
|
|
2
|
+
//
|
|
3
|
+
// This file is a TEMPLATE. It is installed at
|
|
4
|
+
// `<vault>/.quartz/quartz/components/index.ts` by `brain vault render
|
|
5
|
+
// --overlay`, OVERWRITING the stock Quartz barrel. The overlay is a
|
|
6
|
+
// 1:1 file copy (`src/brain/vault/quartz_overlay.py`), so this is
|
|
7
|
+
// the canonical seam for ensuring the brain-extension component
|
|
8
|
+
// files in this directory get re-exported under the
|
|
9
|
+
// `Component.*` namespace consumed by `quartz.layout.ts`.
|
|
10
|
+
//
|
|
11
|
+
// Tested against Quartz v4.5.x (April 2026). If a future Quartz
|
|
12
|
+
// version adds a new stock component, append its export below; the
|
|
13
|
+
// ones that already exist are sourced verbatim from
|
|
14
|
+
// https://github.com/jackyzha0/quartz/blob/v4/quartz/components/index.ts
|
|
15
|
+
// and the brain extensions are appended at the bottom in their own
|
|
16
|
+
// block.
|
|
17
|
+
//
|
|
18
|
+
// brain: stock Quartz component imports + exports — keep in lock-step
|
|
19
|
+
// with the upstream `index.ts` linked above. If you upgrade Quartz,
|
|
20
|
+
// diff upstream against this block and apply additions.
|
|
21
|
+
import Content from "./pages/Content"
|
|
22
|
+
import TagContent from "./pages/TagContent"
|
|
23
|
+
import FolderContent from "./pages/FolderContent"
|
|
24
|
+
import NotFound from "./pages/404"
|
|
25
|
+
import ArticleTitle from "./ArticleTitle"
|
|
26
|
+
import Darkmode from "./Darkmode"
|
|
27
|
+
import ReaderMode from "./ReaderMode"
|
|
28
|
+
import Head from "./Head"
|
|
29
|
+
import PageTitle from "./PageTitle"
|
|
30
|
+
import ContentMeta from "./ContentMeta"
|
|
31
|
+
import Spacer from "./Spacer"
|
|
32
|
+
import TableOfContents from "./TableOfContents"
|
|
33
|
+
import Explorer from "./Explorer"
|
|
34
|
+
import TagList from "./TagList"
|
|
35
|
+
import Graph from "./Graph"
|
|
36
|
+
import Backlinks from "./Backlinks"
|
|
37
|
+
import Search from "./Search"
|
|
38
|
+
import Footer from "./Footer"
|
|
39
|
+
import DesktopOnly from "./DesktopOnly"
|
|
40
|
+
import MobileOnly from "./MobileOnly"
|
|
41
|
+
import RecentNotes from "./RecentNotes"
|
|
42
|
+
import Breadcrumbs from "./Breadcrumbs"
|
|
43
|
+
import Comments from "./Comments"
|
|
44
|
+
import Flex from "./Flex"
|
|
45
|
+
import ConditionalRender from "./ConditionalRender"
|
|
46
|
+
|
|
47
|
+
// brain-extension: brain-only components added by the overlay. Each
|
|
48
|
+
// is documented in its own file's top-of-file comment.
|
|
49
|
+
// * CommandPalette — Lane C redesign — Cmd/Ctrl-P palette modal.
|
|
50
|
+
// Renders hidden markup once globally (registered in
|
|
51
|
+
// `quartz.layout.ts` `afterBody`); the inline script
|
|
52
|
+
// `scripts/commandPalette.inline.ts` owns the open/close + fuzzy
|
|
53
|
+
// search lifecycle.
|
|
54
|
+
import CommandPalette from "./CommandPalette"
|
|
55
|
+
import RelatedDocs from "./RelatedDocs"
|
|
56
|
+
// * SummaryLede — wave Q2-SUMMARY-WIKI — renders the auto-summary
|
|
57
|
+
// stored in `frontmatter.summary` (sourced from Q1-D
|
|
58
|
+
// `documents.summary`) as a TL;DR block above the article body.
|
|
59
|
+
// Pin: `defaultContentPageLayout.beforeBody` slot.
|
|
60
|
+
import SummaryLede from "./SummaryLede"
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
ArticleTitle,
|
|
64
|
+
Content,
|
|
65
|
+
TagContent,
|
|
66
|
+
FolderContent,
|
|
67
|
+
Darkmode,
|
|
68
|
+
ReaderMode,
|
|
69
|
+
Head,
|
|
70
|
+
PageTitle,
|
|
71
|
+
ContentMeta,
|
|
72
|
+
Spacer,
|
|
73
|
+
TableOfContents,
|
|
74
|
+
Explorer,
|
|
75
|
+
TagList,
|
|
76
|
+
Graph,
|
|
77
|
+
Backlinks,
|
|
78
|
+
Search,
|
|
79
|
+
Footer,
|
|
80
|
+
DesktopOnly,
|
|
81
|
+
MobileOnly,
|
|
82
|
+
RecentNotes,
|
|
83
|
+
NotFound,
|
|
84
|
+
Breadcrumbs,
|
|
85
|
+
Comments,
|
|
86
|
+
Flex,
|
|
87
|
+
ConditionalRender,
|
|
88
|
+
// brain-extension exports
|
|
89
|
+
CommandPalette,
|
|
90
|
+
RelatedDocs,
|
|
91
|
+
SummaryLede,
|
|
92
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// Brain TagContent — Phase 3.3 of the Wiki UX Overhaul.
|
|
2
|
+
//
|
|
3
|
+
// This file is a TEMPLATE. It is installed at
|
|
4
|
+
// `<vault>/.quartz/quartz/components/pages/TagContent.tsx` by `brain
|
|
5
|
+
// vault render --overlay`, OVERWRITING the stock Quartz page renderer
|
|
6
|
+
// for tag listing pages. It does NOT compile or run from the brain
|
|
7
|
+
// repo itself; imports are resolved by the cloned Quartz workspace.
|
|
8
|
+
//
|
|
9
|
+
// Why this override exists: stock Quartz's tag pages render via
|
|
10
|
+
// `<PageList>` which produces a flat list with date + title + tag
|
|
11
|
+
// pills. The brain corpus mixes ingested transcripts (krisp / slack /
|
|
12
|
+
// gmail) with authored notes (manual / vault), so a tag listing that
|
|
13
|
+
// doesn't surface the source is visually indistinguishable. P3.3
|
|
14
|
+
// adds:
|
|
15
|
+
//
|
|
16
|
+
// * Source-icon prefix per row — same vocabulary as the search
|
|
17
|
+
// popover (krisp 🎙️ / slack 💬 / gmail 📧 / manual ✍️ / vault 🌱).
|
|
18
|
+
// * 1-line snippet around the first occurrence of the tag in the
|
|
19
|
+
// doc's auto-generated description (falls back to the leading
|
|
20
|
+
// 200 chars when the tag word is not present in the description).
|
|
21
|
+
// * Explicit `tagged: #<tag1> #<tag2>` footer enumerating all the
|
|
22
|
+
// doc's tags — replaces the upstream tag-pill cluster with a
|
|
23
|
+
// single denser footer line.
|
|
24
|
+
//
|
|
25
|
+
// Upstream's index-mode (the `tags === "/"` overview page listing all
|
|
26
|
+
// tags) is preserved verbatim — only the per-tag listing path is
|
|
27
|
+
// overridden. The brain customisation is the rendering of each doc
|
|
28
|
+
// row inside the `else` branch.
|
|
29
|
+
//
|
|
30
|
+
// Tested against Quartz v4.5.x (April 2026). If a future Quartz
|
|
31
|
+
// version restructures the `TagContent` component (e.g. moves the
|
|
32
|
+
// per-tag rendering into its own file, or changes the
|
|
33
|
+
// `QuartzComponentProps` shape), pull the latest from
|
|
34
|
+
// https://github.com/jackyzha0/quartz/blob/v4/quartz/components/pages/TagContent.tsx
|
|
35
|
+
// and re-apply the brain delta marked with `// brain:` below.
|
|
36
|
+
|
|
37
|
+
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
|
38
|
+
import style from "../styles/listPage.scss"
|
|
39
|
+
import { Date as QuartzDate, getDate } from "../Date"
|
|
40
|
+
import { PageList, SortFn, byDateAndAlphabeticalFolderFirst } from "../PageList"
|
|
41
|
+
import { FullSlug, getAllSegmentPrefixes, resolveRelative, simplifySlug } from "../../util/path"
|
|
42
|
+
import { QuartzPluginData } from "../../plugins/vfile"
|
|
43
|
+
import { Root } from "hast"
|
|
44
|
+
import { htmlToJsx } from "../../util/jsx"
|
|
45
|
+
import { i18n } from "../../i18n"
|
|
46
|
+
import { ComponentChildren } from "preact"
|
|
47
|
+
import { concatenateResources } from "../../util/resources"
|
|
48
|
+
import { inferSource, sourceIconFor } from "../../util/sourceIcons"
|
|
49
|
+
|
|
50
|
+
interface TagContentOptions {
|
|
51
|
+
sort?: SortFn
|
|
52
|
+
numPages: number
|
|
53
|
+
// brain-extension: chars on either side of the first tag-occurrence
|
|
54
|
+
// in the description when constructing the per-row snippet. Pinned
|
|
55
|
+
// here so a future tweak (e.g. expanding to 240 chars to match the
|
|
56
|
+
// P3.1 contentIndex snippet budget) is a single-line change.
|
|
57
|
+
snippetWindow: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const defaultOptions: TagContentOptions = {
|
|
61
|
+
numPages: 10,
|
|
62
|
+
snippetWindow: 100,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// brain: trim a single line of whitespace-collapsed text from ``description``
|
|
66
|
+
// centred on the first occurrence of ``tag``. When the tag word is not
|
|
67
|
+
// found we return the leading ``2 * window`` chars so the row still
|
|
68
|
+
// shows context. Whitespace is collapsed (multi-newline + tabs → single
|
|
69
|
+
// space) so the row stays a clean one-liner regardless of how the
|
|
70
|
+
// upstream description plugin built it.
|
|
71
|
+
//
|
|
72
|
+
// Match is case-insensitive on the tag's leaf segment (the part after
|
|
73
|
+
// the last `/` in nested tags like `interview/take-home`) — that's the
|
|
74
|
+
// word users actually expect to see highlighted in body text.
|
|
75
|
+
export function computeTagSnippet(
|
|
76
|
+
description: string,
|
|
77
|
+
tag: string,
|
|
78
|
+
window: number,
|
|
79
|
+
): string {
|
|
80
|
+
const collapsed = description.replace(/\s+/g, " ").trim()
|
|
81
|
+
if (collapsed.length === 0) return ""
|
|
82
|
+
const leaf = tag.includes("/") ? tag.slice(tag.lastIndexOf("/") + 1) : tag
|
|
83
|
+
const idx = collapsed.toLowerCase().indexOf(leaf.toLowerCase())
|
|
84
|
+
if (idx < 0) {
|
|
85
|
+
return collapsed.slice(0, window * 2)
|
|
86
|
+
}
|
|
87
|
+
const start = Math.max(0, idx - window)
|
|
88
|
+
const end = Math.min(collapsed.length, idx + leaf.length + window)
|
|
89
|
+
const prefix = start > 0 ? "…" : ""
|
|
90
|
+
const suffix = end < collapsed.length ? "…" : ""
|
|
91
|
+
return `${prefix}${collapsed.slice(start, end)}${suffix}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default ((opts?: Partial<TagContentOptions>) => {
|
|
95
|
+
const options: TagContentOptions = { ...defaultOptions, ...opts }
|
|
96
|
+
|
|
97
|
+
const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
|
|
98
|
+
const { tree, fileData, allFiles, cfg } = props
|
|
99
|
+
const slug = fileData.slug
|
|
100
|
+
|
|
101
|
+
if (!(slug?.startsWith("tags/") || slug === "tags")) {
|
|
102
|
+
throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const tag = simplifySlug(slug.slice("tags/".length) as FullSlug)
|
|
106
|
+
const allPagesWithTag = (t: string) =>
|
|
107
|
+
allFiles.filter((file) =>
|
|
108
|
+
(file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(t),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const content = (
|
|
112
|
+
(tree as Root).children.length === 0
|
|
113
|
+
? fileData.description
|
|
114
|
+
: htmlToJsx(fileData.filePath!, tree)
|
|
115
|
+
) as ComponentChildren
|
|
116
|
+
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
|
|
117
|
+
const classes = cssClasses.join(" ")
|
|
118
|
+
|
|
119
|
+
if (tag === "/") {
|
|
120
|
+
// brain: index-mode (tags listing all tags) — preserved verbatim
|
|
121
|
+
// from upstream. The brain delta is only applied to per-tag
|
|
122
|
+
// pages where the row count is small enough to render the dense
|
|
123
|
+
// icon + snippet + footer markup without overwhelming the
|
|
124
|
+
// viewport. The aggregate index page lists every tag with a
|
|
125
|
+
// PageList preview underneath, where the upstream rendering is
|
|
126
|
+
// the right fit.
|
|
127
|
+
const tags = [
|
|
128
|
+
...new Set(
|
|
129
|
+
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
|
|
130
|
+
),
|
|
131
|
+
].sort((a, b) => a.localeCompare(b))
|
|
132
|
+
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
|
|
133
|
+
for (const t of tags) {
|
|
134
|
+
tagItemMap.set(t, allPagesWithTag(t))
|
|
135
|
+
}
|
|
136
|
+
return (
|
|
137
|
+
<div class="popover-hint">
|
|
138
|
+
<article class={classes}>
|
|
139
|
+
<p>{content}</p>
|
|
140
|
+
</article>
|
|
141
|
+
<p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p>
|
|
142
|
+
<div>
|
|
143
|
+
{tags.map((t) => {
|
|
144
|
+
const pages = tagItemMap.get(t)!
|
|
145
|
+
const listProps = {
|
|
146
|
+
...props,
|
|
147
|
+
allFiles: pages,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const contentPage = allFiles.filter((file) => file.slug === `tags/${t}`).at(0)
|
|
151
|
+
|
|
152
|
+
const root = contentPage?.htmlAst
|
|
153
|
+
const tagContent =
|
|
154
|
+
!root || root?.children.length === 0
|
|
155
|
+
? contentPage?.description
|
|
156
|
+
: htmlToJsx(contentPage.filePath!, root)
|
|
157
|
+
|
|
158
|
+
const tagListingPage = `/tags/${t}` as FullSlug
|
|
159
|
+
const href = resolveRelative(fileData.slug!, tagListingPage)
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div>
|
|
163
|
+
<h2>
|
|
164
|
+
<a class="internal tag-link" href={href}>
|
|
165
|
+
{t}
|
|
166
|
+
</a>
|
|
167
|
+
</h2>
|
|
168
|
+
{tagContent && <p>{tagContent}</p>}
|
|
169
|
+
<div class="page-listing">
|
|
170
|
+
<p>
|
|
171
|
+
{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}
|
|
172
|
+
{pages.length > options.numPages && (
|
|
173
|
+
<>
|
|
174
|
+
{" "}
|
|
175
|
+
<span>
|
|
176
|
+
{i18n(cfg.locale).pages.tagContent.showingFirst({
|
|
177
|
+
count: options.numPages,
|
|
178
|
+
})}
|
|
179
|
+
</span>
|
|
180
|
+
</>
|
|
181
|
+
)}
|
|
182
|
+
</p>
|
|
183
|
+
<PageList limit={options.numPages} {...listProps} sort={options?.sort} />
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
})}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// brain: per-tag mode — replace the upstream `<PageList>` with a
|
|
194
|
+
// brain-styled row list. Sort uses the same default Quartz uses
|
|
195
|
+
// (`byDateAndAlphabeticalFolderFirst`) so the relative ordering
|
|
196
|
+
// matches user expectations (newest-first, undated trailing).
|
|
197
|
+
const sorter = options.sort ?? byDateAndAlphabeticalFolderFirst(cfg)
|
|
198
|
+
const pages = allPagesWithTag(tag).sort(sorter)
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div class="popover-hint">
|
|
202
|
+
<article class={classes}>{content}</article>
|
|
203
|
+
<div class="page-listing">
|
|
204
|
+
<p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p>
|
|
205
|
+
<ul class="section-ul brain-tag-results">
|
|
206
|
+
{pages.map((page) => {
|
|
207
|
+
const title = page.frontmatter?.title ?? page.slug ?? ""
|
|
208
|
+
const tags = page.frontmatter?.tags ?? []
|
|
209
|
+
const fmSource =
|
|
210
|
+
typeof page.frontmatter?.source === "string"
|
|
211
|
+
? (page.frontmatter.source as string)
|
|
212
|
+
: undefined
|
|
213
|
+
const source = inferSource(page.slug ?? "", fmSource)
|
|
214
|
+
const icon = sourceIconFor(source)
|
|
215
|
+
const description = page.description ?? ""
|
|
216
|
+
const snippet = computeTagSnippet(description, tag, options.snippetWindow)
|
|
217
|
+
const dateObj = getDate(cfg, page)
|
|
218
|
+
const href = resolveRelative(fileData.slug!, page.slug!)
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<li class="section-li brain-tag-row" data-brain-source={source}>
|
|
222
|
+
<a class="internal brain-tag-row-link" href={href}>
|
|
223
|
+
<span
|
|
224
|
+
class="brain-tag-icon"
|
|
225
|
+
aria-hidden="true"
|
|
226
|
+
data-brain-source={source}
|
|
227
|
+
>
|
|
228
|
+
{icon}
|
|
229
|
+
</span>
|
|
230
|
+
<span class="brain-tag-title">{title}</span>
|
|
231
|
+
{dateObj && (
|
|
232
|
+
<span class="brain-tag-date">
|
|
233
|
+
<QuartzDate date={dateObj} locale={cfg.locale} />
|
|
234
|
+
</span>
|
|
235
|
+
)}
|
|
236
|
+
{snippet && <span class="brain-tag-snippet">{snippet}</span>}
|
|
237
|
+
</a>
|
|
238
|
+
{tags.length > 0 && (
|
|
239
|
+
<p class="brain-tag-footer">
|
|
240
|
+
<span class="brain-tag-footer-label">tagged:</span>{" "}
|
|
241
|
+
{tags.map((rowTag, i) => (
|
|
242
|
+
// brain (P3.6 fix-6): bare `<>` fragments inside
|
|
243
|
+
// a `.map()` trip Preact's runtime warning
|
|
244
|
+
// ("Each child in a list should have a unique
|
|
245
|
+
// key prop"). Promote to a span with a stable
|
|
246
|
+
// `key={rowTag}` so the warning goes away and
|
|
247
|
+
// the reconciler can match nodes correctly when
|
|
248
|
+
// the tags list mutates between renders.
|
|
249
|
+
<span key={rowTag} class="brain-tag-footer-fragment">
|
|
250
|
+
{i > 0 ? " " : ""}
|
|
251
|
+
<a
|
|
252
|
+
class="internal tag-link brain-tag-footer-link"
|
|
253
|
+
href={resolveRelative(fileData.slug!, `tags/${rowTag}` as FullSlug)}
|
|
254
|
+
>
|
|
255
|
+
#{rowTag}
|
|
256
|
+
</a>
|
|
257
|
+
</span>
|
|
258
|
+
))}
|
|
259
|
+
</p>
|
|
260
|
+
)}
|
|
261
|
+
</li>
|
|
262
|
+
)
|
|
263
|
+
})}
|
|
264
|
+
</ul>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
TagContent.css = concatenateResources(style, PageList.css)
|
|
271
|
+
return TagContent
|
|
272
|
+
}) satisfies QuartzComponentConstructor
|