Commit graph

31 commits

Author SHA1 Message Date
saberzero1
89e88af565
chore(deps): update @quartz-community/utils 2026-06-03 15:27:28 +02:00
saberzero1
e0e08e7afa
chore: regenerate package-lock.json 2026-05-24 17:32:49 +02:00
saberzero1
a55a890d21
chore: remove vestigial @jackyzha0/quartz peerDependency 2026-05-24 16:23:56 +02:00
saberzero1
1bea12fd35
build: bundle dependencies and ship pre-built dist 2026-04-27 21:37:47 +02:00
saberzero1
23dd2f8aad
build: bundle dependencies and ship pre-built dist 2026-04-27 21:37:47 +02:00
saberzero1
660d0ed6eb
deps: update @quartz-community/utils 2026-04-22 18:17:27 +02:00
saberzero1
604b8ec1d2
deps: update @quartz-community/utils 2026-04-17 02:38:30 +02:00
saberzero1
5e951415cc
deps: update @quartz-community/utils 2026-04-16 22:08:44 +02:00
saberzero1
f3a1ce4ea7
deps: update @quartz-community/types and @quartz-community/utils 2026-04-16 13:25:59 +02:00
saberzero1
a4dd3a9bf7
docs: list bases views in the set of server-side surfaces
Extend the "How unlisted works" section of the README to mention bases
views alongside backlinks/recent-notes/folder-page/tag-page as a
server-side rendered listing surface. The behavior has been correct
since the bases-page plugin started respecting the file.data.unlisted
convention -- this commit only makes the existing limitation discoverable
from the encrypted-pages README instead of hiding it.

Specifically, the README now says:

- Unlisted encrypted pages are absent from every server-side listing
  that respects the unlisted convention: backlinks, recent-notes,
  folder-page, tag-page, and bases views from @quartz-community/bases-page.
- Those server-side surfaces remain statically hidden even after
  client-side decryption. Graph, explorer, and search all re-hydrate
  from the patched in-memory content index and show newly-unlocked
  pages for the rest of the session -- base views do not, because
  they were fully materialized at build time.

No code changes.
2026-04-11 15:26:05 +02:00
saberzero1
8328b55648
feat: add stealth: true frontmatter for permanently hidden encrypted pages
Stealth pages are encrypted pages that are hidden from every listing
surface permanently, even after a successful client-side decryption.
Their metadata is never written to the shadow content index, so there
is nothing for the client to recover -- the page remains invisible to
graph, explorer, search, backlinks, recent-notes, folder-page, and
tag-page for the entire browser session.

This fills a gap between "unlisted" (hidden pre-decryption, revealed
post-decryption via shadow index) and the desire for "secret door"
pages that should only be accessible to users who already know the
exact URL.

Semantics:

- `stealth: true` implies `unlisted: true`. Setting stealth on a page
  automatically hides it from listings regardless of the `unlisted`
  field or the `unlistWhenEncrypted` plugin option.
- `stealth: true` overrides explicit `unlisted: false`. A user who
  writes `stealth: true, unlisted: false` gets a hidden stealth page,
  because stealth without unlisted is semantically incoherent.
- `stealth: true` has no effect on non-encrypted pages. Stealth only
  controls shadow-index emission, which only happens for encrypted
  unlisted pages. Non-encrypted pages with `stealth: true` are silently
  ignored (use `unlisted: true` for non-encrypted hiding, via the
  `unlisted-pages` plugin).
- Non-boolean stealth values are ignored.

Implementation:

- transformer.ts: after setting `data.unlisted`, check if
  `frontmatter.stealth === true` and if so set `data.stealth = true`
  and force `data.unlisted = true`.
- emitter.ts: in the shadow-index loop, skip any page where
  `data.stealth === true`.

Tests: 8 new (6 transformer + 2 emitter) covering stealth -> unlisted
implication, stealth overriding explicit unlisted: false, stealth on
non-encrypted pages being a no-op, non-boolean stealth values being
ignored, stealth pages being excluded from the shadow index while
non-stealth pages pass through unchanged, and mixed stealth/non-stealth
content being correctly partitioned. 29 tests total passing.
2026-04-11 14:52:11 +02:00
saberzero1
bcd9743521
fix: declare plugin as both transformer and emitter in manifest
The previous commit added an EncryptedContentIndex emitter to the package
exports, but left package.json > quartz.category as "transformer". Quartz
v5's plugin loader (quartz/plugins/loader/config-loader.ts) reads the
manifest category field to decide which buckets a plugin belongs to. Since
only "transformer" was declared, the loader only ever searched for a
transformer factory in this module -- EncryptedContentIndex was never
instantiated, emit() was never called, and static/encryptedContentIndex.json
was never written. The shadow content index was effectively dead code.

