From 183b1f9bfa6b6a413edea4b90b6072fa1ba08190 Mon Sep 17 00:00:00 2001 From: eamag Date: Sat, 18 Jul 2026 15:28:13 +0200 Subject: [PATCH] add bsky and hackernews --- README.md | 114 ++++-- dist/components/index.js | 73 +++- dist/components/index.js.map | 2 +- dist/index.d.ts | 6 + dist/index.js | 73 +++- dist/index.js.map | 2 +- package-lock.json | 420 ++++++++++++++++++++ package.json | 5 +- src/components/Comments.tsx | 116 +++++- src/components/scripts/bluesky.inline.ts | 179 +++++++++ src/components/scripts/hackernews.inline.ts | 106 +++++ src/components/styles/bluesky.scss | 64 +++ src/components/styles/hackernews.scss | 68 ++++ test/comments.test.ts | 79 ++++ 14 files changed, 1254 insertions(+), 53 deletions(-) create mode 100644 src/components/scripts/bluesky.inline.ts create mode 100644 src/components/scripts/hackernews.inline.ts create mode 100644 src/components/styles/bluesky.scss create mode 100644 src/components/styles/hackernews.scss diff --git a/README.md b/README.md index 11b7aae..c310443 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @quartz-community/comments -Adds a comment section to pages using Giscus (GitHub Discussions-based). +Adds a comment section to pages using Giscus (GitHub Discussions), Bluesky, or Hacker News. ## Installation @@ -15,46 +15,100 @@ plugins: - source: github:quartz-community/comments enabled: true options: - provider: giscus + provider: giscus # Or "bluesky" or "hackernews" options: {} layout: position: afterBody priority: 10 ``` -For advanced use cases, you can override in TypeScript: +--- -```ts title="quartz.ts (override)" -import * as ExternalPlugin from "./.quartz/plugins"; +## Providers Setup -ExternalPlugin.Comments({ - provider: "giscus", - options: { - repo: "your-repo", - repoId: "your-repo-id", - category: "your-category", - categoryId: "your-category-id", - }, -}); -``` +### 1. Bluesky + +Pulls a reply thread directly from a Bluesky post. + +1. Set the provider to `"bluesky"` in `quartz.config.yaml`. +2. Add the `blueskyUrl` field pointing to the post in the page's frontmatter: + + ```markdown + --- + title: My Blog Post + blueskyUrl: https://bsky.app/profile/username.bsky.social/post/3mq... + --- + ``` + +### 2. Hacker News + +Pulls a reply thread directly from a Hacker News item thread. + +1. Set the provider to `"hackernews"` in `quartz.config.yaml`. +2. Add the `hnId` or `hnUrl` field in the page's frontmatter: + + ```markdown + --- + title: My Blog Post + hnId: 123456 + # Or full url: + # hnUrl: https://news.ycombinator.com/item?id=123456 + --- + ``` + +### 3. Giscus + +GitHub Discussions-based commenting system. + +1. Set the provider to `"giscus"`. +2. Provide your repository options: + + ```ts title="quartz.ts (override)" + import * as ExternalPlugin from "./.quartz/plugins"; + + ExternalPlugin.Comments({ + provider: "giscus", + options: { + repo: "your-repo", + repoId: "your-repo-id", + category: "your-category", + categoryId: "your-category-id", + }, + }); + ``` + +--- ## Configuration -| Option | Type | Default | Description | -| -------------------------- | ------------------- | ----------- | ----------------------------------------------- | -| `provider` | `"giscus"` | - | The comment provider to use. | -| `options.repo` | `string` | - | The GitHub repository to use for comments. | -| `options.repoId` | `string` | - | The ID of the GitHub repository. | -| `options.category` | `string` | - | The GitHub Discussions category to use. | -| `options.categoryId` | `string` | - | The ID of the GitHub Discussions category. | -| `options.themeUrl` | `string` | `undefined` | Custom theme URL for Giscus. | -| `options.lightTheme` | `string` | `"light"` | The light theme for Giscus. | -| `options.darkTheme` | `string` | `"dark"` | The dark theme for Giscus. | -| `options.mapping` | `string` | `"url"` | The mapping between pages and discussions. | -| `options.strict` | `boolean` | `true` | Whether to use strict matching for discussions. | -| `options.reactionsEnabled` | `boolean` | `true` | Whether to enable reactions for comments. | -| `options.inputPosition` | `"top" \| "bottom"` | `"bottom"` | The position of the comment input box. | -| `options.lang` | `string` | `"en"` | The language for Giscus. | +| Option | Type | Default | Description | +| -------------------------- | --------------------------------------- | ----------- | ---------------------------------------------- | +| `provider` | `"giscus" \| "bluesky" \| "hackernews"` | - | The comment provider to use. | +| `options.repo` | `string` | - | (Giscus only) The GitHub repository to use. | +| `options.repoId` | `string` | - | (Giscus only) The ID of the GitHub repository. | +| `options.category` | `string` | - | (Giscus only) The Discussions category to use. | +| `options.categoryId` | `string` | - | (Giscus only) The ID of the category. | +| `options.themeUrl` | `string` | `undefined` | (Giscus only) Custom theme URL for Giscus. | +| `options.lightTheme` | `string` | `"light"` | (Giscus only) The light theme for Giscus. | +| `options.darkTheme` | `string` | `"dark"` | (Giscus only) The dark theme for Giscus. | +| `options.mapping` | `string` | `"url"` | (Giscus only) Page to discussion mapping. | +| `options.strict` | `boolean` | `true` | (Giscus only) Use strict discussion matching. | +| `options.reactionsEnabled` | `boolean` | `true` | (Giscus only) Enable reactions for comments. | +| `options.inputPosition` | `"top" \| "bottom"` | `"bottom"` | (Giscus only) The position of the input box. | +| `options.lang` | `string` | `"en"` | (Giscus only) The language for Giscus. | + +--- + +## Customization + +You can conditionally disable comments on a specific page by setting `comments: false` in that page's frontmatter: + +```markdown +--- +title: Comments Disabled +comments: false +--- +``` ## Documentation diff --git a/dist/components/index.js b/dist/components/index.js index 7e33288..82603f5 100644 --- a/dist/components/index.js +++ b/dist/components/index.js @@ -5,6 +5,18 @@ function classNames(...classes) { // src/components/scripts/comments.inline.ts var comments_inline_default = 'var d=s=>{let e=s.detail.theme,t=document.querySelector("iframe.giscus-frame");t&&t.contentWindow&&t.contentWindow.postMessage({giscus:{setConfig:{theme:c(r(e))}}},"https://giscus.app")},r=s=>{if(s!=="dark"&&s!=="light")return s;let e=document.querySelector(".giscus");if(!e)return s;let t=e.dataset.darkTheme??"dark",a=e.dataset.lightTheme??"light";return s==="dark"?t:a},c=s=>{let e=document.querySelector(".giscus");return e?`${e.dataset.themeUrl??"https://giscus.app/themes"}/${s}.css`:`https://giscus.app/themes/${s}.css`},n=[],u=s=>{n.push(s)};if(typeof document<"u"){let s=()=>{n.forEach(o=>o()),n.length=0;let e=document.querySelector(".giscus");if(!e)return;let t=document.createElement("script");t.src="https://giscus.app/client.js",t.async=!0,t.crossOrigin="anonymous",t.setAttribute("data-loading","lazy"),t.setAttribute("data-emit-metadata","0"),t.setAttribute("data-repo",e.dataset.repo),t.setAttribute("data-repo-id",e.dataset.repoId),t.setAttribute("data-category",e.dataset.category),t.setAttribute("data-category-id",e.dataset.categoryId),t.setAttribute("data-mapping",e.dataset.mapping),t.setAttribute("data-strict",e.dataset.strict),t.setAttribute("data-reactions-enabled",e.dataset.reactionsEnabled),t.setAttribute("data-input-position",e.dataset.inputPosition),t.setAttribute("data-lang",e.dataset.lang);let a=document.documentElement.getAttribute("saved-theme");a&&t.setAttribute("data-theme",c(r(a))),e.appendChild(t);let i=d;document.addEventListener("themechange",i),u(()=>document.removeEventListener("themechange",i))};document.addEventListener("nav",s),document.addEventListener("render",s)}\n'; + +// src/components/scripts/bluesky.inline.ts +var bluesky_inline_default = 'function f(a){let n=new URL(a).pathname.split("/"),t=n[2],o=n[4];if(!t||!o)throw new Error("Invalid Bluesky URL");return{handle:t,postId:o}}async function y(a){let n=await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${a}`);if(!n.ok)throw new Error("Could not resolve handle");let{did:t}=await n.json();return t}async function C(a,n){let t=`at://${a}/app.bsky.feed.post/${n}`,o=await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${t}&depth=10&parentHeight=0`);if(!o.ok)throw new Error("Could not fetch thread");return(await o.json()).thread.replies}function k(a){let n=document.getElementById("bluesky-comments-list");n&&(a&&a.length>0?(n.innerHTML="",a.sort((t,o)=>(o.post.likeCount||0)-(t.post.likeCount||0)),a.forEach(t=>{t.post&&n.appendChild(h(t))})):n.textContent="No comments yet.")}function h(a){let n=a.post,t=n.author,o=new Date(n.indexedAt).toLocaleDateString(),s=document.createElement("div");s.className="comment";let c=document.createElement("div");if(c.className="comment-header",t.avatar){let e=document.createElement("img");e.src=t.avatar,e.alt=t.handle,c.appendChild(e)}let r=document.createElement("a"),d=n.uri.split("/").pop();r.href=`https://bsky.app/profile/${t.did}/post/${d}`,r.target="_blank",r.className="handle",r.textContent=t.displayName||t.handle,c.appendChild(r);let p=document.createElement("span");p.className="time",p.textContent=o,c.appendChild(p),s.appendChild(c),s.appendChild(x(n));let m=document.createElement("div");m.className="comment-actions";let l=document.createElement("span");l.textContent=`\\u2764\\uFE0F ${n.likeCount||0}`,m.appendChild(l);let i=document.createElement("span");if(i.textContent=`\\u{1F501} ${n.repostCount||0}`,m.appendChild(i),s.appendChild(m),a.replies&&a.replies.length>0){let e=document.createElement("div");e.className="replies",a.replies.forEach(u=>{u.post&&e.appendChild(h(u))}),s.appendChild(e)}return s}function x(a){let n=a.record.text,t=a.record.facets,o=document.createElement("div");if(o.className="comment-body",!t||t.length===0)return o.textContent=n,o;t.sort((r,d)=>r.index.byteStart-d.index.byteStart);let s=new TextEncoder().encode(n),c=0;for(let r of t){let{byteStart:d,byteEnd:p}=r.index;if(d>c){let e=s.slice(c,d),u=document.createTextNode(new TextDecoder().decode(e));o.appendChild(u)}let m=s.slice(d,p),l=new TextDecoder().decode(m),i=r.features[0];if(i.$type==="app.bsky.richtext.facet#link"){let e=document.createElement("a");e.href=i.uri,e.target="_blank",e.rel="noopener noreferrer",e.textContent=l,o.appendChild(e)}else if(i.$type==="app.bsky.richtext.facet#mention"){let e=document.createElement("a");e.href=`https://bsky.app/profile/${i.did}`,e.target="_blank",e.rel="noopener noreferrer",e.textContent=l,o.appendChild(e)}else if(i.$type==="app.bsky.richtext.facet#tag"){let e=document.createElement("a");e.href=`https://bsky.app/hashtag/${i.tag}`,e.target="_blank",e.rel="noopener noreferrer",e.textContent=l,o.appendChild(e)}else o.appendChild(document.createTextNode(l));c=p}if(c{let n=document.getElementById("bluesky-comments");if(!n)return;let t=n.getAttribute("data-url");if(!t)return;let o=document.getElementById("bluesky-comments-list");o&&(o.innerHTML="

