@quartz-community/encrypted-pages 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - `EncryptedContentIndex` emitter that writes a versioned shadow content index (`static/encryptedContentIndex.json`) containing opaque encrypted blobs of per-page metadata (slug, title, links, tags) for pages marked `unlisted: true`. The shadow index uses a flat array format so no slugs or titles leak to anonymous visitors.
13
+ - `unlistWhenEncrypted` option on the `EncryptedPages` transformer. When `true`, encrypted pages are marked `file.data.unlisted = true`, hiding them from every build-time listing surface that respects the `unlisted` convention (contentIndex, RSS, sitemap, backlinks, recent-notes, folder-page, tag-page, graph, explorer, search) while still emitting the HTML so the page remains accessible by direct URL.
14
+ - Per-page `unlisted: true | false` frontmatter override. Explicit `unlisted: false` forces the page listed even when `unlistWhenEncrypted` is set.
15
+ - Per-page `stealth: true` frontmatter field. 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. Useful for "secret door" pages that should only be accessible to users who already know the exact URL. `stealth: true` implies `unlisted: true` and overrides any explicit `unlisted: false`. No effect on non-encrypted pages.
16
+ - Client-side shadow-index unlocking: on every page load with cached passwords, the client script fetches the shadow index, decrypts entries with cached passwords, patches the resolved `fetchData` object in place, and dispatches `content-index-updated` so graph, explorer, and search re-initialize with the unlocked pages.
17
+ - `encryptAesGcm`, `decrypt`, and `SHADOW_INDEX_VERSION` exports for test and extension use.
18
+ - `ShadowIndexBlob`, `ShadowIndexFile`, and `ShadowContentIndexEntry` type exports.
19
+ - `EncryptedContentIndexOptions.passwordField` (default `"password"`) so the emitter reads the frontmatter password field from its own merged options rather than spelunking `ctx.cfg.plugins.transformers` at emit time.
20
+ - `outputPath` field in the package manifest's `defaultOptions` and `optionSchema` so users can override the shadow-index output path from `quartz.config.yaml`.
21
+
22
+ ### Fixed
23
+
24
+ - **Shadow content index emitter now actually runs.** Changed `package.json` > `quartz.category` from `"transformer"` to `["transformer", "emitter"]` so Quartz v5's plugin loader instantiates both the transformer and the emitter from the single config entry. Previously, Quartz read `"transformer"` and only ever called `findFactory(module, "transformer")`, so `EncryptedContentIndex` was exported from the module but never invoked — `static/encryptedContentIndex.json` was never written. A user's single `- source: github:quartz-community/encrypted-pages` entry in `quartz.config.yaml` now correctly produces both the transformer and the emitter instances, and both factories receive the same merged options object.
25
+
26
+ ### Removed
27
+
28
+ - **Breaking:** `EncryptedPageFilter`. It used Quartz's `shouldPublish` filter mechanism, which removes pages from the entire build (no HTML emitted), directly contradicting the plugin's own README. Use `unlistWhenEncrypted: true` or per-page `unlisted: true` frontmatter instead.
29
+ - **Breaking:** `visibility` option on the `EncryptedPages` transformer. It set a `file.data.encryptedVisibility` flag that nothing in the Quartz v5 ecosystem read — the option had no effect.
30
+ - **Breaking:** `EncryptedPagesOptions.visibility` type field.
31
+ - **Breaking:** `EncryptedPageFilterOptions` type export.
32
+ - The build-time warning for a missing `EncryptedContentIndex` emitter. Quartz v5's plugin loader now instantiates both the transformer and the emitter automatically from the same config entry (via the `category: ["transformer", "emitter"]` manifest), so the warning is obsolete.
33
+
34
+ ### Changed
35
+
36
+ - Plugin ordering requirement: `EncryptedPages` must run after `CrawlLinks` (or any other transformer that populates `file.data.links`) in the `htmlPlugins` chain. Use the `order` field in `quartz.config.yaml` to control this. Documented in README.
37
+ - The README no longer instructs users to register the emitter separately — Quartz v5 handles that automatically from a single config entry.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Quartz Community
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # @quartz-community/encrypted-pages
2
+
3
+ Password-protected encrypted pages for Quartz v5.
4
+
5
+ ## How it works
6
+
7
+ Build-time encryption with AES-256-GCM, client-side decryption via the Web Crypto API, PBKDF2-SHA256 key derivation.
8
+
9
+ Two plugins work together:
10
+
11
+ - **`EncryptedPages`** (transformer) — encrypts the rendered HTML of any page whose frontmatter contains a password.
12
+ - **`EncryptedContentIndex`** (emitter) — emits a sibling shadow content index that client-side decryption uses to dynamically reveal unlisted encrypted pages in graph, explorer, and search after a successful unlock.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npx quartz plugin add github:quartz-community/encrypted-pages
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Add a `password` field to the frontmatter of any page you want to encrypt. Optionally add `unlisted: true` to hide the page from every build-time listing surface; the page will be revealed dynamically to graph/explorer/search on successful client-side decryption.
23
+
24
+ ```yaml
25
+ ---
26
+ title: My Secret Page
27
+ password: mysecretpassword
28
+ unlisted: true
29
+ ---
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ This plugin ships both a transformer (`EncryptedPages`) and an emitter (`EncryptedContentIndex`) in a single package. Quartz v5 instantiates both automatically from one config entry — you do not need to register them separately.
35
+
36
+ ### quartz.config.yaml
37
+
38
+ ```yaml
39
+ - source: github:quartz-community/encrypted-pages
40
+ enabled: true
41
+ options:
42
+ iterations: 600000
43
+ passwordField: password
44
+ unlistWhenEncrypted: true
45
+ ```
46
+
47
+ **Plugin ordering matters.** `EncryptedPages` replaces the entire HAST tree of an encrypted page with an opaque ciphertext container, so any transformer that must see the real HTML — in particular `CrawlLinks`, which populates `file.data.links` for the shadow content index — must run **before** `EncryptedPages`. Use the `order` field in your config to ensure `CrawlLinks` has a lower order value than `EncryptedPages`.
48
+
49
+ ### Component
50
+
51
+ Add the `EncryptedPage` component to your body layout in `quartz.layout.ts`.
52
+
53
+ ## Options
54
+
55
+ All options below are set at the single plugin-entry level and are shared between the transformer and the emitter. Quartz passes the same merged options object to both factories automatically.
56
+
57
+ | Option | Type | Default | Used by | Description |
58
+ | --------------------- | --------- | ------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
+ | `iterations` | `number` | `600000` | transformer | PBKDF2 iteration count for key derivation. Higher is more secure but slower to unlock. |
60
+ | `passwordField` | `string` | `"password"` | transformer, emitter | Frontmatter field name that holds the page password. |
61
+ | `unlistWhenEncrypted` | `boolean` | `false` | transformer | When `true`, encrypted pages are marked `file.data.unlisted = true`, hiding them from every build-time listing surface that respects the `unlisted` convention (contentIndex, RSS, sitemap, backlinks, recent-notes, folder-page, tag-page, graph, explorer, search). Per-page `frontmatter.unlisted` overrides. |
62
+ | `outputPath` | `string` | `"static/encryptedContentIndex.json"` | emitter | Output path for the shadow content index, relative to Quartz's output directory. |
63
+
64
+ ### `EncryptedPage` (component)
65
+
66
+ | Option | Type | Default | Description |
67
+ | ----------- | -------- | -------------------------- | ------------------------------------ |
68
+ | `className` | `string` | `"encrypted-page-wrapper"` | CSS class for the component wrapper. |
69
+
70
+ ### Frontmatter fields
71
+
72
+ | Field | Type | Description |
73
+ | ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
74
+ | `password` | `string` | Enables encryption for this page. The field name is configurable via `passwordField`. |
75
+ | `unlisted` | `boolean` | Overrides `unlistWhenEncrypted`. `true` forces the page unlisted; `false` forces it listed even when `unlistWhenEncrypted` is also set. |
76
+ | `stealth` | `boolean` | When `true` and the page is encrypted, suppresses the page's entry in the shadow content index. The page stays hidden from every listing surface **even after successful client-side decryption**. Implies `unlisted: true` (overrides any explicit `unlisted: false`). No effect on non-encrypted pages. |
77
+
78
+ ## How `unlisted` works
79
+
80
+ When an encrypted page is marked `unlisted: true`:
81
+
82
+ - The page HTML is still emitted at its normal URL.
83
+ - It is **absent** from `contentIndex.json`, RSS, sitemap, and every server-side listing that respects the `unlisted` convention (backlinks, recent-notes, folder-page, tag-page, and bases views from `@quartz-community/bases-page`).
84
+ - Its metadata (slug, title, links, tags) is encrypted with the page's own password and emitted to `static/encryptedContentIndex.json` in an opaque, flat JSON array. No slugs or titles leak to anonymous visitors.
85
+ - On any page load, if the user has cached passwords in sessionStorage from a previous successful decryption, the client script fetches the shadow index, decrypts entries matching those passwords, and patches the in-memory content index in place. A `content-index-updated` event is dispatched so graph, explorer, and search reinitialize with the unlocked entries.
86
+ - Server-side rendered listings (backlinks, recent-notes, folder-page, tag-page, **bases views**) remain statically hidden even after client-side decryption. This is a deliberate trade-off: those surfaces are HTML baked at build time and cannot be patched client-side. 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.
87
+
88
+ ## How `stealth` works
89
+
90
+ When an encrypted page is marked `stealth: true`, it behaves like an unlisted page with one critical difference: its entry is **never** emitted to the shadow content index. As a result:
91
+
92
+ - The page HTML is still emitted at its normal URL and accessible to anyone who knows it.
93
+ - The page is absent from `contentIndex.json`, RSS, sitemap, graph, explorer, search, backlinks, recent notes, folder listings, and tag listings — same as any `unlisted` page.
94
+ - Unlike `unlisted`, **successful client-side decryption does not reveal the page in graph, explorer, or search**. There is no shadow-index entry to decrypt, so the in-memory content index is never patched for this page.
95
+ - The password cache still works: a user who unlocks a stealth page once will not have to re-enter the password on subsequent visits within the same session. Only discovery surfaces are affected.
96
+
97
+ Use `stealth: true` when you want a "secret door" page: accessible only to users who already know the exact URL, invisible to everyone else permanently, even to users who have successfully decrypted other encrypted pages on the same site.
98
+
99
+ ```yaml
100
+ ---
101
+ title: Deep Secret
102
+ password: hunter2
103
+ stealth: true
104
+ ---
105
+ ```
106
+
107
+ Setting `stealth: true` is equivalent to `unlisted: true` on a page that does not also have a `password` field — the flag has no effect on non-encrypted pages, because stealth only controls the shadow-index emission, and non-encrypted pages do not have shadow-index entries in the first place.
108
+
109
+ ## Security
110
+
111
+ - AES-256-GCM with 16-byte salt, 12-byte IV, 16-byte auth tag.
112
+ - PBKDF2-SHA256 with 600,000 default iterations.
113
+ - Plaintext leakage prevention: `file.data.text` and `file.data.description` are cleared by the transformer.
114
+ - The shadow content index entries are individually encrypted with the same password as the page they describe. An attacker downloading `encryptedContentIndex.json` learns only the number of unlisted encrypted pages and the PBKDF2 iteration count. No slugs, titles, or link relationships are exposed.
115
+ - Passwords are cached in `sessionStorage`, which clears when the browser session ends.
116
+ - This is client-side encryption for a static site. A determined attacker with access to the ciphertext can run an offline brute-force. Use strong passwords.
117
+
118
+ ## Password caching
119
+
120
+ Successful passwords are cached in `sessionStorage` so subsequent encrypted pages with the same password auto-unlock. Successfully decrypted shadow entries are also cached for the session to avoid re-running PBKDF2 on every navigation.
121
+
122
+ ## Render events
123
+
124
+ After a page is decrypted, a `render` `CustomEvent` is dispatched. After the shadow content index is patched, a `content-index-updated` `CustomEvent` is dispatched with a `detail.slugs` array of newly-added slugs. Graph, explorer, and search re-initialize on these events.
125
+
126
+ ## License
127
+
128
+ MIT
@@ -0,0 +1,9 @@
1
+ import { QuartzComponent } from '@quartz-community/types';
2
+
3
+ interface EncryptedPageComponentOptions {
4
+ /** CSS class name for the component wrapper. */
5
+ className?: string;
6
+ }
7
+ declare const _default: (opts?: EncryptedPageComponentOptions) => QuartzComponent;
8
+
9
+ export { _default as EncryptedPage, type EncryptedPageComponentOptions };
@@ -0,0 +1,40 @@
1
+ // src/components/styles/encrypted.scss
2
+ var encrypted_default = ".encrypted-page {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 300px;\n padding: 2rem;\n}\n\n.encrypted-page-form {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n max-width: 400px;\n width: 100%;\n text-align: center;\n}\n\n.encrypted-page-icon {\n color: var(--gray, #6b7280);\n opacity: 0.6;\n}\n\n.encrypted-page-label {\n color: var(--darkgray, #4b5563);\n font-size: 0.95rem;\n margin: 0;\n line-height: 1.5;\n}\n\n.encrypted-page-input-row {\n display: flex;\n gap: 0.5rem;\n width: 100%;\n}\n\n.encrypted-page-input {\n flex: 1;\n padding: 0.5rem 0.75rem;\n border: 1px solid var(--lightgray, #d1d5db);\n border-radius: 4px;\n font-size: 0.9rem;\n font-family: inherit;\n background: var(--light, #fff);\n color: var(--dark, #111);\n outline: none;\n transition: border-color 0.15s ease;\n}\n.encrypted-page-input:focus {\n border-color: var(--secondary, #3b82f6);\n}\n.encrypted-page-input:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.encrypted-page-submit {\n padding: 0.5rem 1.25rem;\n border: none;\n border-radius: 4px;\n font-size: 0.9rem;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--secondary, #3b82f6);\n color: var(--light, #fff);\n transition: background 0.15s ease, opacity 0.15s ease;\n white-space: nowrap;\n}\n.encrypted-page-submit:hover:not(:disabled) {\n opacity: 0.9;\n}\n.encrypted-page-submit:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.encrypted-page-error {\n color: var(--red, #dc2626);\n font-size: 0.85rem;\n margin: 0;\n}";
3
+
4
+ // src/components/scripts/encrypted.inline.ts
5
+ var encrypted_inline_default = `var S="encrypted-pages-passwords",m="encrypted-pages:decryptedShadowEntries";function v(t){let e=atob(t),n=new Uint8Array(e.length);for(let r=0;r<e.length;r++)n[r]=e.charCodeAt(r);return n}async function b(t,e,n){let r=new TextEncoder,o=await window.crypto.subtle.importKey("raw",r.encode(t),"PBKDF2",!1,["deriveKey"]);return window.crypto.subtle.deriveKey({name:"PBKDF2",salt:e,iterations:n,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["decrypt"])}async function T(t,e,n){let r=v(t),o=r.slice(0,16),i=r.slice(16,28),c=r.slice(28,44),a=r.slice(44),s=new Uint8Array(a.length+c.length);s.set(a,0),s.set(c,a.length);let d=await b(e,o,n),l=await window.crypto.subtle.decrypt({name:"AES-GCM",iv:i},d,s);return new TextDecoder().decode(l)}function h(){try{let t=sessionStorage.getItem(S);if(!t)return[];let e=JSON.parse(t);return Array.isArray(e)?e:[]}catch{return[]}}function A(t){let e=h();e.includes(t)||(e.push(t),sessionStorage.setItem(S,JSON.stringify(e)))}function L(){try{let t=sessionStorage.getItem(m);if(!t)return{};let e=JSON.parse(t);return e&&typeof e=="object"?e:{}}catch{return{}}}function N(t){try{sessionStorage.setItem(m,JSON.stringify(t))}catch{}}function I(t,e){let n=t.querySelector(".encrypted-page-error");n&&(n.textContent=e,n.style.display="block")}function _(t){let e=t.querySelector(".encrypted-page-error");e&&(e.style.display="none",e.textContent="")}function w(t,e){let n=t.querySelector(".encrypted-page-submit"),r=t.querySelector(".encrypted-page-input");n&&(n.disabled=e,n.textContent=e?"Decrypting\\u2026":"Unlock"),r&&(r.disabled=e)}function x(){let t=document.querySelectorAll("script");for(let e of t){let r=(e.textContent??"").match(/fetch\\(["']([^"']+contentIndex\\.json)["']\\)/);if(r)return r[1].replace(/contentIndex\\.json$/,"encryptedContentIndex.json")}return new URL("static/encryptedContentIndex.json",document.baseURI).toString()}var p=null;async function H(){if(p)return p;let t=x();return p=fetch(t).then(e=>{if(!e.ok)throw new Error(\`shadow index HTTP \${e.status}\`);return e.json()}).then(e=>!e||e.version!==1||!Array.isArray(e.entries)?{version:1,entries:[]}:e).catch(()=>({version:1,entries:[]})),p}async function D(t,e,n){let r={},o={...n},i=!1;for(let c=0;c<t.entries.length;c++){let a=String(c);if(n[a]){let d=n[a];d&&d.slug&&(r[d.slug]=d.entry);continue}let s=t.entries[c];if(!(!s||typeof s.ciphertext!="string"))for(let d of e)try{let l=await T(s.ciphertext,d,s.iterations),u=JSON.parse(l);u&&typeof u.slug=="string"&&u.entry&&(r[u.slug]=u.entry,o[a]=u,i=!0);break}catch{}}return i&&N(o),r}async function G(t){let e=Object.keys(t);if(e.length!==0){try{let n=await(typeof fetchData<"u"?fetchData:Promise.resolve(null));if(!n||typeof n!="object")return;let r=n.content&&typeof n.content=="object"?n.content:n;for(let o of e)o in r||(r[o]=t[o])}catch{return}document.dispatchEvent(new CustomEvent("content-index-updated",{detail:{slugs:e}})),document.dispatchEvent(new CustomEvent("render"))}}var y=!1;async function f(){if(y)return;let t=h();if(t.length!==0){y=!0;try{let e=await H();if(!e.entries||e.entries.length===0)return;let n=L(),r=await D(e,t,n);await G(r)}finally{y=!1}}}async function E(t,e){let n=t.getAttribute("data-encrypted"),r=parseInt(t.getAttribute("data-iterations")||"600000",10);if(!n)return!1;try{let o=await T(n,e,r);if(t.parentElement){let c=document.createElement("div");c.innerHTML=o,t.replaceWith(...c.childNodes)}return A(e),document.dispatchEvent(new CustomEvent("render")),f(),!0}catch{return!1}}function g(){let t=document.querySelectorAll(".encrypted-page");if(t.length===0){f();return}for(let e of t){if(e.querySelector(".encrypted-page-form")||!e.getAttribute("data-encrypted"))continue;let r=document.createElement("div");r.className="encrypted-page-form",r.innerHTML=['<div class="encrypted-page-icon" aria-hidden="true">','<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">','<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>','<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>',"</svg>","</div>",'<p class="encrypted-page-label">This page is encrypted. Enter the password to view its content.</p>','<div class="encrypted-page-input-row">','<input type="password" class="encrypted-page-input" placeholder="Password" autocomplete="off" />','<button type="button" class="encrypted-page-submit">Unlock</button>',"</div>",'<p class="encrypted-page-error" style="display:none"></p>'].join(""),e.appendChild(r);let o=r.querySelector(".encrypted-page-input"),i=r.querySelector(".encrypted-page-submit");async function c(){let s=o?.value;if(!s)return;_(e),w(e,!0),await E(e,s)||(w(e,!1),I(e,"Incorrect password. Please try again."),o&&(o.value="",o.focus()))}i&&i.addEventListener("click",c),o&&o.addEventListener("keydown",s=>{s.key==="Enter"&&(s.preventDefault(),c())});let a=h();a.length>0&&(async()=>{for(let s of a)if(await E(e,s))return})()}f()}document.addEventListener("nav",()=>{g()});document.addEventListener("render",()=>{document.querySelectorAll(".encrypted-page").length>0&&g()});var C=new MutationObserver(t=>{for(let e of t)for(let n of e.addedNodes){if(!(n instanceof HTMLElement))continue;if((n.classList?.contains("encrypted-page")?[n]:[...n.querySelectorAll(".encrypted-page")]).filter(i=>!i.querySelector(".encrypted-page-form")).length>0){g();return}}});C.observe(document.body,{childList:!0,subtree:!0});
6
+ `;
7
+ var l;
8
+ l = { __e: function(n2, l2, u3, t2) {
9
+ for (var i2, r2, o2; l2 = l2.__; ) if ((i2 = l2.__c) && !i2.__) try {
10
+ if ((r2 = i2.constructor) && null != r2.getDerivedStateFromError && (i2.setState(r2.getDerivedStateFromError(n2)), o2 = i2.__d), null != i2.componentDidCatch && (i2.componentDidCatch(n2, t2 || {}), o2 = i2.__d), o2) return i2.__E = i2;
11
+ } catch (l3) {
12
+ n2 = l3;
13
+ }
14
+ throw n2;
15
+ } }, "function" == typeof Promise ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout, Math.random().toString(8);
16
+
17
+ // node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
18
+ var f2 = 0;
19
+ function u2(e2, t2, n2, o2, i2, u3) {
20
+ t2 || (t2 = {});
21
+ var a2, c2, p2 = t2;
22
+ if ("ref" in p2) for (c2 in p2 = {}, t2) "ref" == c2 ? a2 = t2[c2] : p2[c2] = t2[c2];
23
+ var l2 = { type: e2, props: p2, key: n2, ref: a2, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: --f2, __i: -1, __u: 0, __source: i2, __self: u3 };
24
+ return l.vnode && l.vnode(l2), l2;
25
+ }
26
+
27
+ // src/components/EncryptedPage.tsx
28
+ var EncryptedPage_default = ((opts) => {
29
+ const { className = "encrypted-page-wrapper" } = opts ?? {};
30
+ const Component = (_props) => {
31
+ return /* @__PURE__ */ u2("div", { class: className });
32
+ };
33
+ Component.css = encrypted_default;
34
+ Component.afterDOMLoaded = encrypted_inline_default;
35
+ return Component;
36
+ });
37
+
38
+ export { EncryptedPage_default as EncryptedPage };
39
+ //# sourceMappingURL=index.js.map
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/styles/encrypted.scss","../../src/components/scripts/encrypted.inline.ts","../../node_modules/preact/src/constants.js","../../node_modules/preact/src/options.js","../../node_modules/preact/src/diff/catch-error.js","../../node_modules/preact/src/component.js","../../node_modules/preact/src/diff/props.js","../../node_modules/preact/src/create-context.js","../../node_modules/preact/jsx-runtime/src/index.js","../../src/components/EncryptedPage.tsx"],"names":["options","__e","error","vnode","oldVNode","errorInfo","component","ctor","handled","__","__c","constructor","getDerivedStateFromError","setState","__d","componentDidCatch","__E","e","Promise","prototype","then","bind","resolve","setTimeout","Math","random","toString","vnodeId","createVNode","type","props","key","isStaticChildren","__source","__self","ref","i","normalizedProps","__k","__b","__v","__i","__u","u"],"mappings":";AAAA,IAAA,iBAAA,GAAA,ymDAAA;;;ACAA,IAAA,wBAAA,GAAA,CAAA;AAAA,CAAA;ACCO,ICUDA,CAAAA;AAAAA,CAAAA,GAAU,EACfC,KCDM,SAAqBC,EAAAA,EAAOC,EAAAA,EAAOC,EAAAA,EAAUC,EAAAA,EAAAA;AAQnD,EAAA,KAAA,IANIC,EAAAA,EAEHC,EAAAA,EAEAC,EAAAA,EAEOL,EAAAA,GAAQA,EAAAA,CAAKM,EAAAA,IACpB,IAAA,CAAKH,EAAAA,GAAYH,EAAAA,CAAKO,GAAAA,KAAAA,CAAiBJ,EAAAA,CAASG,IAC/C,IAAA;AAcC,IAAA,IAAA,CAbAF,EAAAA,GAAOD,EAAAA,CAAUK,WAAAA,KFND,IAAA,IEQJJ,GAAKK,wBAAAA,KAChBN,EAAAA,CAAUO,QAAAA,CAASN,EAAAA,CAAKK,wBAAAA,CAAyBV,EAAAA,CAAAA,CAAAA,EACjDM,KAAUF,EAAAA,CAASQ,GAAAA,CAAAA,EFVJ,IAAA,IEaZR,EAAAA,CAAUS,iBAAAA,KACbT,EAAAA,CAAUS,iBAAAA,CAAkBb,EAAAA,EAAOG,MAAa,EAAE,CAAA,EAClDG,EAAAA,GAAUF,EAAAA,CAASQ,GAAAA,CAAAA,EAIhBN,EAAAA,EACH,OAAQF,GAASU,GAAAA,GAAiBV,EAAAA;AAIpC,EAAA,CAAA,CAAA,OAFSW,EAAAA,EAAAA;AACRf,IAAAA,EAAAA,GAAQe,EAAAA;AACT,EAAA;AAIF,EAAA,MAAMf,EAAAA;AACP,CAAA,EAAA,ECiJmB,UAAA,IAAA,OAAXgB,OAAAA,GACJA,QAAQC,SAAAA,CAAUC,IAAAA,CAAKC,IAAAA,CAAKH,OAAAA,CAAQI,OAAAA,EAAAA,IACpCC,UAAAA,ECtLMC,IAAAA,CAAKC,MAAAA,EAAAA,CAASC,QAAAA,CAAS,CAAA,CCPlB;;;ALiBF,IMhBTC,EAAAA,GAAU,CAAA;AAwBd,SAASC,GAAYC,EAAAA,EAAMC,EAAAA,EAAOC,EAAAA,EAAKC,EAAAA,EAAkBC,IAAUC,EAAAA,EAAAA;AAC7DJ,EAAAA,EAAAA,KAAOA,KAAQ,EAAA,CAAA;AAIpB,EAAA,IACCK,EAAAA,EACAC,IAFGC,EAAAA,GAAkBP,EAAAA;AAItB,EAAA,IAAI,SAASO,EAAAA,EAEZ,KAAKD,MADLC,EAAAA,GAAkB,IACRP,EAAAA,EACA,KAAA,IAALM,EAAAA,GACHD,EAAAA,GAAML,GAAMM,EAAAA,CAAAA,GAEZC,GAAgBD,EAAAA,CAAAA,GAAKN,GAAMM,EAAAA,CAAAA;AAM9B,EAAA,IAAMjC,EAAAA,GAAQ,EACb0B,IAAAA,EAAAA,EAAAA,EACAC,OAAOO,EAAAA,EACPN,GAAAA,EAAAA,EAAAA,EACAI,GAAAA,EAAAA,EAAAA,EACAG,GAAAA,EAAW,IAAA,EACX7B,EAAAA,EAAS,MACT8B,GAAAA,EAAQ,CAAA,EACRtC,GAAAA,EAAM,IAAA,EACNS,GAAAA,EAAY,IAAA,EACZC,WAAAA,EAAAA,MAAAA,EACA6B,KAAAA,EAAab,EAAAA,EACbc,GAAAA,EAAAA,EAAAA,EACAC,GAAAA,EAAQ,CAAA,EACRT,QAAAA,EAAAA,EAAAA,EACAC,QAAAA,EAAAA,EAAAA;AAaD,EAAA,OADIlC,CAAAA,CAAQG,KAAAA,IAAOH,CAAAA,CAAQG,KAAAA,CAAMA,EAAAA,CAAAA,EAC1BA,EAAAA;AACR;;;AChEA,IAAO,qBAAA,IAAS,CAAC,IAAA,KAAyC;AACxD,EAAA,MAAM,EAAE,SAAA,GAAY,wBAAA,EAAyB,GAAI,QAAQ,EAAC;AAE1D,EAAA,MAAM,SAAA,GAA6B,CAAC,MAAA,KAAiC;AAInE,IAAA,uBAAOwC,EAAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,SAAA,EAAW,CAAA;AAAA,EAChC,CAAA;AAEA,EAAA,SAAA,CAAU,GAAA,GAAM,iBAAA;AAChB,EAAA,SAAA,CAAU,cAAA,GAAiB,wBAAA;AAE3B,EAAA,OAAO,SAAA;AACT,CAAA","file":"index.js","sourcesContent":[".encrypted-page {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 300px;\n padding: 2rem;\n}\n\n.encrypted-page-form {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1rem;\n max-width: 400px;\n width: 100%;\n text-align: center;\n}\n\n.encrypted-page-icon {\n color: var(--gray, #6b7280);\n opacity: 0.6;\n}\n\n.encrypted-page-label {\n color: var(--darkgray, #4b5563);\n font-size: 0.95rem;\n margin: 0;\n line-height: 1.5;\n}\n\n.encrypted-page-input-row {\n display: flex;\n gap: 0.5rem;\n width: 100%;\n}\n\n.encrypted-page-input {\n flex: 1;\n padding: 0.5rem 0.75rem;\n border: 1px solid var(--lightgray, #d1d5db);\n border-radius: 4px;\n font-size: 0.9rem;\n font-family: inherit;\n background: var(--light, #fff);\n color: var(--dark, #111);\n outline: none;\n transition: border-color 0.15s ease;\n}\n.encrypted-page-input:focus {\n border-color: var(--secondary, #3b82f6);\n}\n.encrypted-page-input:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.encrypted-page-submit {\n padding: 0.5rem 1.25rem;\n border: none;\n border-radius: 4px;\n font-size: 0.9rem;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--secondary, #3b82f6);\n color: var(--light, #fff);\n transition: background 0.15s ease, opacity 0.15s ease;\n white-space: nowrap;\n}\n.encrypted-page-submit:hover:not(:disabled) {\n opacity: 0.9;\n}\n.encrypted-page-submit:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.encrypted-page-error {\n color: var(--red, #dc2626);\n font-size: 0.85rem;\n margin: 0;\n}","var S=\"encrypted-pages-passwords\",m=\"encrypted-pages:decryptedShadowEntries\";function v(t){let e=atob(t),n=new Uint8Array(e.length);for(let r=0;r<e.length;r++)n[r]=e.charCodeAt(r);return n}async function b(t,e,n){let r=new TextEncoder,o=await window.crypto.subtle.importKey(\"raw\",r.encode(t),\"PBKDF2\",!1,[\"deriveKey\"]);return window.crypto.subtle.deriveKey({name:\"PBKDF2\",salt:e,iterations:n,hash:\"SHA-256\"},o,{name:\"AES-GCM\",length:256},!1,[\"decrypt\"])}async function T(t,e,n){let r=v(t),o=r.slice(0,16),i=r.slice(16,28),c=r.slice(28,44),a=r.slice(44),s=new Uint8Array(a.length+c.length);s.set(a,0),s.set(c,a.length);let d=await b(e,o,n),l=await window.crypto.subtle.decrypt({name:\"AES-GCM\",iv:i},d,s);return new TextDecoder().decode(l)}function h(){try{let t=sessionStorage.getItem(S);if(!t)return[];let e=JSON.parse(t);return Array.isArray(e)?e:[]}catch{return[]}}function A(t){let e=h();e.includes(t)||(e.push(t),sessionStorage.setItem(S,JSON.stringify(e)))}function L(){try{let t=sessionStorage.getItem(m);if(!t)return{};let e=JSON.parse(t);return e&&typeof e==\"object\"?e:{}}catch{return{}}}function N(t){try{sessionStorage.setItem(m,JSON.stringify(t))}catch{}}function I(t,e){let n=t.querySelector(\".encrypted-page-error\");n&&(n.textContent=e,n.style.display=\"block\")}function _(t){let e=t.querySelector(\".encrypted-page-error\");e&&(e.style.display=\"none\",e.textContent=\"\")}function w(t,e){let n=t.querySelector(\".encrypted-page-submit\"),r=t.querySelector(\".encrypted-page-input\");n&&(n.disabled=e,n.textContent=e?\"Decrypting\\u2026\":\"Unlock\"),r&&(r.disabled=e)}function x(){let t=document.querySelectorAll(\"script\");for(let e of t){let r=(e.textContent??\"\").match(/fetch\\([\"']([^\"']+contentIndex\\.json)[\"']\\)/);if(r)return r[1].replace(/contentIndex\\.json$/,\"encryptedContentIndex.json\")}return new URL(\"static/encryptedContentIndex.json\",document.baseURI).toString()}var p=null;async function H(){if(p)return p;let t=x();return p=fetch(t).then(e=>{if(!e.ok)throw new Error(`shadow index HTTP ${e.status}`);return e.json()}).then(e=>!e||e.version!==1||!Array.isArray(e.entries)?{version:1,entries:[]}:e).catch(()=>({version:1,entries:[]})),p}async function D(t,e,n){let r={},o={...n},i=!1;for(let c=0;c<t.entries.length;c++){let a=String(c);if(n[a]){let d=n[a];d&&d.slug&&(r[d.slug]=d.entry);continue}let s=t.entries[c];if(!(!s||typeof s.ciphertext!=\"string\"))for(let d of e)try{let l=await T(s.ciphertext,d,s.iterations),u=JSON.parse(l);u&&typeof u.slug==\"string\"&&u.entry&&(r[u.slug]=u.entry,o[a]=u,i=!0);break}catch{}}return i&&N(o),r}async function G(t){let e=Object.keys(t);if(e.length!==0){try{let n=await(typeof fetchData<\"u\"?fetchData:Promise.resolve(null));if(!n||typeof n!=\"object\")return;let r=n.content&&typeof n.content==\"object\"?n.content:n;for(let o of e)o in r||(r[o]=t[o])}catch{return}document.dispatchEvent(new CustomEvent(\"content-index-updated\",{detail:{slugs:e}})),document.dispatchEvent(new CustomEvent(\"render\"))}}var y=!1;async function f(){if(y)return;let t=h();if(t.length!==0){y=!0;try{let e=await H();if(!e.entries||e.entries.length===0)return;let n=L(),r=await D(e,t,n);await G(r)}finally{y=!1}}}async function E(t,e){let n=t.getAttribute(\"data-encrypted\"),r=parseInt(t.getAttribute(\"data-iterations\")||\"600000\",10);if(!n)return!1;try{let o=await T(n,e,r);if(t.parentElement){let c=document.createElement(\"div\");c.innerHTML=o,t.replaceWith(...c.childNodes)}return A(e),document.dispatchEvent(new CustomEvent(\"render\")),f(),!0}catch{return!1}}function g(){let t=document.querySelectorAll(\".encrypted-page\");if(t.length===0){f();return}for(let e of t){if(e.querySelector(\".encrypted-page-form\")||!e.getAttribute(\"data-encrypted\"))continue;let r=document.createElement(\"div\");r.className=\"encrypted-page-form\",r.innerHTML=['<div class=\"encrypted-page-icon\" aria-hidden=\"true\">','<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">','<rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>','<path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>',\"</svg>\",\"</div>\",'<p class=\"encrypted-page-label\">This page is encrypted. Enter the password to view its content.</p>','<div class=\"encrypted-page-input-row\">','<input type=\"password\" class=\"encrypted-page-input\" placeholder=\"Password\" autocomplete=\"off\" />','<button type=\"button\" class=\"encrypted-page-submit\">Unlock</button>',\"</div>\",'<p class=\"encrypted-page-error\" style=\"display:none\"></p>'].join(\"\"),e.appendChild(r);let o=r.querySelector(\".encrypted-page-input\"),i=r.querySelector(\".encrypted-page-submit\");async function c(){let s=o?.value;if(!s)return;_(e),w(e,!0),await E(e,s)||(w(e,!1),I(e,\"Incorrect password. Please try again.\"),o&&(o.value=\"\",o.focus()))}i&&i.addEventListener(\"click\",c),o&&o.addEventListener(\"keydown\",s=>{s.key===\"Enter\"&&(s.preventDefault(),c())});let a=h();a.length>0&&(async()=>{for(let s of a)if(await E(e,s))return})()}f()}document.addEventListener(\"nav\",()=>{g()});document.addEventListener(\"render\",()=>{document.querySelectorAll(\".encrypted-page\").length>0&&g()});var C=new MutationObserver(t=>{for(let e of t)for(let n of e.addedNodes){if(!(n instanceof HTMLElement))continue;if((n.classList?.contains(\"encrypted-page\")?[n]:[...n.querySelectorAll(\".encrypted-page\")]).filter(i=>!i.querySelector(\".encrypted-page-form\")).length>0){g();return}}});C.observe(document.body,{childList:!0,subtree:!0});\n","/** Normal hydration that attaches to a DOM tree but does not diff it. */\nexport const MODE_HYDRATE = 1 << 5;\n/** Signifies this VNode suspended on the previous render */\nexport const MODE_SUSPENDED = 1 << 7;\n/** Indicates that this node needs to be inserted while patching children */\nexport const INSERT_VNODE = 1 << 2;\n/** Indicates a VNode has been matched with another VNode in the diff */\nexport const MATCHED = 1 << 1;\n\n/** Reset all mode flags */\nexport const RESET_MODE = ~(MODE_HYDRATE | MODE_SUSPENDED);\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\nexport const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\nexport const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n\nexport const NULL = null;\nexport const UNDEFINED = undefined;\nexport const EMPTY_OBJ = /** @type {any} */ ({});\nexport const EMPTY_ARR = [];\nexport const IS_NON_DIMENSIONAL =\n\t/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n","import { _catchError } from './diff/catch-error';\n\n/**\n * The `option` object can potentially contain callback functions\n * that are called during various stages of our renderer. This is the\n * foundation on which all our addons like `preact/debug`, `preact/compat`,\n * and `preact/hooks` are based on. See the `Options` type in `internal.d.ts`\n * for a full list of available option hooks (most editors/IDEs allow you to\n * ctrl+click or cmd+click on mac the type definition below).\n * @type {import('./internal').Options}\n */\nconst options = {\n\t_catchError\n};\n\nexport default options;\n","import { NULL } from '../constants';\n\n/**\n * Find the closest error boundary to a thrown error and call it\n * @param {object} error The thrown value\n * @param {import('../internal').VNode} vnode The vnode that threw the error that was caught (except\n * for unmounting when this parameter is the highest parent that was being\n * unmounted)\n * @param {import('../internal').VNode} [oldVNode]\n * @param {import('../internal').ErrorInfo} [errorInfo]\n */\nexport function _catchError(error, vnode, oldVNode, errorInfo) {\n\t/** @type {import('../internal').Component} */\n\tlet component,\n\t\t/** @type {import('../internal').ComponentType} */\n\t\tctor,\n\t\t/** @type {boolean} */\n\t\thandled;\n\n\tfor (; (vnode = vnode._parent); ) {\n\t\tif ((component = vnode._component) && !component._processingException) {\n\t\t\ttry {\n\t\t\t\tctor = component.constructor;\n\n\t\t\t\tif (ctor && ctor.getDerivedStateFromError != NULL) {\n\t\t\t\t\tcomponent.setState(ctor.getDerivedStateFromError(error));\n\t\t\t\t\thandled = component._dirty;\n\t\t\t\t}\n\n\t\t\t\tif (component.componentDidCatch != NULL) {\n\t\t\t\t\tcomponent.componentDidCatch(error, errorInfo || {});\n\t\t\t\t\thandled = component._dirty;\n\t\t\t\t}\n\n\t\t\t\t// This is an error boundary. Mark it as having bailed out, and whether it was mid-hydration.\n\t\t\t\tif (handled) {\n\t\t\t\t\treturn (component._pendingError = component);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\terror = e;\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow error;\n}\n","import { assign } from './util';\nimport { diff, commitRoot } from './diff/index';\nimport options from './options';\nimport { Fragment } from './create-element';\nimport { MODE_HYDRATE, NULL } from './constants';\n\n/**\n * Base Component class. Provides `setState()` and `forceUpdate()`, which\n * trigger rendering\n * @param {object} props The initial component props\n * @param {object} context The initial context from parent components'\n * getChildContext\n */\nexport function BaseComponent(props, context) {\n\tthis.props = props;\n\tthis.context = context;\n}\n\n/**\n * Update component state and schedule a re-render.\n * @this {import('./internal').Component}\n * @param {object | ((s: object, p: object) => object)} update A hash of state\n * properties to update with new values or a function that given the current\n * state and props returns a new partial state\n * @param {() => void} [callback] A function to be called once component state is\n * updated\n */\nBaseComponent.prototype.setState = function (update, callback) {\n\t// only clone state when copying to nextState the first time.\n\tlet s;\n\tif (this._nextState != NULL && this._nextState != this.state) {\n\t\ts = this._nextState;\n\t} else {\n\t\ts = this._nextState = assign({}, this.state);\n\t}\n\n\tif (typeof update == 'function') {\n\t\t// Some libraries like `immer` mark the current state as readonly,\n\t\t// preventing us from mutating it, so we need to clone it. See #2716\n\t\tupdate = update(assign({}, s), this.props);\n\t}\n\n\tif (update) {\n\t\tassign(s, update);\n\t}\n\n\t// Skip update if updater function returned null\n\tif (update == NULL) return;\n\n\tif (this._vnode) {\n\t\tif (callback) {\n\t\t\tthis._stateCallbacks.push(callback);\n\t\t}\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Immediately perform a synchronous re-render of the component\n * @this {import('./internal').Component}\n * @param {() => void} [callback] A function to be called after component is\n * re-rendered\n */\nBaseComponent.prototype.forceUpdate = function (callback) {\n\tif (this._vnode) {\n\t\t// Set render mode so that we can differentiate where the render request\n\t\t// is coming from. We need this because forceUpdate should never call\n\t\t// shouldComponentUpdate\n\t\tthis._force = true;\n\t\tif (callback) this._renderCallbacks.push(callback);\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Accepts `props` and `state`, and returns a new Virtual DOM tree to build.\n * Virtual DOM is generally constructed via [JSX](https://jasonformat.com/wtf-is-jsx).\n * @param {object} props Props (eg: JSX attributes) received from parent\n * element/component\n * @param {object} state The component's current state\n * @param {object} context Context object, as returned by the nearest\n * ancestor's `getChildContext()`\n * @returns {ComponentChildren | void}\n */\nBaseComponent.prototype.render = Fragment;\n\n/**\n * @param {import('./internal').VNode} vnode\n * @param {number | null} [childIndex]\n */\nexport function getDomSibling(vnode, childIndex) {\n\tif (childIndex == NULL) {\n\t\t// Use childIndex==null as a signal to resume the search from the vnode's sibling\n\t\treturn vnode._parent\n\t\t\t? getDomSibling(vnode._parent, vnode._index + 1)\n\t\t\t: NULL;\n\t}\n\n\tlet sibling;\n\tfor (; childIndex < vnode._children.length; childIndex++) {\n\t\tsibling = vnode._children[childIndex];\n\n\t\tif (sibling != NULL && sibling._dom != NULL) {\n\t\t\t// Since updateParentDomPointers keeps _dom pointer correct,\n\t\t\t// we can rely on _dom to tell us if this subtree contains a\n\t\t\t// rendered DOM node, and what the first rendered DOM node is\n\t\t\treturn sibling._dom;\n\t\t}\n\t}\n\n\t// If we get here, we have not found a DOM node in this vnode's children.\n\t// We must resume from this vnode's sibling (in it's parent _children array)\n\t// Only climb up and search the parent if we aren't searching through a DOM\n\t// VNode (meaning we reached the DOM parent of the original vnode that began\n\t// the search)\n\treturn typeof vnode.type == 'function' ? getDomSibling(vnode) : NULL;\n}\n\n/**\n * Trigger in-place re-rendering of a component.\n * @param {import('./internal').Component} component The component to rerender\n */\nfunction renderComponent(component) {\n\tif (component._parentDom && component._dirty) {\n\t\tlet oldVNode = component._vnode,\n\t\t\toldDom = oldVNode._dom,\n\t\t\tcommitQueue = [],\n\t\t\trefQueue = [],\n\t\t\tnewVNode = assign({}, oldVNode);\n\t\tnewVNode._original = oldVNode._original + 1;\n\t\tif (options.vnode) options.vnode(newVNode);\n\n\t\tdiff(\n\t\t\tcomponent._parentDom,\n\t\t\tnewVNode,\n\t\t\toldVNode,\n\t\t\tcomponent._globalContext,\n\t\t\tcomponent._parentDom.namespaceURI,\n\t\t\toldVNode._flags & MODE_HYDRATE ? [oldDom] : NULL,\n\t\t\tcommitQueue,\n\t\t\toldDom == NULL ? getDomSibling(oldVNode) : oldDom,\n\t\t\t!!(oldVNode._flags & MODE_HYDRATE),\n\t\t\trefQueue\n\t\t);\n\n\t\tnewVNode._original = oldVNode._original;\n\t\tnewVNode._parent._children[newVNode._index] = newVNode;\n\t\tcommitRoot(commitQueue, newVNode, refQueue);\n\t\toldVNode._dom = oldVNode._parent = null;\n\n\t\tif (newVNode._dom != oldDom) {\n\t\t\tupdateParentDomPointers(newVNode);\n\t\t}\n\t}\n}\n\n/**\n * @param {import('./internal').VNode} vnode\n */\nfunction updateParentDomPointers(vnode) {\n\tif ((vnode = vnode._parent) != NULL && vnode._component != NULL) {\n\t\tvnode._dom = vnode._component.base = NULL;\n\t\tvnode._children.some(child => {\n\t\t\tif (child != NULL && child._dom != NULL) {\n\t\t\t\treturn (vnode._dom = vnode._component.base = child._dom);\n\t\t\t}\n\t\t});\n\n\t\treturn updateParentDomPointers(vnode);\n\t}\n}\n\n/**\n * The render queue\n * @type {Array<import('./internal').Component>}\n */\nlet rerenderQueue = [];\n\n/*\n * The value of `Component.debounce` must asynchronously invoke the passed in callback. It is\n * important that contributors to Preact can consistently reason about what calls to `setState`, etc.\n * do, and when their effects will be applied. See the links below for some further reading on designing\n * asynchronous APIs.\n * * [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony)\n * * [Callbacks synchronous and asynchronous](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)\n */\n\nlet prevDebounce;\n\nconst defer =\n\ttypeof Promise == 'function'\n\t\t? Promise.prototype.then.bind(Promise.resolve())\n\t\t: setTimeout;\n\n/**\n * Enqueue a rerender of a component\n * @param {import('./internal').Component} c The component to rerender\n */\nexport function enqueueRender(c) {\n\tif (\n\t\t(!c._dirty &&\n\t\t\t(c._dirty = true) &&\n\t\t\trerenderQueue.push(c) &&\n\t\t\t!process._rerenderCount++) ||\n\t\tprevDebounce != options.debounceRendering\n\t) {\n\t\tprevDebounce = options.debounceRendering;\n\t\t(prevDebounce || defer)(process);\n\t}\n}\n\n/**\n * @param {import('./internal').Component} a\n * @param {import('./internal').Component} b\n */\nconst depthSort = (a, b) => a._vnode._depth - b._vnode._depth;\n\n/** Flush the render queue by rerendering all queued components */\nfunction process() {\n\ttry {\n\t\tlet c,\n\t\t\tl = 1;\n\n\t\t// Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary\n\t\t// process() calls from getting scheduled while `queue` is still being consumed.\n\t\twhile (rerenderQueue.length) {\n\t\t\t// Keep the rerender queue sorted by (depth, insertion order). The queue\n\t\t\t// will initially be sorted on the first iteration only if it has more than 1 item.\n\t\t\t//\n\t\t\t// New items can be added to the queue e.g. when rerendering a provider, so we want to\n\t\t\t// keep the order from top to bottom with those new items so we can handle them in a\n\t\t\t// single pass\n\t\t\tif (rerenderQueue.length > l) {\n\t\t\t\trerenderQueue.sort(depthSort);\n\t\t\t}\n\n\t\t\tc = rerenderQueue.shift();\n\t\t\tl = rerenderQueue.length;\n\n\t\t\trenderComponent(c);\n\t\t}\n\t} finally {\n\t\trerenderQueue.length = process._rerenderCount = 0;\n\t}\n}\n\nprocess._rerenderCount = 0;\n","import { IS_NON_DIMENSIONAL, NULL, SVG_NAMESPACE } from '../constants';\nimport options from '../options';\n\n// Per-instance unique key for event clock stamps. Each Preact copy on the page\n// gets its own random suffix so that `_dispatched` / `_attached` properties on\n// shared event objects and handler functions cannot collide across instances.\n// ~1 in 60M collision odds - if you have that many praect versions on the page,\n// you deserve some weird bugs.\n// In 11 we can replace this with a\n// Symbol\nlet _id = Math.random().toString(8),\n\tEVENT_DISPATCHED = '__d' + _id,\n\tEVENT_ATTACHED = '__a' + _id;\n\nfunction setStyle(style, key, value) {\n\tif (key[0] == '-') {\n\t\tstyle.setProperty(key, value == NULL ? '' : value);\n\t} else if (value == NULL) {\n\t\tstyle[key] = '';\n\t} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {\n\t\tstyle[key] = value;\n\t} else {\n\t\tstyle[key] = value + 'px';\n\t}\n}\n\nconst CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;\n\n// A logical clock to solve issues like https://github.com/preactjs/preact/issues/3927.\n// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that\n// an event can trigger on a newly reated DOM-node while the event bubbles up.\n//\n// Originally inspired by Vue\n// (https://github.com/vuejs/core/blob/caeb8a68811a1b0f79/packages/runtime-dom/src/modules/events.ts#L90-L101),\n// but modified to use a logical clock instead of Date.now() in case event handlers get attached\n// and events get dispatched during the same millisecond.\n//\n// The clock is incremented after each new event dispatch. This allows 1 000 000 new events\n// per second for over 280 years before the value reaches Number.MAX_SAFE_INTEGER (2**53 - 1).\nlet eventClock = 0;\n\n/**\n * Set a property value on a DOM node\n * @param {import('../internal').PreactElement} dom The DOM node to modify\n * @param {string} name The name of the property to set\n * @param {*} value The value to set the property to\n * @param {*} oldValue The old value the property had\n * @param {string} namespace Whether or not this DOM node is an SVG node or not\n */\nexport function setProperty(dom, name, value, oldValue, namespace) {\n\tlet useCapture;\n\n\to: if (name == 'style') {\n\t\tif (typeof value == 'string') {\n\t\t\tdom.style.cssText = value;\n\t\t} else {\n\t\t\tif (typeof oldValue == 'string') {\n\t\t\t\tdom.style.cssText = oldValue = '';\n\t\t\t}\n\n\t\t\tif (oldValue) {\n\t\t\t\tfor (name in oldValue) {\n\t\t\t\t\tif (!(value && name in value)) {\n\t\t\t\t\t\tsetStyle(dom.style, name, '');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (value) {\n\t\t\t\tfor (name in value) {\n\t\t\t\t\tif (!oldValue || value[name] != oldValue[name]) {\n\t\t\t\t\t\tsetStyle(dom.style, name, value[name]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6\n\telse if (name[0] == 'o' && name[1] == 'n') {\n\t\tuseCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));\n\t\tconst lowerCaseName = name.toLowerCase();\n\n\t\t// Infer correct casing for DOM built-in events:\n\t\tif (lowerCaseName in dom || name == 'onFocusOut' || name == 'onFocusIn')\n\t\t\tname = lowerCaseName.slice(2);\n\t\telse name = name.slice(2);\n\n\t\tif (!dom._listeners) dom._listeners = {};\n\t\tdom._listeners[name + useCapture] = value;\n\n\t\tif (value) {\n\t\t\tif (!oldValue) {\n\t\t\t\tvalue[EVENT_ATTACHED] = eventClock;\n\t\t\t\tdom.addEventListener(\n\t\t\t\t\tname,\n\t\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\t\tuseCapture\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tvalue[EVENT_ATTACHED] = oldValue[EVENT_ATTACHED];\n\t\t\t}\n\t\t} else {\n\t\t\tdom.removeEventListener(\n\t\t\t\tname,\n\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\tuseCapture\n\t\t\t);\n\t\t}\n\t} else {\n\t\tif (namespace == SVG_NAMESPACE) {\n\t\t\t// Normalize incorrect prop usage for SVG:\n\t\t\t// - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)\n\t\t\t// - className --> class\n\t\t\tname = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');\n\t\t} else if (\n\t\t\tname != 'width' &&\n\t\t\tname != 'height' &&\n\t\t\tname != 'href' &&\n\t\t\tname != 'list' &&\n\t\t\tname != 'form' &&\n\t\t\t// Default value in browsers is `-1` and an empty string is\n\t\t\t// cast to `0` instead\n\t\t\tname != 'tabIndex' &&\n\t\t\tname != 'download' &&\n\t\t\tname != 'rowSpan' &&\n\t\t\tname != 'colSpan' &&\n\t\t\tname != 'role' &&\n\t\t\tname != 'popover' &&\n\t\t\tname in dom\n\t\t) {\n\t\t\ttry {\n\t\t\t\tdom[name] = value == NULL ? '' : value;\n\t\t\t\t// labelled break is 1b smaller here than a return statement (sorry)\n\t\t\t\tbreak o;\n\t\t\t} catch (e) {}\n\t\t}\n\n\t\t// aria- and data- attributes have no boolean representation.\n\t\t// A `false` value is different from the attribute not being\n\t\t// present, so we can't remove it. For non-boolean aria\n\t\t// attributes we could treat false as a removal, but the\n\t\t// amount of exceptions would cost too many bytes. On top of\n\t\t// that other frameworks generally stringify `false`.\n\n\t\tif (typeof value == 'function') {\n\t\t\t// never serialize functions as attribute values\n\t\t} else if (value != NULL && (value !== false || name[4] == '-')) {\n\t\t\tdom.setAttribute(name, name == 'popover' && value == true ? '' : value);\n\t\t} else {\n\t\t\tdom.removeAttribute(name);\n\t\t}\n\t}\n}\n\n/**\n * Create an event proxy function.\n * @param {boolean} useCapture Is the event handler for the capture phase.\n * @private\n */\nfunction createEventProxy(useCapture) {\n\t/**\n\t * Proxy an event to hooked event handlers\n\t * @param {import('../internal').PreactEvent} e The event object from the browser\n\t * @private\n\t */\n\treturn function (e) {\n\t\tif (this._listeners) {\n\t\t\tconst eventHandler = this._listeners[e.type + useCapture];\n\t\t\tif (e[EVENT_DISPATCHED] == NULL) {\n\t\t\t\te[EVENT_DISPATCHED] = eventClock++;\n\n\t\t\t\t// When `e[EVENT_DISPATCHED]` is smaller than the time when the targeted event\n\t\t\t\t// handler was attached we know we have bubbled up to an element that was added\n\t\t\t\t// during patching the DOM.\n\t\t\t} else if (e[EVENT_DISPATCHED] < eventHandler[EVENT_ATTACHED]) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn eventHandler(options.event ? options.event(e) : e);\n\t\t}\n\t};\n}\n\nconst eventProxy = createEventProxy(false);\nconst eventProxyCapture = createEventProxy(true);\n","import { enqueueRender } from './component';\nimport { NULL } from './constants';\n\nexport let i = 0;\n\nexport function createContext(defaultValue) {\n\tfunction Context(props) {\n\t\tif (!this.getChildContext) {\n\t\t\t/** @type {Set<import('./internal').Component> | null} */\n\t\t\tlet subs = new Set();\n\t\t\tlet ctx = {};\n\t\t\tctx[Context._id] = this;\n\n\t\t\tthis.getChildContext = () => ctx;\n\n\t\t\tthis.componentWillUnmount = () => {\n\t\t\t\tsubs = NULL;\n\t\t\t};\n\n\t\t\tthis.shouldComponentUpdate = function (_props) {\n\t\t\t\t// @ts-expect-error even\n\t\t\t\tif (this.props.value != _props.value) {\n\t\t\t\t\tsubs.forEach(c => {\n\t\t\t\t\t\tc._force = true;\n\t\t\t\t\t\tenqueueRender(c);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.sub = c => {\n\t\t\t\tsubs.add(c);\n\t\t\t\tlet old = c.componentWillUnmount;\n\t\t\t\tc.componentWillUnmount = () => {\n\t\t\t\t\tif (subs) {\n\t\t\t\t\t\tsubs.delete(c);\n\t\t\t\t\t}\n\t\t\t\t\tif (old) old.call(c);\n\t\t\t\t};\n\t\t\t};\n\t\t}\n\n\t\treturn props.children;\n\t}\n\n\tContext._id = '__cC' + i++;\n\tContext._defaultValue = defaultValue;\n\n\t/** @type {import('./internal').FunctionComponent} */\n\tContext.Consumer = (props, contextValue) => {\n\t\treturn props.children(contextValue);\n\t};\n\n\t// we could also get rid of _contextRef entirely\n\tContext.Provider =\n\t\tContext._contextRef =\n\t\tContext.Consumer.contextType =\n\t\t\tContext;\n\n\treturn Context;\n}\n","import { options, Fragment } from 'preact';\nimport { encodeEntities } from './utils';\nimport { IS_NON_DIMENSIONAL } from '../../src/constants';\n\nlet vnodeId = 0;\n\nconst isArray = Array.isArray;\n\n/**\n * @fileoverview\n * This file exports various methods that implement Babel's \"automatic\" JSX runtime API:\n * - jsx(type, props, key)\n * - jsxs(type, props, key)\n * - jsxDEV(type, props, key, __source, __self)\n *\n * The implementation of createVNode here is optimized for performance.\n * Benchmarks: https://esbench.com/bench/5f6b54a0b4632100a7dcd2b3\n */\n\n/**\n * JSX.Element factory used by Babel's {runtime:\"automatic\"} JSX transform\n * @param {VNode['type']} type\n * @param {VNode['props']} props\n * @param {VNode['key']} [key]\n * @param {unknown} [isStaticChildren]\n * @param {unknown} [__source]\n * @param {unknown} [__self]\n */\nfunction createVNode(type, props, key, isStaticChildren, __source, __self) {\n\tif (!props) props = {};\n\t// We'll want to preserve `ref` in props to get rid of the need for\n\t// forwardRef components in the future, but that should happen via\n\t// a separate PR.\n\tlet normalizedProps = props,\n\t\tref,\n\t\ti;\n\n\tif ('ref' in normalizedProps) {\n\t\tnormalizedProps = {};\n\t\tfor (i in props) {\n\t\t\tif (i == 'ref') {\n\t\t\t\tref = props[i];\n\t\t\t} else {\n\t\t\t\tnormalizedProps[i] = props[i];\n\t\t\t}\n\t\t}\n\t}\n\n\t/** @type {VNode & { __source: any; __self: any }} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops: normalizedProps,\n\t\tkey,\n\t\tref,\n\t\t_children: null,\n\t\t_parent: null,\n\t\t_depth: 0,\n\t\t_dom: null,\n\t\t_component: null,\n\t\tconstructor: undefined,\n\t\t_original: --vnodeId,\n\t\t_index: -1,\n\t\t_flags: 0,\n\t\t__source,\n\t\t__self\n\t};\n\n\t// If a Component VNode, check for and apply defaultProps.\n\t// Note: `type` is often a String, and can be `undefined` in development.\n\tif (typeof type === 'function' && (ref = type.defaultProps)) {\n\t\tfor (i in ref)\n\t\t\tif (normalizedProps[i] === undefined) {\n\t\t\t\tnormalizedProps[i] = ref[i];\n\t\t\t}\n\t}\n\n\tif (options.vnode) options.vnode(vnode);\n\treturn vnode;\n}\n\n/**\n * Create a template vnode. This function is not expected to be\n * used directly, but rather through a precompile JSX transform\n * @param {string[]} templates\n * @param {Array<string | null | VNode>} exprs\n * @returns {VNode}\n */\nfunction jsxTemplate(templates, ...exprs) {\n\tconst vnode = createVNode(Fragment, { tpl: templates, exprs });\n\t// Bypass render to string top level Fragment optimization\n\tvnode.key = vnode._vnode;\n\treturn vnode;\n}\n\nconst JS_TO_CSS = {};\nconst CSS_REGEX = /[A-Z]/g;\n\n/**\n * Unwrap potential signals.\n * @param {*} value\n * @returns {*}\n */\nfunction normalizeAttrValue(value) {\n\treturn value !== null &&\n\t\ttypeof value === 'object' &&\n\t\ttypeof value.valueOf === 'function'\n\t\t? value.valueOf()\n\t\t: value;\n}\n\n/**\n * Serialize an HTML attribute to a string. This function is not\n * expected to be used directly, but rather through a precompile\n * JSX transform\n * @param {string} name The attribute name\n * @param {*} value The attribute value\n * @returns {string}\n */\nfunction jsxAttr(name, value) {\n\tif (options.attr) {\n\t\tconst result = options.attr(name, value);\n\t\tif (typeof result === 'string') return result;\n\t}\n\n\tvalue = normalizeAttrValue(value);\n\n\tif (name === 'ref' || name === 'key') return '';\n\tif (name === 'style' && typeof value === 'object') {\n\t\tlet str = '';\n\t\tfor (let prop in value) {\n\t\t\tlet val = value[prop];\n\t\t\tif (val != null && val !== '') {\n\t\t\t\tconst name =\n\t\t\t\t\tprop[0] == '-'\n\t\t\t\t\t\t? prop\n\t\t\t\t\t\t: JS_TO_CSS[prop] ||\n\t\t\t\t\t\t\t(JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());\n\n\t\t\t\tlet suffix = ';';\n\t\t\t\tif (\n\t\t\t\t\ttypeof val === 'number' &&\n\t\t\t\t\t// Exclude custom-attributes\n\t\t\t\t\t!name.startsWith('--') &&\n\t\t\t\t\t!IS_NON_DIMENSIONAL.test(name)\n\t\t\t\t) {\n\t\t\t\t\tsuffix = 'px;';\n\t\t\t\t}\n\t\t\t\tstr = str + name + ':' + val + suffix;\n\t\t\t}\n\t\t}\n\t\treturn name + '=\"' + encodeEntities(str) + '\"';\n\t}\n\n\tif (\n\t\tvalue == null ||\n\t\tvalue === false ||\n\t\ttypeof value === 'function' ||\n\t\ttypeof value === 'object'\n\t) {\n\t\treturn '';\n\t} else if (value === true) return name;\n\n\treturn name + '=\"' + encodeEntities('' + value) + '\"';\n}\n\n/**\n * Escape a dynamic child passed to `jsxTemplate`. This function\n * is not expected to be used directly, but rather through a\n * precompile JSX transform\n * @param {*} value\n * @returns {string | null | VNode | Array<string | null | VNode>}\n */\nfunction jsxEscape(value) {\n\tif (\n\t\tvalue == null ||\n\t\ttypeof value === 'boolean' ||\n\t\ttypeof value === 'function'\n\t) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === 'object') {\n\t\t// Check for VNode\n\t\tif (value.constructor === undefined) return value;\n\n\t\tif (isArray(value)) {\n\t\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\t\tvalue[i] = jsxEscape(value[i]);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t}\n\n\treturn encodeEntities('' + value);\n}\n\nexport {\n\tcreateVNode as jsx,\n\tcreateVNode as jsxs,\n\tcreateVNode as jsxDEV,\n\tFragment,\n\t// precompiled JSX transform\n\tjsxTemplate,\n\tjsxAttr,\n\tjsxEscape\n};\n","import type {\n QuartzComponent,\n QuartzComponentProps,\n QuartzComponentConstructor,\n} from \"@quartz-community/types\";\nimport style from \"./styles/encrypted.scss\";\n// @ts-expect-error - inline script import handled by Quartz bundler\nimport script from \"./scripts/encrypted.inline.ts\";\n\nexport interface EncryptedPageComponentOptions {\n /** CSS class name for the component wrapper. */\n className?: string;\n}\n\nexport default ((opts?: EncryptedPageComponentOptions) => {\n const { className = \"encrypted-page-wrapper\" } = opts ?? {};\n\n const Component: QuartzComponent = (_props: QuartzComponentProps) => {\n // The actual encrypted container is injected by the transformer's rehype plugin.\n // This component only provides CSS and the client-side decryption script.\n // It renders an empty wrapper that the encrypted content appears inside.\n return <div class={className} />;\n };\n\n Component.css = style;\n Component.afterDOMLoaded = script;\n\n return Component;\n}) satisfies QuartzComponentConstructor;\n"]}
@@ -0,0 +1,33 @@
1
+ import { QuartzTransformerPlugin, QuartzEmitterPlugin } from '@quartz-community/types';
2
+ export { PageGenerator, PageMatcher, QuartzComponent, QuartzComponentConstructor, QuartzComponentProps, QuartzEmitterPlugin, QuartzFilterPlugin, QuartzPageTypePlugin, QuartzPageTypePluginInstance, QuartzTransformerPlugin, StringResource, VirtualPage } from '@quartz-community/types';
3
+ import { EncryptedPagesOptions, EncryptedContentIndexOptions } from './types.js';
4
+ export { EncryptedPage, EncryptedPageComponentOptions } from './components/index.js';
5
+
6
+ declare function encryptAesGcm(plaintext: string, password: string, iterations: number): string;
7
+ declare function decrypt(encryptedBase64: string, password: string, iterations: number): string;
8
+ declare const EncryptedPages: QuartzTransformerPlugin<Partial<EncryptedPagesOptions>>;
9
+
10
+ declare const SHADOW_INDEX_VERSION: 1;
11
+ interface ShadowIndexBlob {
12
+ ciphertext: string;
13
+ iterations: number;
14
+ }
15
+ interface ShadowIndexFile {
16
+ version: typeof SHADOW_INDEX_VERSION;
17
+ entries: ShadowIndexBlob[];
18
+ }
19
+ interface ShadowContentIndexEntry {
20
+ slug: string;
21
+ entry: {
22
+ slug: string;
23
+ filePath: string;
24
+ title: string;
25
+ links: string[];
26
+ tags: string[];
27
+ content: string;
28
+ description: string;
29
+ };
30
+ }
31
+ declare const EncryptedContentIndex: QuartzEmitterPlugin<Partial<EncryptedContentIndexOptions>>;
32
+
33
+ export { EncryptedContentIndex, EncryptedContentIndexOptions, EncryptedPages, EncryptedPagesOptions, SHADOW_INDEX_VERSION, type ShadowContentIndexEntry, type ShadowIndexBlob, type ShadowIndexFile, decrypt, encryptAesGcm };