fix: use fetchData global for path-prefixed deployments

- Update types/globals.d.ts with centralized type declarations
- Use fetchData global instead of hardcoded /static/contentIndex.json
- Revert @quartz-community/types from file:../types to github:quartz-community/types
- Add .prettierignore, update .eslintrc.json with triple-slash-reference override
- Remove duplicate scripts.d.ts/styles.d.ts
This commit is contained in:
saberzero1 2026-03-16 18:54:25 +01:00
parent 458386f4f1
commit ccbe026e34
No known key found for this signature in database
15 changed files with 42 additions and 651 deletions

View file

@ -22,5 +22,13 @@
"caughtErrorsIgnorePattern": "^_"
}
]
}
},
"overrides": [
{
"files": ["types/**/*.d.ts"],
"rules": {
"@typescript-eslint/triple-slash-reference": "off"
}
}
]
}

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
dist

View file

@ -196,7 +196,7 @@ Component scripts run in the browser and must handle Quartz's SPA navigation. Ke
2. **Listen to `nav` event** - Fires after each page navigation (including initial load)
3. **Listen to `prenav` event** - Fires before navigation, use for saving state
4. **Use `window.addCleanup()`** - Register cleanup functions for event listeners
5. **Fetch `/static/contentIndex.json`** - Access page metadata for search, graph, etc.
5. **Use `fetchData` global** - Access page metadata via the `fetchData` promise (handles base path correctly)
See `src/components/scripts/example.inline.ts` for a complete example with all patterns.
@ -238,9 +238,8 @@ When migrating a v4 component to a standalone plugin:
1. **Replace Quartz imports** with `@quartz-community/types`
2. **Copy utility functions** (path helpers, DOM utils) into your plugin
3. **Use `@ts-nocheck`** for inline scripts that can't be type-checked
4. **Fetch data directly** from `/static/contentIndex.json` instead of using `fetchData`
5. **Handle both data formats**: `data.content || data` for contentIndex compatibility
6. **Test with both local and production builds**
4. **Use the `fetchData` global** to access `contentIndex.json` with the correct base path
5. **Test with both local and production builds**
## License

View file