Loading comments...

");try{let{handle:s,postId:c}=f(t),r=await y(s),d=await C(r,c);k(d)}catch(s){console.error("Error loading Bluesky comments:",s),o&&(o.textContent="Error loading comments.")}};document.addEventListener("nav",a),document.addEventListener("render",a)}\n'; + +// src/components/styles/bluesky.scss +var bluesky_default = ".bluesky-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .bluesky-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n img {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n }\n .handle {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n white-space: pre-wrap;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment-actions {\n margin-top: 0.25rem;\n font-size: 0.8rem;\n color: var(--gray);\n display: flex;\n gap: 1rem;\n }\n }\n}\n"; + +// src/components/scripts/hackernews.inline.ts +var hackernews_inline_default = 'function d(e){if(/^\\d+$/.test(e))return e;try{let n=new URL(e).searchParams.get("id");if(n)return n}catch{}throw new Error("Invalid Hacker News URL or ID")}async function m(e){let t=await fetch(`https://hn.algolia.com/api/v1/items/${e}`);if(!t.ok)throw new Error("Failed to fetch HN comments");return(await t.json()).children}function h(e){let t=document.getElementById("hackernews-comments-list");t&&(e&&e.length>0?(t.innerHTML="",e.forEach(n=>{!n.author&&!n.text||t.appendChild(i(n))})):t.textContent="No comments yet.")}function i(e){let t=document.createElement("div");t.className="comment";let n=document.createElement("div");n.className="comment-header";let a=document.createElement("a");a.href=`https://news.ycombinator.com/user?id=${e.author||""}`,a.target="_blank",a.className="author",a.textContent=e.author||"[deleted]",n.appendChild(a);let o=document.createElement("span");o.className="time",e.created_at?o.textContent=new Date(e.created_at).toLocaleDateString():o.textContent="",n.appendChild(o),t.appendChild(n);let c=document.createElement("div");if(c.className="comment-body",c.innerHTML=e.text||"[deleted]",t.appendChild(c),e.children&&e.children.length>0){let r=document.createElement("div");r.className="replies",e.children.forEach(s=>{!s.author&&!s.text||r.appendChild(i(s))}),t.appendChild(r)}return t}if(typeof document<"u"){let e=async()=>{let t=document.getElementById("hackernews-comments");if(!t)return;let n=t.getAttribute("data-id");if(!n)return;let a=document.getElementById("hackernews-comments-list");a&&(a.innerHTML="

Loading comments...

