mirror of
https://github.com/quartz-community/types.git
synced 2026-07-22 02:50:26 +00:00
Initial implementation of @quartz-community/types package
This commit is contained in:
parent
11813ef85c
commit
a342579c84
18 changed files with 311 additions and 4505 deletions
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"es2022": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": "latest",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
"ignorePatterns": ["dist", "node_modules"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
55
.github/workflows/ci.yml
vendored
55
.github/workflows/ci.yml
vendored
|
|
@ -1,55 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run checks
|
||||
run: npm run check
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
publish:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "npm"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Publish
|
||||
run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all",
|
||||
"arrowParens": "always"
|
||||
}
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -1,12 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Initial Quartz community plugin template.
|
||||
175
README.md
175
README.md
|
|
@ -1,165 +1,42 @@
|
|||
# Quartz Community Plugin Template
|
||||
# @quartz-community/types
|
||||
|
||||
Production-ready template for building, testing, and publishing Quartz community plugins. It mirrors
|
||||
Quartz's native plugin patterns and uses a factory-function API similar to Astro integrations:
|
||||
plugins are created by functions that return objects with `name` and lifecycle hooks.
|
||||
Type definitions for Quartz community plugins. This package provides TypeScript interfaces and types that mirror the internal Quartz types, enabling external plugins to have full type safety without depending on the core Quartz package.
|
||||
|
||||
## Highlights
|
||||
|
||||
- ✅ Quartz-compatible transformer/filter/emitter examples
|
||||
- ✅ TypeScript-first with exported types for consumers
|
||||
- ✅ `tsup` bundling + declaration output
|
||||
- ✅ Vitest testing setup with example tests
|
||||
- ✅ Linting/formatting with ESLint + Prettier
|
||||
- ✅ CI workflow for checks and npm publishing
|
||||
- ✅ Demonstrates CSS/JS resource injection and remark/rehype usage
|
||||
|
||||
## Getting started
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
npm install @quartz-community/types
|
||||
```
|
||||
|
||||
## Usage in Quartz
|
||||
## Usage
|
||||
|
||||
Install your plugin into a Quartz site and register it in `quartz.config.ts`:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
import {
|
||||
ExampleTransformer,
|
||||
ExampleFilter,
|
||||
ExampleEmitter,
|
||||
} from "@quartz-community/plugin-template";
|
||||
QuartzComponent,
|
||||
QuartzComponentProps,
|
||||
ExplorerOptions,
|
||||
} from "@quartz-community/types";
|
||||
|
||||
export default {
|
||||
configuration: {
|
||||
pageTitle: "My Garden",
|
||||
},
|
||||
plugins: {
|
||||
transformers: [ExampleTransformer({ highlightToken: "==" })],
|
||||
filters: [ExampleFilter({ allowDrafts: false })],
|
||||
emitters: [ExampleEmitter({ manifestSlug: "plugin-manifest" })],
|
||||
},
|
||||
const MyComponent: QuartzComponent = (props: QuartzComponentProps) => {
|
||||
// Component implementation
|
||||
};
|
||||
|
||||
MyComponent.css = `
|
||||
.my-component { /* styles */ }
|
||||
`;
|
||||
|
||||
MyComponent.afterDOMLoaded = `
|
||||
// Client-side script
|
||||
`;
|
||||
```
|
||||
|
||||
## Plugin factory pattern (Astro-style)
|
||||
## Included Types
|
||||
|
||||
Quartz plugins are factory functions that return an object with a `name` and hook implementations.
|
||||
This mirrors Astro's integration pattern (a function returning an object of hooks), which makes
|
||||
composition and configuration explicit and predictable.
|
||||
|
||||
```ts
|
||||
import type { QuartzTransformerPlugin } from "@jackyzha0/quartz/plugins/types";
|
||||
|
||||
export const MyTransformer: QuartzTransformerPlugin<{ enabled: boolean }> = (opts) => {
|
||||
return {
|
||||
name: "MyTransformer",
|
||||
markdownPlugins() {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Examples included
|
||||
|
||||
### Transformer
|
||||
|
||||
`ExampleTransformer` shows how to:
|
||||
|
||||
- apply a custom remark plugin
|
||||
- run a rehype plugin
|
||||
- inject CSS/JS resources
|
||||
- perform a text transform hook
|
||||
|
||||
```ts
|
||||
import { ExampleTransformer } from "@quartz-community/plugin-template";
|
||||
|
||||
ExampleTransformer({
|
||||
highlightToken: "==",
|
||||
headingClass: "example-plugin-heading",
|
||||
enableGfm: true,
|
||||
addHeadingSlugs: true,
|
||||
});
|
||||
```
|
||||
|
||||
The transformer uses a custom remark plugin to convert `==highlight==` into bold text and a rehype
|
||||
plugin to attach a class to all headings. It also injects a small inline CSS/JS snippet.
|
||||
|
||||
### Filter
|
||||
|
||||
`ExampleFilter` demonstrates frontmatter-driven filtering:
|
||||
|
||||
```ts
|
||||
ExampleFilter({
|
||||
allowDrafts: false,
|
||||
excludeTags: ["private", "wip"],
|
||||
excludePathPrefixes: ["_drafts/", "_private/"],
|
||||
});
|
||||
```
|
||||
|
||||
### Emitter
|
||||
|
||||
`ExampleEmitter` emits a JSON manifest of all pages:
|
||||
|
||||
```ts
|
||||
ExampleEmitter({
|
||||
manifestSlug: "plugin-manifest",
|
||||
includeFrontmatter: true,
|
||||
metadata: { project: "My Garden" },
|
||||
transformManifest: (json) => json.replace("My Garden", "Quartz"),
|
||||
});
|
||||
```
|
||||
|
||||
## API reference
|
||||
|
||||
### `ExampleTransformer(options)`
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| ----------------- | --------- | -------------------------- | ----------------------------- |
|
||||
| `highlightToken` | `string` | `"=="` | Token used to highlight text. |
|
||||
| `headingClass` | `string` | `"example-plugin-heading"` | Class added to headings. |
|
||||
| `enableGfm` | `boolean` | `true` | Enables `remark-gfm`. |
|
||||
| `addHeadingSlugs` | `boolean` | `true` | Enables `rehype-slug`. |
|
||||
|
||||
### `ExampleFilter(options)`
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| --------------------- | ---------- | --------------------------- | ------------------------- |
|
||||
| `allowDrafts` | `boolean` | `false` | Publish draft pages. |
|
||||
| `excludeTags` | `string[]` | `["private"]` | Tags to exclude. |
|
||||
| `excludePathPrefixes` | `string[]` | `["_drafts/", "_private/"]` | Path prefixes to exclude. |
|
||||
|
||||
### `ExampleEmitter(options)`
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| --------------------- | -------------------------- | ----------------------------------------- | ----------------------------------------- |
|
||||
| `manifestSlug` | `string` | `"plugin-manifest"` | Output filename (without extension). |
|
||||
| `includeFrontmatter` | `boolean` | `true` | Include frontmatter in output. |
|
||||
| `metadata` | `Record<string, unknown>` | `{ generator: "Quartz Plugin Template" }` | Extra metadata in manifest. |
|
||||
| `transformManifest` | `(json: string) => string` | `undefined` | Custom transformer for emitted JSON. |
|
||||
| `manifestScriptClass` | `string` | `undefined` | Optional CSS class if rendered into HTML. |
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## Build and lint
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run lint
|
||||
npm run format
|
||||
```
|
||||
|
||||
## Publishing
|
||||
|
||||
Tags matching `v*` trigger the GitHub Actions publish workflow. Ensure `NPM_TOKEN` is set in the
|
||||
repository secrets.
|
||||
- **Component Types**: `QuartzComponent`, `QuartzComponentProps`, `StringResource`
|
||||
- **Plugin Types**: `QuartzTransformerPlugin`, `QuartzFilterPlugin`, `QuartzEmitterPlugin`
|
||||
- **Data Types**: `FileTrieNode`, `ContentIndex`, `ContentIndexEntry`
|
||||
- **Component Options**: `ExplorerOptions`, `GraphOptions`, `SearchOptions`, `D3Config`
|
||||
- **Utility Types**: `HTMLAttributes`, `EventHandler`, `CleanupFunction`, `ClassValue`
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
3831
package-lock.json
generated
3831
package-lock.json
generated
File diff suppressed because it is too large
Load diff
64
package.json
64
package.json
|
|
@ -1,43 +1,31 @@
|
|||
{
|
||||
"name": "@quartz-community/plugin-template",
|
||||
"name": "@quartz-community/types",
|
||||
"version": "0.1.0",
|
||||
"description": "Template repository for Quartz community plugins.",
|
||||
"description": "Type definitions for Quartz community plugins",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Quartz Community",
|
||||
"homepage": "https://quartz.jzhao.xyz",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/quartz-community/plugin-template"
|
||||
"url": "https://github.com/quartz-community/types"
|
||||
},
|
||||
"keywords": [
|
||||
"quartz",
|
||||
"quartz-plugin",
|
||||
"plugin-template",
|
||||
"remark",
|
||||
"rehype",
|
||||
"mdast",
|
||||
"hast"
|
||||
"types",
|
||||
"typescript"
|
||||
],
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"CHANGELOG.md"
|
||||
"LICENSE"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types.d.ts",
|
||||
"import": "./dist/types.js"
|
||||
},
|
||||
"./components": {
|
||||
"types": "./dist/components/index.d.ts",
|
||||
"import": "./dist/components/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
|
@ -47,47 +35,11 @@
|
|||
"build": "tsup",
|
||||
"prepare": "npm run build",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"format": "prettier . --check",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"check": "npm run typecheck && npm run lint && npm run format && npm run test"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@jackyzha0/quartz": "^4.5.2",
|
||||
"preact": "^10.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@jackyzha0/quartz": {
|
||||
"optional": true
|
||||
},
|
||||
"preact": {
|
||||
"optional": false
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"mdast-util-find-and-replace": "^3.0.1",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vfile": "^6.0.3"
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/node": "^24.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"preact": "^10.28.2",
|
||||
"prettier": "^3.6.2",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^2.1.9"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22",
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
import type { VNode, JSX } from "preact";
|
||||
|
||||
export interface ExampleComponentOptions {
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type QuartzComponentProps = {
|
||||
ctx: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
externalResources: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
fileData: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
cfg: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
children: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
tree: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
allFiles: any[]; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
displayClass?: "mobile-only" | "desktop-only";
|
||||
} & JSX.IntrinsicAttributes & {
|
||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
};
|
||||
|
||||
export interface QuartzComponent {
|
||||
(props: QuartzComponentProps): VNode;
|
||||
css?: string;
|
||||
beforeDOMLoaded?: string;
|
||||
afterDOMLoaded?: string;
|
||||
}
|
||||
|
||||
export const ExampleComponent = (opts?: ExampleComponentOptions) => {
|
||||
const { prefix = "", suffix = "", className = "example-component" } = opts ?? {};
|
||||
|
||||
const Component: QuartzComponent = (props: QuartzComponentProps) => {
|
||||
const title = props.fileData?.frontmatter?.title ?? "Untitled";
|
||||
const fullText = `${prefix}${title}${suffix}`;
|
||||
|
||||
return <div class={className}>{fullText}</div>;
|
||||
};
|
||||
|
||||
Component.css = `
|
||||
.example-component {
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
}
|
||||
`;
|
||||
|
||||
Component.afterDOMLoaded = `
|
||||
document.addEventListener('nav', () => {
|
||||
console.log('ExampleComponent: page navigation occurred');
|
||||
});
|
||||
`;
|
||||
|
||||
return Component;
|
||||
};
|
||||
|
||||
export default ExampleComponent;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { ExampleComponent, type ExampleComponentOptions } from "./ExampleComponent";
|
||||
274
src/index.ts
274
src/index.ts
|
|
@ -1,11 +1,265 @@
|
|||
export { ExampleTransformer } from "./transformer";
|
||||
export { ExampleFilter } from "./filter";
|
||||
export { ExampleEmitter } from "./emitter";
|
||||
export { ExampleComponent } from "./components/ExampleComponent";
|
||||
/**
|
||||
* Core type definitions for Quartz community plugins
|
||||
* These types mirror the internal Quartz types but are available as a standalone package
|
||||
* to avoid circular dependencies and version conflicts.
|
||||
*/
|
||||
|
||||
export type {
|
||||
ExampleTransformerOptions,
|
||||
ExampleFilterOptions,
|
||||
ExampleEmitterOptions,
|
||||
ExampleComponentOptions,
|
||||
} from "./types";
|
||||
// ============================================================================
|
||||
// Component Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Props passed to Quartz components during rendering
|
||||
*/
|
||||
export interface QuartzComponentProps {
|
||||
/** Quartz context object */
|
||||
ctx: any;
|
||||
/** Data about the current file being rendered */
|
||||
fileData: any;
|
||||
/** Global Quartz configuration */
|
||||
cfg: any;
|
||||
/** Layout configuration */
|
||||
layout: any;
|
||||
/** File tree for navigation components */
|
||||
tree: any;
|
||||
/** All files in the site */
|
||||
allFiles: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type for Quartz components
|
||||
*/
|
||||
export type QuartzComponentFunction = (props: QuartzComponentProps) => any;
|
||||
|
||||
/**
|
||||
* A Quartz component with optional CSS and scripts
|
||||
*/
|
||||
export interface QuartzComponent extends QuartzComponentFunction {
|
||||
/** CSS styles to inject into the page */
|
||||
css?: string;
|
||||
/** Script to run after DOM is loaded */
|
||||
afterDOMLoaded?: string;
|
||||
/** Script to run before DOM is loaded */
|
||||
beforeDOMLoaded?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource that can be a single string or array of strings
|
||||
*/
|
||||
export type StringResource = string | string[];
|
||||
|
||||
// ============================================================================
|
||||
// Plugin Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Transformer plugin that modifies markdown/HTML content
|
||||
*/
|
||||
export interface QuartzTransformerPluginInstance {
|
||||
name: string;
|
||||
/** Markdown processing plugins */
|
||||
markdownPlugins?: () => any[];
|
||||
/** HTML processing plugins */
|
||||
htmlPlugins?: () => any[];
|
||||
/** Text transformation function */
|
||||
textTransform?: (ctx: any, text: string) => string;
|
||||
}
|
||||
|
||||
export type QuartzTransformerPlugin<Options = undefined> = (
|
||||
opts: Options,
|
||||
) => QuartzTransformerPluginInstance;
|
||||
|
||||
/**
|
||||
* Filter plugin that determines which files to publish
|
||||
*/
|
||||
export interface QuartzFilterPluginInstance {
|
||||
name: string;
|
||||
/** Returns true if file should be published */
|
||||
shouldPublish?: (ctx: any, file: any) => boolean;
|
||||
}
|
||||
|
||||
export type QuartzFilterPlugin<Options = undefined> = (
|
||||
opts: Options,
|
||||
) => QuartzFilterPluginInstance;
|
||||
|
||||
/**
|
||||
* Emitter plugin that generates output files
|
||||
*/
|
||||
export interface QuartzEmitterPluginInstance {
|
||||
name: string;
|
||||
/** Emit files to the output directory */
|
||||
emit?: (ctx: any, content: any[]) => Promise<any[]>;
|
||||
/** Get dependencies for incremental builds */
|
||||
getDependencies?: (ctx: any, content: any[]) => Promise<string[]>;
|
||||
}
|
||||
|
||||
export type QuartzEmitterPlugin<Options = undefined> = (
|
||||
opts: Options,
|
||||
) => QuartzEmitterPluginInstance;
|
||||
|
||||
// ============================================================================
|
||||
// File/Data Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Node in the file tree (used by Explorer component)
|
||||
*/
|
||||
export interface FileTrieNode {
|
||||
/** URL slug segments */
|
||||
slugSegments: string[];
|
||||
/** File data object */
|
||||
data: any | null;
|
||||
/** Child nodes */
|
||||
children: FileTrieNode[];
|
||||
/** Whether this is a folder */
|
||||
isFolder: boolean;
|
||||
/** File segment hint for display */
|
||||
fileSegmentHint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for file tree operations
|
||||
*/
|
||||
export interface FileTrieConfig {
|
||||
/** Sort function for entries */
|
||||
sortFn?: (a: FileTrieNode, b: FileTrieNode) => number;
|
||||
/** Filter function for entries */
|
||||
filterFn?: (node: FileTrieNode) => boolean;
|
||||
/** Map function for transforming entries */
|
||||
mapFn?: (node: FileTrieNode) => void;
|
||||
/** Order of operations: filter, map, sort */
|
||||
order?: Array<"filter" | "map" | "sort">;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content index entry
|
||||
*/
|
||||
export interface ContentIndexEntry {
|
||||
slug: string;
|
||||
filePath: string;
|
||||
title: string;
|
||||
content: string;
|
||||
tags: string[];
|
||||
links: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Full content index
|
||||
*/
|
||||
export type ContentIndex = Record<string, ContentIndexEntry>;
|
||||
|
||||
// ============================================================================
|
||||
// Component Option Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Common options for all component factory functions
|
||||
*/
|
||||
export interface ComponentOptions {
|
||||
/** Component title/label */
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explorer component options
|
||||
*/
|
||||
export interface ExplorerOptions extends ComponentOptions {
|
||||
/** Default state for folders: "collapsed" or "open" */
|
||||
folderDefaultState: "collapsed" | "open";
|
||||
/** Behavior when clicking folders: "collapse" or "link" */
|
||||
folderClickBehavior: "collapse" | "link";
|
||||
/** Whether to persist folder state in localStorage */
|
||||
useSavedState: boolean;
|
||||
/** Custom sort function */
|
||||
sortFn?: (a: FileTrieNode, b: FileTrieNode) => number;
|
||||
/** Custom filter function */
|
||||
filterFn?: (node: FileTrieNode) => boolean;
|
||||
/** Custom map function */
|
||||
mapFn?: (node: FileTrieNode) => void;
|
||||
/** Order of operations */
|
||||
order?: Array<"filter" | "map" | "sort">;
|
||||
}
|
||||
|
||||
/**
|
||||
* D3 graph configuration
|
||||
*/
|
||||
export interface D3Config {
|
||||
/** Enable node dragging */
|
||||
drag: boolean;
|
||||
/** Enable zooming */
|
||||
zoom: boolean;
|
||||
/** Depth of connections (-1 for all) */
|
||||
depth: number;
|
||||
/** Scale factor */
|
||||
scale: number;
|
||||
/** Repelling force strength */
|
||||
repelForce: number;
|
||||
/** Center force strength */
|
||||
centerForce: number;
|
||||
/** Distance between linked nodes */
|
||||
linkDistance: number;
|
||||
/** Label font size */
|
||||
fontSize: number;
|
||||
/** Opacity multiplier */
|
||||
opacityScale: number;
|
||||
/** Tags to exclude */
|
||||
removeTags: string[];
|
||||
/** Show tags as nodes */
|
||||
showTags: boolean;
|
||||
/** Dim non-hovered nodes */
|
||||
focusOnHover?: boolean;
|
||||
/** Enable radial layout */
|
||||
enableRadial?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Graph component options
|
||||
*/
|
||||
export interface GraphOptions {
|
||||
/** Local page graph configuration */
|
||||
localGraph?: Partial<D3Config>;
|
||||
/** Global site graph configuration */
|
||||
globalGraph?: Partial<D3Config>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search component options
|
||||
*/
|
||||
export interface SearchOptions extends ComponentOptions {
|
||||
/** Enable content preview panel */
|
||||
enablePreview?: boolean;
|
||||
/** Custom placeholder text */
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* HTML attributes for components
|
||||
*/
|
||||
export interface HTMLAttributes {
|
||||
[key: string]: string | number | boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler type
|
||||
*/
|
||||
export type EventHandler = (event: Event) => void;
|
||||
|
||||
/**
|
||||
* Cleanup function returned by event listeners
|
||||
*/
|
||||
export type CleanupFunction = () => void;
|
||||
|
||||
/**
|
||||
* Class name value (for classNames utility)
|
||||
*/
|
||||
export type ClassValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| undefined
|
||||
| null
|
||||
| ClassValue[];
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { ExampleEmitter } from "../src/emitter";
|
||||
import { createCtx, createProcessedContent } from "./helpers";
|
||||
|
||||
describe("ExampleEmitter", () => {
|
||||
it("writes a manifest to the output directory", async () => {
|
||||
const outputDir = await fs.mkdtemp(path.join(tmpdir(), "quartz-plugin-"));
|
||||
const ctx = createCtx({ argv: { output: outputDir } });
|
||||
const emitter = ExampleEmitter({ manifestSlug: "manifest" });
|
||||
|
||||
const content = [
|
||||
createProcessedContent({
|
||||
slug: "hello-world",
|
||||
filePath: "notes/hello-world.md",
|
||||
frontmatter: { title: "Hello", tags: ["docs"] },
|
||||
}),
|
||||
];
|
||||
|
||||
const result = await emitter.emit(ctx, content, {
|
||||
css: [],
|
||||
js: [],
|
||||
additionalHead: [],
|
||||
});
|
||||
const outputPaths = Array.isArray(result) ? result : await collectAsync(result);
|
||||
const outputPath = outputPaths[0];
|
||||
if (!outputPath) {
|
||||
throw new Error("Expected emitter to return an output path");
|
||||
}
|
||||
const manifest = JSON.parse(await fs.readFile(outputPath, "utf8"));
|
||||
|
||||
expect(outputPath).toContain("manifest.json");
|
||||
expect(manifest.pages[0].slug).toBe("hello-world");
|
||||
});
|
||||
});
|
||||
|
||||
const collectAsync = async <T>(iterable: AsyncIterable<T>): Promise<T[]> => {
|
||||
const results: T[] = [];
|
||||
for await (const item of iterable) {
|
||||
results.push(item);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { ExampleFilter } from "../src/filter";
|
||||
import { createCtx, createProcessedContent } from "./helpers";
|
||||
|
||||
describe("ExampleFilter", () => {
|
||||
it("filters drafts by default", () => {
|
||||
const ctx = createCtx();
|
||||
const filter = ExampleFilter();
|
||||
const content = createProcessedContent({ frontmatter: { draft: true } });
|
||||
|
||||
expect(filter.shouldPublish(ctx, content)).toBe(false);
|
||||
});
|
||||
|
||||
it("allows drafts when configured", () => {
|
||||
const ctx = createCtx();
|
||||
const filter = ExampleFilter({ allowDrafts: true });
|
||||
const content = createProcessedContent({ frontmatter: { draft: true } });
|
||||
|
||||
expect(filter.shouldPublish(ctx, content)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import type { BuildCtx } from "@jackyzha0/quartz/util/ctx";
|
||||
import type { QuartzConfig } from "@jackyzha0/quartz/cfg";
|
||||
import type { ProcessedContent, QuartzPluginData } from "@jackyzha0/quartz/plugins/vfile";
|
||||
import { VFile } from "vfile";
|
||||
|
||||
type BuildCtxOverrides = Omit<Partial<BuildCtx>, "argv"> & {
|
||||
argv?: Partial<BuildCtx["argv"]>;
|
||||
};
|
||||
|
||||
export const createCtx = (overrides: BuildCtxOverrides = {}): BuildCtx => {
|
||||
const { argv: argvOverrides, ...rest } = overrides;
|
||||
const argv: BuildCtx["argv"] = {
|
||||
directory: "content",
|
||||
verbose: false,
|
||||
output: "dist",
|
||||
serve: false,
|
||||
watch: false,
|
||||
port: 0,
|
||||
wsPort: 0,
|
||||
...argvOverrides,
|
||||
};
|
||||
|
||||
return {
|
||||
buildId: "test-build",
|
||||
argv,
|
||||
cfg: {} as QuartzConfig,
|
||||
allSlugs: [],
|
||||
allFiles: [],
|
||||
incremental: false,
|
||||
...rest,
|
||||
};
|
||||
};
|
||||
|
||||
export const createProcessedContent = (data: Partial<QuartzPluginData> = {}): ProcessedContent => {
|
||||
const vfile = new VFile("");
|
||||
vfile.data = data;
|
||||
return [{ type: "root", children: [] }, vfile];
|
||||
};
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { unified } from "unified";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkStringify from "remark-stringify";
|
||||
import { ExampleTransformer } from "../src/transformer";
|
||||
import { createCtx } from "./helpers";
|
||||
|
||||
describe("ExampleTransformer", () => {
|
||||
it("highlights text wrapped in the token", async () => {
|
||||
const ctx = createCtx();
|
||||
const transformer = ExampleTransformer({ highlightToken: "==" });
|
||||
const plugins = transformer.markdownPlugins?.(ctx) ?? [];
|
||||
|
||||
const file = await unified()
|
||||
.use(remarkParse)
|
||||
.use(plugins)
|
||||
.use(remarkStringify)
|
||||
.process("Hello ==Quartz==");
|
||||
|
||||
expect(String(file)).toContain("**Quartz**");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,26 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"module": "NodeNext",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "NodeNext",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["vitest/globals", "node"],
|
||||
"verbatimModuleSyntax": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact"
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src", "test", "types", "tsup.config.ts", "vitest.config.ts"],
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,11 @@ import { defineConfig } from "tsup";
|
|||
export default defineConfig({
|
||||
entry: {
|
||||
index: "src/index.ts",
|
||||
types: "src/types.ts",
|
||||
"components/index": "src/components/index.ts",
|
||||
},
|
||||
format: ["esm"],
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
target: "es2022",
|
||||
splitting: false,
|
||||
outDir: "dist",
|
||||
esbuildOptions(options) {
|
||||
options.jsx = "automatic";
|
||||
options.jsxImportSource = "preact";
|
||||
},
|
||||
});
|
||||
|
|
|
|||
150
types/quartz-compat.d.ts
vendored
150
types/quartz-compat.d.ts
vendored
|
|
@ -1,150 +0,0 @@
|
|||
declare module "@jackyzha0/quartz/components/types" {
|
||||
export type QuartzComponent = unknown;
|
||||
}
|
||||
|
||||
declare module "@jackyzha0/quartz/util/path" {
|
||||
export type FilePath = string & { _brand: "FilePath" };
|
||||
export type FullSlug = string & { _brand: "FullSlug" };
|
||||
export function joinSegments(...segments: string[]): FilePath;
|
||||
}
|
||||
|
||||
declare module "@jackyzha0/quartz/util/resources" {
|
||||
export type JSResource =
|
||||
| {
|
||||
loadTime: "beforeDOMReady" | "afterDOMReady";
|
||||
moduleType?: "module";
|
||||
spaPreserve?: boolean;
|
||||
src: string;
|
||||
contentType: "external";
|
||||
}
|
||||
| {
|
||||
loadTime: "beforeDOMReady" | "afterDOMReady";
|
||||
moduleType?: "module";
|
||||
spaPreserve?: boolean;
|
||||
script: string;
|
||||
contentType: "inline";
|
||||
};
|
||||
|
||||
export type CSSResource = {
|
||||
content: string;
|
||||
inline?: boolean;
|
||||
spaPreserve?: boolean;
|
||||
};
|
||||
|
||||
export interface StaticResources {
|
||||
css: CSSResource[];
|
||||
js: JSResource[];
|
||||
additionalHead: unknown[];
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@jackyzha0/quartz/util/ctx" {
|
||||
import type { QuartzConfig } from "@jackyzha0/quartz/cfg";
|
||||
import type { FilePath, FullSlug } from "@jackyzha0/quartz/util/path";
|
||||
|
||||
export interface Argv {
|
||||
directory: string;
|
||||
verbose: boolean;
|
||||
output: string;
|
||||
serve: boolean;
|
||||
watch: boolean;
|
||||
port: number;
|
||||
wsPort: number;
|
||||
remoteDevHost?: string;
|
||||
concurrency?: number;
|
||||
}
|
||||
|
||||
export interface BuildCtx {
|
||||
buildId: string;
|
||||
argv: Argv;
|
||||
cfg: QuartzConfig;
|
||||
allSlugs: FullSlug[];
|
||||
allFiles: FilePath[];
|
||||
incremental: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@jackyzha0/quartz/cfg" {
|
||||
export interface QuartzConfig {
|
||||
configuration: {
|
||||
baseUrl?: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@jackyzha0/quartz/plugins/vfile" {
|
||||
import type { Root as HtmlRoot } from "hast";
|
||||
import type { Root as MdRoot } from "mdast";
|
||||
import type { Data, VFile } from "vfile";
|
||||
|
||||
export type QuartzPluginData = Data;
|
||||
export type MarkdownContent = [MdRoot, VFile];
|
||||
export type ProcessedContent = [HtmlRoot, VFile];
|
||||
}
|
||||
|
||||
declare module "@jackyzha0/quartz/plugins/types" {
|
||||
import type { PluggableList } from "unified";
|
||||
import type { StaticResources } from "@jackyzha0/quartz/util/resources";
|
||||
import type { ProcessedContent } from "@jackyzha0/quartz/plugins/vfile";
|
||||
import type { QuartzComponent } from "@jackyzha0/quartz/components/types";
|
||||
import type { FilePath } from "@jackyzha0/quartz/util/path";
|
||||
import type { BuildCtx } from "@jackyzha0/quartz/util/ctx";
|
||||
import type { VFile } from "vfile";
|
||||
|
||||
export interface PluginTypes {
|
||||
transformers: QuartzTransformerPluginInstance[];
|
||||
filters: QuartzFilterPluginInstance[];
|
||||
emitters: QuartzEmitterPluginInstance[];
|
||||
}
|
||||
|
||||
type OptionType = object | undefined;
|
||||
type ExternalResourcesFn = (ctx: BuildCtx) => Partial<StaticResources> | undefined;
|
||||
|
||||
export type QuartzTransformerPlugin<Options extends OptionType = undefined> = (
|
||||
opts?: Options,
|
||||
) => QuartzTransformerPluginInstance;
|
||||
|
||||
export type QuartzTransformerPluginInstance = {
|
||||
name: string;
|
||||
textTransform?: (ctx: BuildCtx, src: string) => string;
|
||||
markdownPlugins?: (ctx: BuildCtx) => PluggableList;
|
||||
htmlPlugins?: (ctx: BuildCtx) => PluggableList;
|
||||
externalResources?: ExternalResourcesFn;
|
||||
};
|
||||
|
||||
export type QuartzFilterPlugin<Options extends OptionType = undefined> = (
|
||||
opts?: Options,
|
||||
) => QuartzFilterPluginInstance;
|
||||
|
||||
export type QuartzFilterPluginInstance = {
|
||||
name: string;
|
||||
shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean;
|
||||
};
|
||||
|
||||
export type ChangeEvent = {
|
||||
type: "add" | "change" | "delete";
|
||||
path: FilePath;
|
||||
file?: VFile;
|
||||
};
|
||||
|
||||
export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
|
||||
opts?: Options,
|
||||
) => QuartzEmitterPluginInstance;
|
||||
|
||||
export type QuartzEmitterPluginInstance = {
|
||||
name: string;
|
||||
emit: (
|
||||
ctx: BuildCtx,
|
||||
content: ProcessedContent[],
|
||||
resources: StaticResources,
|
||||
) => Promise<FilePath[]> | AsyncGenerator<FilePath>;
|
||||
partialEmit?: (
|
||||
ctx: BuildCtx,
|
||||
content: ProcessedContent[],
|
||||
resources: StaticResources,
|
||||
changeEvents: ChangeEvent[],
|
||||
) => Promise<FilePath[]> | AsyncGenerator<FilePath> | null;
|
||||
getQuartzComponents?: (ctx: BuildCtx) => QuartzComponent[];
|
||||
externalResources?: ExternalResourcesFn;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: "node",
|
||||
include: ["test/**/*.test.ts"],
|
||||
reporters: ["default"],
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue