├── 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 | :
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 |
12 |
13 |
14 | {title}
15 | →
16 |
17 |
18 | {body}
19 |
20 |
21 |
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 |
41 |
42 |
47 |
52 |
57 |
62 |
63 |
64 |
65 |
66 |
116 |
--------------------------------------------------------------------------------