github jhofker/obsidian-hoarder 2.0.0

8 hours ago

Summary

  • Custom note templates — full Eta template engine support for customizing bookmark note layout (frontmatter fields, body sections, ordering). Default template is a drop-in replacement for the previous
    hard-coded formatter (zero breaking changes).
  • CodeMirror 6 template editor — syntax-highlighted template editor in settings using Obsidian's already-bundled CM6 (zero bundle overhead). Highlights Eta tags, YAML keys, and Markdown headings.
  • Semantic sync comparison — file rewrites no longer trigger on format-only changes (whitespace, field reordering). Frontmatter is parsed and compared key-by-key; mtime is only updated when content actually
    differs. Closes #19.
  • Correct attachment extensions — asset file extensions are now resolved from the HTTP Content-Type header (priority), then asset label, then URL. Per-type download toggles for banners, screenshots, PDF
    archives, and full-page archives. Closes #36.
  • Robust note extraction — 3-tier fallback for bi-directional note sync: comment-delimited block () → ## Notes section → frontmatter note field. Custom templates can use it.noteBlock
    for template-independent note editing. Partially addresses #22.
  • content_html template variable — crawled HTML content exposed as it.content_html in templates; only fetched from the API when the active template references the variable.
  • HTML sanitization — content_html strips scripts, iframes, event handlers, and javascript: URLs before exposure to templates.
  • Settings panel cleanup — fixed indented section headers and per-theme bordered setting cards; scoped all plugin CSS to .hoarder-settings.
  • @dylan-park contributed a great fix to avoid constantly moving and renaming archived bookmarks in #40

Breaking changes

None for default users. The default template reproduces the previous formatter output character-for-character.

Users who have already added a ## Notes section above other sections in a custom layout: the ## Notes extractor now strips trailing footer links rather than truncating at any [-prefixed line, which may
change note boundaries in edge cases.


New in this release: Custom Templates

Getting started

In plugin settings, scroll to Note Template, toggle on Use custom template, and the editor will appear pre-populated with the default template.

Templates use Eta syntax (a lightweight EJS variant). All variables are accessed via it.*.


Bi-directional note sync

If you use Sync notes back to Karakeep, your template must support note extraction. There are two ways:

Option A — it.noteBlock (recommended for custom templates)

Place <%= it.noteBlock %> anywhere in your template body. This wraps the note in HTML comment delimiters that the plugin can reliably find regardless of surrounding structure:

Your note text here

Option B — ## Notes section (default template)

The default template uses a ## Notes heading. If you keep this section, the plugin will extract notes from it. The heading must appear in the template, and the note must be followed only by footer links.

Either way, the frontmatter must also include:
original_note: <%= it.yaml.note %>

The plugin uses this field to detect whether your local edits differ from Karakeep before deciding to push changes back.


Warnings

The editor validates your template as you type. Warnings (not errors) are shown for:

  • Missing YAML frontmatter
  • Missing bookmark_id in frontmatter
  • Missing original_note (breaks note sync)
  • Missing ## Notes section (only if it.noteBlock is also absent)

If the template has a runtime error during sync, the plugin falls back to the default template and logs an error to the console.


Reset to default

Click Reset to Default to restore the original template at any time.


Syntax

Syntax Purpose
<%= it.title %> Output a value
<% if (it.summary) { %>...<% } %> Conditional block
<% it.tags.forEach(function(tag) { %>...<% }) %> Loop

Available variables

Bookmark fields

Variable Type Description
it.bookmark_id string Karakeep bookmark ID
it.title string Resolved title
it.url string | null Source URL
it.description string | null Page description or text content
it.created_at string ISO 8601 creation date
it.modified_at string | null ISO 8601 modification date
it.note string Your note text
it.noteBlock string Note wrapped in comment delimiters (see below)
it.summary string | null AI-generated summary
it.archived boolean Whether the bookmark is archived
it.favourited boolean Whether the bookmark is favourited
it.content_type string "link", "text", or "asset"
it.content_html string | null Full crawled HTML (sanitized)
it.tags string[] Sanitized tag names
it.hoarder_url string Link to bookmark in Karakeep
it.visit_link string | null Escaped source URL (null for assets)
it.sync_highlights boolean Whether highlight sync is enabled

Pre-escaped YAML values

Use these inside frontmatter — they handle quoting and special characters automatically.

Variable Description
it.yaml.url URL, safe for YAML
it.yaml.title Title, safe for YAML
it.yaml.note Note, safe for YAML
it.yaml.summary Summary, safe for YAML

Assets

Variable Type Description
it.assets.content string Inline embed(s) for attachments
it.assets.image string | undefined Wikilink to image file
it.assets.banner string | undefined Wikilink to banner image
it.assets.screenshot string | undefined Wikilink to screenshot
it.assets.full_page_archive string | undefined Wikilink to HTML archive
it.assets.pdf_archive string | undefined Wikilink to PDF
it.assets.video string | undefined Wikilink to video
it.assets.additional string[] | undefined Wikilinks to other files

Highlights

it.highlights is an array sorted by position in the source. Each item has:

Field Description
h.text Highlighted text
h.color Highlight color (e.g. "yellow")
h.note Annotation on the highlight
h.date Formatted date (e.g. "January 15, 2024")
h.created_at ISO 8601 date
h.id Highlight ID

Helper functions

Function Description
it.escapeYaml(str) Escape a string for use in YAML
it.escapeMarkdownPath(str) Escape a path for use in a Markdown link
it.formatDate(iso) Format an ISO date as "Month DD, YYYY"

Don't miss a new obsidian-hoarder release

NewReleases is sending notifications on new releases.