");try{let o=d(n),c=await m(o);h(c)}catch(o){console.error("Error loading Hacker News comments:",o),a&&(a.textContent="Error loading comments.")}};document.addEventListener("nav",e),document.addEventListener("render",e)}\n'; + +// src/components/styles/hackernews.scss +var hackernews_default = ".hackernews-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .hackernews-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n .author {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n p {\n margin: 0.5rem 0;\n }\n pre {\n background-color: var(--lightgray);\n padding: 0.5rem;\n border-radius: 4px;\n overflow-x: auto;\n }\n code {\n font-family: var(--codeVal);\n font-size: 0.85rem;\n }\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n }\n}\n"; var l; function S(n2) { return n2.children; @@ -39,6 +51,57 @@ var Comments_default = ((opts) => { if (commentsOverride === false || commentsOverride === "false") { return /* @__PURE__ */ u2(S, {}); } + if (opts.provider === "bluesky") { + const blueskyUrl = fileData.frontmatter?.blueskyUrl; + if (!blueskyUrl) { + return /* @__PURE__ */ u2(S, {}); + } + return /* @__PURE__ */ u2( + "div", + { + class: classNames(displayClass, "bluesky-comments-container"), + id: "bluesky-comments", + "data-url": blueskyUrl, + children: [ + /* @__PURE__ */ u2("h2", { children: "Comments" }), + /* @__PURE__ */ u2("p", { class: "bluesky-meta", children: [ + "Post a reply on", + " ", + /* @__PURE__ */ u2("a", { href: blueskyUrl, target: "_blank", rel: "noopener noreferrer", children: "Bluesky" }), + " ", + "to join the conversation." + ] }), + /* @__PURE__ */ u2("div", { id: "bluesky-comments-list", children: /* @__PURE__ */ u2("p", { children: "Loading comments..." }) }) + ] + } + ); + } + if (opts.provider === "hackernews") { + const hnId = fileData.frontmatter?.hnId ? String(fileData.frontmatter.hnId) : fileData.frontmatter?.hnUrl; + if (!hnId) { + return /* @__PURE__ */ u2(S, {}); + } + const threadUrl = hnId.startsWith("http") ? hnId : `https://news.ycombinator.com/item?id=${hnId}`; + return /* @__PURE__ */ u2( + "div", + { + class: classNames(displayClass, "hackernews-comments-container"), + id: "hackernews-comments", + "data-id": hnId, + children: [ + /* @__PURE__ */ u2("h2", { children: "Comments" }), + /* @__PURE__ */ u2("p", { class: "hackernews-meta", children: [ + "Post a reply on", + " ", + /* @__PURE__ */ u2("a", { href: threadUrl, target: "_blank", rel: "noopener noreferrer", children: "Hacker News" }), + " ", + "to join the conversation." + ] }), + /* @__PURE__ */ u2("div", { id: "hackernews-comments-list", children: /* @__PURE__ */ u2("p", { children: "Loading comments..." }) }) + ] + } + ); + } return /* @__PURE__ */ u2( "div", { @@ -58,7 +121,15 @@ var Comments_default = ((opts) => { } ); }; - Comments.afterDOMLoaded = comments_inline_default; + if (opts.provider === "giscus") { + Comments.afterDOMLoaded = comments_inline_default; + } else if (opts.provider === "bluesky") { + Comments.afterDOMLoaded = bluesky_inline_default; + Comments.css = bluesky_default; + } else if (opts.provider === "hackernews") { + Comments.afterDOMLoaded = hackernews_inline_default; + Comments.css = hackernews_default; + } return Comments; }); diff --git a/dist/components/index.js.map b/dist/components/index.js.map index 3a46b10..47b16ab 100644 --- a/dist/components/index.js.map +++ b/dist/components/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../node_modules/@quartz-community/utils/src/lang.ts","../../src/components/scripts/comments.inline.ts","../../node_modules/preact/src/constants.js","../../node_modules/preact/src/options.js","../../node_modules/preact/src/create-element.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/Comments.tsx"],"names":["options","Fragment","props","children","__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","key","isStaticChildren","__source","__self","ref","i","normalizedProps","__k","__b","__v","__i","__u","defaultProps","b","u"],"mappings":"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","file":"index.js","sourcesContent":["export function capitalize(s: string): string {\n return s.substring(0, 1).toUpperCase() + s.substring(1);\n}\n\nexport function classNames(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n","var d=s=>{let e=s.detail.theme,t=document.querySelector(\"iframe.giscus-frame\");t&&t.contentWindow&&t.contentWindow.postMessage({giscus:{setConfig:{theme:c(r(e))}}},\"https://giscus.app\")},r=s=>{if(s!==\"dark\"&&s!==\"light\")return s;let e=document.querySelector(\".giscus\");if(!e)return s;let t=e.dataset.darkTheme??\"dark\",a=e.dataset.lightTheme??\"light\";return s===\"dark\"?t:a},c=s=>{let e=document.querySelector(\".giscus\");return e?`${e.dataset.themeUrl??\"https://giscus.app/themes\"}/${s}.css`:`https://giscus.app/themes/${s}.css`},n=[],u=s=>{n.push(s)};if(typeof document<\"u\"){let s=()=>{n.forEach(o=>o()),n.length=0;let e=document.querySelector(\".giscus\");if(!e)return;let t=document.createElement(\"script\");t.src=\"https://giscus.app/client.js\",t.async=!0,t.crossOrigin=\"anonymous\",t.setAttribute(\"data-loading\",\"lazy\"),t.setAttribute(\"data-emit-metadata\",\"0\"),t.setAttribute(\"data-repo\",e.dataset.repo),t.setAttribute(\"data-repo-id\",e.dataset.repoId),t.setAttribute(\"data-category\",e.dataset.category),t.setAttribute(\"data-category-id\",e.dataset.categoryId),t.setAttribute(\"data-mapping\",e.dataset.mapping),t.setAttribute(\"data-strict\",e.dataset.strict),t.setAttribute(\"data-reactions-enabled\",e.dataset.reactionsEnabled),t.setAttribute(\"data-input-position\",e.dataset.inputPosition),t.setAttribute(\"data-lang\",e.dataset.lang);let a=document.documentElement.getAttribute(\"saved-theme\");a&&t.setAttribute(\"data-theme\",c(r(a))),e.appendChild(t);let i=d;document.addEventListener(\"themechange\",i),u(()=>document.removeEventListener(\"themechange\",i))};document.addEventListener(\"nav\",s),document.addEventListener(\"render\",s)}\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 { slice } from './util';\nimport options from './options';\nimport { NULL, UNDEFINED } from './constants';\n\nlet vnodeId = 0;\n\n/**\n * Create an virtual node (used for JSX)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component constructor for this\n * virtual node\n * @param {object | null | undefined} [props] The properties of the virtual node\n * @param {Array} [children] The children of the\n * virtual node\n * @returns {import('./internal').VNode}\n */\nexport function createElement(type, props, children) {\n\tlet normalizedProps = {},\n\t\tkey,\n\t\tref,\n\t\ti;\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse normalizedProps[i] = props[i];\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\t// If a Component VNode, check for and apply defaultProps\n\t// Note: type may be undefined in development, must never error here.\n\tif (typeof type == 'function' && type.defaultProps != NULL) {\n\t\tfor (i in type.defaultProps) {\n\t\t\tif (normalizedProps[i] === UNDEFINED) {\n\t\t\t\tnormalizedProps[i] = type.defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn createVNode(type, normalizedProps, key, ref, NULL);\n}\n\n/**\n * Create a VNode (used internally by Preact)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component\n * Constructor for this virtual node\n * @param {object | string | number | null} props The properties of this virtual node.\n * If this virtual node represents a text node, this is the text of the node (string or number).\n * @param {string | number | null} key The key for this virtual node, used when\n * diffing it against its children\n * @param {import('./internal').VNode[\"ref\"]} ref The ref property that will\n * receive a reference to its created child\n * @returns {import('./internal').VNode}\n */\nexport function createVNode(type, props, key, ref, original) {\n\t// V8 seems to be better at detecting type shapes if the object is allocated from the same call site\n\t// Do not inline into createElement and coerceToVNode!\n\t/** @type {import('./internal').VNode} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops,\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: original == NULL ? ++vnodeId : original,\n\t\t_index: -1,\n\t\t_flags: 0\n\t};\n\n\t// Only invoke the vnode hook if this was *not* a direct copy:\n\tif (original == NULL && options.vnode != NULL) options.vnode(vnode);\n\n\treturn vnode;\n}\n\nexport function createRef() {\n\treturn { current: NULL };\n}\n\nexport function Fragment(props) {\n\treturn props.children;\n}\n\n/**\n * Check if a the argument is a valid Preact VNode.\n * @param {*} vnode\n * @returns {vnode is VNode}\n */\nexport const isValidElement = vnode =>\n\tvnode != NULL && vnode.constructor === UNDEFINED;\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}\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 | 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} 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}\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 { QuartzComponent, QuartzComponentProps } from \"@quartz-community/types\";\nimport { classNames } from \"../util/lang\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport script from \"./scripts/comments.inline\";\n\ntype RequiredOptsConstructor = (opts: Options) => QuartzComponent;\n\nexport type CommentsOptions = {\n provider: \"giscus\";\n options: {\n repo: `${string}/${string}`;\n repoId: string;\n category: string;\n categoryId: string;\n themeUrl?: string;\n lightTheme?: string;\n darkTheme?: string;\n mapping?: \"url\" | \"title\" | \"og:title\" | \"specific\" | \"number\" | \"pathname\";\n strict?: boolean;\n reactionsEnabled?: boolean;\n inputPosition?: \"top\" | \"bottom\";\n lang?: string;\n };\n};\n\nfunction boolToStringBool(b: boolean): string {\n return b ? \"1\" : \"0\";\n}\n\nexport default ((opts: CommentsOptions) => {\n const Comments: QuartzComponent = ({ displayClass, fileData, cfg }: QuartzComponentProps) => {\n const commentsOverride = fileData.frontmatter?.comments;\n if (commentsOverride === false || commentsOverride === \"false\") {\n return <>;\n }\n\n return (\n \n );\n };\n\n Comments.afterDOMLoaded = script;\n\n return Comments;\n}) satisfies RequiredOptsConstructor;\n"]} \ No newline at end of file +{"version":3,"sources":["../../node_modules/@quartz-community/utils/src/lang.ts","../../src/components/scripts/comments.inline.ts","../../src/components/scripts/bluesky.inline.ts","../../src/components/styles/bluesky.scss","../../src/components/scripts/hackernews.inline.ts","../../src/components/styles/hackernews.scss","../../node_modules/preact/src/constants.js","../../node_modules/preact/src/options.js","../../node_modules/preact/src/create-element.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/Comments.tsx"],"names":["options","Fragment","props","children","__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","key","isStaticChildren","__source","__self","ref","i","normalizedProps","__k","__b","__v","__i","__u","defaultProps","b","u"],"mappings":"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","file":"index.js","sourcesContent":["export function capitalize(s: string): string {\n return s.substring(0, 1).toUpperCase() + s.substring(1);\n}\n\nexport function classNames(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n","var d=s=>{let e=s.detail.theme,t=document.querySelector(\"iframe.giscus-frame\");t&&t.contentWindow&&t.contentWindow.postMessage({giscus:{setConfig:{theme:c(r(e))}}},\"https://giscus.app\")},r=s=>{if(s!==\"dark\"&&s!==\"light\")return s;let e=document.querySelector(\".giscus\");if(!e)return s;let t=e.dataset.darkTheme??\"dark\",a=e.dataset.lightTheme??\"light\";return s===\"dark\"?t:a},c=s=>{let e=document.querySelector(\".giscus\");return e?`${e.dataset.themeUrl??\"https://giscus.app/themes\"}/${s}.css`:`https://giscus.app/themes/${s}.css`},n=[],u=s=>{n.push(s)};if(typeof document<\"u\"){let s=()=>{n.forEach(o=>o()),n.length=0;let e=document.querySelector(\".giscus\");if(!e)return;let t=document.createElement(\"script\");t.src=\"https://giscus.app/client.js\",t.async=!0,t.crossOrigin=\"anonymous\",t.setAttribute(\"data-loading\",\"lazy\"),t.setAttribute(\"data-emit-metadata\",\"0\"),t.setAttribute(\"data-repo\",e.dataset.repo),t.setAttribute(\"data-repo-id\",e.dataset.repoId),t.setAttribute(\"data-category\",e.dataset.category),t.setAttribute(\"data-category-id\",e.dataset.categoryId),t.setAttribute(\"data-mapping\",e.dataset.mapping),t.setAttribute(\"data-strict\",e.dataset.strict),t.setAttribute(\"data-reactions-enabled\",e.dataset.reactionsEnabled),t.setAttribute(\"data-input-position\",e.dataset.inputPosition),t.setAttribute(\"data-lang\",e.dataset.lang);let a=document.documentElement.getAttribute(\"saved-theme\");a&&t.setAttribute(\"data-theme\",c(r(a))),e.appendChild(t);let i=d;document.addEventListener(\"themechange\",i),u(()=>document.removeEventListener(\"themechange\",i))};document.addEventListener(\"nav\",s),document.addEventListener(\"render\",s)}\n","function f(a){let n=new URL(a).pathname.split(\"/\"),t=n[2],o=n[4];if(!t||!o)throw new Error(\"Invalid Bluesky URL\");return{handle:t,postId:o}}async function y(a){let n=await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${a}`);if(!n.ok)throw new Error(\"Could not resolve handle\");let{did:t}=await n.json();return t}async function C(a,n){let t=`at://${a}/app.bsky.feed.post/${n}`,o=await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${t}&depth=10&parentHeight=0`);if(!o.ok)throw new Error(\"Could not fetch thread\");return(await o.json()).thread.replies}function k(a){let n=document.getElementById(\"bluesky-comments-list\");n&&(a&&a.length>0?(n.innerHTML=\"\",a.sort((t,o)=>(o.post.likeCount||0)-(t.post.likeCount||0)),a.forEach(t=>{t.post&&n.appendChild(h(t))})):n.textContent=\"No comments yet.\")}function h(a){let n=a.post,t=n.author,o=new Date(n.indexedAt).toLocaleDateString(),s=document.createElement(\"div\");s.className=\"comment\";let c=document.createElement(\"div\");if(c.className=\"comment-header\",t.avatar){let e=document.createElement(\"img\");e.src=t.avatar,e.alt=t.handle,c.appendChild(e)}let r=document.createElement(\"a\"),d=n.uri.split(\"/\").pop();r.href=`https://bsky.app/profile/${t.did}/post/${d}`,r.target=\"_blank\",r.className=\"handle\",r.textContent=t.displayName||t.handle,c.appendChild(r);let p=document.createElement(\"span\");p.className=\"time\",p.textContent=o,c.appendChild(p),s.appendChild(c),s.appendChild(x(n));let m=document.createElement(\"div\");m.className=\"comment-actions\";let l=document.createElement(\"span\");l.textContent=`\\u2764\\uFE0F ${n.likeCount||0}`,m.appendChild(l);let i=document.createElement(\"span\");if(i.textContent=`\\u{1F501} ${n.repostCount||0}`,m.appendChild(i),s.appendChild(m),a.replies&&a.replies.length>0){let e=document.createElement(\"div\");e.className=\"replies\",a.replies.forEach(u=>{u.post&&e.appendChild(h(u))}),s.appendChild(e)}return s}function x(a){let n=a.record.text,t=a.record.facets,o=document.createElement(\"div\");if(o.className=\"comment-body\",!t||t.length===0)return o.textContent=n,o;t.sort((r,d)=>r.index.byteStart-d.index.byteStart);let s=new TextEncoder().encode(n),c=0;for(let r of t){let{byteStart:d,byteEnd:p}=r.index;if(d>c){let e=s.slice(c,d),u=document.createTextNode(new TextDecoder().decode(e));o.appendChild(u)}let m=s.slice(d,p),l=new TextDecoder().decode(m),i=r.features[0];if(i.$type===\"app.bsky.richtext.facet#link\"){let e=document.createElement(\"a\");e.href=i.uri,e.target=\"_blank\",e.rel=\"noopener noreferrer\",e.textContent=l,o.appendChild(e)}else if(i.$type===\"app.bsky.richtext.facet#mention\"){let e=document.createElement(\"a\");e.href=`https://bsky.app/profile/${i.did}`,e.target=\"_blank\",e.rel=\"noopener noreferrer\",e.textContent=l,o.appendChild(e)}else if(i.$type===\"app.bsky.richtext.facet#tag\"){let e=document.createElement(\"a\");e.href=`https://bsky.app/hashtag/${i.tag}`,e.target=\"_blank\",e.rel=\"noopener noreferrer\",e.textContent=l,o.appendChild(e)}else o.appendChild(document.createTextNode(l));c=p}if(c{let n=document.getElementById(\"bluesky-comments\");if(!n)return;let t=n.getAttribute(\"data-url\");if(!t)return;let o=document.getElementById(\"bluesky-comments-list\");o&&(o.innerHTML=\"

Loading comments...

\");try{let{handle:s,postId:c}=f(t),r=await y(s),d=await C(r,c);k(d)}catch(s){console.error(\"Error loading Bluesky comments:\",s),o&&(o.textContent=\"Error loading comments.\")}};document.addEventListener(\"nav\",a),document.addEventListener(\"render\",a)}\n",".bluesky-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .bluesky-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n img {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n }\n .handle {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n white-space: pre-wrap;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment-actions {\n margin-top: 0.25rem;\n font-size: 0.8rem;\n color: var(--gray);\n display: flex;\n gap: 1rem;\n }\n }\n}\n","function d(e){if(/^\\d+$/.test(e))return e;try{let n=new URL(e).searchParams.get(\"id\");if(n)return n}catch{}throw new Error(\"Invalid Hacker News URL or ID\")}async function m(e){let t=await fetch(`https://hn.algolia.com/api/v1/items/${e}`);if(!t.ok)throw new Error(\"Failed to fetch HN comments\");return(await t.json()).children}function h(e){let t=document.getElementById(\"hackernews-comments-list\");t&&(e&&e.length>0?(t.innerHTML=\"\",e.forEach(n=>{!n.author&&!n.text||t.appendChild(i(n))})):t.textContent=\"No comments yet.\")}function i(e){let t=document.createElement(\"div\");t.className=\"comment\";let n=document.createElement(\"div\");n.className=\"comment-header\";let a=document.createElement(\"a\");a.href=`https://news.ycombinator.com/user?id=${e.author||\"\"}`,a.target=\"_blank\",a.className=\"author\",a.textContent=e.author||\"[deleted]\",n.appendChild(a);let o=document.createElement(\"span\");o.className=\"time\",e.created_at?o.textContent=new Date(e.created_at).toLocaleDateString():o.textContent=\"\",n.appendChild(o),t.appendChild(n);let c=document.createElement(\"div\");if(c.className=\"comment-body\",c.innerHTML=e.text||\"[deleted]\",t.appendChild(c),e.children&&e.children.length>0){let r=document.createElement(\"div\");r.className=\"replies\",e.children.forEach(s=>{!s.author&&!s.text||r.appendChild(i(s))}),t.appendChild(r)}return t}if(typeof document<\"u\"){let e=async()=>{let t=document.getElementById(\"hackernews-comments\");if(!t)return;let n=t.getAttribute(\"data-id\");if(!n)return;let a=document.getElementById(\"hackernews-comments-list\");a&&(a.innerHTML=\"

Loading comments...

\");try{let o=d(n),c=await m(o);h(c)}catch(o){console.error(\"Error loading Hacker News comments:\",o),a&&(a.textContent=\"Error loading comments.\")}};document.addEventListener(\"nav\",e),document.addEventListener(\"render\",e)}\n",".hackernews-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .hackernews-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n .author {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n p {\n margin: 0.5rem 0;\n }\n pre {\n background-color: var(--lightgray);\n padding: 0.5rem;\n border-radius: 4px;\n overflow-x: auto;\n }\n code {\n font-family: var(--codeVal);\n font-size: 0.85rem;\n }\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n }\n}\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 { slice } from './util';\nimport options from './options';\nimport { NULL, UNDEFINED } from './constants';\n\nlet vnodeId = 0;\n\n/**\n * Create an virtual node (used for JSX)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component constructor for this\n * virtual node\n * @param {object | null | undefined} [props] The properties of the virtual node\n * @param {Array} [children] The children of the\n * virtual node\n * @returns {import('./internal').VNode}\n */\nexport function createElement(type, props, children) {\n\tlet normalizedProps = {},\n\t\tkey,\n\t\tref,\n\t\ti;\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse normalizedProps[i] = props[i];\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\t// If a Component VNode, check for and apply defaultProps\n\t// Note: type may be undefined in development, must never error here.\n\tif (typeof type == 'function' && type.defaultProps != NULL) {\n\t\tfor (i in type.defaultProps) {\n\t\t\tif (normalizedProps[i] === UNDEFINED) {\n\t\t\t\tnormalizedProps[i] = type.defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn createVNode(type, normalizedProps, key, ref, NULL);\n}\n\n/**\n * Create a VNode (used internally by Preact)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component\n * Constructor for this virtual node\n * @param {object | string | number | null} props The properties of this virtual node.\n * If this virtual node represents a text node, this is the text of the node (string or number).\n * @param {string | number | null} key The key for this virtual node, used when\n * diffing it against its children\n * @param {import('./internal').VNode[\"ref\"]} ref The ref property that will\n * receive a reference to its created child\n * @returns {import('./internal').VNode}\n */\nexport function createVNode(type, props, key, ref, original) {\n\t// V8 seems to be better at detecting type shapes if the object is allocated from the same call site\n\t// Do not inline into createElement and coerceToVNode!\n\t/** @type {import('./internal').VNode} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops,\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: original == NULL ? ++vnodeId : original,\n\t\t_index: -1,\n\t\t_flags: 0\n\t};\n\n\t// Only invoke the vnode hook if this was *not* a direct copy:\n\tif (original == NULL && options.vnode != NULL) options.vnode(vnode);\n\n\treturn vnode;\n}\n\nexport function createRef() {\n\treturn { current: NULL };\n}\n\nexport function Fragment(props) {\n\treturn props.children;\n}\n\n/**\n * Check if a the argument is a valid Preact VNode.\n * @param {*} vnode\n * @returns {vnode is VNode}\n */\nexport const isValidElement = vnode =>\n\tvnode != NULL && vnode.constructor === UNDEFINED;\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}\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 | 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} 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}\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 { QuartzComponent, QuartzComponentProps } from \"@quartz-community/types\";\nimport { classNames } from \"../util/lang\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport script from \"./scripts/comments.inline\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport blueskyScript from \"./scripts/bluesky.inline\";\nimport blueskyStyle from \"./styles/bluesky.scss\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport hackernewsScript from \"./scripts/hackernews.inline\";\nimport hackernewsStyle from \"./styles/hackernews.scss\";\n\ntype RequiredOptsConstructor = (opts: Options) => QuartzComponent;\n\nexport type CommentsOptions =\n | {\n provider: \"giscus\";\n options: {\n repo: `${string}/${string}`;\n repoId: string;\n category: string;\n categoryId: string;\n themeUrl?: string;\n lightTheme?: string;\n darkTheme?: string;\n mapping?: \"url\" | \"title\" | \"og:title\" | \"specific\" | \"number\" | \"pathname\";\n strict?: boolean;\n reactionsEnabled?: boolean;\n inputPosition?: \"top\" | \"bottom\";\n lang?: string;\n };\n }\n | {\n provider: \"bluesky\";\n options?: Record;\n }\n | {\n provider: \"hackernews\";\n options?: Record;\n };\n\nfunction boolToStringBool(b: boolean): string {\n return b ? \"1\" : \"0\";\n}\n\nexport default ((opts: CommentsOptions) => {\n const Comments: QuartzComponent = ({ displayClass, fileData, cfg }: QuartzComponentProps) => {\n const commentsOverride = fileData.frontmatter?.comments;\n if (commentsOverride === false || commentsOverride === \"false\") {\n return <>;\n }\n\n if (opts.provider === \"bluesky\") {\n const blueskyUrl = fileData.frontmatter?.blueskyUrl as string | undefined;\n if (!blueskyUrl) {\n return <>;\n }\n return (\n \n

Comments

\n

\n Post a reply on{\" \"}\n \n Bluesky\n {\" \"}\n to join the conversation.\n

\n
\n

Loading comments...

\n
\n \n );\n }\n\n if (opts.provider === \"hackernews\") {\n const hnId = fileData.frontmatter?.hnId\n ? String(fileData.frontmatter.hnId)\n : (fileData.frontmatter?.hnUrl as string | undefined);\n if (!hnId) {\n return <>;\n }\n const threadUrl = hnId.startsWith(\"http\")\n ? hnId\n : `https://news.ycombinator.com/item?id=${hnId}`;\n return (\n \n

Comments

\n

\n Post a reply on{\" \"}\n \n Hacker News\n {\" \"}\n to join the conversation.\n

\n
\n

Loading comments...

\n
\n \n );\n }\n\n return (\n \n );\n };\n\n if (opts.provider === \"giscus\") {\n Comments.afterDOMLoaded = script;\n } else if (opts.provider === \"bluesky\") {\n Comments.afterDOMLoaded = blueskyScript;\n Comments.css = blueskyStyle;\n } else if (opts.provider === \"hackernews\") {\n Comments.afterDOMLoaded = hackernewsScript;\n Comments.css = hackernewsStyle;\n }\n\n return Comments;\n}) satisfies RequiredOptsConstructor;\n"]} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index e4223cb..749b5d0 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -16,6 +16,12 @@ type CommentsOptions = { inputPosition?: "top" | "bottom"; lang?: string; }; +} | { + provider: "bluesky"; + options?: Record; +} | { + provider: "hackernews"; + options?: Record; }; declare const _default: (opts: CommentsOptions) => QuartzComponent; diff --git a/dist/index.js b/dist/index.js index 7e33288..82603f5 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5,6 +5,18 @@ function classNames(...classes) { // src/components/scripts/comments.inline.ts var comments_inline_default = 'var d=s=>{let e=s.detail.theme,t=document.querySelector("iframe.giscus-frame");t&&t.contentWindow&&t.contentWindow.postMessage({giscus:{setConfig:{theme:c(r(e))}}},"https://giscus.app")},r=s=>{if(s!=="dark"&&s!=="light")return s;let e=document.querySelector(".giscus");if(!e)return s;let t=e.dataset.darkTheme??"dark",a=e.dataset.lightTheme??"light";return s==="dark"?t:a},c=s=>{let e=document.querySelector(".giscus");return e?`${e.dataset.themeUrl??"https://giscus.app/themes"}/${s}.css`:`https://giscus.app/themes/${s}.css`},n=[],u=s=>{n.push(s)};if(typeof document<"u"){let s=()=>{n.forEach(o=>o()),n.length=0;let e=document.querySelector(".giscus");if(!e)return;let t=document.createElement("script");t.src="https://giscus.app/client.js",t.async=!0,t.crossOrigin="anonymous",t.setAttribute("data-loading","lazy"),t.setAttribute("data-emit-metadata","0"),t.setAttribute("data-repo",e.dataset.repo),t.setAttribute("data-repo-id",e.dataset.repoId),t.setAttribute("data-category",e.dataset.category),t.setAttribute("data-category-id",e.dataset.categoryId),t.setAttribute("data-mapping",e.dataset.mapping),t.setAttribute("data-strict",e.dataset.strict),t.setAttribute("data-reactions-enabled",e.dataset.reactionsEnabled),t.setAttribute("data-input-position",e.dataset.inputPosition),t.setAttribute("data-lang",e.dataset.lang);let a=document.documentElement.getAttribute("saved-theme");a&&t.setAttribute("data-theme",c(r(a))),e.appendChild(t);let i=d;document.addEventListener("themechange",i),u(()=>document.removeEventListener("themechange",i))};document.addEventListener("nav",s),document.addEventListener("render",s)}\n'; + +// src/components/scripts/bluesky.inline.ts +var bluesky_inline_default = 'function f(a){let n=new URL(a).pathname.split("/"),t=n[2],o=n[4];if(!t||!o)throw new Error("Invalid Bluesky URL");return{handle:t,postId:o}}async function y(a){let n=await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${a}`);if(!n.ok)throw new Error("Could not resolve handle");let{did:t}=await n.json();return t}async function C(a,n){let t=`at://${a}/app.bsky.feed.post/${n}`,o=await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${t}&depth=10&parentHeight=0`);if(!o.ok)throw new Error("Could not fetch thread");return(await o.json()).thread.replies}function k(a){let n=document.getElementById("bluesky-comments-list");n&&(a&&a.length>0?(n.innerHTML="",a.sort((t,o)=>(o.post.likeCount||0)-(t.post.likeCount||0)),a.forEach(t=>{t.post&&n.appendChild(h(t))})):n.textContent="No comments yet.")}function h(a){let n=a.post,t=n.author,o=new Date(n.indexedAt).toLocaleDateString(),s=document.createElement("div");s.className="comment";let c=document.createElement("div");if(c.className="comment-header",t.avatar){let e=document.createElement("img");e.src=t.avatar,e.alt=t.handle,c.appendChild(e)}let r=document.createElement("a"),d=n.uri.split("/").pop();r.href=`https://bsky.app/profile/${t.did}/post/${d}`,r.target="_blank",r.className="handle",r.textContent=t.displayName||t.handle,c.appendChild(r);let p=document.createElement("span");p.className="time",p.textContent=o,c.appendChild(p),s.appendChild(c),s.appendChild(x(n));let m=document.createElement("div");m.className="comment-actions";let l=document.createElement("span");l.textContent=`\\u2764\\uFE0F ${n.likeCount||0}`,m.appendChild(l);let i=document.createElement("span");if(i.textContent=`\\u{1F501} ${n.repostCount||0}`,m.appendChild(i),s.appendChild(m),a.replies&&a.replies.length>0){let e=document.createElement("div");e.className="replies",a.replies.forEach(u=>{u.post&&e.appendChild(h(u))}),s.appendChild(e)}return s}function x(a){let n=a.record.text,t=a.record.facets,o=document.createElement("div");if(o.className="comment-body",!t||t.length===0)return o.textContent=n,o;t.sort((r,d)=>r.index.byteStart-d.index.byteStart);let s=new TextEncoder().encode(n),c=0;for(let r of t){let{byteStart:d,byteEnd:p}=r.index;if(d>c){let e=s.slice(c,d),u=document.createTextNode(new TextDecoder().decode(e));o.appendChild(u)}let m=s.slice(d,p),l=new TextDecoder().decode(m),i=r.features[0];if(i.$type==="app.bsky.richtext.facet#link"){let e=document.createElement("a");e.href=i.uri,e.target="_blank",e.rel="noopener noreferrer",e.textContent=l,o.appendChild(e)}else if(i.$type==="app.bsky.richtext.facet#mention"){let e=document.createElement("a");e.href=`https://bsky.app/profile/${i.did}`,e.target="_blank",e.rel="noopener noreferrer",e.textContent=l,o.appendChild(e)}else if(i.$type==="app.bsky.richtext.facet#tag"){let e=document.createElement("a");e.href=`https://bsky.app/hashtag/${i.tag}`,e.target="_blank",e.rel="noopener noreferrer",e.textContent=l,o.appendChild(e)}else o.appendChild(document.createTextNode(l));c=p}if(c{let n=document.getElementById("bluesky-comments");if(!n)return;let t=n.getAttribute("data-url");if(!t)return;let o=document.getElementById("bluesky-comments-list");o&&(o.innerHTML="

Loading comments...

");try{let{handle:s,postId:c}=f(t),r=await y(s),d=await C(r,c);k(d)}catch(s){console.error("Error loading Bluesky comments:",s),o&&(o.textContent="Error loading comments.")}};document.addEventListener("nav",a),document.addEventListener("render",a)}\n'; + +// src/components/styles/bluesky.scss +var bluesky_default = ".bluesky-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .bluesky-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n img {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n }\n .handle {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n white-space: pre-wrap;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment-actions {\n margin-top: 0.25rem;\n font-size: 0.8rem;\n color: var(--gray);\n display: flex;\n gap: 1rem;\n }\n }\n}\n"; + +// src/components/scripts/hackernews.inline.ts +var hackernews_inline_default = 'function d(e){if(/^\\d+$/.test(e))return e;try{let n=new URL(e).searchParams.get("id");if(n)return n}catch{}throw new Error("Invalid Hacker News URL or ID")}async function m(e){let t=await fetch(`https://hn.algolia.com/api/v1/items/${e}`);if(!t.ok)throw new Error("Failed to fetch HN comments");return(await t.json()).children}function h(e){let t=document.getElementById("hackernews-comments-list");t&&(e&&e.length>0?(t.innerHTML="",e.forEach(n=>{!n.author&&!n.text||t.appendChild(i(n))})):t.textContent="No comments yet.")}function i(e){let t=document.createElement("div");t.className="comment";let n=document.createElement("div");n.className="comment-header";let a=document.createElement("a");a.href=`https://news.ycombinator.com/user?id=${e.author||""}`,a.target="_blank",a.className="author",a.textContent=e.author||"[deleted]",n.appendChild(a);let o=document.createElement("span");o.className="time",e.created_at?o.textContent=new Date(e.created_at).toLocaleDateString():o.textContent="",n.appendChild(o),t.appendChild(n);let c=document.createElement("div");if(c.className="comment-body",c.innerHTML=e.text||"[deleted]",t.appendChild(c),e.children&&e.children.length>0){let r=document.createElement("div");r.className="replies",e.children.forEach(s=>{!s.author&&!s.text||r.appendChild(i(s))}),t.appendChild(r)}return t}if(typeof document<"u"){let e=async()=>{let t=document.getElementById("hackernews-comments");if(!t)return;let n=t.getAttribute("data-id");if(!n)return;let a=document.getElementById("hackernews-comments-list");a&&(a.innerHTML="