Change quartz.category from "transformer" to ["transformer", "emitter"].
Quartz v5 supports array categories and pushes the plugin entry into every
matching processing bucket (see config-loader.ts lines 329-337), then
instantiates each category separately via findFactory(module, category),
which iterates all exports and picks the one whose shape matches. Both
EncryptedPages (has htmlPlugins) and EncryptedContentIndex (has emit) are
now correctly resolved from a single config entry in quartz.config.yaml.

Follow-on cleanups:

- EncryptedContentIndexOptions gains a passwordField field so the emitter
  reads it from its own merged options instead of spelunking
  ctx.cfg.plugins.transformers for the transformer's option. The Quartz
  loader passes the same merged options object to every factory instantiated
  from a given config entry, so the two plugins stay in sync automatically.
- Removed the warnIfEmitterMissing() helper and its dead BuildCtx import.
  With category: ["transformer", "emitter"], the loader always instantiates
  both when the package entry is enabled -- the warning is obsolete.
- package.json defaultOptions / optionSchema gain the outputPath field so
  users can override the shadow-index output path from quartz.config.yaml.
- README no longer instructs users to register two plugin entries.

All 21 tests still pass. Build clean.
2026-04-11 14:07:25 +02:00
saberzero1
efa3032973
feat!: replace visibility with unlisted convention and shadow content index
The previous visibility option on the transformer was a dead flag (nothing
read file.data.encryptedVisibility), and EncryptedPageFilter used shouldPublish
to return false, which removed encrypted pages from the build entirely --
directly contradicting the README promise that pages remain accessible by
direct URL.

This change rips both out and replaces them with a first-class file.data.unlisted
convention plus a companion emitter that writes an encrypted shadow content
index. On client-side decryption, cached passwords are replayed against the
shadow index, and the resolved fetchData object is mutated in place so graph,
explorer, and search pick up decrypted pages for the rest of the browser
session without any Quartz core changes.

BREAKING CHANGES:
- Removed EncryptedPageFilter. It was actively harmful -- it deleted pages
  from the build instead of hiding them from listings. Use unlistWhenEncrypted
  or per-page frontmatter unlisted: true instead.
- Removed visibility option from EncryptedPages transformer. It had no effect.
- Removed EncryptedPagesOptions.visibility and EncryptedPageFilterOptions types.

New API:
- EncryptedPages.unlistWhenEncrypted: boolean (default false) -- marks all
  encrypted pages with file.data.unlisted = true.
- Per-page frontmatter unlisted: true | false overrides unlistWhenEncrypted.
- New EncryptedContentIndex emitter writes static/encryptedContentIndex.json
  as a versioned {version: 1, entries: [...]} wrapper. Each entry is an
  opaque {ciphertext, iterations} blob. Slugs, titles, and link relationships
  never leak -- they live only inside the ciphertext.
- Client-side encrypted.inline.ts fetches the shadow index when cached
  passwords exist, tries each cached password against each blob, batches
  successful patches, awaits fetchData, Object.assigns patches onto the
  resolved object, and dispatches content-index-updated + render events.
- Transformer emits a build-time warning if unlistWhenEncrypted: true but
  EncryptedContentIndex is not registered in plugins.emitters.
- encryptAesGcm, decrypt, SHADOW_INDEX_VERSION, and shadow index types are
  exported for test and extension use.

Plugin ordering: EncryptedPages must run after CrawlLinks (or any other
transformer that populates file.data.links), because it replaces the HAST
tree with an opaque ciphertext container.

