Skip to content

Feat/auto markdown links tags - #81

Closed
Floper wants to merge 11 commits into
philips:mainfrom
Floper:feat/auto-markdown-links-tags
Closed

Feat/auto markdown links tags#81
Floper wants to merge 11 commits into
philips:mainfrom
Floper:feat/auto-markdown-links-tags

Conversation

@Floper

@Floper Floper commented Jun 3, 2026

Copy link
Copy Markdown

Markdown export: links, tags, OCR markup, auto-sync and mirror folder

This PR builds on the updated supernote-typescript submodule (which now parses
internal links and keyword stars) to surface those features as Obsidian-native
constructs in the exported markdown. It also fixes several bugs in the auto-sync
workflow and adds new settings to control the behaviour.

Supernote keywords → Obsidian tags

Starred keywords from the Supernote app are emitted as Obsidian tags (e.g.
#Supernote_Keyword) on the correct page of the exported markdown, rather than
as a flat list at the top of the document.

  • The source page is derived from the KEYWORD footer key prefix (first 4 digits,
    1-indexed), matching the LINKO key format. This is more reliable than the
    KEYWORDPAGE field, which can be '0' (invalid).
  • Tags are emitted before the OCR text on each page but deduplicated: if the
    same tag was already embedded in the processed OCR text (see below), it is not
    repeated in the page header.
  • A note-level keyword map is built at export time so that brand-new tags — not
    yet indexed in the Obsidian vault — are still recognised as tags rather than
    headings during OCR processing.

Supernote links → Wikilinks

Internal links created via the Supernote link feature are emitted as Obsidian
Wikilinks on the correct page.

  • The source page is derived from the LINKO footer key prefix (first 4 digits,
    1-indexed) rather than OBJPAGE, which is unreliable.
  • Links within a page are ordered top-to-bottom by sorting LINKO keys
    (the key encodes the Y coordinate after the page prefix).
  • Page anchors: for same-document links the text field already contains
    #Page N (resolved by the library). For cross-file links the plugin loads the
    target .note from the vault, parses it, and resolves the PAGEID to a page
    number — e.g. [[other-note#Page 2]]. A per-call cache avoids re-parsing the
    same file multiple times.
  • Links with PAGEID: 'none' or where the target file is not in the vault
    produce a plain Wikilink without a page anchor.

OCR markup: # → tags/headings, @ → Wikilinks

A new processHashtagsAndMentions function post-processes the OCR text of each
page:

  • #Word or # Word: checked against the Obsidian vault tag index. If the tag
    exists → inline tag (#Word); otherwise → Markdown heading (# Word). Two-word
    combinations (e.g. # Existing Tag) are also checked as #existing_tag before
    falling back to a heading. The note's own keyword stars are checked first so
    newly created tags are recognised even before they appear in the vault index.
  • @ Word(s): everything after the @ on the same line becomes a Wikilink
    ([[Word(s)]]), supporting multi-word note names.

Auto-sync fixes

  • Path construction bug: the previous code used
    `${file.parent?.path}/${file.basename}.md` which produces //name.md for
    vault-root files (Obsidian returns "/" as the root folder path). The output
    path is now derived from file.path.replace(/\.note$/i, '.md'), which Obsidian
    always formats correctly.
  • Race condition: when a .note file syncs, Obsidian fires both 'create'
    and 'modify' events in quick succession. Both handlers called
    vault.create, causing a "File already exists" error. The overwrite path
    now wraps vault.create in a try/catch and retries with vault.modify when
    the file appeared between the existence check and the create.
  • attachMarkdownFile now properly awaits writeMarkdownFile so errors
    surface rather than being silently dropped as unhandled promise rejections.
  • .catch() handlers added to both vault event callbacks so any remaining errors
    appear in the developer console instead of as uncaught exceptions.

Markdown mirror folder

A new optional setting lets users save all exported markdown files into a
dedicated vault folder that mirrors the .note file structure, instead of
placing each .md file alongside its .note file.

  • Setting: Markdown mirror folder (text input, empty by default = save
    alongside). Example value: SupernoteMarkdowns.
  • ensureFolderExists walks the path segment by segment, creating any missing
    intermediate folders (with a concurrent-creation guard).
  • Works correctly for notes at the vault root and in nested subfolders.

New settings

All new behaviours are opt-out via toggles in the plugin settings:

Setting Default Description
isAutoSyncMarkdownEnabled false Auto-export markdown when a .note file changes
isKeywordsAndLinksEnabled true Convert keywords to tags and links to Wikilinks
isHashtagsMentionsEnabled true Convert # and @ in OCR text
markdownMirrorFolder "" Output folder for mirrored markdown files

@Floper Floper mentioned this pull request Jun 3, 2026
@Floper
Floper marked this pull request as draft June 4, 2026 08:12
@philips-clanker

Copy link
Copy Markdown
Collaborator

Thanks for this — it's a lot of useful functionality. main has moved substantially since this was opened (settings.ts moved to a declarative API, main.ts roughly quadrupled in size), so rather than force a mechanical rebase, I've split this into four smaller PRs ported onto current main, in dependency order:

  1. Auto-sync .note files to markdown, with mirror/watch folders #120 - Auto-sync .note files to markdown, with mirror/watch folders (independent, includes the path-construction and race-condition fixes)
  2. Bump supernote-typescript submodule to pick up link/keyword parsing #121 - Bump supernote-typescript submodule (picks up Parse Supernote internal links and keyword stars (rebase of #9) supernote-typescript#24, "Parse Supernote internal links and keyword stars" - your original branch depended on a submodule change that never landed; it has now landed on the submodule's main)
  3. Export Supernote keywords as tags and links as Wikilinks #122 - Export Supernote keywords as tags and links as Wikilinks (stacked on Bump supernote-typescript submodule to pick up link/keyword parsing #121)
  4. OCR markup: # to tags/headings, @ to Wikilinks #123 - OCR markup: # to tags/headings, @ to Wikilinks (stacked on Export Supernote keywords as tags and links as Wikilinks #122)

Not carried forward: the image-js/bresenham-zingl pin from this branch is obsolete - main already migrated to image-js ^1.7.0 with a cleaner encode/encodeDataURL approach that doesn't need the esbuild alias workaround.

Leaving this one open for reference; happy to close it once the split PRs are reviewed, or keep it open longer if you'd rather. Attribution is preserved in each PR description.

pull Bot pushed a commit to ben-vargas/supernote-obsidian-plugin that referenced this pull request Jul 27, 2026
Brings in philips/supernote-typescript#24 (internal Supernote links and
keyword stars, exported as ILink/sn.links and richer sn.keywords),
needed by the keywords-to-tags and links-to-Wikilinks export feature
split out of philips#81.

No plugin-side code changes; this is purely the submodule pointer
bump so that dependent feature branches have something to build on.
@philips

philips commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Heyo! So, I have been thinking about this quite a bit and I have decided to try and enable people to hook the note file conversion process. If this is of interest to you here is an LLM generated prototype that implements some (most?) of what you want on top of the beta hook.

https://github.com/philips/supernote-obsidian-ocr-keywords

@philips

philips commented Jul 29, 2026

Copy link
Copy Markdown
Owner

I am going to close this PR. Please take a look at the plugin and I am still noodling on #106 / #120

@philips philips closed this Jul 29, 2026
@Floper

Floper commented Jul 31, 2026

Copy link
Copy Markdown
Author

@philips amazing.
I don't have access to a PC at the moment so cannot test. But I like the approach. Happy to see you were able to move this forward properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants