Skip to content

Parse Supernote internal links and keyword stars (rebase of #9) - #24

Merged
philips merged 2 commits into
mainfrom
rebase-pr-9-links
Jul 27, 2026
Merged

Parse Supernote internal links and keyword stars (rebase of #9)#24
philips merged 2 commits into
mainfrom
rebase-pr-9-links

Conversation

@philips

@philips philips commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Rebase of #9 (originally by @Floper) onto current main, which had drifted significantly (dependency bumps, SupernoteX refactored to explicit declare fields for implements ISupernote).

Original description

Parse Supernote internal links and keyword stars

This PR adds two new parsing features to the library: internal link resolution
(with page anchors) and keyword/star text extraction. It also exposes ILink
and IPage as public types.

Links (LINKO)

The Supernote format stores internal links in the footer under LINKO_* keys.
These were not previously parsed at all.

  • Added _parseLinks / _parseLink methods to SupernoteX — symmetrical to
    the existing keyword and title parsers
  • Added ILink interface to format.ts and exposed it on ISupernote.links as
    Record<string, ILink[]>
  • The LINKFILE field is a Base64-encoded absolute device path; _parseLink
    decodes it and extracts the filename (without path or .note extension) into a
    text field ready for use as a Wikilink
  • Page anchor resolution: each link carries a PAGEID that identifies the
    target page. _parseLink looks up that PAGEID in this.pages (populated
    before links are parsed). If matched — i.e. the link points to a page in the
    same document — it appends #Page N to text (e.g. "my-note#Page 1").
    Cross-file links where the target is not in this document produce no anchor.
  • Added PAGEID?: string to IPage so page identity is accessible for the lookup

Keywords (KEYWORD)

The KEYWORD_* and LINKO_* footer entries use a journaled/append-only format,
meaning the same key can appear multiple times. The previous footer parser used
extractNestedKeyValue which silently drops repeated keys. These two groups are
now extracted separately, preserving every entry as a string or string[].

Additionally, _parseKeyword previously left KEYWORD empty because it misread
KEYWORDSITE as a bitmap address. KEYWORDSITE actually points to a
length-prefixed block containing the OCR text of the starred word. This is now
correctly decoded into the KEYWORD field.

Public API

ILink and IPage are now exported from the package index, allowing consumers
to type link and page data without reaching into internal modules.

Tests

  • Added nomad-3.26.40-link-tag-3p.note as a test fixture (3 pages, 3 links on
    page 2, 3 keyword stars on page 3 including a brand-new tag)
  • links suite: verifies same-file links get a #Page N anchor via PAGEID
    lookup; cross-file links with a PAGEID but no matching page in this document
    produce no anchor; links with PAGEID: 'none' produce no anchor
  • keywords suite: verifies all 3 keyword stars are parsed with correct OCR
    text; verifies that the KEYWORD footer key prefix (first 4 digits, 1-indexed)
    reliably encodes the source page — including the known case where KEYWORDPAGE
    itself is '0' (invalid) while the key prefix is correct

Rebase notes

  • Dropped the pin image-js to 1.2.0 commit: it became a no-op since main already moved to image-js ^1.7.0 with its own breakage fix (92f9033).
  • Added a declare links: Record<string, ILink[]>; field to SupernoteXmain had since refactored the class to explicitly declare fields for the implements ISupernote pattern, and the rebase merged cleanly but silently dropped this field, breaking the build.
  • Build, lint, and full test suite (17 tests, including the new links and keywords suites) pass on the rebased branch.

Closes #9 (superseding it — original branch had unresolvable merge conflicts with current main).

Adds two new parsing features and exposes ILink/IPage as public types:

Links (LINKO): the footer's LINKO_* keys were previously unparsed. Adds
_parseLinks/_parseLink to SupernoteX, an ILink interface, and
ISupernote.links: Record<string, ILink[]>. LINKFILE is a Base64-encoded
device path; _parseLink decodes it into a Wikilink-ready `text` field and,
via each link's PAGEID, resolves same-document targets to a `#Page N`
anchor (cross-file or unmatched links get no anchor). IPage gains an
optional PAGEID for this lookup.

Keywords (KEYWORD): KEYWORD_* and LINKO_* footer entries are
journaled/append-only, so the same key can repeat; these groups are now
parsed separately instead of via extractNestedKeyValue, which silently
dropped repeats. Also fixes _parseKeyword misreading KEYWORDSITE as a
bitmap address instead of a length-prefixed OCR-text block, which had
left KEYWORD empty.

ILink and IPage are now exported from the package index.

Adds nomad-3.26.40-link-tag-3p.note as a test fixture (3 pages, 3 links
on page 2, 3 keyword stars on page 3) plus `links` and `keywords` test
suites covering anchor resolution and keyword/page parsing.

Originally authored by Floper in #9; rebased onto current main (dependency
bumps, SupernoteX refactored to explicit `declare` fields for `implements
ISupernote`) and squashed for a clean history.
@philips
philips force-pushed the rebase-pr-9-links branch from d7d22e2 to 20241b5 Compare July 25, 2026 06:32
@philips
philips marked this pull request as ready for review July 25, 2026 06:34
@philips

philips commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

@Floper WDYT of this squash and rebase?

@philips
philips merged commit 19ba8e5 into main Jul 27, 2026
1 check passed
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.
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.

2 participants