Loading comments...

");try{let o=d(n),c=await m(o);h(c)}catch(o){console.error("Error loading Hacker News comments:",o),a&&(a.textContent="Error loading comments.")}};document.addEventListener("nav",e),document.addEventListener("render",e)}\n'; + +// src/components/styles/hackernews.scss +var hackernews_default = ".hackernews-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .hackernews-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n .author {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n p {\n margin: 0.5rem 0;\n }\n pre {\n background-color: var(--lightgray);\n padding: 0.5rem;\n border-radius: 4px;\n overflow-x: auto;\n }\n code {\n font-family: var(--codeVal);\n font-size: 0.85rem;\n }\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n }\n}\n"; var l; function S(n2) { return n2.children; @@ -39,6 +51,57 @@ var Comments_default = ((opts) => { if (commentsOverride === false || commentsOverride === "false") { return /* @__PURE__ */ u2(S, {}); } + if (opts.provider === "bluesky") { + const blueskyUrl = fileData.frontmatter?.blueskyUrl; + if (!blueskyUrl) { + return /* @__PURE__ */ u2(S, {}); + } + return /* @__PURE__ */ u2( + "div", + { + class: classNames(displayClass, "bluesky-comments-container"), + id: "bluesky-comments", + "data-url": blueskyUrl, + children: [ + /* @__PURE__ */ u2("h2", { children: "Comments" }), + /* @__PURE__ */ u2("p", { class: "bluesky-meta", children: [ + "Post a reply on", + " ", + /* @__PURE__ */ u2("a", { href: blueskyUrl, target: "_blank", rel: "noopener noreferrer", children: "Bluesky" }), + " ", + "to join the conversation." + ] }), + /* @__PURE__ */ u2("div", { id: "bluesky-comments-list", children: /* @__PURE__ */ u2("p", { children: "Loading comments..." }) }) + ] + } + ); + } + if (opts.provider === "hackernews") { + const hnId = fileData.frontmatter?.hnId ? String(fileData.frontmatter.hnId) : fileData.frontmatter?.hnUrl; + if (!hnId) { + return /* @__PURE__ */ u2(S, {}); + } + const threadUrl = hnId.startsWith("http") ? hnId : `https://news.ycombinator.com/item?id=${hnId}`; + return /* @__PURE__ */ u2( + "div", + { + class: classNames(displayClass, "hackernews-comments-container"), + id: "hackernews-comments", + "data-id": hnId, + children: [ + /* @__PURE__ */ u2("h2", { children: "Comments" }), + /* @__PURE__ */ u2("p", { class: "hackernews-meta", children: [ + "Post a reply on", + " ", + /* @__PURE__ */ u2("a", { href: threadUrl, target: "_blank", rel: "noopener noreferrer", children: "Hacker News" }), + " ", + "to join the conversation." + ] }), + /* @__PURE__ */ u2("div", { id: "hackernews-comments-list", children: /* @__PURE__ */ u2("p", { children: "Loading comments..." }) }) + ] + } + ); + } return /* @__PURE__ */ u2( "div", { @@ -58,7 +121,15 @@ var Comments_default = ((opts) => { } ); }; - Comments.afterDOMLoaded = comments_inline_default; + if (opts.provider === "giscus") { + Comments.afterDOMLoaded = comments_inline_default; + } else if (opts.provider === "bluesky") { + Comments.afterDOMLoaded = bluesky_inline_default; + Comments.css = bluesky_default; + } else if (opts.provider === "hackernews") { + Comments.afterDOMLoaded = hackernews_inline_default; + Comments.css = hackernews_default; + } return Comments; }); diff --git a/dist/index.js.map b/dist/index.js.map index 2b5a9e8..2eb98a2 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../node_modules/@quartz-community/utils/src/lang.ts","../src/components/scripts/comments.inline.ts","../node_modules/preact/src/constants.js","../node_modules/preact/src/options.js","../node_modules/preact/src/create-element.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/Comments.tsx"],"names":["options","Fragment","props","children","__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","key","isStaticChildren","__source","__self","ref","i","normalizedProps","__k","__b","__v","__i","__u","defaultProps","b","u"],"mappings":"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","file":"index.js","sourcesContent":["export function capitalize(s: string): string {\n return s.substring(0, 1).toUpperCase() + s.substring(1);\n}\n\nexport function classNames(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n","var d=s=>{let e=s.detail.theme,t=document.querySelector(\"iframe.giscus-frame\");t&&t.contentWindow&&t.contentWindow.postMessage({giscus:{setConfig:{theme:c(r(e))}}},\"https://giscus.app\")},r=s=>{if(s!==\"dark\"&&s!==\"light\")return s;let e=document.querySelector(\".giscus\");if(!e)return s;let t=e.dataset.darkTheme??\"dark\",a=e.dataset.lightTheme??\"light\";return s===\"dark\"?t:a},c=s=>{let e=document.querySelector(\".giscus\");return e?`${e.dataset.themeUrl??\"https://giscus.app/themes\"}/${s}.css`:`https://giscus.app/themes/${s}.css`},n=[],u=s=>{n.push(s)};if(typeof document<\"u\"){let s=()=>{n.forEach(o=>o()),n.length=0;let e=document.querySelector(\".giscus\");if(!e)return;let t=document.createElement(\"script\");t.src=\"https://giscus.app/client.js\",t.async=!0,t.crossOrigin=\"anonymous\",t.setAttribute(\"data-loading\",\"lazy\"),t.setAttribute(\"data-emit-metadata\",\"0\"),t.setAttribute(\"data-repo\",e.dataset.repo),t.setAttribute(\"data-repo-id\",e.dataset.repoId),t.setAttribute(\"data-category\",e.dataset.category),t.setAttribute(\"data-category-id\",e.dataset.categoryId),t.setAttribute(\"data-mapping\",e.dataset.mapping),t.setAttribute(\"data-strict\",e.dataset.strict),t.setAttribute(\"data-reactions-enabled\",e.dataset.reactionsEnabled),t.setAttribute(\"data-input-position\",e.dataset.inputPosition),t.setAttribute(\"data-lang\",e.dataset.lang);let a=document.documentElement.getAttribute(\"saved-theme\");a&&t.setAttribute(\"data-theme\",c(r(a))),e.appendChild(t);let i=d;document.addEventListener(\"themechange\",i),u(()=>document.removeEventListener(\"themechange\",i))};document.addEventListener(\"nav\",s),document.addEventListener(\"render\",s)}\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 { slice } from './util';\nimport options from './options';\nimport { NULL, UNDEFINED } from './constants';\n\nlet vnodeId = 0;\n\n/**\n * Create an virtual node (used for JSX)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component constructor for this\n * virtual node\n * @param {object | null | undefined} [props] The properties of the virtual node\n * @param {Array} [children] The children of the\n * virtual node\n * @returns {import('./internal').VNode}\n */\nexport function createElement(type, props, children) {\n\tlet normalizedProps = {},\n\t\tkey,\n\t\tref,\n\t\ti;\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse normalizedProps[i] = props[i];\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\t// If a Component VNode, check for and apply defaultProps\n\t// Note: type may be undefined in development, must never error here.\n\tif (typeof type == 'function' && type.defaultProps != NULL) {\n\t\tfor (i in type.defaultProps) {\n\t\t\tif (normalizedProps[i] === UNDEFINED) {\n\t\t\t\tnormalizedProps[i] = type.defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn createVNode(type, normalizedProps, key, ref, NULL);\n}\n\n/**\n * Create a VNode (used internally by Preact)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component\n * Constructor for this virtual node\n * @param {object | string | number | null} props The properties of this virtual node.\n * If this virtual node represents a text node, this is the text of the node (string or number).\n * @param {string | number | null} key The key for this virtual node, used when\n * diffing it against its children\n * @param {import('./internal').VNode[\"ref\"]} ref The ref property that will\n * receive a reference to its created child\n * @returns {import('./internal').VNode}\n */\nexport function createVNode(type, props, key, ref, original) {\n\t// V8 seems to be better at detecting type shapes if the object is allocated from the same call site\n\t// Do not inline into createElement and coerceToVNode!\n\t/** @type {import('./internal').VNode} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops,\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: original == NULL ? ++vnodeId : original,\n\t\t_index: -1,\n\t\t_flags: 0\n\t};\n\n\t// Only invoke the vnode hook if this was *not* a direct copy:\n\tif (original == NULL && options.vnode != NULL) options.vnode(vnode);\n\n\treturn vnode;\n}\n\nexport function createRef() {\n\treturn { current: NULL };\n}\n\nexport function Fragment(props) {\n\treturn props.children;\n}\n\n/**\n * Check if a the argument is a valid Preact VNode.\n * @param {*} vnode\n * @returns {vnode is VNode}\n */\nexport const isValidElement = vnode =>\n\tvnode != NULL && vnode.constructor === UNDEFINED;\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}\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 | 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} 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}\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 { QuartzComponent, QuartzComponentProps } from \"@quartz-community/types\";\nimport { classNames } from \"../util/lang\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport script from \"./scripts/comments.inline\";\n\ntype RequiredOptsConstructor = (opts: Options) => QuartzComponent;\n\nexport type CommentsOptions = {\n provider: \"giscus\";\n options: {\n repo: `${string}/${string}`;\n repoId: string;\n category: string;\n categoryId: string;\n themeUrl?: string;\n lightTheme?: string;\n darkTheme?: string;\n mapping?: \"url\" | \"title\" | \"og:title\" | \"specific\" | \"number\" | \"pathname\";\n strict?: boolean;\n reactionsEnabled?: boolean;\n inputPosition?: \"top\" | \"bottom\";\n lang?: string;\n };\n};\n\nfunction boolToStringBool(b: boolean): string {\n return b ? \"1\" : \"0\";\n}\n\nexport default ((opts: CommentsOptions) => {\n const Comments: QuartzComponent = ({ displayClass, fileData, cfg }: QuartzComponentProps) => {\n const commentsOverride = fileData.frontmatter?.comments;\n if (commentsOverride === false || commentsOverride === \"false\") {\n return <>;\n }\n\n return (\n \n );\n };\n\n Comments.afterDOMLoaded = script;\n\n return Comments;\n}) satisfies RequiredOptsConstructor;\n"]} \ No newline at end of file +{"version":3,"sources":["../node_modules/@quartz-community/utils/src/lang.ts","../src/components/scripts/comments.inline.ts","../src/components/scripts/bluesky.inline.ts","../src/components/styles/bluesky.scss","../src/components/scripts/hackernews.inline.ts","../src/components/styles/hackernews.scss","../node_modules/preact/src/constants.js","../node_modules/preact/src/options.js","../node_modules/preact/src/create-element.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/Comments.tsx"],"names":["options","Fragment","props","children","__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","key","isStaticChildren","__source","__self","ref","i","normalizedProps","__k","__b","__v","__i","__u","defaultProps","b","u"],"mappings":"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","file":"index.js","sourcesContent":["export function capitalize(s: string): string {\n return s.substring(0, 1).toUpperCase() + s.substring(1);\n}\n\nexport function classNames(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n","var d=s=>{let e=s.detail.theme,t=document.querySelector(\"iframe.giscus-frame\");t&&t.contentWindow&&t.contentWindow.postMessage({giscus:{setConfig:{theme:c(r(e))}}},\"https://giscus.app\")},r=s=>{if(s!==\"dark\"&&s!==\"light\")return s;let e=document.querySelector(\".giscus\");if(!e)return s;let t=e.dataset.darkTheme??\"dark\",a=e.dataset.lightTheme??\"light\";return s===\"dark\"?t:a},c=s=>{let e=document.querySelector(\".giscus\");return e?`${e.dataset.themeUrl??\"https://giscus.app/themes\"}/${s}.css`:`https://giscus.app/themes/${s}.css`},n=[],u=s=>{n.push(s)};if(typeof document<\"u\"){let s=()=>{n.forEach(o=>o()),n.length=0;let e=document.querySelector(\".giscus\");if(!e)return;let t=document.createElement(\"script\");t.src=\"https://giscus.app/client.js\",t.async=!0,t.crossOrigin=\"anonymous\",t.setAttribute(\"data-loading\",\"lazy\"),t.setAttribute(\"data-emit-metadata\",\"0\"),t.setAttribute(\"data-repo\",e.dataset.repo),t.setAttribute(\"data-repo-id\",e.dataset.repoId),t.setAttribute(\"data-category\",e.dataset.category),t.setAttribute(\"data-category-id\",e.dataset.categoryId),t.setAttribute(\"data-mapping\",e.dataset.mapping),t.setAttribute(\"data-strict\",e.dataset.strict),t.setAttribute(\"data-reactions-enabled\",e.dataset.reactionsEnabled),t.setAttribute(\"data-input-position\",e.dataset.inputPosition),t.setAttribute(\"data-lang\",e.dataset.lang);let a=document.documentElement.getAttribute(\"saved-theme\");a&&t.setAttribute(\"data-theme\",c(r(a))),e.appendChild(t);let i=d;document.addEventListener(\"themechange\",i),u(()=>document.removeEventListener(\"themechange\",i))};document.addEventListener(\"nav\",s),document.addEventListener(\"render\",s)}\n","function f(a){let n=new URL(a).pathname.split(\"/\"),t=n[2],o=n[4];if(!t||!o)throw new Error(\"Invalid Bluesky URL\");return{handle:t,postId:o}}async function y(a){let n=await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${a}`);if(!n.ok)throw new Error(\"Could not resolve handle\");let{did:t}=await n.json();return t}async function C(a,n){let t=`at://${a}/app.bsky.feed.post/${n}`,o=await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${t}&depth=10&parentHeight=0`);if(!o.ok)throw new Error(\"Could not fetch thread\");return(await o.json()).thread.replies}function k(a){let n=document.getElementById(\"bluesky-comments-list\");n&&(a&&a.length>0?(n.innerHTML=\"\",a.sort((t,o)=>(o.post.likeCount||0)-(t.post.likeCount||0)),a.forEach(t=>{t.post&&n.appendChild(h(t))})):n.textContent=\"No comments yet.\")}function h(a){let n=a.post,t=n.author,o=new Date(n.indexedAt).toLocaleDateString(),s=document.createElement(\"div\");s.className=\"comment\";let c=document.createElement(\"div\");if(c.className=\"comment-header\",t.avatar){let e=document.createElement(\"img\");e.src=t.avatar,e.alt=t.handle,c.appendChild(e)}let r=document.createElement(\"a\"),d=n.uri.split(\"/\").pop();r.href=`https://bsky.app/profile/${t.did}/post/${d}`,r.target=\"_blank\",r.className=\"handle\",r.textContent=t.displayName||t.handle,c.appendChild(r);let p=document.createElement(\"span\");p.className=\"time\",p.textContent=o,c.appendChild(p),s.appendChild(c),s.appendChild(x(n));let m=document.createElement(\"div\");m.className=\"comment-actions\";let l=document.createElement(\"span\");l.textContent=`\\u2764\\uFE0F ${n.likeCount||0}`,m.appendChild(l);let i=document.createElement(\"span\");if(i.textContent=`\\u{1F501} ${n.repostCount||0}`,m.appendChild(i),s.appendChild(m),a.replies&&a.replies.length>0){let e=document.createElement(\"div\");e.className=\"replies\",a.replies.forEach(u=>{u.post&&e.appendChild(h(u))}),s.appendChild(e)}return s}function x(a){let n=a.record.text,t=a.record.facets,o=document.createElement(\"div\");if(o.className=\"comment-body\",!t||t.length===0)return o.textContent=n,o;t.sort((r,d)=>r.index.byteStart-d.index.byteStart);let s=new TextEncoder().encode(n),c=0;for(let r of t){let{byteStart:d,byteEnd:p}=r.index;if(d>c){let e=s.slice(c,d),u=document.createTextNode(new TextDecoder().decode(e));o.appendChild(u)}let m=s.slice(d,p),l=new TextDecoder().decode(m),i=r.features[0];if(i.$type===\"app.bsky.richtext.facet#link\"){let e=document.createElement(\"a\");e.href=i.uri,e.target=\"_blank\",e.rel=\"noopener noreferrer\",e.textContent=l,o.appendChild(e)}else if(i.$type===\"app.bsky.richtext.facet#mention\"){let e=document.createElement(\"a\");e.href=`https://bsky.app/profile/${i.did}`,e.target=\"_blank\",e.rel=\"noopener noreferrer\",e.textContent=l,o.appendChild(e)}else if(i.$type===\"app.bsky.richtext.facet#tag\"){let e=document.createElement(\"a\");e.href=`https://bsky.app/hashtag/${i.tag}`,e.target=\"_blank\",e.rel=\"noopener noreferrer\",e.textContent=l,o.appendChild(e)}else o.appendChild(document.createTextNode(l));c=p}if(c{let n=document.getElementById(\"bluesky-comments\");if(!n)return;let t=n.getAttribute(\"data-url\");if(!t)return;let o=document.getElementById(\"bluesky-comments-list\");o&&(o.innerHTML=\"

Loading comments...

\");try{let{handle:s,postId:c}=f(t),r=await y(s),d=await C(r,c);k(d)}catch(s){console.error(\"Error loading Bluesky comments:\",s),o&&(o.textContent=\"Error loading comments.\")}};document.addEventListener(\"nav\",a),document.addEventListener(\"render\",a)}\n",".bluesky-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .bluesky-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n img {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n }\n .handle {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n white-space: pre-wrap;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment-actions {\n margin-top: 0.25rem;\n font-size: 0.8rem;\n color: var(--gray);\n display: flex;\n gap: 1rem;\n }\n }\n}\n","function d(e){if(/^\\d+$/.test(e))return e;try{let n=new URL(e).searchParams.get(\"id\");if(n)return n}catch{}throw new Error(\"Invalid Hacker News URL or ID\")}async function m(e){let t=await fetch(`https://hn.algolia.com/api/v1/items/${e}`);if(!t.ok)throw new Error(\"Failed to fetch HN comments\");return(await t.json()).children}function h(e){let t=document.getElementById(\"hackernews-comments-list\");t&&(e&&e.length>0?(t.innerHTML=\"\",e.forEach(n=>{!n.author&&!n.text||t.appendChild(i(n))})):t.textContent=\"No comments yet.\")}function i(e){let t=document.createElement(\"div\");t.className=\"comment\";let n=document.createElement(\"div\");n.className=\"comment-header\";let a=document.createElement(\"a\");a.href=`https://news.ycombinator.com/user?id=${e.author||\"\"}`,a.target=\"_blank\",a.className=\"author\",a.textContent=e.author||\"[deleted]\",n.appendChild(a);let o=document.createElement(\"span\");o.className=\"time\",e.created_at?o.textContent=new Date(e.created_at).toLocaleDateString():o.textContent=\"\",n.appendChild(o),t.appendChild(n);let c=document.createElement(\"div\");if(c.className=\"comment-body\",c.innerHTML=e.text||\"[deleted]\",t.appendChild(c),e.children&&e.children.length>0){let r=document.createElement(\"div\");r.className=\"replies\",e.children.forEach(s=>{!s.author&&!s.text||r.appendChild(i(s))}),t.appendChild(r)}return t}if(typeof document<\"u\"){let e=async()=>{let t=document.getElementById(\"hackernews-comments\");if(!t)return;let n=t.getAttribute(\"data-id\");if(!n)return;let a=document.getElementById(\"hackernews-comments-list\");a&&(a.innerHTML=\"

Loading comments...

\");try{let o=d(n),c=await m(o);h(c)}catch(o){console.error(\"Error loading Hacker News comments:\",o),a&&(a.textContent=\"Error loading comments.\")}};document.addEventListener(\"nav\",e),document.addEventListener(\"render\",e)}\n",".hackernews-comments-container {\n margin-top: 4rem;\n padding-top: 2rem;\n border-top: 1px solid var(--lightgray);\n h2 {\n margin-bottom: 1rem;\n }\n .hackernews-meta {\n margin-bottom: 1.5rem;\n color: var(--gray);\n font-size: 0.9rem;\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n .comment {\n margin-top: 1rem;\n padding-left: 1rem;\n border-left: 2px solid var(--lightgray);\n .comment-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n .author {\n font-weight: bold;\n color: var(--dark);\n font-size: 0.9rem;\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n .time {\n color: var(--gray);\n font-size: 0.8rem;\n }\n }\n .comment-body {\n font-size: 0.95rem;\n color: var(--darkgray);\n p {\n margin: 0.5rem 0;\n }\n pre {\n background-color: var(--lightgray);\n padding: 0.5rem;\n border-radius: 4px;\n overflow-x: auto;\n }\n code {\n font-family: var(--codeVal);\n font-size: 0.85rem;\n }\n a {\n color: var(--secondary);\n text-decoration: none;\n &:hover {\n text-decoration: underline;\n }\n }\n }\n }\n}\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 { slice } from './util';\nimport options from './options';\nimport { NULL, UNDEFINED } from './constants';\n\nlet vnodeId = 0;\n\n/**\n * Create an virtual node (used for JSX)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component constructor for this\n * virtual node\n * @param {object | null | undefined} [props] The properties of the virtual node\n * @param {Array} [children] The children of the\n * virtual node\n * @returns {import('./internal').VNode}\n */\nexport function createElement(type, props, children) {\n\tlet normalizedProps = {},\n\t\tkey,\n\t\tref,\n\t\ti;\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse normalizedProps[i] = props[i];\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\t// If a Component VNode, check for and apply defaultProps\n\t// Note: type may be undefined in development, must never error here.\n\tif (typeof type == 'function' && type.defaultProps != NULL) {\n\t\tfor (i in type.defaultProps) {\n\t\t\tif (normalizedProps[i] === UNDEFINED) {\n\t\t\t\tnormalizedProps[i] = type.defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn createVNode(type, normalizedProps, key, ref, NULL);\n}\n\n/**\n * Create a VNode (used internally by Preact)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component\n * Constructor for this virtual node\n * @param {object | string | number | null} props The properties of this virtual node.\n * If this virtual node represents a text node, this is the text of the node (string or number).\n * @param {string | number | null} key The key for this virtual node, used when\n * diffing it against its children\n * @param {import('./internal').VNode[\"ref\"]} ref The ref property that will\n * receive a reference to its created child\n * @returns {import('./internal').VNode}\n */\nexport function createVNode(type, props, key, ref, original) {\n\t// V8 seems to be better at detecting type shapes if the object is allocated from the same call site\n\t// Do not inline into createElement and coerceToVNode!\n\t/** @type {import('./internal').VNode} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops,\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: original == NULL ? ++vnodeId : original,\n\t\t_index: -1,\n\t\t_flags: 0\n\t};\n\n\t// Only invoke the vnode hook if this was *not* a direct copy:\n\tif (original == NULL && options.vnode != NULL) options.vnode(vnode);\n\n\treturn vnode;\n}\n\nexport function createRef() {\n\treturn { current: NULL };\n}\n\nexport function Fragment(props) {\n\treturn props.children;\n}\n\n/**\n * Check if a the argument is a valid Preact VNode.\n * @param {*} vnode\n * @returns {vnode is VNode}\n */\nexport const isValidElement = vnode =>\n\tvnode != NULL && vnode.constructor === UNDEFINED;\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}\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 | 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} 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}\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 { QuartzComponent, QuartzComponentProps } from \"@quartz-community/types\";\nimport { classNames } from \"../util/lang\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport script from \"./scripts/comments.inline\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport blueskyScript from \"./scripts/bluesky.inline\";\nimport blueskyStyle from \"./styles/bluesky.scss\";\n// @ts-expect-error - inline script imported as string by esbuild loader\nimport hackernewsScript from \"./scripts/hackernews.inline\";\nimport hackernewsStyle from \"./styles/hackernews.scss\";\n\ntype RequiredOptsConstructor = (opts: Options) => QuartzComponent;\n\nexport type CommentsOptions =\n | {\n provider: \"giscus\";\n options: {\n repo: `${string}/${string}`;\n repoId: string;\n category: string;\n categoryId: string;\n themeUrl?: string;\n lightTheme?: string;\n darkTheme?: string;\n mapping?: \"url\" | \"title\" | \"og:title\" | \"specific\" | \"number\" | \"pathname\";\n strict?: boolean;\n reactionsEnabled?: boolean;\n inputPosition?: \"top\" | \"bottom\";\n lang?: string;\n };\n }\n | {\n provider: \"bluesky\";\n options?: Record;\n }\n | {\n provider: \"hackernews\";\n options?: Record;\n };\n\nfunction boolToStringBool(b: boolean): string {\n return b ? \"1\" : \"0\";\n}\n\nexport default ((opts: CommentsOptions) => {\n const Comments: QuartzComponent = ({ displayClass, fileData, cfg }: QuartzComponentProps) => {\n const commentsOverride = fileData.frontmatter?.comments;\n if (commentsOverride === false || commentsOverride === \"false\") {\n return <>;\n }\n\n if (opts.provider === \"bluesky\") {\n const blueskyUrl = fileData.frontmatter?.blueskyUrl as string | undefined;\n if (!blueskyUrl) {\n return <>;\n }\n return (\n \n

Comments

\n

\n Post a reply on{\" \"}\n \n Bluesky\n {\" \"}\n to join the conversation.\n

\n
\n

Loading comments...

\n
\n \n );\n }\n\n if (opts.provider === \"hackernews\") {\n const hnId = fileData.frontmatter?.hnId\n ? String(fileData.frontmatter.hnId)\n : (fileData.frontmatter?.hnUrl as string | undefined);\n if (!hnId) {\n return <>;\n }\n const threadUrl = hnId.startsWith(\"http\")\n ? hnId\n : `https://news.ycombinator.com/item?id=${hnId}`;\n return (\n \n

Comments

\n

\n Post a reply on{\" \"}\n \n Hacker News\n {\" \"}\n to join the conversation.\n

\n
\n

Loading comments...

\n
\n \n );\n }\n\n return (\n \n );\n };\n\n if (opts.provider === \"giscus\") {\n Comments.afterDOMLoaded = script;\n } else if (opts.provider === \"bluesky\") {\n Comments.afterDOMLoaded = blueskyScript;\n Comments.css = blueskyStyle;\n } else if (opts.provider === \"hackernews\") {\n Comments.afterDOMLoaded = hackernewsScript;\n Comments.css = hackernewsStyle;\n }\n\n return Comments;\n}) satisfies RequiredOptsConstructor;\n"]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3cefa4b..c58f65e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "eslint-config-prettier": "^9.1.0", "preact": "^10.28.2", "prettier": "^3.6.2", + "sass": "^1.101.0", "tsup": "^8.5.0", "typescript": "^5.9.3", "vitest": "^2.1.9" @@ -707,6 +708,348 @@ "node": ">= 8" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@quartz-community/types": { "version": "0.2.1", "resolved": "git+ssh://git@github.com/quartz-community/types.git#d342893db8240099cfd9d4499996333f1ca636b4", @@ -1826,6 +2169,17 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -2424,6 +2778,13 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -2768,6 +3129,14 @@ "dev": true, "license": "MIT" }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3210,6 +3579,57 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sass": { + "version": "1.101.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.0.tgz", + "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", diff --git a/package.json b/package.json index 267df0f..d36c9de 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "eslint-config-prettier": "^9.1.0", "preact": "^10.28.2", "prettier": "^3.6.2", + "sass": "^1.101.0", "tsup": "^8.5.0", "typescript": "^5.9.3", "vitest": "^2.1.9" @@ -97,7 +98,9 @@ "provider": { "type": "enum", "values": [ - "giscus" + "giscus", + "bluesky", + "hackernews" ] }, "options": { diff --git a/src/components/Comments.tsx b/src/components/Comments.tsx index 3cd5dc5..48f25c4 100644 --- a/src/components/Comments.tsx +++ b/src/components/Comments.tsx @@ -2,26 +2,41 @@ import type { QuartzComponent, QuartzComponentProps } from "@quartz-community/ty import { classNames } from "../util/lang"; // @ts-expect-error - inline script imported as string by esbuild loader import script from "./scripts/comments.inline"; +// @ts-expect-error - inline script imported as string by esbuild loader +import blueskyScript from "./scripts/bluesky.inline"; +import blueskyStyle from "./styles/bluesky.scss"; +// @ts-expect-error - inline script imported as string by esbuild loader +import hackernewsScript from "./scripts/hackernews.inline"; +import hackernewsStyle from "./styles/hackernews.scss"; type RequiredOptsConstructor = (opts: Options) => QuartzComponent; -export type CommentsOptions = { - provider: "giscus"; - options: { - repo: `${string}/${string}`; - repoId: string; - category: string; - categoryId: string; - themeUrl?: string; - lightTheme?: string; - darkTheme?: string; - mapping?: "url" | "title" | "og:title" | "specific" | "number" | "pathname"; - strict?: boolean; - reactionsEnabled?: boolean; - inputPosition?: "top" | "bottom"; - lang?: string; - }; -}; +export type CommentsOptions = + | { + provider: "giscus"; + options: { + repo: `${string}/${string}`; + repoId: string; + category: string; + categoryId: string; + themeUrl?: string; + lightTheme?: string; + darkTheme?: string; + mapping?: "url" | "title" | "og:title" | "specific" | "number" | "pathname"; + strict?: boolean; + reactionsEnabled?: boolean; + inputPosition?: "top" | "bottom"; + lang?: string; + }; + } + | { + provider: "bluesky"; + options?: Record; + } + | { + provider: "hackernews"; + options?: Record; + }; function boolToStringBool(b: boolean): string { return b ? "1" : "0"; @@ -34,6 +49,63 @@ export default ((opts: CommentsOptions) => { return <>; } + if (opts.provider === "bluesky") { + const blueskyUrl = fileData.frontmatter?.blueskyUrl as string | undefined; + if (!blueskyUrl) { + return <>; + } + return ( +
+