Tests: 21 passing (13 transformer + 8 emitter). Covers visibility removal,
unlistWhenEncrypted, frontmatter override precedence in both directions,
shadow index format, roundtrip decryption with per-page iteration counts,
non-encrypted skip, and independent ciphertexts across pages sharing a
password.
2026-04-11 13:48:48 +02:00
saberzero1
41d2f12c93
deps: add @quartz-community/utils dependency 2026-04-03 19:33:44 +02:00
saberzero1
6296bc81ff
refactor: use shared classNames from @quartz-community/utils 2026-04-03 19:12:07 +02:00
saberzero1
f386591aa5
deps: update @quartz-community/types and @quartz-community/utils 2026-04-03 16:17:02 +02:00
saberzero1
c6dea06d31
fix: move tsconfig from dts object to top-level option
The DtsConfig type in tsup does not have a tsconfig property.
Use top-level tsconfig option instead of dts: { tsconfig: ... }.
2026-03-22 13:25:12 +01:00
saberzero1
59572baa21
fix: use separate tsconfig for DTS build to avoid vitest dependency 2026-03-22 13:12:01 +01:00
saberzero1
e9f6ed7e9b
chore: set tsup platform to node
Plugins run server-side during the Quartz build step, not in the
browser. Setting platform: 'node' tells esbuild to treat Node builtins
as available externals and avoids CJS-in-ESM require() failures for
any bundled dependencies that use Node APIs.
2026-03-17 20:41:14 +01:00
saberzero1
0663ce3eda
ci: add dist externals verification step 2026-03-17 19:41:47 +01:00
saberzero1
044295e0c4
fix: bundle non-shared deps into dist to prevent runtime resolution failures
Move non-allowlisted packages from dependencies/peerDependencies to
devDependencies so tsup bundles them into dist/ instead of leaving
them as external imports that may not resolve at runtime.
2026-03-17 19:31:51 +01:00
saberzero1
e3c5adf296
fix: add vfile as devDependency for CI typecheck
vfile is imported in source/test files but was only listed as a
peerDependency (or not at all). CI needs it installed to resolve
type declarations during tsc --noEmit and tsup DTS build.
2026-03-16 19:12:16 +01:00
saberzero1
02d37eed70
fix: centralize type declarations and add missing dependencies
- Update types/globals.d.ts with centralized type declarations (CustomEvent<object>)
- Add missing devDependencies for CI compliance
- Add .prettierignore, update .eslintrc.json with triple-slash-reference override
- Remove duplicate scripts.d.ts/styles.d.ts
2026-03-16 18:54:32 +01:00
saberzero1
be9105ac10
fix: update types/utils lockfile to commits with dist/ 2026-03-14 22:43:10 +01:00
saberzero1
b847929601
fix: add build-time deps for DTS generation
Peer dependencies needed at build time for TypeScript declaration file
generation were missing from devDependencies. Without them, the DTS build
fails silently, producing no index.d.ts.
2026-03-14 22:26:14 +01:00
saberzero1
4c77c22930
chore: commit dist/ and remove prepare script
Pre-built output is now committed to the repository so that
Quartz can skip the build step during plugin installation.
The prepare script is removed to prevent redundant builds
when installing from npm/git.
2026-03-14 21:58:19 +01:00
saberzero1
513e5a33b7
chore: move build tools to devDependencies, shared deps to peerDependencies
tsup and typescript are only needed at build time — move to devDependencies
so they can be pruned after build. Dependencies already available from the
host Quartz installation are moved to peerDependencies to avoid duplication.
2026-03-14 21:27:00 +01:00
saberzero1
4e26afa5b9
feat: support decryption in popovers
- Add popover-hint class to encrypted container so popovers pick up the element
- Add MutationObserver to detect encrypted containers injected into the DOM
  (popovers append elements without firing nav/render events)
2026-03-12 20:25:53 +01:00
saberzero1
ac15cf2cbb
feat: implement encrypted pages plugin with AES-256-GCM encryption
- Build-time encryption via Node.js crypto (PBKDF2 + AES-256-GCM)
- Client-side decryption via Web Crypto API
- Per-page passwords via frontmatter field
- SessionStorage password caching across encrypted pages
- Configurable visibility in graph/explorer/search (visible, icon, hidden)
- Password prompt component with Quartz-themed UI
- Dispatches render event after decryption for component re-initialization
- 14 tests covering encryption roundtrip, filter logic, and edge cases
2026-03-12 20:01:13 +01:00
saberzero1
8827eaa0ae
feat: add render event listener pattern to template boilerplate 2026-03-12 19:45:46 +01:00
Emile Bangma
3d185bb00b
Initial commit 2026-03-12 19:20:13 +01:00