@ -9,7 +9,7 @@ function classNames(...classes) {
var example_default = ".example-component {\n padding: 8px 16px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border-radius: 4px;\n font-weight: 600;\n display: inline-block;\n}";
// src/components/scripts/example.inline.ts
var example_inline_default = 'function c(){let e=window.location.pathname;return e.startsWith("/")&&(e=e.slice(1)),e.endsWith("/")&&(e=e.slice(0,-1)),e||"index"}function r(){let e=document.querySelectorAll(".example-component");if(e.length===0)return;let t=[];function o(n){(n.ctrlKey||n.metaKey)&&n.shiftKey&&n.key.toLowerCase()==="e"&&(n.preventDefault(),console.log("[ExampleComponent] Keyboard shortcut triggered!"))}document.addEventListener("keydown",o),t.push(()=>document.removeEventListener("keydown",o));for(let n of e){let i=()=>{console.log("[ExampleComponent] Clicked!")};n.addEventListener("click",i),t.push(()=>n.removeEventListener("click",i))}typeof window<"u"&&window.addCleanup&&window.addCleanup(()=>{t.forEach(n=>n())}),console.log("[ExampleComponent] Initialized with",e.length,"component(s)")}document.addEventListener("nav",e=>{let t=e.detail?.url||c();console.log("[ExampleComponent] Navigation to:",t),r()});document.addEventListener("render",()=>{console.log("[ExampleComponent] Render event - re-initializing"),r()});document.addEventListener("prenav",()=>{let e=document.querySelector(".example-component");e&&sessionStorage.setItem("exampleScrollTop",e.scrollTop?.toString()||"0")});\n';
var example_inline_default = 'function l(){let e=window.location.pathname;return e.startsWith("/")&&(e=e.slice(1)),e.endsWith("/")&&(e=e.slice(0,-1)),e||"index"}function r(){let e=document.querySelectorAll(".example-component");if(e.length===0)return;let t=[];function o(n){(n.ctrlKey||n.metaKey)&&n.shiftKey&&n.key.toLowerCase()==="e"&&(n.preventDefault(),console.log("[ExampleComponent] Keyboard shortcut triggered!"))}document.addEventListener("keydown",o),t.push(()=>document.removeEventListener("keydown",o));for(let n of e){let i=()=>{console.log("[ExampleComponent] Clicked!")};n.addEventListener("click",i),t.push(()=>n.removeEventListener("click",i))}typeof window<"u"&&window.addCleanup&&window.addCleanup(()=>{t.forEach(n=>n())}),console.log("[ExampleComponent] Initialized with",e.length,"component(s)")}document.addEventListener("nav",e=>{let t=e.detail?.url||l();console.log("[ExampleComponent] Navigation to:",t),r()});document.addEventListener("render",()=>{console.log("[ExampleComponent] Render event - re-initializing"),r()});document.addEventListener("prenav",()=>{let e=document.querySelector(".example-component");e&&sessionStorage.setItem("exampleScrollTop",e.scrollTop?.toString()||"0")});\n';
var ExampleComponent_default = ((opts) => {
const { prefix = "", suffix = "", className = "example-component" } = opts ?? {};
const Component = (props) => {

File diff suppressed because one or more lines are too long

2
dist/index.js vendored
View file

@ -191,7 +191,7 @@ function classNames(...classes) {
var example_default = ".example-component {\n padding: 8px 16px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border-radius: 4px;\n font-weight: 600;\n display: inline-block;\n}";
// src/components/scripts/example.inline.ts
var example_inline_default = 'function c(){let e=window.location.pathname;return e.startsWith("/")&&(e=e.slice(1)),e.endsWith("/")&&(e=e.slice(0,-1)),e||"index"}function r(){let e=document.querySelectorAll(".example-component");if(e.length===0)return;let t=[];function o(n){(n.ctrlKey||n.metaKey)&&n.shiftKey&&n.key.toLowerCase()==="e"&&(n.preventDefault(),console.log("[ExampleComponent] Keyboard shortcut triggered!"))}document.addEventListener("keydown",o),t.push(()=>document.removeEventListener("keydown",o));for(let n of e){let i=()=>{console.log("[ExampleComponent] Clicked!")};n.addEventListener("click",i),t.push(()=>n.removeEventListener("click",i))}typeof window<"u"&&window.addCleanup&&window.addCleanup(()=>{t.forEach(n=>n())}),console.log("[ExampleComponent] Initialized with",e.length,"component(s)")}document.addEventListener("nav",e=>{let t=e.detail?.url||c();console.log("[ExampleComponent] Navigation to:",t),r()});document.addEventListener("render",()=>{console.log("[ExampleComponent] Render event - re-initializing"),r()});document.addEventListener("prenav",()=>{let e=document.querySelector(".example-component");e&&sessionStorage.setItem("exampleScrollTop",e.scrollTop?.toString()||"0")});\n';
var example_inline_default = 'function l(){let e=window.location.pathname;return e.startsWith("/")&&(e=e.slice(1)),e.endsWith("/")&&(e=e.slice(0,-1)),e||"index"}function r(){let e=document.querySelectorAll(".example-component");if(e.length===0)return;let t=[];function o(n){(n.ctrlKey||n.metaKey)&&n.shiftKey&&n.key.toLowerCase()==="e"&&(n.preventDefault(),console.log("[ExampleComponent] Keyboard shortcut triggered!"))}document.addEventListener("keydown",o),t.push(()=>document.removeEventListener("keydown",o));for(let n of e){let i=()=>{console.log("[ExampleComponent] Clicked!")};n.addEventListener("click",i),t.push(()=>n.removeEventListener("click",i))}typeof window<"u"&&window.addCleanup&&window.addCleanup(()=>{t.forEach(n=>n())}),console.log("[ExampleComponent] Initialized with",e.length,"component(s)")}document.addEventListener("nav",e=>{let t=e.detail?.url||l();console.log("[ExampleComponent] Navigation to:",t),r()});document.addEventListener("render",()=>{console.log("[ExampleComponent] Render event - re-initializing"),r()});document.addEventListener("prenav",()=>{let e=document.querySelector(".example-component");e&&sessionStorage.setItem("exampleScrollTop",e.scrollTop?.toString()||"0")});\n';
var ExampleComponent_default = ((opts) => {
const { prefix = "", suffix = "", className = "example-component" } = opts ?? {};
const Component = (props) => {

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

602
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -52,8 +52,8 @@
},
"peerDependencies": {
"@jackyzha0/quartz": "^4.5.2",
"preact": "^10.0.0",
"mdast-util-find-and-replace": "^3.0.1",
"preact": "^10.0.0",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",

View file

@ -3,7 +3,7 @@ import type {
QuartzComponentProps,
QuartzComponentConstructor,
} from "@quartz-community/types";
import type { StackedPagesOptions } from "../types";
import style from "./styles/stacked.scss";
// @ts-expect-error - inline script import handled by Quartz bundler
import script from "./scripts/stacked.inline.ts";

View file

@ -1,4 +0,0 @@
declare module "*.inline.ts" {
const content: string;
export default content;
}

View file

@ -39,10 +39,8 @@ function getCurrentSlug() {
// Helper: Fetch content index (commonly needed for search, graph, explorer)
async function _fetchContentIndex() {
try {
const response = await fetch("/static/contentIndex.json");
const data = await response.json();
// Handle both formats: { "slug": {...} } or { "content": { "slug": {...} } }
return data.content || data;
const data = await fetchData;
return data;
} catch (error) {
console.error("[Plugin] Error fetching content index:", error);
return null;

View file

@ -1,4 +1,3 @@
// @ts-nocheck
// Stacked Pages — Binder-style tab navigation for Quartz v5
//
// Architecture:
@ -49,18 +48,20 @@ export function loadState(): BinderState {
return parsed;
}
}
} catch {}
} catch {
// Ignore corrupted state
}
return { tabs: [], activeIndex: -1 };
}
export function saveState(state: BinderState): void {
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} catch {}
} catch {
// Ignore storage write failures (e.g. quota exceeded, private browsing)
}
}
// ── Config ────────────────────────────────────────────────────────────
function readConfig(container: HTMLElement) {
return {
maxTabs: parseInt(container.dataset.maxTabs || "8", 10),
@ -104,7 +105,8 @@ function renderBinder(container: HTMLElement, state: BinderState) {
const leftStrip = document.createElement("div");
leftStrip.className = "binder-strip binder-strip-left";
for (let i = 0; i < leftTabs.length; i++) {
leftStrip.appendChild(createTabElement(leftTabs[i], i, "left", state, config));
const tab = leftTabs[i]!;
leftStrip.appendChild(createTabElement(tab, i, "left", state, config));
}
container.appendChild(leftStrip);
}
@ -115,7 +117,8 @@ function renderBinder(container: HTMLElement, state: BinderState) {
rightStrip.className = "binder-strip binder-strip-right";
for (let i = 0; i < rightTabs.length; i++) {
const realIndex = state.activeIndex + 1 + i;
rightStrip.appendChild(createTabElement(rightTabs[i], realIndex, "right", state, config));
const tab = rightTabs[i]!;
rightStrip.appendChild(createTabElement(tab, realIndex, "right", state, config));
}
container.appendChild(rightStrip);
}
@ -179,6 +182,7 @@ function navigateToTab(index: number) {
if (index < 0 || index >= state.tabs.length) return;
const tab = state.tabs[index];
if (!tab) return;
// Update active index before navigation so the nav handler knows this is a tab switch
state.activeIndex = index;
saveState(state);
@ -240,7 +244,7 @@ function handleNavigation() {
if (existingIndex >= 0) {
// Tab already exists — just switch to it, update title in case it changed
state.tabs[existingIndex].title = title;
state.tabs[existingIndex]!.title = title;
state.activeIndex = existingIndex;
} else {
// New page — insert as a new tab after the current active tab

View file

@ -1,4 +0,0 @@
declare module "*.scss" {
const content: string;
export default content;
}

27
types/globals.d.ts vendored
View file

@ -1,22 +1,11 @@
interface CustomEventMap {
nav: CustomEvent<{ url: string }>;
prenav: CustomEvent<undefined>;
themechange: CustomEvent<{ theme: "light" | "dark" }>;
/// <reference path="../node_modules/@quartz-community/types/globals.d.ts" />
declare module "*.scss" {
const content: string;
export default content;
}
interface Document {
addEventListener<K extends keyof CustomEventMap>(
type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void,
): void;
removeEventListener<K extends keyof CustomEventMap>(
type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void,
): void;
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K] | UIEvent): void;
}
interface Window {
addCleanup(fn: (...args: unknown[]) => void): void;
spaNavigate?(url: URL, isBack: boolean): Promise<void>;
declare module "*.inline.ts" {
const content: string;
export default content;
}