Comments

+

+ Post a reply on{" "} + + Bluesky + {" "} + to join the conversation. +

+
+

Loading comments...

+
+
+ ); + } + + if (opts.provider === "hackernews") { + const hnId = fileData.frontmatter?.hnId + ? String(fileData.frontmatter.hnId) + : (fileData.frontmatter?.hnUrl as string | undefined); + if (!hnId) { + return <>; + } + const threadUrl = hnId.startsWith("http") + ? hnId + : `https://news.ycombinator.com/item?id=${hnId}`; + return ( +
+

Comments

+

+ Post a reply on{" "} + + Hacker News + {" "} + to join the conversation. +

+
+

Loading comments...

+
+
+ ); + } + return (
{ ); }; - Comments.afterDOMLoaded = script; + if (opts.provider === "giscus") { + Comments.afterDOMLoaded = script; + } else if (opts.provider === "bluesky") { + Comments.afterDOMLoaded = blueskyScript; + Comments.css = blueskyStyle; + } else if (opts.provider === "hackernews") { + Comments.afterDOMLoaded = hackernewsScript; + Comments.css = hackernewsStyle; + } return Comments; }) satisfies RequiredOptsConstructor; diff --git a/src/components/scripts/bluesky.inline.ts b/src/components/scripts/bluesky.inline.ts new file mode 100644 index 0000000..b9cd0d3 --- /dev/null +++ b/src/components/scripts/bluesky.inline.ts @@ -0,0 +1,179 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +function parseBlueskyUrl(url: string) { + const urlParts = new URL(url).pathname.split("/"); + const handle = urlParts[2]; + const postId = urlParts[4]; + if (!handle || !postId) throw new Error("Invalid Bluesky URL"); + return { handle, postId }; +} + +async function resolveHandle(handle: string) { + const resolveRes = await fetch( + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`, + ); + if (!resolveRes.ok) throw new Error("Could not resolve handle"); + const { did } = await resolveRes.json(); + return did; +} + +async function fetchThread(did: string, postId: string) { + const atUri = `at://${did}/app.bsky.feed.post/${postId}`; + const threadRes = await fetch( + `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${atUri}&depth=10&parentHeight=0`, + ); + if (!threadRes.ok) throw new Error("Could not fetch thread"); + const data = await threadRes.json(); + return data.thread.replies; +} + +function renderComments(replies: any[]) { + const commentsList = document.getElementById("bluesky-comments-list"); + if (!commentsList) return; + if (replies && replies.length > 0) { + commentsList.innerHTML = ""; // Clear loading text + // Sort by likes + replies.sort((a: any, b: any) => (b.post.likeCount || 0) - (a.post.likeCount || 0)); + replies.forEach((reply: any) => { + if (!reply.post) return; + commentsList.appendChild(createCommentNode(reply)); + }); + } else { + commentsList.textContent = "No comments yet."; + } +} + +function createCommentNode(reply: any): HTMLElement { + const post = reply.post; + const author = post.author; + const date = new Date(post.indexedAt).toLocaleDateString(); + const div = document.createElement("div"); + div.className = "comment"; + // Header + const header = document.createElement("div"); + header.className = "comment-header"; + if (author.avatar) { + const avatar = document.createElement("img"); + avatar.src = author.avatar; + avatar.alt = author.handle; + header.appendChild(avatar); + } + const handleLink = document.createElement("a"); + const postId = post.uri.split("/").pop(); + handleLink.href = `https://bsky.app/profile/${author.did}/post/${postId}`; + handleLink.target = "_blank"; + handleLink.className = "handle"; + handleLink.textContent = author.displayName || author.handle; + header.appendChild(handleLink); + const timeSpan = document.createElement("span"); + timeSpan.className = "time"; + timeSpan.textContent = date; + header.appendChild(timeSpan); + div.appendChild(header); + // Body + div.appendChild(renderPostBody(post)); + // Actions + const actions = document.createElement("div"); + actions.className = "comment-actions"; + const likes = document.createElement("span"); + likes.textContent = `❤️ ${post.likeCount || 0}`; + actions.appendChild(likes); + const reposts = document.createElement("span"); + reposts.textContent = `🔁 ${post.repostCount || 0}`; + actions.appendChild(reposts); + div.appendChild(actions); + // Recursive rendering for nested replies + if (reply.replies && reply.replies.length > 0) { + const repliesContainer = document.createElement("div"); + repliesContainer.className = "replies"; + reply.replies.forEach((child: any) => { + if (!child.post) return; + repliesContainer.appendChild(createCommentNode(child)); + }); + div.appendChild(repliesContainer); + } + return div; +} + +function renderPostBody(post: any): HTMLElement { + const text = post.record.text; + const facets = post.record.facets; + const body = document.createElement("div"); + body.className = "comment-body"; + if (!facets || facets.length === 0) { + body.textContent = text; + return body; + } + facets.sort((a: any, b: any) => a.index.byteStart - b.index.byteStart); + const bytes = new TextEncoder().encode(text); + let lastByteIndex = 0; + for (const facet of facets) { + const { byteStart, byteEnd } = facet.index; + if (byteStart > lastByteIndex) { + const slice = bytes.slice(lastByteIndex, byteStart); + const textNode = document.createTextNode(new TextDecoder().decode(slice)); + body.appendChild(textNode); + } + const slice = bytes.slice(byteStart, byteEnd); + const facetText = new TextDecoder().decode(slice); + const feature = facet.features[0]; + if (feature["$type"] === "app.bsky.richtext.facet#link") { + const link = document.createElement("a"); + link.href = feature.uri; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = facetText; + body.appendChild(link); + } else if (feature["$type"] === "app.bsky.richtext.facet#mention") { + const link = document.createElement("a"); + link.href = `https://bsky.app/profile/${feature.did}`; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = facetText; + body.appendChild(link); + } else if (feature["$type"] === "app.bsky.richtext.facet#tag") { + const link = document.createElement("a"); + link.href = `https://bsky.app/hashtag/${feature.tag}`; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = facetText; + body.appendChild(link); + } else { + body.appendChild(document.createTextNode(facetText)); + } + lastByteIndex = byteEnd; + } + if (lastByteIndex < bytes.length) { + const slice = bytes.slice(lastByteIndex); + body.appendChild(document.createTextNode(new TextDecoder().decode(slice))); + } + return body; +} + +if (typeof document !== "undefined") { + const setupBluesky = async () => { + const container = document.getElementById("bluesky-comments"); + if (!container) return; + const url = container.getAttribute("data-url"); + if (!url) return; + + const commentsList = document.getElementById("bluesky-comments-list"); + if (commentsList) { + commentsList.innerHTML = "

Loading comments...

"; + } + + try { + const { handle, postId } = parseBlueskyUrl(url); + const did = await resolveHandle(handle); + const replies = await fetchThread(did, postId); + renderComments(replies); + } catch (e) { + console.error("Error loading Bluesky comments:", e); + if (commentsList) { + commentsList.textContent = "Error loading comments."; + } + } + }; + + document.addEventListener("nav", setupBluesky); + document.addEventListener("render", setupBluesky); +} diff --git a/src/components/scripts/hackernews.inline.ts b/src/components/scripts/hackernews.inline.ts new file mode 100644 index 0000000..a4ab099 --- /dev/null +++ b/src/components/scripts/hackernews.inline.ts @@ -0,0 +1,106 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +function parseHnUrl(urlOrId: string): string { + if (/^\d+$/.test(urlOrId)) return urlOrId; + try { + const url = new URL(urlOrId); + const id = url.searchParams.get("id"); + if (id) return id; + } catch (e) { + // Ignore URL parse error + } + throw new Error("Invalid Hacker News URL or ID"); +} + +async function fetchHnThread(id: string) { + const res = await fetch(`https://hn.algolia.com/api/v1/items/${id}`); + if (!res.ok) throw new Error("Failed to fetch HN comments"); + const data = await res.json(); + return data.children; +} + +function renderHnComments(replies: any[]) { + const commentsList = document.getElementById("hackernews-comments-list"); + if (!commentsList) return; + if (replies && replies.length > 0) { + commentsList.innerHTML = ""; // Clear loading text + replies.forEach((reply: any) => { + if (!reply.author && !reply.text) return; + commentsList.appendChild(createHnCommentNode(reply)); + }); + } else { + commentsList.textContent = "No comments yet."; + } +} + +function createHnCommentNode(reply: any): HTMLElement { + const div = document.createElement("div"); + div.className = "comment"; + + // Header + const header = document.createElement("div"); + header.className = "comment-header"; + + const authorLink = document.createElement("a"); + authorLink.href = `https://news.ycombinator.com/user?id=${reply.author || ""}`; + authorLink.target = "_blank"; + authorLink.className = "author"; + authorLink.textContent = reply.author || "[deleted]"; + header.appendChild(authorLink); + + const timeSpan = document.createElement("span"); + timeSpan.className = "time"; + if (reply.created_at) { + timeSpan.textContent = new Date(reply.created_at).toLocaleDateString(); + } else { + timeSpan.textContent = ""; + } + header.appendChild(timeSpan); + div.appendChild(header); + + // Body + const body = document.createElement("div"); + body.className = "comment-body"; + body.innerHTML = reply.text || "[deleted]"; + div.appendChild(body); + + // Recursive replies + if (reply.children && reply.children.length > 0) { + const repliesContainer = document.createElement("div"); + repliesContainer.className = "replies"; + reply.children.forEach((child: any) => { + if (!child.author && !child.text) return; + repliesContainer.appendChild(createHnCommentNode(child)); + }); + div.appendChild(repliesContainer); + } + + return div; +} + +if (typeof document !== "undefined") { + const setupHnComments = async () => { + const container = document.getElementById("hackernews-comments"); + if (!container) return; + const urlOrId = container.getAttribute("data-id"); + if (!urlOrId) return; + + const commentsList = document.getElementById("hackernews-comments-list"); + if (commentsList) { + commentsList.innerHTML = "

Loading comments...

"; + } + + try { + const id = parseHnUrl(urlOrId); + const replies = await fetchHnThread(id); + renderHnComments(replies); + } catch (e) { + console.error("Error loading Hacker News comments:", e); + if (commentsList) { + commentsList.textContent = "Error loading comments."; + } + } + }; + + document.addEventListener("nav", setupHnComments); + document.addEventListener("render", setupHnComments); +} diff --git a/src/components/styles/bluesky.scss b/src/components/styles/bluesky.scss new file mode 100644 index 0000000..1eedd4d --- /dev/null +++ b/src/components/styles/bluesky.scss @@ -0,0 +1,64 @@ +.bluesky-comments-container { + margin-top: 4rem; + padding-top: 2rem; + border-top: 1px solid var(--lightgray); + h2 { + margin-bottom: 1rem; + } + .bluesky-meta { + margin-bottom: 1.5rem; + color: var(--gray); + font-size: 0.9rem; + a { + color: var(--secondary); + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + } + .comment { + margin-top: 1rem; + padding-left: 1rem; + border-left: 2px solid var(--lightgray); + .comment-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; + img { + width: 24px; + height: 24px; + border-radius: 50%; + } + .handle { + font-weight: bold; + color: var(--dark); + font-size: 0.9rem; + } + .time { + color: var(--gray); + font-size: 0.8rem; + } + } + .comment-body { + font-size: 0.95rem; + color: var(--darkgray); + white-space: pre-wrap; + a { + color: var(--secondary); + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + } + .comment-actions { + margin-top: 0.25rem; + font-size: 0.8rem; + color: var(--gray); + display: flex; + gap: 1rem; + } + } +} diff --git a/src/components/styles/hackernews.scss b/src/components/styles/hackernews.scss new file mode 100644 index 0000000..5c9cc82 --- /dev/null +++ b/src/components/styles/hackernews.scss @@ -0,0 +1,68 @@ +.hackernews-comments-container { + margin-top: 4rem; + padding-top: 2rem; + border-top: 1px solid var(--lightgray); + h2 { + margin-bottom: 1rem; + } + .hackernews-meta { + margin-bottom: 1.5rem; + color: var(--gray); + font-size: 0.9rem; + a { + color: var(--secondary); + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + } + .comment { + margin-top: 1rem; + padding-left: 1rem; + border-left: 2px solid var(--lightgray); + .comment-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; + .author { + font-weight: bold; + color: var(--dark); + font-size: 0.9rem; + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + .time { + color: var(--gray); + font-size: 0.8rem; + } + } + .comment-body { + font-size: 0.95rem; + color: var(--darkgray); + p { + margin: 0.5rem 0; + } + pre { + background-color: var(--lightgray); + padding: 0.5rem; + border-radius: 4px; + overflow-x: auto; + } + code { + font-family: var(--codeVal); + font-size: 0.85rem; + } + a { + color: var(--secondary); + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + } + } +} diff --git a/test/comments.test.ts b/test/comments.test.ts index 1e36a4b..8aa376f 100644 --- a/test/comments.test.ts +++ b/test/comments.test.ts @@ -187,3 +187,82 @@ describe("Comments: data attribute wiring", () => { expect(props?.["data-theme-url"]).toBe("https://cdn.test/theme"); }); }); + +describe("Comments: Bluesky Provider", () => { + const bskyOpts: CommentsOptions = { + provider: "bluesky", + }; + + it("should create a component with bluesky options", () => { + const component = Comments(bskyOpts); + expect(component).toBeDefined(); + expect(typeof component).toBe("function"); + }); + + it("renders nothing when frontmatter.blueskyUrl is missing", () => { + const result = renderComments(bskyOpts, buildProps({ frontmatter: {} })); + expect(isFragmentOrEmpty(result)).toBe(true); + }); + + it("renders the bluesky container when frontmatter.blueskyUrl is present", () => { + const url = "https://bsky.app/profile/user/post/123456"; + const result = renderComments(bskyOpts, buildProps({ frontmatter: { blueskyUrl: url } })); + const props = getDivProps(result); + expect(props).not.toBeNull(); + expect(props?.id).toBe("bluesky-comments"); + expect(props?.["data-url"]).toBe(url); + }); + + it("suppresses bluesky comments when frontmatter.comments is false", () => { + const url = "https://bsky.app/profile/user/post/123456"; + const result = renderComments( + bskyOpts, + buildProps({ frontmatter: { blueskyUrl: url, comments: false } }), + ); + expect(isFragmentOrEmpty(result)).toBe(true); + }); +}); + +describe("Comments: Hacker News Provider", () => { + const hnOpts: CommentsOptions = { + provider: "hackernews", + }; + + it("should create a component with hackernews options", () => { + const component = Comments(hnOpts); + expect(component).toBeDefined(); + expect(typeof component).toBe("function"); + }); + + it("renders nothing when frontmatter.hnId/hnUrl is missing", () => { + const result = renderComments(hnOpts, buildProps({ frontmatter: {} })); + expect(isFragmentOrEmpty(result)).toBe(true); + }); + + it("renders the HN container when frontmatter.hnId is present", () => { + const id = "123456"; + const result = renderComments(hnOpts, buildProps({ frontmatter: { hnId: id } })); + const props = getDivProps(result); + expect(props).not.toBeNull(); + expect(props?.id).toBe("hackernews-comments"); + expect(props?.["data-id"]).toBe(id); + }); + + it("renders the HN container when frontmatter.hnUrl is present", () => { + const url = "https://news.ycombinator.com/item?id=123456"; + const result = renderComments(hnOpts, buildProps({ frontmatter: { hnUrl: url } })); + const props = getDivProps(result); + expect(props).not.toBeNull(); + expect(props?.id).toBe("hackernews-comments"); + expect(props?.["data-id"]).toBe(url); + }); + + it("suppresses HN comments when frontmatter.comments is false", () => { + const id = "123456"; + const result = renderComments( + hnOpts, + buildProps({ frontmatter: { hnId: id, comments: false } }), + ); + expect(isFragmentOrEmpty(result)).toBe(true); + }); +});