├── CHANGELOG.md ├── src ├── env.d.ts ├── Pintora.astro └── main.ts ├── example ├── src │ ├── env.d.ts │ ├── layouts │ │ └── Layout.astro │ ├── components │ │ └── Card.astro │ └── pages │ │ └── index.astro ├── astro.config.ts ├── tsconfig.json └── public │ └── favicon.svg ├── index.ts ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ └── release.yml ├── .eslintrc.cjs ├── LICENSE ├── package.json └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.0.1] - 2023-12-13 4 | First version 5 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference 2 | /// 3 | -------------------------------------------------------------------------------- /example/src/env.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference 2 | /// 3 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import Pintora from './src/Pintora.astro' 2 | export type { RenderOptions } from './src/main.ts' 3 | 4 | export default Pintora 5 | -------------------------------------------------------------------------------- /example/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | 3 | // https://astro.build/config 4 | export default defineConfig({}) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # generated types 2 | .astro/ 3 | 4 | # dependencies 5 | node_modules/ 6 | 7 | # logs 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | pnpm-debug.log* 12 | 13 | # macOS-specific files 14 | .DS_Store 15 | 16 | # Jetbrains-sepcific files 17 | .idea/* 18 | 19 | # Specific to the project 20 | public_to_encrypt/* 21 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "baseUrl": ".", 6 | "paths": { 7 | "~/*": ["../*"], 8 | "~": ["../"] 9 | }, 10 | "strictNullChecks": true, 11 | "plugins": [ 12 | { 13 | "name": "@astrojs/ts-plugin" 14 | }, 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "include": ["index.ts", "src"], 4 | "compilerOptions": { 5 | "jsx": "preserve", 6 | "baseUrl": ".", 7 | "paths": { 8 | "~/*": ["src/*"] 9 | }, 10 | "strictNullChecks": true, 11 | "plugins": [ 12 | { 13 | "name": "@astrojs/ts-plugin" 14 | }, 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: 18 14 | registry-url: 'https://registry.npmjs.org' 15 | - run: npm ci 16 | - id: get_npm_label 17 | run: if (npx semver $(node -p "require('./package.json').version") --range '>0.0.0'); then echo ::set-output name=NPM_LABEL::latest; else echo ::set-output name=NPM_LABEL::beta; fi; # Using the fact that semver by default considers that pre-releases do not respect stable ranges 18 | - run: npm publish --tag=${NPM_LABEL} --access public 19 | env: 20 | NPM_LABEL: ${{ steps.get_npm_label.outputs.NPM_LABEL }} 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /example/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | title: string 4 | } 5 | 6 | const { title } = Astro.props; 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {title} 17 | 18 | 19 | 20 | 21 | 22 | 38 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true, 5 | 'node': true 6 | }, 7 | 'extends': 'standard-with-typescript', 8 | 'overrides': [ 9 | { 10 | 'env': { 11 | 'node': true 12 | }, 13 | 'files': [ 14 | '.eslintrc.{js,cjs}' 15 | ], 16 | 'parserOptions': { 17 | 'sourceType': 'script' 18 | } 19 | }, 20 | { 21 | // Define the configuration for `.astro` file. 22 | files: ['*.astro'], 23 | // Allows Astro components to be parsed. 24 | parser: 'astro-eslint-parser', 25 | // Parse the script in `.astro` as TypeScript by adding the following configuration. 26 | // It's the setting you need when using TypeScript. 27 | parserOptions: { 28 | parser: '@typescript-eslint/parser', 29 | extraFileExtensions: ['.astro'], 30 | } 31 | } 32 | ], 33 | 'parserOptions': { 34 | 'ecmaVersion': 'latest', 35 | 'sourceType': 'module' 36 | }, 37 | 'rules': {} 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 tex0l 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Pintora.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { cachedComputeDiagram, computeDiagram } from './main.ts' 3 | import type { RenderOptions } from './main.ts' 4 | 5 | export interface Props { 6 | renderOptions?: RenderOptions 7 | code: string 8 | } 9 | 10 | const { code = '', renderOptions = {} } = Astro.props 11 | 12 | let result 13 | try { 14 | result = await cachedComputeDiagram(code, renderOptions) 15 | } catch (error) { 16 | console.error(error) 17 | result = await computeDiagram(`componentDiagram 18 | @param componentBackground #f00 19 | component error [ 20 | Could not render diagram 21 | ]`, { mimeType: 'image/svg+xml', backgroundColor: 'transparent' }) 22 | } 23 | --- 24 |
25 | { 26 | result.type === 'svg' 27 | ? 28 | : diagram 29 | } 30 |
31 | 32 | 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-pintora", 3 | "version": "0.0.1", 4 | "description": "An Astro component for Pintora diagrams", 5 | "type": "module", 6 | "exports": { 7 | ".": "./index.ts" 8 | }, 9 | "files": [ 10 | "src", 11 | "index.ts" 12 | ], 13 | "keywords": [ 14 | "astro-component", 15 | "pintora", 16 | "mermaid", 17 | "diagram", 18 | "flowchart", 19 | "charts" 20 | ], 21 | "scripts": { 22 | "lint": "eslint . --ext .ts,.js,.astro src", 23 | "dev": "cd example && astro dev" 24 | }, 25 | "devDependencies": { 26 | "@astrojs/ts-plugin": "^1.3.1", 27 | "@types/eslint": "^8.44.8", 28 | "@types/node": "^20.10.4", 29 | "@typescript-eslint/eslint-plugin": "^6.13.1", 30 | "@typescript-eslint/parser": "^6.13.1", 31 | "astro": "^3.6.4", 32 | "astro-eslint-parser": "^0.16.0", 33 | "eslint": "^8.55.0", 34 | "eslint-config-standard-with-typescript": "^40.0.0", 35 | "eslint-plugin-astro": "^0.30.0", 36 | "eslint-plugin-import": "^2.29.0", 37 | "eslint-plugin-n": "^16.3.1", 38 | "eslint-plugin-promise": "^6.1.1", 39 | "typescript": "^5.3.2" 40 | }, 41 | "peerDependencies": { 42 | "astro": "^3.6.4" 43 | }, 44 | "dependencies": { 45 | "@pintora/cli": "^0.6.3", 46 | "@pintora/standalone": "^0.6.3" 47 | }, 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /example/src/components/Card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | title: string 4 | body: string 5 | href: string 6 | } 7 | 8 | const { href, title, body } = Astro.props; 9 | --- 10 | 11 | 22 | 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro Pintora 2 | This package aims at making an of-the-shelves Astro Component for [Pintora](https://pintorajs.vercel.app/) diagrams. 3 | 4 | ## Quick start 5 | You can install `astro-pintora` as follows: 6 | 7 | ```shell 8 | npm install astro-pintora 9 | # OR 10 | yarn add astro-pintora 11 | # OR 12 | pnpn install astro-pintora 13 | ``` 14 | 15 | And use it as follows in a page: 16 | ```astro 17 | --- 18 | import Pintora from 'astro-pintora' 19 | --- 20 | 26 | ``` 27 | 28 | ## Props 29 | This component accepts two props: 30 | - `code: string`: the Pintora diagram declaration 31 | - `renderOptions?: RenderOptions`: the rest of the [CLIRenderOptions](https://pintorajs.vercel.app/docs/advanced/api-usage/#renderoptions): 32 | - `devicePixelRatio?: number`: the pixel ratio. 33 | - `mimeType?: 'image/png' | 'image/jpeg' | 'image/svg+xml'`: specifies the output format. Defaults to `'image/png'`. 34 | - `backgroundColor?: string`: the background color. 35 | - `pintoraConfig?: Partial`: the Pintora config object, can be used to override the theme through `pintoraConfig.themeConfig.theme`. 36 | - `width?: number`: width of the output. 37 | 38 | For example, to render an SVG with a transparent background that uses the `dark` theme: 39 | 40 | ```astro 41 | --- 42 | import Pintora, { type RenderOptions } from 'astro-pintora' 43 | 44 | const renderOptions: RenderOptions = { 45 | mimeType: 'image/svg+xml', 46 | backgroundColor: 'transparent', 47 | pintoraConfig: { themeConfig: { theme: 'dark' } } 48 | } 49 | --- 50 | 57 | ``` 58 | 59 | ## Acknowledgments 60 | The structure of the package is inspired by [`astro-diagrams`](https://github.com/JulianCataldo/web-garden/tree/develop/components/Diagram) which aims at providing an equivalent Astro component for Mermaid diagrams. 61 | 62 | Many thanks to [@hikerpig](https://github.com/hikerpig) who maintains [Pintora](https://github.com/hikerpig/pintora) for the quick support. 63 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from 'node:crypto' 2 | import { render } from '@pintora/cli' 3 | import type { CLIRenderOptions } from '@pintora/cli/lib/render' 4 | import type { PintoraConfig } from '@pintora/core/lib/config' 5 | 6 | type Cache = Record 7 | 8 | const cache: Cache = {} 9 | 10 | type DeepPartial = T extends object ? { 11 | [P in keyof T]?: DeepPartial; 12 | } : T 13 | 14 | export interface RenderOptions extends Omit { 15 | // remove null from the possibilities for clarity 16 | devicePixelRatio?: number 17 | // override the mimeType to an enum to avoid typos 18 | mimeType?: 'image/png' | 'image/jpeg' | 'image/svg+xml' 19 | // Hack because themeConfig has some deep required properties which have default values already set 20 | pintoraConfig?: DeepPartial 21 | } 22 | 23 | export interface Result { 24 | type: 'img' | 'svg' 25 | value: string 26 | } 27 | 28 | const generateHash = (input: string): string => { 29 | const hash = createHash('sha256') 30 | hash.update(input) 31 | return hash.digest().toString('hex') 32 | } 33 | 34 | export const computeDiagram = async (code: string, renderOptions: RenderOptions): Promise => { 35 | const opts = { 36 | code, 37 | ...renderOptions 38 | } 39 | 40 | const result = await render(opts) 41 | 42 | if (result instanceof Buffer) { 43 | return { 44 | type: 'img', 45 | value: `data:${opts.mimeType ?? 'image/png'};base64,${result 46 | .toString('base64') 47 | .replace(/-/g, '+') 48 | .replace(/_/g, '/')}` 49 | } 50 | } else { 51 | return { 52 | type: 'svg', 53 | value: result 54 | } 55 | } 56 | } 57 | 58 | const computeUniqueKey = (code: string, renderOptions: RenderOptions): string => 59 | generateHash( 60 | JSON.stringify({ 61 | code, 62 | renderOptions 63 | }) 64 | ) 65 | 66 | export const cachedComputeDiagram = async ( 67 | code: string, 68 | renderOptions: RenderOptions 69 | ): Promise => { 70 | const key = computeUniqueKey(code, renderOptions) 71 | if (cache[key] === undefined) { 72 | cache[key] = await computeDiagram(code, renderOptions) 73 | } 74 | return cache[key] 75 | } 76 | -------------------------------------------------------------------------------- /example/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../layouts/Layout.astro' 3 | import Card from '../components/Card.astro' 4 | import Pintora, { type RenderOptions } from '~' 5 | 6 | const renderOptions: RenderOptions = { 7 | mimeType: 'image/svg+xml', 8 | backgroundColor: 'transparent', 9 | pintoraConfig: { themeConfig: { theme: 'dark' } } 10 | } 11 | --- 12 | 13 | 14 |
15 |

Welcome to Astro

16 |

17 | To get started, open the directory src/pages in your project.
18 | Code Challenge: Tweak the "Welcome to Astro" message above. 19 |

20 |
21 | [JPEG diagram] : base64 image`} 23 | renderOptions={{ mimeType: 'image/jpeg', devicePixelRatio: 0.5 }}/> 24 | [PNG diagram] : base64 image`} 26 | renderOptions={{ mimeType: 'image/png', devicePixelRatio: 0.5 }}/> 27 | [SVG diagram] : as DOM`} 29 | renderOptions={{ mimeType: 'image/svg+xml' }}/> 30 | [SVG diagram] : as DOM`} 32 | renderOptions={{ mimeType: 'image/svg+xml', backgroundColor: 'transparent' }}/> 33 | 36 | 39 |
40 | 63 |
64 |
65 | 66 | 116 | --------------------------------------------------------------------------------