Documentation
¶
Overview ¶
Package render answers three questions about one vault file, in one place so every face of the program answers them alike: how a note reads, what words it contributes to a search, and what kind of file it is. It reports what it cannot render rather than repairing it, and authored HTML is inert display input: the ruby subset survives, and executable markup is shown as text.
Index ¶
- Constants
- Variables
- func Excerpt(body, fragment string) (slice string, found bool)
- func ExcerptHeading(slice string) string
- func ExcerptWithheld(relPath, fragment string, lang wording.Lang) string
- func InjectConceptTriggers(htmlOut string, lookup func(relPath string) (id string, ok bool)) (out string, refs []string)
- func InjectTTS(htmlOut string, lang wording.Lang) string
- func IsPDF(relPath string) bool
- func IsPicture(relPath string) bool
- func IsText(b []byte) bool
- func IsTextPrefix(b []byte) bool
- func PlainText(body string) string
- func SourceHTML(filename, source string) string
- func StripAnchors(htmlOut string) string
- type Diagnostic
- type DiagnosticKind
- type Files
- type Pipeline
- type Result
- type TOCEntry
- type Titles
- type Transclusions
Constants ¶
const MaxSourceBytes = 1 << 20 // 1 MiB
MaxSourceBytes is the comfort cap on a file read as characters. Past it the file gets an information page pointing at its bytes and search leaves it out, so both faces say the same thing. A note is not subject to this.
Variables ¶
var ChromaCSS = sync.OnceValue(func() string { light, dark := paletteCSS(chromaLightStyleName), paletteCSS(chromaDarkStyleName) if light == "" || dark == "" { return "" } var b strings.Builder b.WriteString("@layer " + codeLayerName + " {\n") b.WriteString(light) b.WriteString("@media not print {\n:root[data-theme=\"dark\"] {\n") b.WriteString(".chroma span { color: inherit; background-color: transparent; }\n") b.WriteString(dark) b.WriteString("}\n}\n}\n") return b.String() })
ChromaCSS is the highlighting stylesheet, computed once and cached. It carries both palettes, light as the base and dark as a scoped override, because the markup is theme-independent. The dark scope opens by returning every token to the surrounding ink, since the palettes name different token sets, and sits behind a print guard, so printing in dark mode gives light rules on paper.
Functions ¶
func Excerpt ¶ added in v0.2.0
Excerpt is the part of body that a link's own fragment addresses: a caret opens a block address, anything else names a section, and an empty fragment asks for the note itself. Obsidian's %% comments come off before any edge is chosen, so a marker cannot span the cut and arrive visible in the excerpt.
An address the note does not answer to comes back not found, and the caller says so. Widening to the whole note would answer a question nobody asked — the reader named one place, and being shown a different one without being told reads as the place they named.
The fragment is the one an anchor already carries, already folded by the pass that wrote it; nothing here folds a name a second time.
func ExcerptHeading ¶ added in v0.2.0
ExcerptHeading is the words a reader sees in the heading an excerpt opens on, or empty when it does not open on one — a block excerpt, or a note read from its first line. It reduces the heading the way the anchor pass does, so a reading that names a section names it as its own table of contents does.
func ExcerptWithheld ¶ added in v0.2.0
ExcerptWithheld is what a surface says where an excerpt it could not cut would have stood: the address the note does not answer to, spelled the way the author's own link spells it, and the note it was asked of. The reading page says it inside the block an embed leaves behind and the hover card says it under the note's name, from this one sentence, so the two report one fact in one voice. fragment is the one Excerpt reads.
func InjectConceptTriggers ¶
func InjectConceptTriggers(htmlOut string, lookup func(relPath string) (id string, ok bool)) (out string, refs []string)
InjectConceptTriggers upgrades the wikilinks pointing at concept notes into concept-sheet triggers, and reports which concepts a body references. A trigger stays a real navigable anchor and only gains an attribute and a class, so without scripting it still opens the concept note's own page. The returned refs are the concept paths, deduped in first-seen order.
func InjectTTS ¶
InjectTTS gives each explicitly marked Japanese paragraph a speak button whose data-tts attribute holds the segment's spoken text, computed here with the furigana stripped, so the front end never crawls the DOM for it. It only adds a control. Ruby is reading apparatus rather than a language declaration, so a paragraph merely containing it stays untouched and the author opts in.
func IsPDF ¶ added in v0.2.0
IsPDF reports whether relPath names a PDF, by its final extension in any case; like IsPicture, the name alone chooses the viewer. Search leaves a PDF out of the text index, since its bytes are never shown as characters.
func IsText ¶
IsText reports whether b is text yomihon reads as characters: no NUL byte and valid UTF-8. The extension is deliberately not consulted, so a .txt holding a compiled object stays out and an extensionless build file still reads as what it is. b must not end mid-character; pass a truncated window to IsTextPrefix.
func IsTextPrefix ¶
IsTextPrefix is IsText over a fixed-size window, ignoring a character the window cut in half, so a text file does not read as binary merely because its opening bytes ended in the middle of one.
func PlainText ¶
PlainText returns the searchable plain text of a note body: prose, headings, table cells, task text, code-fence contents and the base and reading of hand-written ruby, but not the HTML tags or the callout marker syntax. A wikilink contributes both its target and its display text. The body must arrive with its frontmatter removed, and the text keeps its case and Unicode form.
A course branch's role declaration stays in the text, although the page takes it off the heading it shows. Keeping it is what lets an author search for the notes that declare one; taking it off would mean assembling each heading's words here and reading a declaration back out of them, which is the page's own job and not this walk's. The cost is one incongruity: a search for the declaration finds a note whose page no longer shows those words.
func SourceHTML ¶
SourceHTML highlights a whole file as source text, returning the same class-based markup a fenced code block produces. filename is the base name and selects the lexer by name as well as extension, which is how an extensionless build file reads as what it is; an unrecognized name falls back to plain text. The formatter escapes the source, so a file carrying markup is shown, not run.
func StripAnchors ¶ added in v0.2.0
StripAnchors returns rendered HTML with every place inside it removed: the names, and the addresses that reach them. It is for an excerpt shown beside the page it was fetched for rather than as a page of its own. Each name it carried would be a second element answering to one the page's own headings and blocks already have, so a fragment naming it would reach whichever came first; and each address left behind afterwards would be a footnote number that looks live, does nothing, and drops a fragment in the address bar.
The addresses are dropped rather than pointed at the note's own page: an excerpt's footnote ids are qualified for the region it was rendered in, so the name the address carries is one the note's page does not have either. The footnote marker stays visible and stops being a control.
Types ¶
type Diagnostic ¶
type Diagnostic struct {
Kind DiagnosticKind
Target string // the offending wikilink target / callout type / etc.
Message string // human-readable
// Section and Block carry the two halves of an address a link wrote after
// "#", so a panel can say how the link was read; Target stays the bare name,
// which is what other readers of that field look planned names up by. The two
// are never both set, since "#^name" is a block address and anything else
// after "#" is a section name.
Section string
Block string
}
Diagnostic is one note about content yomihon could read but could not present the way its author probably meant. Display-only: yomihon reports, it never fixes or rejects. Deciding that something is worth surfacing is a rendering decision, which is why the set lives here and not beside the resolver — and why the one kind nothing here raises is declared here too, so that a surface has a single table to look a diagnostic up in and a single set of words to say it with.
type DiagnosticKind ¶
type DiagnosticKind string
DiagnosticKind classifies one Diagnostic. All but one are raised by a pass in this package; DiagTitleTruncatedAtHash says where its own comes from.
const ( // DiagWikilinkBroken means a [[wikilink]] or ![[embed]] target does // not resolve to any note or file. DiagWikilinkBroken DiagnosticKind = "wikilink-broken" // DiagWikilinkAmbiguous means a target resolves to more than one // file; the candidates are listed, never guessed at. DiagWikilinkAmbiguous DiagnosticKind = "wikilink-ambiguous" // DiagWikilinkTitleOnly means the target names some note's declared title, // which is not a name a link resolves by, so the link is broken either way. // It is a separate kind because the repair is: nothing needs writing, and an // alias on the note the citation meant makes the existing link work. DiagWikilinkTitleOnly DiagnosticKind = "wikilink-title-only" // DiagTitleTruncatedAtHash means this note's own title is exactly what its // filename becomes when cut where YAML starts a comment in an unquoted value. // It is an observation, not an accusation: a title deliberately written short // in quotes produces the same coincidence, and nothing parsed tells them apart. // No pass here raises it: the coincidence is between a note's frontmatter and // its filename, both of which are read where a note is assembled, so that is // where it is noticed and this is only where it is named. DiagTitleTruncatedAtHash DiagnosticKind = "title-truncated-at-hash" // DiagUnknownCallout means a "> [!type]" callout's type is not one // of the recognized callout types; it was rendered as a plain // blockquote instead of being dropped. DiagUnknownCallout DiagnosticKind = "unknown-callout" // DiagRiskyFence means a fenced code block's content looks like the // wikilink, callout or table syntax the dialect passes would otherwise // convert; it was left untouched. At most one per render call. DiagRiskyFence DiagnosticKind = "risky-fence" // DiagEmbedFragmentMissing means an embed named a section or block its // target's captured body does not contain, so nothing of that note is shown // and a notice stands where the excerpt would. The author named one place; // widening to the whole note would present a scope they never chose as // their own. DiagEmbedFragmentMissing DiagnosticKind = "embed-fragment-missing" // DiagEmbedFragmentRepeated means an embed named a section its target carries // more than once. The first is shown, and the count is reported because an // excerpt chosen from several looks exactly like the only one there was. DiagEmbedFragmentRepeated DiagnosticKind = "embed-fragment-repeated" // DiagEmbedNotExpanded means an embed written inside a transcluded body was // rendered as an ordinary link, since transclusion stops one level down. DiagEmbedNotExpanded DiagnosticKind = "embed-not-expanded" // DiagLinkFragmentMissing means a plain link named a block its target's // captured body does not carry. The link is left leading to the note itself, // since an address the destination stamps no anchor for would promise a block // and land the reader somewhere else. DiagLinkFragmentMissing DiagnosticKind = "link-fragment-missing" // DiagLinkSectionMissing means a plain link named a section neither scan of // its target's captured body could find. The address is kept exactly as // written, because a heading id is stamped by a pass that sees headings these // scans do not, so a miss is a name they failed to find rather than one the // page is certain to lack. That is why it is a separate kind from a missing // block, which withdraws the author's address. DiagLinkSectionMissing DiagnosticKind = "link-section-missing" // DiagCommentUnclosed means a "%%" comment marker never met a second one, so // everything after it is hidden from the page. Obsidian hides it too, so the // words are not restored; the reader is told where the silence begins. DiagCommentUnclosed DiagnosticKind = "comment-unclosed" // DiagImageMissing means a note showed a picture from a path inside the // vault and the vault holds no file there. The image is left where the // author put it, marked the way an unwritten citation is: the page says the // same thing about a name with nothing behind it whether the author reached // for it with a link or with a picture. DiagImageMissing DiagnosticKind = "image-missing" // DiagRenderFailed means the markdown renderer returned an error. It is // normally unreachable, and kept so an extension that breaks that assumption // produces a visible diagnostic rather than a blank page. DiagRenderFailed DiagnosticKind = "render-failed" )
type Files ¶ added in v0.2.0
Files answers what the vault has at one vault-relative path. The resolver's index cannot: it keys on names, so a picture of the same name in another folder would read as the one a note asked for.
The question is "missing" rather than "present" because absent and unseen are different answers and only the implementor can tell them apart: the scan never walks a path with a hidden segment, so a picture under one is not gone, it is somewhere this reader does not look, and marking it missing would report the reader's own boundary as a fault in somebody's note.
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline is a configured, reusable markdown pipeline for one captured vault generation.
func New ¶
New builds a rendering pipeline from one generation's link resolver and captured transclusion bodies, both of which describe the same generation and must not be nil. It enables GFM, footnotes, the inert authored-markup subset the Japanese lessons use, and the ==highlight== inline extension. Footnotes are enabled so "[^name]" is not read as an ordinary reference link, which resolved the reference against the definition's prose.
func (*Pipeline) HTML ¶
HTML renders one note's body: the markdown pipeline, plus the three passes that only make sense once at the top level — removing a leading H1 that duplicates the page's title, assigning heading slugs and collecting the table of contents over the assembled HTML so ids stay unique, and resolving assets. relPath is required because markdown writes an image path relative to its own note.
func (*Pipeline) HTMLIn ¶ added in v0.2.0
HTMLIn is HTML for a body placed on a page already carrying another separately rendered one, so the caller names the region this one occupies. What that buys is bounded: footnote ids are named under region and nested regions carry it, while heading ids are not qualified. region must be distinct per body and derived from the page rather than a running process, so two readers of one lesson receive the same bytes.
type Result ¶
type Result struct {
HTML string
Diagnostics []Diagnostic
TOC []TOCEntry
// TitleAnchor is the id the page's visible title has to carry, set only when
// this render removed an authored opening heading saying the same thing. That
// heading was a place a link could name, so the anchor moves to where its
// words now are. It is empty when no such heading was written.
TitleAnchor string
// TranscludedIdentity is one digest over everything this render pulled in
// from other notes: each excerpt expanded, in the order the page shows them,
// bound to its source note and the scope decision that cut it. Empty when
// nothing was transcluded. The reading page stamps it beside its content
// identity, so a freshness answer can say whether a reload would deliver
// different transcluded words.
TranscludedIdentity string
}
Result is everything one HTML render produces.
type TOCEntry ¶
TOCEntry is one heading in document order, with the id assigned to it (see heading.go's CJK-safe slug algorithm).
type Titles ¶ added in v0.2.0
Titles answers which notes declare a given title. A title is not a name a link resolves by, so this never affects resolution: it is how a page that already failed to resolve a name can say the note is there under a name links do not follow. It returns display names rather than paths, which is all a sentence needs.
type Transclusions ¶
Transclusions is the captured note-body set used by embed expansion. The concrete reading snapshot implements this narrow consumer-owned capability, so link resolution and transclusion bodies can come from one generation.