├── .node-version ├── website ├── static │ ├── .nojekyll │ ├── img │ │ ├── logo.jpg │ │ └── logo.svg │ └── style │ │ └── carbon.css ├── src │ ├── const.ts │ ├── components │ │ └── HomepageFeatures.module.css │ ├── pages │ │ └── index.module.css │ └── css │ │ └── custom.css ├── docs │ ├── advanced │ │ ├── _category_.json │ │ └── pre-block.md │ ├── diagrams │ │ └── _category_.json │ ├── configuration │ │ └── _category_.json │ └── getting-started │ │ ├── _category_.json │ │ └── basic-syntax.mdx ├── babel.config.js ├── tsconfig.json ├── i18n │ ├── zh-CN │ │ ├── docusaurus-plugin-content-docs │ │ │ ├── current │ │ │ │ ├── advanced │ │ │ │ │ └── _category_.json │ │ │ │ ├── getting-started │ │ │ │ │ └── basic-syntax.mdx │ │ │ │ ├── intro.md │ │ │ │ └── diagrams │ │ │ │ │ └── mindmap.mdx │ │ │ └── current.json │ │ └── docusaurus-theme-classic │ │ │ ├── navbar.json │ │ │ └── footer.json │ └── en │ │ ├── docusaurus-theme-classic │ │ ├── navbar.json │ │ └── footer.json │ │ └── docusaurus-plugin-content-docs │ │ └── current.json ├── .gitignore ├── plugins │ ├── site-ad │ │ └── client-module.js │ ├── docusaurus-theme-pintora │ │ ├── theme │ │ │ ├── PintoraPlay │ │ │ │ ├── PintoraPlay.less │ │ │ │ └── highlight.ts │ │ │ └── CodeBlock │ │ │ │ └── index.jsx │ │ └── index.js │ ├── webpack5-plugin.js │ └── site-ad-plugin.js ├── README.md ├── sidebars.js └── package.json ├── packages ├── pintora-standalone │ ├── .gitignore │ ├── scripts │ │ └── tsconfig.json │ ├── jest.config.js │ ├── tsconfig.json │ └── LICENSE.txt ├── pintora-diagrams │ ├── .npmignore │ ├── src │ │ ├── util │ │ │ ├── time.ts │ │ │ ├── theme.ts │ │ │ ├── number.ts │ │ │ ├── symbols │ │ │ │ ├── index.ts │ │ │ │ ├── circle.ts │ │ │ │ ├── ellipse.ts │ │ │ │ └── actor.ts │ │ │ ├── env.ts │ │ │ ├── ir.ts │ │ │ ├── text.ts │ │ │ ├── base-artist.ts │ │ │ ├── preproccesor │ │ │ │ └── __tests__ │ │ │ │ │ └── __snapshots__ │ │ │ │ │ └── preprocessor.spec.ts.snap │ │ │ ├── style-engine │ │ │ │ ├── shared.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── style-engine-parser.spec.ts.snap │ │ │ │ │ └── style-engine-parser.spec.ts │ │ │ │ ├── parser.ts │ │ │ │ └── parser-test.ts │ │ │ ├── line-util.ts │ │ │ ├── event-recognizer.ts │ │ │ ├── dagre-wrapper.ts │ │ │ └── font-config.ts │ │ ├── vendor.d.ts │ │ ├── gantt │ │ │ ├── type.ts │ │ │ ├── parser.ts │ │ │ ├── index.ts │ │ │ └── __tests__ │ │ │ │ ├── gantt-artist.spec.js │ │ │ │ └── gantt-config.spec.js │ │ ├── dot │ │ │ ├── artist │ │ │ │ ├── const.ts │ │ │ │ ├── style-context.ts │ │ │ │ └── draw-node.ts │ │ │ ├── parser.ts │ │ │ ├── index.ts │ │ │ └── __tests__ │ │ │ │ ├── dot-config.spec.js │ │ │ │ ├── style-context.spec.js │ │ │ │ └── dot-artist.spec.js │ │ ├── component │ │ │ ├── parser.ts │ │ │ ├── index.ts │ │ │ └── __tests__ │ │ │ │ ├── component-artist.spec.js │ │ │ │ └── component-config.spec.js │ │ ├── er │ │ │ ├── parser.ts │ │ │ ├── index.ts │ │ │ ├── event-recognizer.ts │ │ │ └── __tests__ │ │ │ │ ├── er-config.spec.js │ │ │ │ └── er-artist.spec.ts │ │ ├── class │ │ │ ├── parser.ts │ │ │ └── index.ts │ │ ├── activity │ │ │ ├── parser.ts │ │ │ ├── artist-util.ts │ │ │ ├── index.ts │ │ │ └── __tests__ │ │ │ │ └── activity-config.spec.js │ │ ├── sequence │ │ │ ├── parser.ts │ │ │ ├── artist-util.ts │ │ │ ├── index.ts │ │ │ ├── event-recognizer.ts │ │ │ └── artist │ │ │ │ └── type.ts │ │ ├── mindmap │ │ │ ├── parser.ts │ │ │ ├── index.ts │ │ │ └── __tests__ │ │ │ │ ├── mindmap-artist.spec.ts │ │ │ │ └── mindmap-config.spec.js │ │ ├── __tests__ │ │ │ └── parser-special-char.spec.ts │ │ ├── type.ts │ │ └── index.ts │ ├── shared-grammars │ │ ├── comment.ne │ │ ├── whitespace.ne │ │ ├── config.d.ts │ │ ├── bind.ne │ │ └── style.ne │ ├── jest.config.js │ ├── tsconfig.json │ ├── LICENSE.txt │ └── scripts │ │ └── build-grammar.js ├── development-kit │ ├── typings │ │ └── index.d.ts │ ├── src │ │ ├── index.ts │ │ └── compie-grammar.ts │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── package.json ├── pintora-cli │ ├── bin │ │ └── pintora │ ├── src │ │ ├── ambient.d.ts │ │ ├── sameprocess-render.ts │ │ ├── const.ts │ │ ├── index.ts │ │ ├── __tests__ │ │ │ └── render-cases.spec.ts │ │ ├── render.ts │ │ ├── type.ts │ │ └── subprocess-render │ │ │ ├── render-in-subprocess.ts │ │ │ └── index.ts │ ├── jest.config.js │ ├── examples │ │ ├── tsconfig.json │ │ └── nodejs-render-example.ts │ ├── tsconfig.json │ ├── LICENSE.txt │ └── package.json ├── pintora-target-wintercg │ ├── aliases │ │ ├── canvas.js │ │ └── url.js │ ├── .gitignore │ ├── fonts │ │ └── SourceCodePro-Medium.ttf │ ├── build │ │ ├── tsconfig.json │ │ └── ESBuildPintoraRuntimePlugin.ts │ ├── runtime │ │ ├── tsconfig.json │ │ ├── index.ts │ │ └── platforms │ │ │ └── edge-handler.js │ ├── deno.json │ ├── README.md │ ├── examples │ │ └── deno-example.ts │ ├── tsconfig.json │ ├── types │ │ └── index.d.ts │ ├── scripts │ │ ├── snippets │ │ │ └── edge-server-handler.ts │ │ ├── edge-runtime-server.ts │ │ ├── node-pintora.ts │ │ └── edge-runtime-pintora.ts │ └── package.json ├── pintora-core │ ├── src │ │ ├── consts.ts │ │ ├── types │ │ │ └── helper.ts │ │ ├── __tests__ │ │ │ ├── theme.spec.ts │ │ │ └── config.spec.js │ │ ├── util │ │ │ ├── artist.ts │ │ │ ├── index.ts │ │ │ ├── encode.ts │ │ │ ├── color.ts │ │ │ ├── matrix.ts │ │ │ └── mark.ts │ │ ├── themes │ │ │ ├── theme-default.ts │ │ │ ├── index.ts │ │ │ ├── theme-lark-light.ts │ │ │ ├── theme-dark.ts │ │ │ ├── theme-lark-dark.ts │ │ │ ├── base.ts │ │ │ └── palette.ts │ │ └── diagram-registry.ts │ ├── tsconfig.json │ ├── jest.config.js │ ├── package.json │ └── LICENSE.txt ├── test-shared │ ├── src │ │ ├── index.ts │ │ ├── type.ts │ │ └── util.ts │ ├── README.md │ ├── tsconfig.json │ ├── example-files │ │ ├── er-1.pintora │ │ ├── mindmap-1.pintora │ │ ├── crlf.pintora │ │ ├── sequence-1.pintora │ │ ├── gantt-1.pintora │ │ ├── activity-1.pintora │ │ ├── component-1.pintora │ │ ├── wintercg-component.pintora │ │ └── dot-1.pintora │ └── package.json └── pintora-renderer │ ├── src │ ├── type.ts │ ├── renderers │ │ ├── CanvasRenderer.ts │ │ ├── index.ts │ │ └── SvgRenderer.ts │ ├── util.ts │ ├── index.ts │ └── event.ts │ ├── tsconfig.json │ ├── package.json │ └── LICENSE.txt ├── demo ├── src │ ├── live-editor │ │ ├── containers │ │ │ ├── Examples │ │ │ │ ├── Examples.less │ │ │ │ └── index.tsx │ │ │ ├── AppSidebar │ │ │ │ ├── AppSidebar.less │ │ │ │ └── index.tsx │ │ │ ├── Preview │ │ │ │ └── Preview.less │ │ │ ├── Editor │ │ │ │ ├── Editor.less │ │ │ │ └── index.tsx │ │ │ ├── ThemePreviewSpace │ │ │ │ └── ThemePreviewSpace.less │ │ │ └── ConfigEditor │ │ │ │ └── index.tsx │ │ ├── env.d.ts │ │ ├── type.ts │ │ ├── components │ │ │ ├── Buttons │ │ │ │ ├── Buttons.less │ │ │ │ └── index.tsx │ │ │ ├── Panel │ │ │ │ ├── Panel.less │ │ │ │ └── index.tsx │ │ │ ├── PanelHeader │ │ │ │ ├── PanelHeader.less │ │ │ │ └── index.tsx │ │ │ ├── CodeMirrorEditor │ │ │ │ ├── CMEditor.less │ │ │ │ └── utils.ts │ │ │ ├── Header.less │ │ │ └── Header.tsx │ │ ├── pwa.ts │ │ ├── App.css │ │ ├── redux │ │ │ ├── store.ts │ │ │ └── themeSlice.ts │ │ └── main.tsx │ ├── env.ts │ ├── utils │ │ ├── misc.ts │ │ └── errors.ts │ ├── pages │ │ ├── demo │ │ │ ├── components │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ │ ├── App.tsx │ │ │ ├── App.less │ │ │ ├── main.tsx │ │ │ └── index.css │ │ └── preview │ │ │ └── index.less │ ├── styles │ │ └── base.css │ ├── sw.ts │ ├── globals.d.ts │ ├── components │ │ └── PintoraPreview │ │ │ └── PintoraPreview.less │ └── const.ts ├── postcss.config.js ├── entries │ ├── demo │ │ └── index.html │ └── highlight │ │ └── index.html ├── cypress.config.ts ├── tailwind.config.js ├── preview │ └── index.html ├── cypress │ ├── support │ │ ├── e2e.js │ │ └── commands.js │ ├── e2e │ │ ├── class │ │ │ └── class.spec.ts │ │ ├── sequence │ │ │ └── sequence.spec.ts │ │ ├── test-utils │ │ │ └── render.ts │ │ ├── component │ │ │ └── component.spec.ts │ │ └── dot │ │ │ └── dot.spec.ts │ └── plugins │ │ └── index.js ├── live-editor │ └── index.html ├── public │ └── img │ │ └── logo.svg └── tsconfig.json ├── pnpm-workspace.yaml ├── .husky └── pre-commit ├── .github └── FUNDING.yml ├── .prettierignore ├── .editorconfig ├── .npmrc ├── .prettierrc.yml ├── renovate.json ├── codecov.yml ├── scripts ├── build-site.sh └── watch-for-browser.sh ├── .changeset └── config.json ├── jest.config.js ├── tsconfig-build.json ├── jest.config.base.js ├── tsconfig.json ├── eslint.config.mjs ├── turbo.json ├── LICENSE.txt ├── .gitignore └── .vscode └── launch.json /.node-version: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/pintora-standalone/.gitignore: -------------------------------------------------------------------------------- 1 | types/ 2 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/Examples/Examples.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/src/env.ts: -------------------------------------------------------------------------------- 1 | export const isProd = import.meta.env.PROD 2 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | *.js.map 3 | -------------------------------------------------------------------------------- /demo/src/live-editor/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'logrocket-react' 2 | -------------------------------------------------------------------------------- /demo/src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | export { debounce } from 'throttle-debounce' 2 | -------------------------------------------------------------------------------- /packages/development-kit/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'shell-exec' 2 | -------------------------------------------------------------------------------- /packages/pintora-cli/bin/pintora: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli') -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/aliases/canvas.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/.gitignore: -------------------------------------------------------------------------------- 1 | build-meta.json 2 | dist 3 | out-scripts 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' 3 | - "demo" 4 | - "website" 5 | -------------------------------------------------------------------------------- /website/src/const.ts: -------------------------------------------------------------------------------- 1 | export const PINTORA_LIVE_EDITOR_URL = '/demo/live-editor/' 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /packages/development-kit/src/index.ts: -------------------------------------------------------------------------------- 1 | export { compileGrammar } from './compie-grammar' 2 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jsdom/lib/jsdom/living/generated/utils' 2 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/time.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | 3 | export { dayjs } 4 | -------------------------------------------------------------------------------- /website/docs/advanced/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Advanced", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: pintora 4 | -------------------------------------------------------------------------------- /website/docs/diagrams/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Diagram Syntax", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | *.tsbuildinfo 3 | 4 | package-lock.json 5 | 6 | packages/**/*/parser/*.[tj]s 7 | -------------------------------------------------------------------------------- /packages/pintora-core/src/consts.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_FONT_FAMILY = 'Source Code Pro, sans-serif' 2 | -------------------------------------------------------------------------------- /website/docs/configuration/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Configuration", 3 | "position": 4 4 | } 5 | -------------------------------------------------------------------------------- /website/static/img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hikerpig/pintora/HEAD/website/static/img/logo.jpg -------------------------------------------------------------------------------- /demo/src/pages/demo/components/index.css: -------------------------------------------------------------------------------- 1 | .figure-container svg { 2 | background-color: whitesmoke; 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/getting-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting Started", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/vendor.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@hikerpig/nearley' { 2 | export * from 'nearley' 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_size = 2 3 | indent_style = space 4 | end_of_line = lf 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /demo/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@docusaurus/tsconfig/tsconfig.json", 3 | "include": ["src/", "plugins/"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/test-shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export { EXAMPLES } from './data/examples' 2 | export { stripStartEmptyLines } from './util' 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | legacy-peer-deps=true 3 | strict-peer-dependencies=false 4 | resolution-mode=highest 5 | -------------------------------------------------------------------------------- /packages/test-shared/src/type.ts: -------------------------------------------------------------------------------- 1 | export type DiagramExample = { 2 | name: string 3 | description: string 4 | code: string 5 | } 6 | -------------------------------------------------------------------------------- /website/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Advanced", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /demo/src/live-editor/type.ts: -------------------------------------------------------------------------------- 1 | export type ErrorInfo = { 2 | line: number 3 | col: number 4 | offset: number 5 | message: string 6 | } 7 | -------------------------------------------------------------------------------- /packages/pintora-cli/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../jest.config.base') 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | } 6 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/shared-grammars/comment.ne: -------------------------------------------------------------------------------- 1 | @{% 2 | export const COMMENT_LINE = /%%.*/ 3 | %} 4 | 5 | comment -> %COMMENT_LINE {% (d) => null %} 6 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | trailingComma: all 2 | tabWidth: 2 3 | semi: false 4 | singleQuote: true 5 | bracketSpacing: true 6 | printWidth: 120 7 | arrowParens: avoid 8 | -------------------------------------------------------------------------------- /packages/pintora-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", ":preserveSemverRanges"], 3 | "rangeStrategy": "replace", 4 | "constraints": { 5 | "npm": "^7.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/src/utils/errors.ts: -------------------------------------------------------------------------------- 1 | export class AbortError extends DOMException { 2 | constructor(message = 'Request Aborted') { 3 | super(message, 'AbortError') 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/theme.ts: -------------------------------------------------------------------------------- 1 | import { themeRegistry } from '@pintora/core' 2 | 3 | // ayu light 4 | export const PALETTE = themeRegistry.palettes.AYU_LIGHT 5 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/fonts/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hikerpig/pintora/HEAD/packages/pintora-target-wintercg/fonts/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /packages/pintora-standalone/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node"] 5 | }, 6 | "include": ["."] 7 | } 8 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/aliases/url.js: -------------------------------------------------------------------------------- 1 | const URL = require('url') 2 | 3 | module.exports = { 4 | ...URL, 5 | fileURLToPath(s) { 6 | return s || '' 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | github_checks: 2 | annotations: false 3 | 4 | coverage: 5 | status: 6 | project: 7 | default: 8 | target: 95% 9 | threshold: 5% 10 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/Buttons/Buttons.less: -------------------------------------------------------------------------------- 1 | .Buttons { 2 | .btn { 3 | text-transform: initial; 4 | margin-right: 0.5em; 5 | margin-bottom: 0.5em; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/src/styles/base.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --demo-header-height: 52px; 7 | --demo-sidebar-width: 50px; 8 | } 9 | -------------------------------------------------------------------------------- /scripts/build-site.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | pnpm exec turbo run build --filter='pintora-demo' 4 | pushd website && pnpm run build && popd 5 | cp -r demo/dist/ website/build/demo 6 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/gantt/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Date format string, e.g. 'YYYY-MM-DD' 3 | * See https://dayjs.gitee.io/docs/en/display/format 4 | */ 5 | export type DateFormat = string 6 | -------------------------------------------------------------------------------- /packages/pintora-renderer/src/type.ts: -------------------------------------------------------------------------------- 1 | import { IRenderer, GrahpicEventHandler } from '@pintora/core' 2 | 3 | export type EventHandler = GrahpicEventHandler 4 | 5 | export type { IRenderer } 6 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/AppSidebar/AppSidebar.less: -------------------------------------------------------------------------------- 1 | .AppSidebar { 2 | width: var(--demo-sidebar-width); 3 | flex-shrink: 0; 4 | } 5 | 6 | .AppSidebar__item { 7 | font-size: 1.4em; 8 | } 9 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/number.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Trim number to a given precision 3 | */ 4 | export function toFixed(num: number, digits = 2) { 5 | return parseFloat(num.toFixed(digits)) 6 | } 7 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/Preview/Preview.less: -------------------------------------------------------------------------------- 1 | .Preview { 2 | } 3 | 4 | .Preview__toolbar { 5 | border-bottom: solid 1px #aaa; 6 | 7 | label { 8 | margin-right: 0.5em; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/pintora-core/src/types/helper.ts: -------------------------------------------------------------------------------- 1 | export type OrNull = T | null 2 | 3 | export type Maybe = T | undefined 4 | 5 | export type DeepPartial = { 6 | [P in keyof T]?: DeepPartial 7 | } 8 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/build/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "types": ["node"] 6 | }, 7 | "include": ["."] 8 | } 9 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/artist/const.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_LINE_WIDTH = 1 2 | 3 | export const BOLD_LINE_WIDTH = 2 4 | 5 | export const DASHED_LINE_DASH = [5, 5] 6 | 7 | export const DOTTED_LINE_DASH = [2, 4] 8 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/symbols/index.ts: -------------------------------------------------------------------------------- 1 | import './database' 2 | import './package' 3 | import './node' 4 | import './cloud' 5 | import './actor' 6 | import './ellipse' 7 | import './circle' 8 | import './diamond' 9 | -------------------------------------------------------------------------------- /packages/pintora-standalone/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../jest.config.base') 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | testEnvironment: 'jsdom', 6 | testPathIgnorePatterns: ['types/'], 7 | } 8 | -------------------------------------------------------------------------------- /packages/test-shared/README.md: -------------------------------------------------------------------------------- 1 | # `@pintora/test-shared` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const testShared = require('@pintora/test-shared'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /demo/src/pages/preview/index.less: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | } 5 | 6 | .PintoraPreview { 7 | padding: 0; 8 | } 9 | 10 | .PintoraPreview__figure-container:hover { 11 | svg { 12 | box-shadow: none; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/development-kit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "rootDir": "src", 6 | "outDir": "lib" 7 | }, 8 | "references": [ 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "strict": false, 7 | "checkJs": false, 8 | "allowJs": true 9 | } 10 | } -------------------------------------------------------------------------------- /demo/src/live-editor/containers/Editor/Editor.less: -------------------------------------------------------------------------------- 1 | .Editor { 2 | // min-width: 40vw; 3 | height: 70vh; 4 | display: flex; 5 | flex-direction: column; 6 | overflow-y: hidden; 7 | 8 | .CMEditor { 9 | height: 100%; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/shared-grammars/whitespace.ne: -------------------------------------------------------------------------------- 1 | # Whitespace: `_` is optional, `__` is mandatory. 2 | _ -> wschar:* {% function(d) {return null;} %} 3 | __ -> wschar:+ {% function(d) {return null;} %} 4 | 5 | wschar -> [ \t\n\v\f\r] {% id %} 6 | -------------------------------------------------------------------------------- /packages/pintora-renderer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib" 6 | }, 7 | "references": [ 8 | { "path": "../pintora-core" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/pintora-renderer/src/renderers/CanvasRenderer.ts: -------------------------------------------------------------------------------- 1 | import { BaseRenderer } from './base' 2 | import { Canvas } from '@antv/g-canvas' 3 | 4 | export class CanvasRenderer extends BaseRenderer { 5 | getCanvasClass() { 6 | return Canvas 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/er-1.pintora: -------------------------------------------------------------------------------- 1 | erDiagram 2 | CUSTOMER ||--o{ ORDER : places 3 | ORDER ||--|{ LINE-ITEM : contains 4 | CUSTOMER }|..|{ DELIVERY-ADDRESS : uses 5 | ORDER { 6 | int orderNumber PK 7 | string deliveryAddress 8 | } 9 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/mindmap-1.pintora: -------------------------------------------------------------------------------- 1 | mindmap 2 | + UML Diagrams 3 | ++ Behavior Diagrams 4 | +++ Sequence Diagram 5 | +++ State Diagram 6 | +++ Activity Diagram 7 | -- Structural Diagrams 8 | --- Class Diagram 9 | --- Component Diagram 10 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/sameprocess-render.ts: -------------------------------------------------------------------------------- 1 | import type { CLIRenderOptions } from './render-impl' 2 | import { render as renderImpl } from './render-impl' 3 | 4 | export function renderInCurrentProcess(opts: CLIRenderOptions) { 5 | return renderImpl(opts) 6 | } 7 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/component/parser.ts: -------------------------------------------------------------------------------- 1 | import db from './db' 2 | import grammar, { setYY } from './parser/componentDiagram' 3 | import { genParserWithRules } from '../util/parser-util' 4 | 5 | setYY(db) 6 | 7 | export const parse = genParserWithRules(grammar) 8 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/Panel/Panel.less: -------------------------------------------------------------------------------- 1 | .Panel { 2 | display: flex; 3 | flex-direction: column; 4 | flex-grow: 1; 5 | margin-bottom: 1em; 6 | border-width: 1px; 7 | border-style: solid; 8 | } 9 | 10 | .Panel__content { 11 | height: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /packages/pintora-cli/examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "module": "commonjs" 7 | }, 8 | "references": [ 9 | { "path": ".." }, 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/runtime/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "module": "ESNext", 6 | "target": "ESNext", 7 | "types": ["node"] 8 | }, 9 | "include": ["."] 10 | } 11 | -------------------------------------------------------------------------------- /demo/src/pages/demo/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './App.less' 3 | import Content from './components' 4 | 5 | function App() { 6 | return ( 7 |
8 | 9 |
10 | ) 11 | } 12 | 13 | export default App 14 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/ThemePreviewSpace/ThemePreviewSpace.less: -------------------------------------------------------------------------------- 1 | .ThemePreviewSpace { 2 | .Panel__content { 3 | overflow: scroll; 4 | } 5 | } 6 | 7 | .ThemePreviewSpace__item-title { 8 | margin-top: 0.5em; 9 | margin-bottom: 0.5em; 10 | padding: 0 0.5em; 11 | } 12 | -------------------------------------------------------------------------------- /packages/pintora-core/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../jest.config.base') 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | // testEnvironment: 'jsdom', 6 | testMatch: ['**/(*.)+(spec|test).[jt]s?(x)'], 7 | transformIgnorePatterns: ['/node_modules/(?!(d3-*))'], 8 | } 9 | -------------------------------------------------------------------------------- /packages/test-shared/src/util.ts: -------------------------------------------------------------------------------- 1 | export function stripStartEmptyLines(input: string) { 2 | const lines = input.split('\n') 3 | return lines 4 | .reduce((acc: string[], line) => { 5 | if (line) acc.push(line) 6 | return acc 7 | }, []) 8 | .join('\n') 9 | } 10 | -------------------------------------------------------------------------------- /scripts/watch-for-browser.sh: -------------------------------------------------------------------------------- 1 | pnpm exec turbo run watch --concurrency=10 \ 2 | --no-cache --continue --parallel \ 3 | --filter="@pintora/renderer" \ 4 | --filter="@pintora/diagrams" \ 5 | --filter="@pintora/core" \ 6 | --filter="@pintora/test-shared" \ 7 | --filter="@pintora/standalone" 8 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | .features { 4 | display: flex; 5 | align-items: center; 6 | padding: 2rem 0; 7 | width: 100%; 8 | } 9 | 10 | .featureSvg { 11 | height: 200px; 12 | width: 200px; 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/PanelHeader/PanelHeader.less: -------------------------------------------------------------------------------- 1 | .PanelHeader { 2 | @padding-x: 0.6em; 3 | @padding-y: 0.3em; 4 | 5 | padding: @padding-y @padding-x @padding-y @padding-x; 6 | 7 | background-color: #f5c326; 8 | } 9 | 10 | .PanelHeader__title { 11 | font-weight: bold; 12 | } 13 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/const.ts: -------------------------------------------------------------------------------- 1 | export const SVG_MIME_TYPE = 'image/svg+xml' 2 | 3 | export const PNG_MIME_TYPE = 'image/png' 4 | 5 | export const SUPPORTED_MIME_TYPES = [PNG_MIME_TYPE, 'image/jpeg', SVG_MIME_TYPE] 6 | 7 | export const DEFAUT_BGS = { 8 | light: '#FFFFFF', 9 | dark: '#282A36', 10 | } 11 | -------------------------------------------------------------------------------- /demo/src/pages/demo/App.less: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | min-height: 100vh; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | font-size: calc(10px + 2vmin); 9 | color: white; 10 | } 11 | 12 | .App-link { 13 | color: #61dafb; 14 | } 15 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json", 3 | "changelog": "@changesets/changelog-git", 4 | "commit": false, 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "master", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/crlf.pintora: -------------------------------------------------------------------------------- 1 | erDiagram 2 | %% should support CRLR as eol 3 | @param edgeType ortho 4 | CUSTOMER ||--o{ ORDER : places 5 | ORDER ||--|{ LINE-ITEM : contains 6 | CUSTOMER }|..|{ DELIVERY-ADDRESS : uses 7 | ORDER { 8 | int orderNumber PK 9 | string deliveryAddress 10 | } 11 | -------------------------------------------------------------------------------- /demo/src/sw.ts: -------------------------------------------------------------------------------- 1 | // sw.js 2 | import { precacheAndRoute } from 'workbox-precaching' 3 | import { clientsClaim, skipWaiting } from 'workbox-core' 4 | 5 | declare let self: ServiceWorkerGlobalScope 6 | 7 | // self.__WB_MANIFEST is default injection point 8 | precacheAndRoute(self.__WB_MANIFEST) 9 | 10 | skipWaiting() 11 | clientsClaim() 12 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/env.ts: -------------------------------------------------------------------------------- 1 | export const isDev = globalThis.isPintoraDev ?? false 2 | 3 | export const pintoraDevGlobals = {} 4 | 5 | export function setDevGlobal(key: string, value: unknown) { 6 | pintoraDevGlobals[key] = value 7 | if (isDev) { 8 | globalThis.pintoraDevGlobals = pintoraDevGlobals 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /demo/src/live-editor/pwa.ts: -------------------------------------------------------------------------------- 1 | import { registerSW } from 'virtual:pwa-register' 2 | 3 | const updateSW = registerSW({ 4 | onNeedRefresh() { 5 | console.log('[pwa] onNeedRefresh') 6 | }, 7 | onOfflineReady() { 8 | // show a ready to work offline to user 9 | console.log('[pwa] onOfflineReady') 10 | }, 11 | }) 12 | 13 | updateSW() 14 | -------------------------------------------------------------------------------- /packages/pintora-core/src/__tests__/theme.spec.ts: -------------------------------------------------------------------------------- 1 | import { themeRegistry } from '../themes' 2 | 3 | describe('pintora core themeRegistry', () => { 4 | it('registerTheme', () => { 5 | const duskTheme = {} 6 | themeRegistry.registerTheme('dusk', duskTheme as any) 7 | 8 | expect(themeRegistry.themes['dusk']).toBe(duskTheme) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/Buttons/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from 'react' 2 | import './Buttons.less' 3 | 4 | interface ButtonsProps {} 5 | 6 | const Buttons: React.FC> = ({ children }) => { 7 | return
{children}
8 | } 9 | 10 | export default Buttons 11 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/parser.ts: -------------------------------------------------------------------------------- 1 | import db from './db' 2 | import grammar from './parser/dotDiagram' 3 | import { genParserWithRules } from '../util/parser-util' 4 | 5 | export const parse = genParserWithRules(grammar, { 6 | dedupeAmbigousResults: true, 7 | postProcess(results) { 8 | db.apply(results) 9 | return results 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/er/parser.ts: -------------------------------------------------------------------------------- 1 | import { genParserWithRules } from '../util/parser-util' 2 | import db from './db' 3 | import grammar from './parser/erDiagram' 4 | 5 | export const parse = genParserWithRules(grammar, { 6 | dedupeAmbigousResults: true, 7 | postProcess(results) { 8 | db.apply(results) 9 | return results 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /demo/src/live-editor/App.css: -------------------------------------------------------------------------------- 1 | .App__content { 2 | height: calc(100vh - var(--demo-header-height)); 3 | } 4 | 5 | .App__left { 6 | min-width: 40vw; 7 | width: 40vw; 8 | height: 100%; 9 | } 10 | 11 | .App__right { 12 | max-width: calc(60vw - 85px); 13 | flex-grow: 1; 14 | margin-left: 20px; 15 | } 16 | 17 | .App.dark-mode { 18 | --bc: 100% 0 0; 19 | } 20 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/class/parser.ts: -------------------------------------------------------------------------------- 1 | import db from './db' 2 | import grammar from './parser/classDiagram' 3 | import { genParserWithRules } from '../util/parser-util' 4 | 5 | export const parse = genParserWithRules(grammar, { 6 | dedupeAmbigousResults: true, 7 | postProcess(results) { 8 | db.apply(results) 9 | return results 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/gantt/parser.ts: -------------------------------------------------------------------------------- 1 | import grammar from './parser/gantt' 2 | import { genParserWithRules } from '../util/parser-util' 3 | import ganttDb from './db' 4 | 5 | export const parse = genParserWithRules(grammar, { 6 | dedupeAmbigousResults: true, 7 | postProcess(results) { 8 | ganttDb.apply(results) 9 | return results 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /packages/pintora-renderer/src/util.ts: -------------------------------------------------------------------------------- 1 | export class Stack { 2 | protected list: T[] = [] 3 | top() { 4 | return this.list[this.list.length - 1] 5 | } 6 | push(v: T) { 7 | this.list.push(v) 8 | } 9 | pop() { 10 | return this.list.pop() 11 | } 12 | clear() { 13 | this.list = [] 14 | } 15 | } 16 | 17 | export const noop = () => undefined 18 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/activity/parser.ts: -------------------------------------------------------------------------------- 1 | import { db } from './db' 2 | import grammar from './parser/activityDiagram' 3 | import { genParserWithRules } from '../util/parser-util' 4 | 5 | export const parse = genParserWithRules(grammar, { 6 | dedupeAmbigousResults: true, 7 | postProcess(results) { 8 | db.apply(results) 9 | return results 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /demo/entries/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pintora Demo 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/src/live-editor/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import slice from './slice' 3 | import theme from './themeSlice' 4 | 5 | const store = configureStore({ 6 | reducer: { 7 | main: slice.reducer, 8 | theme: theme.reducer, 9 | }, 10 | }) 11 | 12 | export type StoreState = ReturnType 13 | 14 | export default store 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reporters: ['default', ['jest-junit', { outputDirectory: './reports' }]], 3 | projects: [ 4 | '/packages/pintora-core/jest.config.js', 5 | '/packages/pintora-diagrams/jest.config.js', 6 | '/packages/pintora-cli/jest.config.js', 7 | '/packages/pintora-standalone/jest.config.js', 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /demo/src/pages/demo/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './index.css' 3 | import App from './App' 4 | import { createRoot } from 'react-dom/client' 5 | 6 | const container = document.getElementById('root') 7 | if (container) { 8 | const root = createRoot(container) 9 | root.render( 10 | 11 | 12 | , 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # live editor and others 8 | static/demo 9 | 10 | # Generated files 11 | .docusaurus 12 | .cache-loader 13 | 14 | # Misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /website/plugins/site-ad/client-module.js: -------------------------------------------------------------------------------- 1 | export default (function adModule() { 2 | return { 3 | onRouteUpdate() { 4 | if (typeof _carbonads !== 'undefined') { 5 | const ele = document.getElementById('carbonads') 6 | if (ele) { 7 | ele.parentElement.removeChild(ele) 8 | _carbonads.refresh() 9 | } 10 | } 11 | }, 12 | } 13 | })() 14 | -------------------------------------------------------------------------------- /packages/pintora-core/src/util/artist.ts: -------------------------------------------------------------------------------- 1 | import { IDiagramArtist } from '../type' 2 | import { GraphicsIR } from '../types/graphics' 3 | 4 | export function makeArtist = IDiagramArtist>(opts: { 5 | draw: (this: A, ...args: Parameters) => GraphicsIR 6 | }) { 7 | const artist = opts 8 | return artist as IDiagramArtist 9 | } 10 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/ir.ts: -------------------------------------------------------------------------------- 1 | import { PintoraConfig } from '@pintora/core' 2 | import { ConfigParam } from './config' 3 | import type { BindRule, StyleRule } from './style-engine/shared' 4 | 5 | export type BaseDiagramIR = { 6 | title?: string 7 | configParams: ConfigParam[] 8 | overrideConfig: Partial 9 | styleRules?: StyleRule[] 10 | bindRules?: BindRule[] 11 | } 12 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pintora/target-wintercg", 3 | "version": "0.1.4", 4 | "exports": "./dist/runtime.esm.js", 5 | "publish": { 6 | "include": ["dist/"], 7 | "exclude": ["!dist/"] 8 | }, 9 | "compilerOptions": { 10 | "checkJs": true, 11 | "allowJs": true, 12 | "types": [ 13 | "node", 14 | "svgdom" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/entries/highlight/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pintora Syntax Highlight Demo 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/sequence/parser.ts: -------------------------------------------------------------------------------- 1 | import db from './db' 2 | import grammar, { setYY } from './parser/sequenceDiagram' 3 | import { genParserWithRules } from '../util/parser-util' 4 | 5 | setYY(db) 6 | 7 | export const parse = genParserWithRules(grammar, { 8 | dedupeAmbigousResults: true, 9 | postProcess(results) { 10 | db.apply(results as any) 11 | return results 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/development-kit/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pintora/development-kit 2 | 3 | ## 0.1.2 4 | 5 | ### Patch Changes 6 | 7 | - 4d75fe8: upgrade typescript to 5.9 8 | 9 | ## 0.1.1 10 | 11 | ### Patch Changes 12 | 13 | - fc12ccb: optimize(development-kit): specify executeCommand rather than 'npx' 14 | 15 | ## 0.1.0 16 | 17 | ### Minor Changes 18 | 19 | - 79f4887: feat: add @pintora/development-kit package for grammar development 20 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/jest.config.js: -------------------------------------------------------------------------------- 1 | const { defaults } = require('jest-config') 2 | const baseConfig = require('../../jest.config.base') 3 | 4 | module.exports = { 5 | ...baseConfig, 6 | moduleFileExtensions: [...defaults.moduleFileExtensions, 'd.ts'], 7 | testEnvironment: 'jsdom', 8 | testMatch: ['**/(*.)+(spec|test).[jt]s?(x)'], 9 | transformIgnorePatterns: ['/node_modules/.pnpm/(?!(d3-*|internmap))'], 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "references": [ 7 | { "path": "./packages/pintora-core" }, 8 | { "path": "./packages/pintora-diagrams" }, 9 | { "path": "./packages/pintora-renderer" }, 10 | { "path": "./packages/pintora-standalone" }, 11 | { "path": "./packages/development-kit" } 12 | ], 13 | "include": [] 14 | } 15 | -------------------------------------------------------------------------------- /website/plugins/docusaurus-theme-pintora/theme/PintoraPlay/PintoraPlay.less: -------------------------------------------------------------------------------- 1 | .PintoraPlay__code-wrap { 2 | position: relative; 3 | 4 | pre { 5 | margin-bottom: 0; 6 | } 7 | } 8 | 9 | .PintoraPlay__float-button { 10 | position: absolute; 11 | top: 5px; 12 | right: 5px; 13 | } 14 | 15 | .PintoraPlay__preview { 16 | margin-top: 5px; 17 | overflow: hidden; // to hide svg that width exceeds container's size 18 | } 19 | -------------------------------------------------------------------------------- /packages/pintora-cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "module": "commonjs" 7 | }, 8 | "exclude": ["examples", "lib"], 9 | "references": [ 10 | { "path": "../pintora-core" }, 11 | { "path": "../pintora-renderer" }, 12 | { "path": "../pintora-standalone" }, 13 | { "path": "../test-shared" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "strict": false, 7 | "checkJs": false, 8 | "allowJs": true, 9 | "noImplicitThis": true 10 | }, 11 | "references": [ 12 | { "path": "../pintora-core" }, 13 | { "path": "../development-kit" } 14 | ], 15 | "include": [ 16 | "./src/**/*" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /demo/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | projectId: 'cc111n', 5 | e2e: { 6 | // We've imported your old cypress plugins here. 7 | // You may want to clean this up later by importing these. 8 | setupNodeEvents(on, config) { 9 | return require('./cypress/plugins/index.js')(on, config) 10 | }, 11 | specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/pintora-core/src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from './util' 2 | export * from './matrix' 3 | export * from './geometry' 4 | export { calculateTextDimensions, textMetrics } from './text-metric' 5 | export type { IFont, ITextMetricCalculator } from './text-metric' 6 | export { encodeForUrl, decodeCodeInUrl } from './encode' 7 | export { makeMark } from './mark' 8 | export { parseColor, tinycolor } from './color' 9 | export { makeArtist } from './artist' 10 | -------------------------------------------------------------------------------- /jest.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testPathIgnorePatterns: ['/node_modules/', '/lib/'], 3 | coveragePathIgnorePatterns: ['node_modules', '__tests__'], 4 | transformIgnorePatterns: ['/node_modules/.pnpm/(?!(d3-*|internmap))'], 5 | transform: { 6 | '\\.[jt]sx?$': [ 7 | 'esbuild-jest', 8 | { 9 | sourcemap: true, 10 | loaders: { 11 | '.spec.ts': 'tsx', 12 | }, 13 | }, 14 | ], 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/CodeMirrorEditor/CMEditor.less: -------------------------------------------------------------------------------- 1 | .CMEditor { 2 | display: flex; 3 | 4 | .cm-editor { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | } 9 | 10 | .cm-scroller { 11 | padding: 0.5em; 12 | } 13 | 14 | .cm-content { 15 | font-size: 16px; 16 | line-height: 1.5; 17 | font-family: Menlo, Consolas, monaco, 'Segoe UI'; 18 | color: #d8d8d8; 19 | } 20 | 21 | .cm-lineNumbers .cm-gutterElement { 22 | user-select: non; 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/live-editor/redux/themeSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | import { EXAMPLES } from '@pintora/test-shared' 3 | 4 | export type State = { 5 | examples: typeof EXAMPLES 6 | } 7 | 8 | const initialState: State = { 9 | examples: EXAMPLES, 10 | } 11 | 12 | const slice = createSlice({ 13 | name: 'theme', 14 | initialState, 15 | reducers: {}, 16 | }) 17 | 18 | export const actions = slice.actions 19 | 20 | export default slice 21 | -------------------------------------------------------------------------------- /demo/src/pages/demo/index.css: -------------------------------------------------------------------------------- 1 | @import '../../styles/base.css'; 2 | 3 | body { 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 6 | 'Droid Sans', 'Helvetica Neue', sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/pintora-core/src/util/encode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encode string to share in url. 3 | * Used in docs. 4 | */ 5 | export function encodeForUrl(code: string) { 6 | const encoded = encodeURIComponent(btoa(escape(code))) 7 | return encoded 8 | } 9 | 10 | /** 11 | * Decode the code part in url. 12 | * Used in live-editor and other demo pages. 13 | */ 14 | export function decodeCodeInUrl(input: string) { 15 | return unescape(atob(decodeURIComponent(input))) 16 | } 17 | -------------------------------------------------------------------------------- /demo/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | import type { pintoraStandalone } from '@pintora/standalone' 2 | import { DOMAttributes } from 'react' 3 | 4 | declare global { 5 | interface Window { 6 | pintora: typeof pintoraStandalone 7 | } 8 | } 9 | 10 | type CustomElement = Partial & { children: any }> 11 | 12 | declare global { 13 | namespace JSX { 14 | interface IntrinsicElements { 15 | ['iconify-icon']: CustomElement 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/pintora-core/src/util/color.ts: -------------------------------------------------------------------------------- 1 | import tinycolor from 'tinycolor2' 2 | 3 | const HEX_PATTERN = /^#(?:[0-9a-fA-F]{3}){1,2}$/ 4 | 5 | export function parseColor(input: string) { 6 | let isValid = false 7 | let color = input 8 | if (HEX_PATTERN.test(input)) { 9 | color = input 10 | isValid = true 11 | } else { 12 | color = input.replace('#', '') 13 | } 14 | return { 15 | color, 16 | isValid, 17 | } 18 | } 19 | 20 | export { tinycolor } 21 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/index.ts: -------------------------------------------------------------------------------- 1 | import { pintoraStandalone } from '@pintora/standalone' 2 | 3 | export { renderInCurrentProcess } from './sameprocess-render' 4 | export { renderInSubprocess } from './subprocess-render' 5 | export { render } from './render' 6 | 7 | export { pintoraStandalone } 8 | 9 | export type { PintoraConfig } from '@pintora/standalone' 10 | 11 | export const setConfig = pintoraStandalone.setConfig 12 | export const getConfig = pintoraStandalone.getConfig 13 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/sequence-1.pintora: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | User->>Pintora: render this 4 | activate Pintora 5 | loop Check input 6 | Pintora-->>Pintora: Has input changed? 7 | end 8 | Pintora-->>User: your figure here 9 | deactivate Pintora 10 | @note over User,Pintora: note over 11 | @note right of User: note aside actor 12 | @start_note right of User 13 | multiline note 14 | is possible 15 | @end_note 16 | == Divider == 17 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/mindmap/parser.ts: -------------------------------------------------------------------------------- 1 | import { genParserWithRules } from '../util/parser-util' 2 | import { ParserWithPreprocessor } from '../util/preproccesor' 3 | import db from './db' 4 | import grammar from './parser/mindmap' 5 | 6 | export const parse = genParserWithRules(grammar, { 7 | postProcess(results) { 8 | db.apply(results as any) 9 | return results 10 | }, 11 | }) 12 | 13 | export const parser = new ParserWithPreprocessor({ 14 | db, 15 | parse, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/mindmap/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import db, { MindmapIR } from './db' 3 | import artist from './artist' 4 | import { parser } from './parser' 5 | import { configKey, MindmapConf } from './config' 6 | 7 | export type { MindmapIR, MindmapConf } 8 | 9 | export const mindmap: IDiagram = { 10 | pattern: /^\s*mindmap/, 11 | parser, 12 | artist, 13 | configKey, 14 | clear() { 15 | db.clear() 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /website/plugins/webpack5-plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = function (context, options) { 2 | return { 3 | name: 'docusaurus-webpack5-plugin', 4 | // eslint-disable-next-line 5 | configureWebpack(config, isServer, utils) { 6 | return { 7 | resolve: { 8 | alias: { 9 | path: require.resolve('path-browserify'), 10 | }, 11 | fallback: { 12 | fs: false, 13 | }, 14 | }, 15 | }; 16 | }, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2019", 5 | "module": "es6", 6 | "declaration": true, 7 | "sourceMap": true, 8 | "composite": true, 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "baseUrl": "./packages", 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "allowJs": false, 15 | "checkJs": false, 16 | "isolatedModules": true, 17 | "forceConsistentCasingInFileNames": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/text.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_FONT_FAMILY, calculateTextDimensions, IFont } from '@pintora/core' 2 | import dedent from 'dedent' 3 | import { toFixed } from './number' 4 | 5 | export { dedent, DEFAULT_FONT_FAMILY } 6 | 7 | export function getTextDimensionsInPresicion(text: string, fontConfig?: IFont, precision = 2) { 8 | const { width, height } = calculateTextDimensions(text, fontConfig) 9 | return { 10 | width: toFixed(width, precision), 11 | height: toFixed(height, precision), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/README.md: -------------------------------------------------------------------------------- 1 | Stil at a very early stage. 2 | 3 | Bundle a big JS including pintora and its dependencies and some Node.js module polyfills, so that it can run inside a WinterCG or other lightweight JS runtime. 4 | 5 | 6 | ```text 7 | . 8 | ├── aliases // JS polyfills 9 | ├── build // build scripts 10 | ├── dist // output 11 | ├── fonts 12 | ├── package.json 13 | ├── runtime // runtime source 14 | ├── scripts // some local manual test scripts 15 | └── types 16 | 17 | 10 directories, 5 files 18 | 19 | ``` 20 | -------------------------------------------------------------------------------- /website/i18n/zh-CN/docusaurus-theme-classic/navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "message": "Pintora", 4 | "description": "The title in the navbar" 5 | }, 6 | "item.label.Tutorial": { 7 | "message": "教程", 8 | "description": "Navbar item with label Tutorial" 9 | }, 10 | "item.label.Live Editor": { 11 | "message": "在线编辑器", 12 | "description": "Navbar item with label Live Editor" 13 | }, 14 | "item.label.GitHub": { 15 | "message": "GitHub", 16 | "description": "Navbar item with label GitHub" 17 | } 18 | } -------------------------------------------------------------------------------- /website/i18n/en/docusaurus-theme-classic/navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "message": "Pintora", 4 | "description": "The title in the navbar" 5 | }, 6 | "item.label.Tutorial": { 7 | "message": "Tutorial", 8 | "description": "Navbar item with label Tutorial" 9 | }, 10 | "item.label.Live Editor": { 11 | "message": "Live Editor", 12 | "description": "Navbar item with label Live Editor" 13 | }, 14 | "item.label.GitHub": { 15 | "message": "GitHub", 16 | "description": "Navbar item with label GitHub" 17 | } 18 | } -------------------------------------------------------------------------------- /demo/src/components/PintoraPreview/PintoraPreview.less: -------------------------------------------------------------------------------- 1 | .PintoraPreview { 2 | padding: 15px; 3 | max-height: calc(100% - 40px); 4 | overflow: overlay; 5 | } 6 | 7 | .PintoraPreview__figure-container { 8 | max-height: 100%; 9 | } 10 | 11 | .PintoraPreview__figure-container:hover svg, 12 | .PintoraPreview__figure-container:hover canvas { 13 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); 14 | } 15 | 16 | .PintoraPreview__error { 17 | font-size: 14px; 18 | 19 | pre { 20 | white-space: pre-wrap; 21 | color: #ef4444; 22 | padding: 1em; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/pintora-standalone/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "strict": false, 7 | "checkJs": false, 8 | "allowJs": true, 9 | "resolveJsonModule": true, 10 | "declarationDir": "types", 11 | "types": ["tinycolor2", "jest"] 12 | }, 13 | "references": [ 14 | { "path": "../pintora-core" }, 15 | { "path": "../pintora-diagrams" }, 16 | { "path": "../pintora-renderer" } 17 | ], 18 | "include": [ 19 | "./src/**/*" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /demo/tailwind.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const colors = require('tailwindcss/colors') 3 | 4 | module.exports = { 5 | content: ['**/index.html', './src/**/*.{js,ts,jsx,tsx}'], 6 | darkMode: 'media', // or 'media' or 'class' 7 | theme: { 8 | extend: { 9 | colors: { 10 | warmGray: colors.stone, 11 | }, 12 | }, 13 | }, 14 | variants: { 15 | extend: {}, 16 | }, 17 | plugins: [require('daisyui')], 18 | daisyui: { 19 | themes: ['light', 'halloween', 'bumblebee'], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Local Development 6 | 7 | ```console 8 | npm run start 9 | ``` 10 | 11 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 12 | 13 | ## Build 14 | 15 | ```console 16 | npm run build 17 | ``` 18 | 19 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 20 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/shared-grammars/config.d.ts: -------------------------------------------------------------------------------- 1 | /** action type for `@param` statement */ 2 | export type ParamAction = { 3 | type: 'addParam' 4 | key: string 5 | value: string 6 | } 7 | 8 | /** action type for `@config` statement */ 9 | export type OverrideConfigAction = 10 | | { 11 | type: 'overrideConfig' 12 | value: T 13 | } 14 | | { 15 | type: 'overrideConfig' 16 | error: Error 17 | } 18 | 19 | /** action type for `title` statement */ 20 | export type SetTitleAction = { 21 | type: 'setTitle' 22 | text: string 23 | } 24 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | margin-top: 1.5em; 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/Header.less: -------------------------------------------------------------------------------- 1 | .Header { 2 | height: var(--demo-header-height); 3 | box-sizing: border-box; 4 | } 5 | 6 | .Header__brand { 7 | height: 100%; 8 | font-weight: bold; 9 | font-size: 14px; 10 | } 11 | 12 | .Header__logo { 13 | height: 100%; 14 | margin-right: 0.5rem; 15 | } 16 | 17 | .Header__link { 18 | margin-left: 1rem; 19 | } 20 | 21 | .Header__link:hover { 22 | text-decoration: underline; 23 | } 24 | 25 | .Header__dark-mode-icon { 26 | margin-right: 0.5em; 27 | position: relative; 28 | top: 2px; 29 | display: inline-block; 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/pages/demo/components/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { setLogLevel } from '@pintora/core' 3 | import { EXAMPLES } from '@pintora/test-shared' 4 | import PintoraPreview from 'src/components/PintoraPreview' 5 | import './index.css' 6 | 7 | setLogLevel('debug') 8 | 9 | const testSequenceDiagram = EXAMPLES.sequence.code 10 | 11 | export default function Basic() { 12 | useEffect(() => { 13 | // set pintora to dev mode 14 | ;(globalThis as any).isPintoraDev = true 15 | }, []) 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /website/plugins/docusaurus-theme-pintora/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | function theme() { 4 | return { 5 | name: 'docusaurus-theme-pintora', 6 | 7 | getThemePath() { 8 | return path.resolve(__dirname, './theme'); 9 | }, 10 | 11 | configureWebpack() { 12 | return { 13 | resolve: { 14 | alias: { 15 | path: require.resolve('path-browserify'), 16 | }, 17 | fallback: { 18 | fs: false, 19 | }, 20 | }, 21 | }; 22 | }, 23 | }; 24 | } 25 | 26 | module.exports = theme; 27 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/base-artist.ts: -------------------------------------------------------------------------------- 1 | import type { DiagramArtistOptions, GraphicsIR, IDiagramArtist } from '@pintora/core' 2 | import { styleEngine } from './style-engine' 3 | 4 | export abstract class BaseArtist implements IDiagramArtist { 5 | draw(ir: DiagramIR, config?: Conf, opts?: DiagramArtistOptions): GraphicsIR { 6 | const gir = this.customDraw(ir, config, opts) 7 | styleEngine.apply(gir.mark, ir) 8 | return gir 9 | } 10 | 11 | abstract customDraw(diagramIR: DiagramIR, config?: Conf, opts?: DiagramArtistOptions): GraphicsIR 12 | } 13 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/shared-grammars/bind.ne: -------------------------------------------------------------------------------- 1 | @lexer lexer 2 | 3 | @{% 4 | // @ts-ignore 5 | const BIND_CLASS = /@bindClass/; 6 | %} 7 | 8 | bindClassStatement -> 9 | %BIND_CLASS %WS:+ nodeList %WS:+ %VALID_TEXT %WS:* %NL {% 10 | (d) => ({ 11 | type: 'bindClass', 12 | nodes: d[2], 13 | className: tv(d[4]) 14 | }) 15 | %} 16 | 17 | nodeList -> 18 | %VALID_TEXT {% (d) => [tv(d[0])] %} 19 | | nodeList %COMMA %WS:* %VALID_TEXT {% 20 | (d) => { 21 | const list = d[0] 22 | list.push(tv(d[3])) 23 | return list 24 | } 25 | %} 26 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/gantt/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import db, { GanttIR } from './db' 3 | import artist from './artist' 4 | import { parse } from './parser' 5 | import { configKey, GanttConf } from './config' 6 | import { ParserWithPreprocessor } from '../util/preproccesor' 7 | 8 | export type { GanttIR, GanttConf } 9 | 10 | export const gantt: IDiagram = { 11 | pattern: /^\s*gantt/, 12 | parser: new ParserWithPreprocessor({ 13 | db, 14 | parse, 15 | }), 16 | artist, 17 | configKey, 18 | clear() { 19 | db.clear() 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/gantt-1.pintora: -------------------------------------------------------------------------------- 1 | gantt 2 | title Gantt example 3 | 4 | dateFormat YYYY-MM-DDTHH 5 | axisFormat MM-DD 6 | axisInterval 1w 7 | 8 | section Develop Prototype 9 | "Write grammar" : t-a, 2022-2-17, 2022-2-23 10 | "Write artist" : t-b, 2022-2-23, 2022-3-15 11 | 12 | %% the day I started typing the docs 13 | markDate 2022-3-15T20 14 | 15 | section Documentation 16 | "Write docs" : t-c, 2022-3-15, 5d 17 | 18 | section Optimize 19 | "Add axisInterval" : 2022-3-28, 2022-4-04 20 | 21 | section Release 22 | "Release" : milestone, 2022-4-06, 0d 23 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/activity-1.pintora: -------------------------------------------------------------------------------- 1 | activityDiagram 2 | start 3 | partition Init { 4 | :read config; 5 | :init internal services; 6 | note left: init themes 7 | } 8 | :Diagram requested; 9 | if (diagram registered ?) then 10 | :get implementation; 11 | else (no) 12 | :print error; 13 | endif 14 | switch ( renderer type ) 15 | case ( svg ) 16 | :Generate svg; 17 | case ( canvas ) 18 | :Draw canvas; 19 | case ( custom ) 20 | :Custom renderer output; 21 | endswitch 22 | 23 | while (data available?) is (available) 24 | :read data; 25 | :generate diagrams; 26 | endwhile (no) 27 | 28 | end 29 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/gantt/__tests__/gantt-artist.spec.js: -------------------------------------------------------------------------------- 1 | import * as pintora from '@pintora/core' 2 | import { EXAMPLES } from '@pintora/test-shared' 3 | import { testDraw, prepareDiagramConfig, stripDrawResultForSnapshot } from '../../__tests__/test-util' 4 | import { gantt } from '../index' 5 | 6 | describe('gantt-artist', () => { 7 | beforeAll(() => { 8 | prepareDiagramConfig() 9 | pintora.diagramRegistry.registerDiagram('gantt', gantt) 10 | }) 11 | 12 | it('match example snapshot', () => { 13 | expect(stripDrawResultForSnapshot(testDraw(EXAMPLES.gantt.code))).toMatchSnapshot() 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/__tests__/render-cases.spec.ts: -------------------------------------------------------------------------------- 1 | import { stripStartEmptyLines } from '@pintora/test-shared' 2 | import { PNG_MIME_TYPE } from '../const' 3 | import { render } from '../render' 4 | 5 | describe('render cases', () => { 6 | // #147 7 | it('will not throw CanvasPattern ReferenceError', async () => { 8 | const code = stripStartEmptyLines(` 9 | sequenceDiagram 10 | alt ffffff I am the label 11 | a --> b : text 12 | end 13 | `) 14 | 15 | const svgString = await render({ 16 | code, 17 | mimeType: PNG_MIME_TYPE, 18 | }) 19 | 20 | console.log(svgString) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/preproccesor/__tests__/__snapshots__/preprocessor.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`Preproccessor can parse @style block 1`] = ` 4 | [ 5 | { 6 | "attrs": { 7 | "borderColor": "yellow", 8 | "textColor": "green", 9 | }, 10 | "selector": { 11 | "target": "entity-CUSTOMER", 12 | "type": "id", 13 | }, 14 | }, 15 | { 16 | "attrs": { 17 | "fontStyle": "italic", 18 | }, 19 | "selector": { 20 | "target": "er__entity", 21 | "type": "class", 22 | }, 23 | }, 24 | ] 25 | `; 26 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import db, { DotIR } from './db' 3 | import artist from './artist' 4 | import { parse } from './parser' 5 | import { DOTConf } from './config' 6 | import { ParserWithPreprocessor } from '../util/preproccesor' 7 | 8 | export type { DOTConf, DotIR } 9 | 10 | export const dotDiagram: IDiagram = { 11 | pattern: /^\s*dotDiagram/, 12 | parser: new ParserWithPreprocessor({ 13 | db, 14 | parse, 15 | }), 16 | artist, 17 | configKey: 'dot', 18 | clear() { 19 | db.clear() 20 | }, 21 | } 22 | 23 | export default dotDiagram 24 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/examples/deno-example.ts: -------------------------------------------------------------------------------- 1 | // try this example with `deno run --allow-write examples/deno-example.ts` 2 | 3 | // import { render } from '../dist/runtime.esm.js' 4 | import { render } from 'jsr:@pintora/target-wintercg' 5 | import { writeFileSync } from 'node:fs' 6 | 7 | async function main() { 8 | const result = await render({ 9 | code: ` 10 | mindmap 11 | title: Mind Map levels 12 | * UML Diagrams 13 | ** Behavior Diagrams 14 | *** Sequence Diagram 15 | *** State Diagram 16 | `, 17 | }) 18 | 19 | console.log('result is', result) 20 | writeFileSync('./result.svg', result.data) 21 | } 22 | main() 23 | -------------------------------------------------------------------------------- /website/plugins/docusaurus-theme-pintora/theme/CodeBlock/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PintoraPlay from '@theme/PintoraPlay' 3 | import CodeBlock from '@theme-init/CodeBlock' 4 | 5 | const withPintoraPlay = Component => { 6 | const WrappedComponent = props => { 7 | if (props.className?.includes('language-pintora') && props.metastring?.includes('play')) { 8 | const { children: code, ...restProps } = props 9 | return 10 | } 11 | return 12 | } 13 | 14 | return WrappedComponent 15 | } 16 | 17 | export default withPintoraPlay(CodeBlock) 18 | -------------------------------------------------------------------------------- /demo/preview/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pintora preview 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/class/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import db, { ClassIR } from './db' 3 | import artist from './artist' 4 | import { parse } from './parser' 5 | import { ClassConf } from './config' 6 | import { ParserWithPreprocessor } from '../util/preproccesor' 7 | 8 | export type { ClassConf, ClassIR } 9 | 10 | export const classDiagram: IDiagram = { 11 | pattern: /^\s*classDiagram/, 12 | parser: new ParserWithPreprocessor({ 13 | db, 14 | parse, 15 | }), 16 | artist, 17 | configKey: 'class', 18 | clear() { 19 | db.clear() 20 | }, 21 | } 22 | 23 | export default classDiagram 24 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/activity/artist-util.ts: -------------------------------------------------------------------------------- 1 | import { Text, TSize } from '@pintora/core' 2 | import { makeMark } from '../util/artist-util' 3 | import { ActivityConf } from './config' 4 | 5 | /** 6 | * Based on action text config 7 | */ 8 | export function makeTextMark(conf: ActivityConf, text: string, textDims: TSize, attrs: Partial) { 9 | return makeMark('text', { 10 | text, 11 | width: textDims.width, 12 | height: textDims.height, 13 | fill: conf.textColor, 14 | fontSize: conf.fontSize, 15 | fontFamily: conf.fontFamily, 16 | textBaseline: 'middle', 17 | textAlign: 'center', 18 | ...attrs, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "scripts", 5 | "outDir": "out-scripts", 6 | "strict": true, 7 | "checkJs": true, 8 | "allowJs": true, 9 | "resolveJsonModule": true, 10 | "allowSyntheticDefaultImports": false, 11 | "module": "CommonJS", 12 | "target": "esnext", 13 | "esModuleInterop": false, 14 | "types": ["@edge-runtime/types", "node", "svgdom"] 15 | }, 16 | "references": [ 17 | { "path": "../pintora-standalone" }, 18 | { "path": "../pintora-core" } 19 | ], 20 | "include": [ 21 | "./scripts/**/*", 22 | "./types/*" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/runtime/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * pintora target for wintercg, this module will be bundled into one file. It will be combined with other handler code runs inside edge runtime 3 | */ 4 | import { pintoraStandalone } from '@pintora/standalone' 5 | import { doRender, RuntimeRenderOptions } from './render' 6 | import './text-metric' 7 | 8 | export async function render(opts: RuntimeRenderOptions) { 9 | const svg = await doRender({ 10 | ...opts, 11 | code: opts.code, 12 | }) 13 | // console.log('svg output', svg) 14 | const pintoraOutput = { 15 | type: 'svg', 16 | data: svg, 17 | } 18 | return pintoraOutput 19 | } 20 | 21 | export default pintoraStandalone 22 | -------------------------------------------------------------------------------- /demo/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | import '@percy/cypress' 20 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/activity/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import { db, ActivityDiagramIR } from './db' 3 | import artist from './artist' 4 | import { parse } from './parser' 5 | import { ActivityConf } from './config' 6 | import { ParserWithPreprocessor } from '../util/preproccesor' 7 | 8 | export type { ActivityConf, ActivityDiagramIR } 9 | 10 | export const activityDiagram: IDiagram = { 11 | pattern: /^\s*activityDiagram/, 12 | parser: new ParserWithPreprocessor({ 13 | db, 14 | parse, 15 | }), 16 | artist, 17 | configKey: 'activity', 18 | clear() { 19 | db.clear() 20 | }, 21 | } 22 | 23 | export default activityDiagram 24 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/style-engine/shared.ts: -------------------------------------------------------------------------------- 1 | import { MarkAttrs } from '@pintora/core' 2 | 3 | export type StylableAttrs = { 4 | backgroundColor: string 5 | borderColor: string 6 | fontFamily: MarkAttrs['fontFamily'] 7 | fontStyle: MarkAttrs['fontStyle'] 8 | fontWeight: MarkAttrs['fontWeight'] 9 | opacity: MarkAttrs['opacity'] 10 | textColor: string 11 | } 12 | export type StylableAttrKey = keyof StylableAttrs 13 | 14 | export type StyleSelector = { 15 | type: 'class' | 'id' 16 | target: string 17 | } 18 | 19 | export class StyleRule { 20 | selector: StyleSelector 21 | attrs: Partial 22 | } 23 | 24 | export class BindRule { 25 | nodes: string[] 26 | className: string 27 | } 28 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | module.exports = { 13 | // By default, Docusaurus generates a sidebar from the docs folder structure 14 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 15 | 16 | // But you can create a sidebar manually 17 | /* 18 | tutorialSidebar: [ 19 | { 20 | type: 'category', 21 | label: 'Tutorial', 22 | items: ['hello'], 23 | }, 24 | ], 25 | */ 26 | }; 27 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/render.ts: -------------------------------------------------------------------------------- 1 | import type { CLIRenderOptions } from './render-impl' 2 | import { renderInCurrentProcess } from './sameprocess-render' 3 | import { renderInSubprocess } from './subprocess-render' 4 | import { render as renderImpl } from './render-impl' 5 | 6 | const shouldEnableRenderInSubprocess = typeof jest === 'undefined' 7 | 8 | export async function render(opts: CLIRenderOptions): Promise> { 9 | if (typeof opts.renderInSubprocess === 'undefined') { 10 | opts.renderInSubprocess = shouldEnableRenderInSubprocess 11 | } 12 | if (opts.renderInSubprocess) { 13 | return renderInSubprocess(opts) as any 14 | } else { 15 | return renderInCurrentProcess(opts) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/er/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import db, { ErDiagramIR } from './db' 3 | import artist from './artist' 4 | import { parse } from './parser' 5 | import { configKey, ErConf } from './config' 6 | import { eventRecognizer, ErDiagramItemDatas } from './event-recognizer' 7 | import { ParserWithPreprocessor } from '../util/preproccesor' 8 | 9 | export type { ErDiagramIR, ErConf, ErDiagramItemDatas } 10 | 11 | export const erDiagram: IDiagram = { 12 | pattern: /^\s*erDiagram/, 13 | parser: new ParserWithPreprocessor({ 14 | db, 15 | parse, 16 | }), 17 | artist, 18 | configKey, 19 | eventRecognizer, 20 | clear() { 21 | db.clear() 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/__tests__/parser-special-char.spec.ts: -------------------------------------------------------------------------------- 1 | import { stripStartEmptyLines, EXAMPLES } from '@pintora/test-shared' 2 | import { replaceEofToCrlf } from './test-util' 3 | import { DIAGRAMS } from '../index' 4 | import { diagramRegistry, parseAndDraw } from '@pintora/core' 5 | 6 | describe('parser edge case', () => { 7 | beforeAll(() => { 8 | for (const [name, diagramDef] of Object.entries(DIAGRAMS)) { 9 | diagramRegistry.registerDiagram(name, diagramDef) 10 | } 11 | }) 12 | 13 | it('can parse code with crlf as eol', () => { 14 | Object.values(EXAMPLES).forEach(e => { 15 | const code = replaceEofToCrlf(stripStartEmptyLines(e.code)) 16 | parseAndDraw(code, {}) 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /demo/cypress/e2e/class/class.spec.ts: -------------------------------------------------------------------------------- 1 | import { stripStartEmptyLines } from '@pintora/test-shared' 2 | import { makeSnapshotCases } from '../test-utils/render' 3 | 4 | describe('Class Diagram', () => { 5 | makeSnapshotCases([ 6 | { 7 | description: 'Should render class diagram example', 8 | code: stripStartEmptyLines(` 9 | classDiagram 10 | class Fruit { 11 | float sweetness 12 | -float age 13 | 14 | float getAge() 15 | } 16 | 17 | class Apple { 18 | } 19 | 20 | %% There are so many kind of fruits 21 | Fruit <|-- Apple 22 | Fruit <|-- Kiwi 23 | Fruit <|-- Banana 24 | 25 | Container "1" *-- "many" Fruit : holds 26 | `), 27 | }, 28 | ]) 29 | }) 30 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/sequence/artist-util.ts: -------------------------------------------------------------------------------- 1 | import { Point, PointTuple } from '@pintora/core' 2 | import { makeMark, getBaseText, drawArrowTo, drawCrossTo, getBaseNote } from '../util/artist-util' 3 | 4 | export { makeMark, getBaseText, drawArrowTo, drawCrossTo, getBaseNote } 5 | 6 | export function makeLoopLabelBox(position: Point, width: number, height: number, cut: number) { 7 | const { x, y } = position 8 | const points: PointTuple[] = [ 9 | [x, y], 10 | [x + width, y], 11 | [x + width, y + height - cut], 12 | [x + width - cut * 1.2, y + height], 13 | [x, y + height], 14 | ] 15 | return makeMark( 16 | 'polygon', 17 | { 18 | points, 19 | }, 20 | { class: 'loop__label-box' }, 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import pintoraStandalone from '@pintora/standalone' 2 | 3 | export type RuntimeRenderOptions = { 4 | /** 5 | * pintora DSL to render 6 | */ 7 | code: string 8 | devicePixelRatio?: number | null 9 | mimeType?: string 10 | /** 11 | * Assign extra background color 12 | */ 13 | backgroundColor?: string 14 | // pintoraConfig?: DeepPartial 15 | /** 16 | * width of the output, height will be calculated according to the diagram content ratio 17 | */ 18 | width?: number 19 | } 20 | 21 | export type PintoraTarget = { 22 | render(opts: RuntimeRenderOptions): Promise<{ 23 | type: string 24 | data: any 25 | }> 26 | } 27 | 28 | export default pintoraStandalone 29 | -------------------------------------------------------------------------------- /demo/live-editor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pintora Live Editor 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/style-engine/__tests__/__snapshots__/style-engine-parser.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`style engine parser can parse @style block 1`] = ` 4 | { 5 | "bindRules": [], 6 | "configParams": [], 7 | "overrideConfig": {}, 8 | "styleRules": [ 9 | { 10 | "attrs": { 11 | "fontFamily": "serif", 12 | }, 13 | "selector": { 14 | "target": "Alice", 15 | "type": "id", 16 | }, 17 | }, 18 | { 19 | "attrs": { 20 | "textColor": "#ff0000", 21 | }, 22 | "selector": { 23 | "target": "message__text", 24 | "type": "class", 25 | }, 26 | }, 27 | ], 28 | "title": "", 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/build/ESBuildPintoraRuntimePlugin.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import { Plugin } from 'esbuild' 3 | 4 | const RUNTIME_CODE_NS = 'pintora-runtime-code' 5 | 6 | export function makeESBuildNodePolyfillsPlugin(opts: { runtimeLibPath: string }) { 7 | return { 8 | name: 'ESBuildNodePolyfillsPlugin', 9 | setup(build) { 10 | build.onResolve({ filter: /virtual:pintora/ }, args => { 11 | return { namespace: RUNTIME_CODE_NS, path: opts.runtimeLibPath } 12 | }) 13 | build.onLoad({ filter: /.*/, namespace: RUNTIME_CODE_NS }, args => { 14 | const contents = fs.readFileSync(opts.runtimeLibPath) 15 | return { 16 | contents, 17 | } 18 | }) 19 | }, 20 | } as Plugin 21 | } 22 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/component/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import { ParserWithPreprocessor } from '../util/preproccesor' 3 | import artist from './artist' 4 | import { ComponentConf, configKey } from './config' 5 | import db, { ComponentDiagramIR } from './db' 6 | import { parse } from './parser' 7 | 8 | export type { ComponentConf, ComponentDiagramIR } 9 | 10 | export const componentDiagram: IDiagram = { 11 | pattern: /^\s*componentDiagram/, 12 | parser: new ParserWithPreprocessor({ 13 | db, 14 | parse(text) { 15 | parse(text) 16 | db.fillMissingElements() 17 | }, 18 | }), 19 | artist, 20 | configKey, 21 | clear() { 22 | db.clear() 23 | }, 24 | } 25 | 26 | export default componentDiagram 27 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/type.ts: -------------------------------------------------------------------------------- 1 | import { PintoraConfig, DeepPartial } from '@pintora/standalone' 2 | 3 | export type CLIRenderOptions = { 4 | /** 5 | * pintora DSL to render 6 | */ 7 | code: string 8 | devicePixelRatio?: number | null 9 | mimeType?: string 10 | /** 11 | * Assign extra background color 12 | */ 13 | backgroundColor?: string 14 | pintoraConfig?: DeepPartial 15 | /** 16 | * width of the output, height will be calculated according to the diagram content ratio 17 | */ 18 | width?: number 19 | /** 20 | * Whether we should run render in a subprocess rather in current process. 21 | * If you call the `render` function, by default this is true, to avoid polluting the global environment. 22 | */ 23 | renderInSubprocess?: boolean 24 | } 25 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/sequence/index.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from '@pintora/core' 2 | import { db, SequenceDiagramIR } from './db' 3 | import artist from './artist' 4 | import { parse } from './parser' 5 | import { SequenceConf } from './config' 6 | import { eventRecognizer, SequenceDiagramItemDatas } from './event-recognizer' 7 | import { ParserWithPreprocessor } from '../util/preproccesor' 8 | 9 | export type { SequenceDiagramIR, SequenceConf, SequenceDiagramItemDatas } 10 | 11 | export const sequenceDiagram: IDiagram = { 12 | pattern: /^\s*sequenceDiagram/, 13 | parser: new ParserWithPreprocessor({ 14 | db, 15 | parse, 16 | }), 17 | artist, 18 | configKey: 'sequence', 19 | eventRecognizer, 20 | clear() { 21 | db.clear() 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /packages/pintora-renderer/src/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphicsIR } from '@pintora/core' 2 | import { IRenderer } from './type' 3 | import { makeRenderer, RendererType, BaseRenderer, rendererRegistry } from './renderers' 4 | import { GraphicEvent } from './event' 5 | 6 | export type RenderOptions = { 7 | container: HTMLElement 8 | renderer?: RendererType 9 | onRender?(renderer: IRenderer): void 10 | } 11 | 12 | export { BaseRenderer, rendererRegistry, GraphicEvent } 13 | export type { IRenderer } 14 | 15 | export function render(ir: GraphicsIR, opts: RenderOptions) { 16 | // console.log('TBD, render', ir) 17 | const renderer = makeRenderer(ir, opts.renderer).setContainer(opts.container) 18 | 19 | renderer.render() 20 | 21 | if (opts.onRender) { 22 | opts.onRender(renderer) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/public/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/pintora-core/src/util/matrix.ts: -------------------------------------------------------------------------------- 1 | import { mat3, ext } from '@antv/matrix-util' 2 | 3 | export { mat3 } 4 | 5 | export function createMat3() { 6 | return mat3.create() as number[] 7 | } 8 | 9 | export function createTranslation(x: number, y = 0) { 10 | return mat3.fromTranslation(mat3.create(), [x, y]) 11 | } 12 | 13 | export const transform = ext.transform 14 | 15 | export const translate = ext.leftTranslate 16 | 17 | export const leftRotate = ext.leftRotate 18 | 19 | export const leftScale = ext.leftScale 20 | 21 | /** 22 | * Rotate, with point (x, y) as the center 23 | */ 24 | export function createRotateAtPoint(x: number, y: number, rotate: number) { 25 | const newMatrix = transform(undefined as any, [ 26 | ['t', -x, -y], 27 | ['r', rotate], 28 | ['t', x, y], 29 | ]) 30 | return newMatrix 31 | } 32 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/line-util.ts: -------------------------------------------------------------------------------- 1 | import { PathCommand, Point } from '@pintora/core' 2 | import { line as d3Line, curveBasis, CurveFactory } from 'd3-shape' 3 | 4 | export function getPointsCurvePath(points: Point[], factory: CurveFactory = curveBasis) { 5 | const pathString = d3Line().curve(factory)(points.map(o => [o.x, o.y])) 6 | return pathString 7 | } 8 | 9 | export function getPointsLinearPath(points: Point[]): PathCommand[] { 10 | const [startPoint, ...restPoints] = points 11 | return [ 12 | ['M', startPoint.x, startPoint.y], 13 | ...restPoints.map(point => { 14 | return ['L', point.x, point.y] as any 15 | }), 16 | ] 17 | } 18 | 19 | export function getMedianPoint(points: Point[]) { 20 | const len = points.length 21 | const index = Math.floor(len / 2) 22 | return { index, point: points[index] } 23 | } 24 | -------------------------------------------------------------------------------- /demo/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import tseslint from 'typescript-eslint' 3 | import prettierPlugin from 'eslint-plugin-prettier' 4 | import unusedImportPlugin from 'eslint-plugin-unused-imports' 5 | 6 | export default [ 7 | ...tseslint.config(...tseslint.configs.recommended), 8 | { 9 | plugins: { 10 | prettier: prettierPlugin, 11 | 'unused-imports': unusedImportPlugin, 12 | }, 13 | rules: { 14 | '@typescript-eslint/no-explicit-any': 'off', 15 | '@typescript-eslint/no-this-alias': 'warn', 16 | '@typescript-eslint/no-extra-semi': 'off', 17 | '@typescript-eslint/no-unused-vars': ['warn', { varsIgnorePattern: '^_' }], 18 | '@typescript-eslint/ban-ts-comment': 'off', 19 | 'unused-imports/no-unused-imports': 'error', 20 | 'prefer-spread': 0, 21 | 'prettier/prettier': 'warn', 22 | }, 23 | }, 24 | ] 25 | -------------------------------------------------------------------------------- /packages/pintora-core/src/util/mark.ts: -------------------------------------------------------------------------------- 1 | import { MarkTypeMap, Mark, Group } from '../types/graphics' 2 | 3 | export function makeMark( 4 | type: T, 5 | attrs: M['attrs'], 6 | other?: Partial, 7 | ) { 8 | return { 9 | type, 10 | ...(other || {}), 11 | attrs, 12 | } as M 13 | } 14 | 15 | export function cloneMark(mark: T): T { 16 | const newMark: any = { 17 | ...mark, 18 | attrs: { ...mark.attrs }, 19 | } 20 | if ('children' in mark) { 21 | newMark.children = (mark as any).children.map((child: any) => cloneMark(child)) 22 | } 23 | return newMark 24 | } 25 | 26 | // function scalePathCommand(c: T, scale: number): T { 27 | // const [command, ...numbers] = c 28 | // return [command, ...numbers.map(n => n * scale)] as T 29 | // } 30 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/type.ts: -------------------------------------------------------------------------------- 1 | import { SequenceConf, SequenceDiagramItemDatas } from './sequence' 2 | import { ErConf, ErDiagramItemDatas } from './er' 3 | import { ComponentConf } from './component' 4 | import { ActivityConf } from './activity' 5 | import { MindmapConf } from './mindmap' 6 | import { GanttConf } from './gantt' 7 | import { DOTConf } from './dot' 8 | import { ClassConf } from './class' 9 | 10 | // type augmentation 11 | declare module '@pintora/core' { 12 | interface PintoraConfig { 13 | component: ComponentConf 14 | er: ErConf 15 | sequence: SequenceConf 16 | activity: ActivityConf 17 | mindmap: MindmapConf 18 | gantt: GanttConf 19 | dot: DOTConf 20 | class: ClassConf 21 | } 22 | 23 | interface PintoraDiagramItemDatas { 24 | er: ErDiagramItemDatas 25 | sequence: SequenceDiagramItemDatas 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/scripts/snippets/edge-server-handler.ts: -------------------------------------------------------------------------------- 1 | // this module runs inside edge runtime, and pintoraTarget will be prepended by bundler 2 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 3 | /// 4 | 5 | const target = (globalThis as any).pintoraTarget 6 | 7 | addEventListener('fetch', async event => { 8 | const requestText = await event.request.text() 9 | 10 | const code = 11 | requestText || 12 | ` 13 | sequenceDiagram 14 | title: Sequence Diagram Example 15 | autonumber 16 | User>>Pintora: render this 17 | ` 18 | const result = await target.render({ 19 | code, 20 | }) 21 | const response = new Response(result.data, { 22 | headers: { 23 | ContentType: 'image/svg+xml', 24 | }, 25 | }) 26 | return event.respondWith(response) 27 | }) 28 | -------------------------------------------------------------------------------- /demo/cypress/e2e/sequence/sequence.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EXAMPLES } from '@pintora/test-shared' 3 | import { makeSnapshotCases, startRender } from '../test-utils/render' 4 | 5 | describe('Sequence Diagram', () => { 6 | it('renders', () => { 7 | const c = startRender({ code: EXAMPLES.sequence.code }) 8 | c.get('svg .sequence__actor').should('exist') // svg 9 | 10 | cy.percySnapshot() 11 | }) 12 | 13 | makeSnapshotCases([ 14 | { 15 | description: 'excessive box width bug, #153', 16 | code: ` 17 | sequenceDiagram 18 | participant service_2 as "Service 2" 19 | participant service_3 as "Service 3" 20 | participant service_4 as "Service 4" 21 | 22 | par one 23 | service_2->>service_3: random 24 | service_4->>service_4: problem 25 | end 26 | `, 27 | }, 28 | ]) 29 | }) 30 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/scripts/edge-runtime-server.ts: -------------------------------------------------------------------------------- 1 | import { EdgeRuntime, runServer } from 'edge-runtime' 2 | import { onExit } from 'signal-exit' 3 | import * as fs from 'fs' 4 | import * as path from 'path' 5 | 6 | const dir = __dirname 7 | 8 | async function main() { 9 | const runtimeCode = fs.readFileSync(path.join(dir, '../dist/runtime.iife.js'), 'utf-8').toString() 10 | const handlerCode = fs.readFileSync(path.join(dir, './snippets/edge-server-handler.js'), 'utf-8').toString() 11 | const initialCode = ` 12 | ${runtimeCode} 13 | // separation 14 | ${handlerCode} 15 | ` 16 | 17 | const edgeRuntime = new EdgeRuntime({ initialCode }) 18 | 19 | const server = await runServer({ runtime: edgeRuntime, port: 9000 }) 20 | console.log(`> Edge server running at ${server.url}`) 21 | onExit(() => { 22 | server.close() 23 | return 24 | }) 25 | } 26 | 27 | main() 28 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], 5 | "types": ["vite/client", "vite-plugin-pwa/client", "@percy/cypress", "cypress"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react", 18 | "composite": true, 19 | "baseUrl": "./", 20 | "paths": { 21 | "src/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["./src"], 25 | "exclude": [ 26 | "./dist/**/*" 27 | ], 28 | "references": [{ 29 | "path": "../packages/pintora-standalone" 30 | }] 31 | } 32 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/style-engine/__tests__/style-engine-parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { StyleParse } from '../parser-test' 2 | import { stripStartEmptyLines } from '@pintora/test-shared' 3 | 4 | describe('style engine parser', () => { 5 | it('can parse @style block', () => { 6 | const example = stripStartEmptyLines(` 7 | @style { 8 | #Alice { 9 | fontFamily: serif; 10 | } 11 | .message__text { 12 | textColor: #ff0000; 13 | } 14 | } 15 | `) 16 | const result = StyleParse.parse(example) 17 | expect(result).toMatchSnapshot() 18 | }) 19 | 20 | it('can parse bindClass', () => { 21 | const example = stripStartEmptyLines(` 22 | @bindClass e1,e2 test-class 23 | `) 24 | const result = StyleParse.parse(example) 25 | expect(result.bindRules[0]).toMatchObject({ 26 | nodes: ['e1', 'e2'], 27 | className: 'test-class', 28 | }) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "tasks": { 4 | "compile": { 5 | "dependsOn": [ 6 | "^compile" 7 | ], 8 | "outputs": [ 9 | "lib/**", 10 | "dist/**", 11 | "types/**" 12 | ] 13 | }, 14 | "build": { 15 | "dependsOn": [ 16 | "^build" 17 | ], 18 | "outputs": [ 19 | "dist/**", 20 | "build/**", 21 | "types/**" 22 | ] 23 | }, 24 | "watch": { 25 | "dependsOn": [ 26 | "^watch" 27 | ], 28 | "outputs": [ 29 | "lib/**", 30 | "dist/**" 31 | ], 32 | "cache": false 33 | }, 34 | "prepare-setup": { 35 | "dependsOn": [ 36 | "^prepare-setup" 37 | ], 38 | "outputs": [ 39 | "lib/**", 40 | "dist/**", 41 | "types/**" 42 | ] 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /website/i18n/zh-CN/docusaurus-plugin-content-docs/current.json: -------------------------------------------------------------------------------- 1 | { 2 | "version.label": { 3 | "message": "Next", 4 | "description": "The label for version current" 5 | }, 6 | "sidebar.tutorialSidebar.category.Getting Started": { 7 | "message": "开始使用", 8 | "description": "The label for category Getting Started in sidebar tutorialSidebar" 9 | }, 10 | "sidebar.tutorialSidebar.category.Diagram Syntax": { 11 | "message": "图表语法", 12 | "description": "The label for category Diagram Syntax in sidebar tutorialSidebar" 13 | }, 14 | "sidebar.tutorialSidebar.category.Configuration": { 15 | "message": "配置", 16 | "description": "The label for category Configuration in sidebar tutorialSidebar" 17 | }, 18 | "sidebar.tutorialSidebar.category.Advanced": { 19 | "message": "进阶内容", 20 | "description": "The label for category Advanced in sidebar tutorialSidebar" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/pintora-core/src/themes/theme-default.ts: -------------------------------------------------------------------------------- 1 | import { ITheme } from './base' 2 | import { AYU_LIGHT, NOTE_BACKGROUND } from './palette' 3 | 4 | export class ThemeDefault implements ITheme { 5 | schemeOppsiteTheme = 'dark' 6 | 7 | primaryColor = AYU_LIGHT.orange 8 | secondaryColor = AYU_LIGHT.yellow 9 | teritaryColor = AYU_LIGHT.purple 10 | 11 | primaryLineColor = AYU_LIGHT.normalDark 12 | secondaryLineColor = AYU_LIGHT.normalDark 13 | 14 | textColor = AYU_LIGHT.normalDark 15 | primaryTextColor = AYU_LIGHT.normalDark 16 | secondaryTextColor = AYU_LIGHT.normalDark 17 | teritaryTextColor = AYU_LIGHT.normalDark 18 | 19 | primaryBorderColor = AYU_LIGHT.normalDark 20 | secondaryBorderColor = AYU_LIGHT.neutralGray 21 | 22 | canvasBackground = AYU_LIGHT.white 23 | groupBackground = AYU_LIGHT.white 24 | background1 = AYU_LIGHT.neutralGray 25 | 26 | noteBackground = NOTE_BACKGROUND 27 | } 28 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/component-1.pintora: -------------------------------------------------------------------------------- 1 | componentDiagram 2 | package "@pintora/core" { 3 | () GraphicsIR 4 | () IRenderer 5 | () IDiagram 6 | [Diagram Registry] as registry 7 | } 8 | package "@pintora/diagrams" { 9 | [...Multiple Diagrams...] as diagrams 10 | [diagrams] 11 | [diagrams] --> IDiagram : implements 12 | } 13 | package "@pintora/renderer" { 14 | () "render()" as renderFn 15 | [SVGRender] 16 | [CanvasRender] 17 | [SVGRender] --> IRenderer : implements 18 | [CanvasRender] --> IRenderer : implements 19 | IRenderer ..> GraphicsIR : accepts 20 | } 21 | package "@pintora/standalone" { 22 | [standalone] 23 | } 24 | [IDiagram] --> GraphicsIR : generate 25 | [standalone] --> registry : register all of @pintora/diagrams 26 | [@pintora/standalone] --> [@pintora/diagrams] : import 27 | [standalone] --> renderFn : call with GraphicsIR 28 | -------------------------------------------------------------------------------- /packages/pintora-renderer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pintora/renderer", 3 | "version": "0.8.1", 4 | "description": "Pintora's default renderer", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "keywords": [], 8 | "author": "hikerpigwinnie@gmail.com", 9 | "license": "MIT", 10 | "sideEffects": false, 11 | "files": [ 12 | "lib", 13 | "CHANGELOG.md" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/hikerpig/pintora.git", 18 | "directory": "packages/pintora-renderer" 19 | }, 20 | "scripts": { 21 | "compile": "tsc", 22 | "watch": "tsc -w" 23 | }, 24 | "dependencies": { 25 | "@antv/g-canvas": "^0.5.12", 26 | "@antv/g-svg": "^0.5.6", 27 | "@pintora/core": "workspace:^0.8.1", 28 | "@types/node": "^16.11.7" 29 | }, 30 | "devDependencies": { 31 | "@antv/g-base": "^0.5.11", 32 | "typescript": "^5.9.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/test-shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pintora/test-shared", 3 | "version": "0.5.2", 4 | "description": "Shared testing utils for pintora", 5 | "keywords": [ 6 | "pintora" 7 | ], 8 | "author": "hikerpig ", 9 | "homepage": "https://github.com/hikerpig/pintora#readme", 10 | "license": "MIT", 11 | "main": "lib/index.js", 12 | "sideEffects": false, 13 | "directories": { 14 | "lib": "lib", 15 | "test": "__tests__" 16 | }, 17 | "files": [ 18 | "lib" 19 | ], 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/hikerpig/pintora.git" 26 | }, 27 | "scripts": { 28 | "compile": "tsc", 29 | "watch": "tsc -w" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/hikerpig/pintora/issues" 33 | }, 34 | "gitHead": "92814f05b3df654b588fe35e3af275a60ea61296" 35 | } 36 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/subprocess-render/render-in-subprocess.ts: -------------------------------------------------------------------------------- 1 | import { render, type CLIRenderOptions } from '../render-impl' 2 | 3 | export type MainProcessSentMessage = { 4 | type: 'start' 5 | opts: CLIRenderOptions 6 | } 7 | 8 | export type SubprocessSentMessage = 9 | | { 10 | type: 'success' 11 | data: 12 | | { 13 | type: 'buffer' 14 | data: Buffer 15 | } 16 | | string 17 | } 18 | | { 19 | type: 'error' 20 | } 21 | 22 | process.on('message', (message: MainProcessSentMessage) => { 23 | if (message.type === 'start') { 24 | render(message.opts) 25 | .then(data => { 26 | process.send?.({ 27 | type: 'success', 28 | data, 29 | }) 30 | }) 31 | .catch(error => { 32 | console.error('error', error) 33 | process.send?.({ 34 | type: 'error', 35 | }) 36 | }) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /packages/pintora-core/src/themes/index.ts: -------------------------------------------------------------------------------- 1 | import { ThemeDefault } from './theme-default' 2 | import { ThemeDark } from './theme-dark' 3 | import { ThemeLarkLight } from './theme-lark-light' 4 | import { ThemeLarkDark } from './theme-lark-dark' 5 | import { ITheme } from './base' 6 | 7 | import { AYU_LIGHT, DRACULA } from './palette' 8 | 9 | export type { ITheme } 10 | 11 | export class ThemeRegistry { 12 | themes: Record = { 13 | default: new ThemeDefault(), 14 | dark: new ThemeDark(), 15 | larkLight: new ThemeLarkLight(), 16 | larkDark: new ThemeLarkDark(), 17 | } 18 | 19 | palettes = { 20 | AYU_LIGHT, 21 | DRACULA, 22 | } 23 | 24 | registerTheme(name: string, variables: ITheme) { 25 | if (this.themes[name]) { 26 | console.warn(`[pintora] override theme ${name}`) 27 | } 28 | this.themes[name] = variables 29 | } 30 | } 31 | 32 | export const themeRegistry = new ThemeRegistry() 33 | -------------------------------------------------------------------------------- /packages/pintora-core/src/themes/theme-lark-light.ts: -------------------------------------------------------------------------------- 1 | import { ITheme } from './base' 2 | import { BLUE_LARK, NOTE_BACKGROUND } from './palette' 3 | 4 | export class ThemeLarkLight implements ITheme { 5 | schemeOppsiteTheme = 'larkDark' 6 | 7 | primaryColor = BLUE_LARK.brightBlue 8 | secondaryColor = BLUE_LARK.cyan 9 | teritaryColor = BLUE_LARK.cyan 10 | 11 | primaryLineColor = BLUE_LARK.green 12 | secondaryLineColor = BLUE_LARK.white 13 | 14 | textColor = BLUE_LARK.normalDark 15 | primaryTextColor = BLUE_LARK.normalDark 16 | secondaryTextColor = BLUE_LARK.normalDark 17 | teritaryTextColor = BLUE_LARK.normalDark 18 | 19 | primaryBorderColor = BLUE_LARK.blue 20 | secondaryBorderColor = BLUE_LARK.normalDark 21 | 22 | canvasBackground = BLUE_LARK.white 23 | groupBackground = BLUE_LARK.white 24 | background1 = BLUE_LARK.white 25 | 26 | noteTextColor = BLUE_LARK.normalDark 27 | noteBackground = NOTE_BACKGROUND 28 | } 29 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/component/__tests__/component-artist.spec.js: -------------------------------------------------------------------------------- 1 | import { diagramRegistry } from '@pintora/core' 2 | import { EXAMPLES } from '@pintora/test-shared' 3 | import { testDraw, prepareDiagramConfig, stripDrawResultForSnapshot } from '../../__tests__/test-util' 4 | import { componentDiagram } from '../index' 5 | 6 | describe('component-artist', () => { 7 | beforeAll(() => { 8 | prepareDiagramConfig() 9 | diagramRegistry.registerDiagram('componentDiagram', componentDiagram) 10 | }) 11 | 12 | it('match example snapshot', () => { 13 | expect(stripDrawResultForSnapshot(testDraw(EXAMPLES.component.code))).toMatchSnapshot() 14 | }) 15 | 16 | it('can parse and handle bindClass', () => { 17 | const code = ` 18 | componentDiagram 19 | component comp1 20 | 21 | @bindClass node-comp1 test-class 22 | ` 23 | expect(stripDrawResultForSnapshot(testDraw(code))).toMatchSnapshot() 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/pintora-core/src/themes/theme-dark.ts: -------------------------------------------------------------------------------- 1 | import { ITheme } from './base' 2 | import { DRACULA, NOTE_BACKGROUND } from './palette' 3 | 4 | export class ThemeDark implements ITheme { 5 | isDark = true 6 | schemeOppsiteTheme = 'default' 7 | 8 | // primaryColor = DRACULA.pink 9 | primaryColor = DRACULA.purpleDark 10 | secondaryColor = DRACULA.pink 11 | teritaryColor = DRACULA.cyan 12 | 13 | primaryLineColor = DRACULA.white 14 | secondaryLineColor = DRACULA.white 15 | 16 | textColor = DRACULA.white 17 | primaryTextColor = DRACULA.normalDark 18 | secondaryTextColor = DRACULA.white 19 | teritaryTextColor = DRACULA.normalDark 20 | 21 | primaryBorderColor = DRACULA.white 22 | secondaryBorderColor = DRACULA.normalDark 23 | 24 | canvasBackground = DRACULA.normalDark 25 | groupBackground = DRACULA.normalDark 26 | background1 = '#555' 27 | 28 | noteTextColor = DRACULA.normalDark 29 | noteBackground = NOTE_BACKGROUND 30 | } 31 | -------------------------------------------------------------------------------- /packages/pintora-core/src/themes/theme-lark-dark.ts: -------------------------------------------------------------------------------- 1 | import { ITheme } from './base' 2 | import { BLUE_LARK, NOTE_BACKGROUND } from './palette' 3 | 4 | export class ThemeLarkDark implements ITheme { 5 | isDark = true 6 | schemeOppsiteTheme = 'larkLight' 7 | 8 | primaryColor = BLUE_LARK.blue 9 | secondaryColor = BLUE_LARK.blue 10 | teritaryColor = BLUE_LARK.darkBlue 11 | 12 | primaryLineColor = BLUE_LARK.green 13 | secondaryLineColor = BLUE_LARK.white 14 | 15 | textColor = BLUE_LARK.white 16 | primaryTextColor = BLUE_LARK.white 17 | secondaryTextColor = BLUE_LARK.white 18 | teritaryTextColor = BLUE_LARK.white 19 | 20 | primaryBorderColor = BLUE_LARK.blue 21 | secondaryBorderColor = BLUE_LARK.normalDark 22 | 23 | canvasBackground = BLUE_LARK.normalDark 24 | groupBackground = BLUE_LARK.normalDark 25 | background1 = BLUE_LARK.normalDark 26 | 27 | noteTextColor = BLUE_LARK.normalDark 28 | noteBackground = NOTE_BACKGROUND 29 | } 30 | -------------------------------------------------------------------------------- /demo/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /packages/development-kit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pintora/development-kit", 3 | "version": "0.1.2", 4 | "description": "Diagram development kit for pintora extensions", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "keywords": [ 8 | "pintora" 9 | ], 10 | "author": "hikerpigwinnie@gmail.com", 11 | "license": "MIT", 12 | "files": [ 13 | "lib", 14 | "CHANGELOG.md" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/hikerpig/pintora.git", 19 | "directory": "packages/development-kit" 20 | }, 21 | "scripts": { 22 | "compile": "tsc", 23 | "watch": "tsc -w", 24 | "prepare-setup": "tsc --build" 25 | }, 26 | "dependencies": { 27 | "shell-exec": "^1.0.2" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^16.11.7", 31 | "typescript": "^5.9.0" 32 | }, 33 | "peerDependencies": { 34 | "@hikerpig/nearley": "^2.21.0-beta.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/pintora-renderer/src/event.ts: -------------------------------------------------------------------------------- 1 | import { Event as GEvent } from '@antv/g-base' 2 | import { IGraphicEvent, Mark, DiagramEventType } from '@pintora/core' 3 | 4 | export type EventType = DiagramEventType 5 | 6 | /** 7 | * wire @antv/g with IGraphicEvent interface 8 | */ 9 | export class GraphicEvent implements IGraphicEvent { 10 | type: EventType 11 | gEvent: GEvent 12 | 13 | mark: Mark | undefined 14 | markPath: Mark[] | undefined 15 | 16 | constructor(gEvent: GEvent) { 17 | this.type = gEvent.type as EventType 18 | this.gEvent = gEvent 19 | } 20 | 21 | public get originEvent(): any { 22 | return this.gEvent.originalEvent 23 | } 24 | 25 | public get x(): number { 26 | return this.gEvent.x 27 | } 28 | 29 | public get y(): number { 30 | return this.gEvent.y 31 | } 32 | 33 | public get clientX(): number { 34 | return this.gEvent.clientX 35 | } 36 | 37 | public get clientY(): number { 38 | return this.gEvent.clientY 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /website/plugins/site-ad-plugin.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const isProd = process.env.NODE_ENV === 'production' 3 | 4 | module.exports = function () { 5 | return { 6 | name: 'pintora-site-ad-plugin', 7 | getClientModules() { 8 | return isProd ? [path.resolve(__dirname, './site-ad/client-module')] : [] 9 | }, 10 | injectHtmlTags() { 11 | if (!isProd) { 12 | return {} 13 | } 14 | return { 15 | postBodyTags: [ 16 | { 17 | tagName: 'script', 18 | attributes: { 19 | async: true, 20 | src: `//cdn.carbonads.com/carbon.js?serve=CEADPK77&placement=pintorajsvercelapp`, 21 | id: '_carbonads_js', 22 | }, 23 | }, 24 | { 25 | tagName: 'link', 26 | attributes: { 27 | rel: 'stylesheet', 28 | href: '/style/carbon.css', 29 | }, 30 | }, 31 | ], 32 | } 33 | }, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/__tests__/dot-config.spec.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import { parse } from '../parser' 3 | import db from '../db' 4 | import { getConf } from '../config' 5 | 6 | describe('dot config', () => { 7 | afterEach(() => { 8 | db.clear() 9 | }) 10 | it('can parse param clause', () => { 11 | const example = ` 12 | dotDiagram 13 | @param nodePadding 16 14 | @param { 15 | layoutDirection LR 16 | } 17 | ` 18 | parse(example) 19 | const ir = db.getDiagramIR() 20 | const conf = getConf(ir) 21 | expect(conf).toMatchObject({ 22 | nodePadding: 16, 23 | layoutDirection: 'LR', 24 | }) 25 | }) 26 | 27 | it('can parse override clause', () => { 28 | const example = ` 29 | dotDiagram 30 | @config({ 31 | "dot": { 32 | "edgeColor": "#111" 33 | } 34 | }) 35 | ` 36 | parse(example) 37 | const ir = db.getDiagramIR() 38 | const conf = getConf(ir) 39 | expect(conf).toMatchObject({ 40 | edgeColor: '#111', 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/scripts/node-pintora.ts: -------------------------------------------------------------------------------- 1 | import { runInThisContext } from 'vm' 2 | import * as fs from 'fs' 3 | import * as path from 'path' 4 | import type { PintoraTarget } from '../types' 5 | 6 | const dir = __dirname 7 | 8 | async function main() { 9 | const runtimeCode = fs.readFileSync(path.join(dir, '../dist/runtime.iife.js'), 'utf-8').toString() 10 | const codeToRun = runtimeCode 11 | 12 | if (process.env.DEBUG_CODE === 'true') { 13 | fs.writeFileSync(path.join(process.cwd(), 'dist/code-to-run.js'), codeToRun) 14 | } 15 | await runInThisContext(codeToRun) 16 | const target = (globalThis as any).pintoraTarget as PintoraTarget 17 | let pintoraDsl = '' 18 | const inputFilePath = process.argv[2] 19 | if (inputFilePath) { 20 | pintoraDsl = fs.readFileSync(inputFilePath, 'utf-8').toString() 21 | } 22 | // console.log('code is', pintoraDsl) 23 | const output = await target.render({ 24 | code: pintoraDsl, 25 | }) 26 | fs.writeFileSync('test.svg', output.data) 27 | } 28 | 29 | main() 30 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/wintercg-component.pintora: -------------------------------------------------------------------------------- 1 | componentDiagram 2 | title: Component Diagram Example 3 | 4 | package "@pintora/core" { 5 | () GraphicsIR 6 | () IRenderer 7 | () IDiagram 8 | [Diagram Registry] as registry 9 | } 10 | package "@pintora/diagrams" { 11 | [...Multiple Diagrams...] as diagrams 12 | [diagrams] 13 | [diagrams] --> IDiagram : implements 14 | } 15 | package "@pintora/renderer" { 16 | () "render()" as renderFn 17 | [SVGRender] 18 | [CanvasRender] 19 | [SVGRender] --> IRenderer : implements 20 | [CanvasRender] --> IRenderer : implements 21 | IRenderer ..> GraphicsIR : accepts 22 | } 23 | package "@pintora/standalone" { 24 | [standalone] 25 | } 26 | package "@pintora/target-wintercg" { 27 | () "render()" 28 | } 29 | [IDiagram] --> GraphicsIR : generate 30 | [standalone] --> registry : register all of @pintora/diagrams 31 | [@pintora/standalone] --> [@pintora/diagrams] : import 32 | [standalone] --> renderFn : call with GraphicsIR 33 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/er/event-recognizer.ts: -------------------------------------------------------------------------------- 1 | import { diagramEventMakerFactory } from '@pintora/core' 2 | import { BaseEventRecognizer } from '../util/event-recognizer' 3 | import { Entity, ErDiagramIR, Relationship } from './db' 4 | 5 | const ENTITY_ITEM_PATTERN = /^entity\-/ 6 | const RELATIONSHIP_ITEM_PATTERN = /^relationship\-/ 7 | 8 | export type ErDiagramItemDatas = { 9 | entity: Entity 10 | relationship: Relationship 11 | } 12 | 13 | const createErDiagramEvent = diagramEventMakerFactory('er') 14 | 15 | export const eventRecognizer = new BaseEventRecognizer() 16 | .addPatternRecognizerRule(ENTITY_ITEM_PATTERN, (e, m, ir) => { 17 | const entityName = m.itemId.replace(ENTITY_ITEM_PATTERN, '') 18 | return createErDiagramEvent(e, m, m.itemId, 'entity', ir.entities[entityName]) 19 | }) 20 | .addPatternRecognizerRule(RELATIONSHIP_ITEM_PATTERN, (e, m, ir) => { 21 | const data = ir.relationships.find(r => r.itemId === m.itemId) 22 | return createErDiagramEvent(e, m, m.itemId, 'relationship', data) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/scripts/edge-runtime-pintora.ts: -------------------------------------------------------------------------------- 1 | // test edge runtime 2 | // node out-scripts/edge-runtime-server.js 3 | import { EdgeRuntime } from 'edge-runtime' 4 | import * as fs from 'fs' 5 | import * as path from 'path' 6 | import type { PintoraTarget } from '../types' 7 | 8 | const runtime = new EdgeRuntime() 9 | 10 | const dir = __dirname 11 | async function main() { 12 | const runtimeCode = fs.readFileSync(path.join(dir, '../dist/runtime.iife.js'), 'utf-8').toString() 13 | 14 | const codeToRun = ` 15 | ${runtimeCode} 16 | ` 17 | await runtime.evaluate(codeToRun) 18 | // console.log('runtime context', runtime.context) 19 | const target: PintoraTarget = runtime.context.pintoraTarget 20 | 21 | let pintoraDsl = '' 22 | const inputFilePath = process.argv[2] 23 | if (inputFilePath) { 24 | pintoraDsl = fs.readFileSync(inputFilePath, 'utf-8').toString() 25 | } 26 | const output = await target.render({ 27 | code: pintoraDsl, 28 | }) 29 | fs.writeFileSync('test.svg', output.data) 30 | } 31 | 32 | main() 33 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/sequence/event-recognizer.ts: -------------------------------------------------------------------------------- 1 | import { diagramEventMakerFactory } from '@pintora/core' 2 | import { BaseEventRecognizer } from '../util/event-recognizer' 3 | import { Actor, Message, SequenceDiagramIR } from './db' 4 | 5 | const ACTOR_ITEM_PATTERN = /^actor\-/ 6 | const MESSAGE_ITEM_PATTERN = /^message\-/ 7 | 8 | export type SequenceDiagramItemDatas = { 9 | actor: Actor 10 | message: Message 11 | } 12 | 13 | const createSequenceDiagramEvent = diagramEventMakerFactory('sequence') 14 | 15 | export const eventRecognizer = new BaseEventRecognizer() 16 | .addPatternRecognizerRule(ACTOR_ITEM_PATTERN, (e, m, ir) => { 17 | const actor = ir.actors[m.itemId.replace(ACTOR_ITEM_PATTERN, '')] 18 | return createSequenceDiagramEvent(e, m, m.itemId, 'actor', actor) 19 | }) 20 | .addPatternRecognizerRule(MESSAGE_ITEM_PATTERN, (e, m, ir) => { 21 | const message = ir.messages.find(message => message.itemId === m.itemId) 22 | return createSequenceDiagramEvent(e, m, m.itemId, 'message', message) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/pintora-core/src/diagram-registry.ts: -------------------------------------------------------------------------------- 1 | import { IDiagram } from './type' 2 | import { logger } from './logger' 3 | import { diagramEventManager } from './diagram-event' 4 | 5 | export class DiagramRegistry { 6 | diagrams: Record = {} 7 | 8 | registerDiagram(name: string, diagram: IDiagram) { 9 | if (this.diagrams[name]) { 10 | logger.warn(`[pintora] duplicate diagram: ${name}`) 11 | } else { 12 | if (diagram.eventRecognizer) { 13 | diagramEventManager.addRecognizer(diagram.eventRecognizer) 14 | } 15 | } 16 | this.diagrams[name] = diagram 17 | } 18 | 19 | detectDiagram(text: string) { 20 | let diagram = this.diagrams['sequenceDiagram'] // default 21 | for (const d of Object.values(this.diagrams)) { 22 | if (d.pattern.test(text)) { 23 | diagram = d 24 | break 25 | } 26 | } 27 | return diagram 28 | } 29 | 30 | getDiagram(name: string) { 31 | return this.diagrams[name] 32 | } 33 | } 34 | 35 | export const diagramRegistry = new DiagramRegistry() 36 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/mindmap/__tests__/mindmap-artist.spec.ts: -------------------------------------------------------------------------------- 1 | import * as pintora from '@pintora/core' 2 | import { EXAMPLES, stripStartEmptyLines } from '@pintora/test-shared' 3 | import { testDraw, prepareDiagramConfig } from '../../__tests__/test-util' 4 | import { mindmap, type MindmapIR } from '../index' 5 | 6 | describe('mindmap-artist', () => { 7 | beforeAll(() => { 8 | prepareDiagramConfig() 9 | pintora.diagramRegistry.registerDiagram('mindmap', mindmap) 10 | }) 11 | 12 | it('should generate graphicIR', () => { 13 | expect((testDraw(EXAMPLES.mindmap.code).graphicIR.mark as any).children.length).toBeGreaterThan(0) 14 | }) 15 | 16 | describe('mindmap @pre block', () => { 17 | it('can parse param in @pre', () => { 18 | const example = stripStartEmptyLines(` 19 | @pre 20 | @title Hello Pre 21 | @endpre 22 | mindmap 23 | %% comment here 24 | `) 25 | const diagramIR = pintora.parseAndDraw(example, {}).diagramIR as MindmapIR 26 | expect(diagramIR.title).toBe('Hello Pre') 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /packages/pintora-cli/src/subprocess-render/index.ts: -------------------------------------------------------------------------------- 1 | import { fork } from 'node:child_process' 2 | 3 | import type { CLIRenderOptions } from '../render-impl' 4 | import path from 'node:path' 5 | import type { SubprocessSentMessage } from './render-in-subprocess' 6 | 7 | export async function renderInSubprocess(opts: CLIRenderOptions) { 8 | return new Promise((resolve, reject) => { 9 | const subprocess = fork(path.join(__dirname, 'render-in-subprocess'), { 10 | stdio: 'inherit', 11 | }) 12 | subprocess.on('message', (message: SubprocessSentMessage) => { 13 | switch (message.type) { 14 | case 'success': { 15 | if (typeof message.data === 'string') { 16 | resolve(message.data) 17 | } else { 18 | resolve(message.data.data) 19 | } 20 | subprocess.kill() 21 | break 22 | } 23 | case 'error': { 24 | reject() 25 | break 26 | } 27 | } 28 | }) 29 | subprocess.send({ 30 | type: 'start', 31 | opts, 32 | }) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2020 hikerpig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/pintora-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pintora/core", 3 | "version": "0.8.1", 4 | "description": "Awesome functions", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "keywords": [], 8 | "author": "hikerpigwinnie@gmail.com", 9 | "license": "MIT", 10 | "sideEffects": false, 11 | "files": [ 12 | "lib", 13 | "CHANGELOG.md" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/hikerpig/pintora.git", 18 | "directory": "packages/pintora-core" 19 | }, 20 | "scripts": { 21 | "compile": "tsc", 22 | "watch": "tsc -w", 23 | "test": "jest", 24 | "coverage": "npx jest --collect-coverage" 25 | }, 26 | "dependencies": { 27 | "@antv/event-emitter": "^0.1.2", 28 | "@antv/matrix-util": "^3.0.4", 29 | "@types/node": "^16.11.7", 30 | "clone-deep": "^4.0.1", 31 | "deepmerge": "^4.2.2", 32 | "tinycolor2": "^1.4.2" 33 | }, 34 | "devDependencies": { 35 | "@types/clone-deep": "^4.0.1", 36 | "@types/tinycolor2": "^1.4.3", 37 | "typescript": "^5.9.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/CodeMirrorEditor/utils.ts: -------------------------------------------------------------------------------- 1 | import { KeyBinding } from '@codemirror/view' 2 | import { indentMore, indentLess } from '@codemirror/commands' 3 | 4 | const getSoftTabChars = (tabSize: number) => { 5 | const arr = [] 6 | for (let i = tabSize; i > 0; i--) { 7 | arr.push(' ') 8 | } 9 | return arr.join('') 10 | } 11 | 12 | export const tabKeymaps: KeyBinding[] = [ 13 | { 14 | key: 'Tab', 15 | run(view) { 16 | const { dispatch, state } = view 17 | const { from, to } = state.selection.main 18 | const hasCrossRowSelection = state.sliceDoc(from, to).includes(state.lineBreak) 19 | if (hasCrossRowSelection) { 20 | indentMore(view) 21 | } else { 22 | dispatch( 23 | state.update(state.replaceSelection(getSoftTabChars(state.tabSize)), { 24 | scrollIntoView: true, 25 | userEvent: 'input', 26 | }), 27 | ) 28 | } 29 | return true 30 | }, 31 | }, 32 | { 33 | key: 'Shift-Tab', 34 | run(view) { 35 | return indentLess(view) 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /website/i18n/en/docusaurus-plugin-content-docs/current.json: -------------------------------------------------------------------------------- 1 | { 2 | "version.label": { 3 | "message": "Next", 4 | "description": "The label for version current" 5 | }, 6 | "sidebar.tutorialSidebar.category.Diagrams": { 7 | "message": "Diagrams", 8 | "description": "The label for category Diagrams in sidebar tutorialSidebar" 9 | }, 10 | "sidebar.tutorialSidebar.category.Getting Started": { 11 | "message": "Getting Started", 12 | "description": "The label for category Getting Started in sidebar tutorialSidebar" 13 | }, 14 | "sidebar.tutorialSidebar.category.Diagram Syntax": { 15 | "message": "Diagram Syntax", 16 | "description": "The label for category Diagram Syntax in sidebar tutorialSidebar" 17 | }, 18 | "sidebar.tutorialSidebar.category.Configuration": { 19 | "message": "Configuration", 20 | "description": "The label for category Configuration in sidebar tutorialSidebar" 21 | }, 22 | "sidebar.tutorialSidebar.category.Advanced": { 23 | "message": "Advanced", 24 | "description": "The label for category Advanced in sidebar tutorialSidebar" 25 | } 26 | } -------------------------------------------------------------------------------- /packages/pintora-cli/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2020 hikerpig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/pintora-core/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2020 hikerpig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2020 hikerpig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/pintora-renderer/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2020 hikerpig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/pintora-standalone/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2020 hikerpig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/sequence/artist/type.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '../db' 2 | 3 | export type ActivationData = { 4 | startx: number 5 | starty: number 6 | stopx: number 7 | stopy: number 8 | actor: string 9 | } 10 | 11 | export type LoopModel = { 12 | startx: number 13 | stopx: number 14 | starty: number 15 | stopy: number 16 | width: number 17 | height: number 18 | title: string 19 | wrap?: boolean 20 | sections?: LoopSection[] 21 | fill?: string | null 22 | } 23 | 24 | export type LoopSection = { 25 | y: number 26 | width: number 27 | height: number 28 | fill: string | undefined 29 | message: Message 30 | } 31 | 32 | export type SequenceDiagramBounds = { 33 | startx: number 34 | stopx: number 35 | starty: number 36 | stopy: number 37 | } 38 | 39 | export type MessageModel = { 40 | width: number 41 | height: number 42 | startx: number 43 | stopx: number 44 | starty: number 45 | stopy: number 46 | text: Message['text'] 47 | type: Message['type'] 48 | itemId: string 49 | sequenceIndex?: number 50 | fromBound?: number 51 | toBound?: number 52 | } 53 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/gantt/__tests__/gantt-config.spec.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import { parse } from '../parser' 3 | import db from '../db' 4 | import { getConf } from '../config' 5 | 6 | describe('gantt config', () => { 7 | afterEach(() => { 8 | db.clear() 9 | }) 10 | it('can parse param clause', () => { 11 | const example = ` 12 | gantt 13 | @param sidePadding 16 14 | @param { 15 | markLineColor #990000 16 | } 17 | title "gantt param" 18 | ` 19 | parse(example) 20 | const ir = db.getDiagramIR() 21 | const conf = getConf(ir) 22 | expect(conf).toMatchObject({ 23 | sidePadding: 16, 24 | markLineColor: '#990000', 25 | }) 26 | }) 27 | 28 | it('can parse override clause', () => { 29 | const example = ` 30 | gantt 31 | @config({ 32 | "gantt": { 33 | "sectionBackgrounds": ["#ff0000", null] 34 | } 35 | }) 36 | title gantt config 37 | ` 38 | parse(example) 39 | const ir = db.getDiagramIR() 40 | const conf = getConf(ir) 41 | expect(conf).toMatchObject({ 42 | sectionBackgrounds: ['#ff0000', null], 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /packages/test-shared/example-files/dot-1.pintora: -------------------------------------------------------------------------------- 1 | dotDiagram 2 | @param ranksep 30 3 | @param edgeType curved 4 | digraph G { 5 | bgcolor="#faf5f5" 6 | node [bgcolor="orange"] 7 | label="package dependencies" 8 | 9 | standalone [label="@pintora/standalone"] 10 | core [label="@pintora/core"] 11 | renderer [label="@pintora/renderer"] 12 | diagrams [label="@pintora/diagrams"] 13 | cli [label="@pintora/cli"] 14 | dev-kit [label="@pintora/development-kit"] 15 | test-shared [label="@pintora/test-shared"] 16 | 17 | subgraph external { 18 | label="external" 19 | 20 | dagre-layout [label="@pintora/dagre"] 21 | graphlib [label="@pintora/graphlib"] 22 | 23 | dagre-layout -> graphlib 24 | } 25 | 26 | cli -> standalone 27 | standalone -> diagrams 28 | standalone -> renderer 29 | 30 | diagrams -> core 31 | diagrams -> dagre-layout 32 | renderer -> core 33 | 34 | diagrams -> dev-kit [style="dashed"] 35 | diagrams -> test-shared [style="dashed"] 36 | cli -> test-shared [style="dashed"] 37 | standalone -> test-shared [style="dashed"] 38 | } -------------------------------------------------------------------------------- /demo/cypress/e2e/test-utils/render.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { encodeForUrl } from '@pintora/core' 3 | 4 | export function startRender(opts: { code: string }) { 5 | const { code } = opts 6 | const DEMO_HOST = 'http://localhost:3001' 7 | const encodedCode = encodeForUrl(code) 8 | const demoUrl = `${DEMO_HOST}/demo/preview/?code=${encodedCode}&e2e=true` 9 | return cy.visit(demoUrl) 10 | } 11 | 12 | export type SnapshotCaseItem = { 13 | description: string 14 | code: string 15 | existSelectors?: string[] 16 | onRender?(c: ReturnType): void 17 | } 18 | 19 | export function makeSnapshotCases(items: SnapshotCaseItem[]) { 20 | items.forEach(item => { 21 | it(item.description, function () { 22 | const c = startRender({ code: item.code }) 23 | c.get('svg').should('exist') // svg 24 | 25 | if (item.onRender) item.onRender(c) 26 | 27 | if (item.existSelectors) { 28 | item.existSelectors.forEach(selector => { 29 | c.get(selector).should('exist') 30 | }) 31 | } 32 | 33 | cy.percySnapshot() 34 | }) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/mindmap/__tests__/mindmap-config.spec.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import { parse } from '../parser' 3 | import db from '../db' 4 | import { getConf } from '../config' 5 | 6 | describe('mindmap config', () => { 7 | afterEach(() => { 8 | db.clear() 9 | }) 10 | 11 | it('can parse param clause', () => { 12 | const example = ` 13 | mindmap 14 | @param maxFontSize 16 15 | @param { 16 | l1NodeBgColor #555555 17 | l2NodeBgColor red 18 | } 19 | * Level 20 | ` 21 | parse(example) 22 | const ir = db.getDiagramIR() 23 | const conf = getConf(ir) 24 | expect(conf).toMatchObject({ 25 | maxFontSize: 16, 26 | l1NodeBgColor: '#555555', 27 | l2NodeBgColor: 'red', 28 | }) 29 | }) 30 | 31 | it('can parse override clause', () => { 32 | const example = ` 33 | mindmap 34 | @config({ 35 | "mindmap": { 36 | "nodeBgColor": "#ff0000" 37 | } 38 | }) 39 | ` 40 | parse(example) 41 | const ir = db.getDiagramIR() 42 | const conf = getConf(ir) 43 | expect(conf).toMatchObject({ 44 | nodeBgColor: '#ff0000', 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/__tests__/style-context.spec.js: -------------------------------------------------------------------------------- 1 | import { StyleContext } from '../artist/style-context' 2 | 3 | describe('StyleContext', () => { 4 | it('getValue through parent chain', () => { 5 | const p = new StyleContext() 6 | const c = new StyleContext() 7 | c.setParent(p) 8 | 9 | p.setValues({ 10 | color: 'green', 11 | fontcolor: 'purple', 12 | }) 13 | expect(c.getValue('color')).toEqual('green') 14 | 15 | c.set('color', 'red') 16 | expect(c.getValue('color')).toEqual('red') 17 | expect(c.getValue('fontcolor')).toEqual('purple') 18 | 19 | const c2 = c.spawn() 20 | expect(c2.getValue('fontcolor')).toEqual('purple') 21 | }) 22 | 23 | it('resolve through parent chain', () => { 24 | const p = new StyleContext() 25 | const c = new StyleContext() 26 | c.setParent(p) 27 | 28 | p.setValues({ 29 | color: 'green', 30 | }) 31 | 32 | expect(c.resolve('not_exist')).toMatchObject({ 33 | resolved: false, 34 | }) 35 | expect(c.resolve('color')).toMatchObject({ 36 | resolved: true, 37 | value: 'green', 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/runtime/platforms/edge-handler.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // this module runs inside edge runtime, and pintoraTarget will be prepended by bundler 3 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 4 | /// 5 | 6 | const target = pintoraTarget 7 | 8 | export const config = { 9 | runtime: 'edge', 10 | } 11 | 12 | /** 13 | * 14 | * @param {Request} request 15 | * @returns {Promise} 16 | */ 17 | export default async function handler(request) { 18 | const requestText = await request.text() 19 | 20 | try { 21 | const code = 22 | requestText || 23 | ` 24 | sequenceDiagram 25 | title: Sequence Diagram Example 26 | autonumber 27 | User->>Pintora: render this 28 | ` 29 | const result = await target.render({ 30 | code, 31 | }) 32 | const response = new Response(result.data, { 33 | headers: { 34 | 'Content-Type': 'image/svg+xml', 35 | }, 36 | }) 37 | return response 38 | } catch (error) { 39 | console.log('got error', error) 40 | return new Response('error', {}) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /website/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/basic-syntax.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 语法入门 3 | --- 4 | 5 | ## 图表语法规则 6 | 7 | 所有的图表定义都以一个图表类型开始,接下来才是图表的内容。 8 | 9 | 例如,以下内容使用 `componentDiagram` 声明开头,因此它描述的是一个 [Component Diagram 组件图](/diagrams/component-diagram.mdx)。 10 | 11 | ```pintora play 12 | componentDiagram 13 | [Pintora] --> [DiagramRegistry] : Get diagram by type 14 | ``` 15 | 16 | ## 注释 17 | 18 | 若在一行中使用 `%%` 开头,此行内容会被视为注释,不会被处理。 19 | 20 | ```pintora play 21 | sequenceDiagram 22 | %% here is line comment 23 | %% another line comment 24 | A-->B: Hi there! 25 | ``` 26 | 27 | ## Directives 指令 28 | 29 | ### @param 和 @config 30 | 31 | 可以使用这两个指令来覆盖当前图表配置。 32 | 33 | 更多细节请查看 [Config](/configuration/config.md)。 34 | 35 | ```pintora play 36 | erDiagram 37 | @param fontSize 18 38 | @config({ 39 | "themeConfig": { 40 | "theme": "larkLight" 41 | }, 42 | "er": { 43 | "edgeType": "ortho" 44 | } 45 | }) 46 | CUSTOMER { 47 | int id PK 48 | int address FK 49 | } 50 | CUSTOMER ||--o{ ORDER : places 51 | ORDER ||--|{ LINE-ITEM : contains 52 | CUSTOMER }|..|{ DELIVERY-ADDRESS : uses 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/pintora-renderer/src/renderers/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphicsIR } from '@pintora/core/lib/type' 2 | import { BaseRenderer } from './base' 3 | import { SvgRenderer } from './SvgRenderer' 4 | import { CanvasRenderer } from './CanvasRenderer' 5 | 6 | export { BaseRenderer } 7 | 8 | export type RendererType = 'svg' | 'canvas' 9 | 10 | type RendererConstructor = { 11 | new (ir: GraphicsIR): BaseRenderer 12 | } 13 | 14 | class RendererRegistry { 15 | renderers: Record = { 16 | svg: SvgRenderer, 17 | canvas: CanvasRenderer, 18 | } 19 | 20 | getRendererClass(name: RendererType) { 21 | return this.renderers[name] 22 | } 23 | 24 | register(name: RendererType, cls: RendererConstructor) { 25 | this.renderers[name] = cls 26 | } 27 | } 28 | 29 | export const rendererRegistry = new RendererRegistry() 30 | 31 | export function makeRenderer(ir: GraphicsIR, type?: RendererType | void): BaseRenderer { 32 | type = type || 'svg' 33 | const rendererCtor = rendererRegistry.getRendererClass(type) 34 | if (!rendererCtor) { 35 | return new SvgRenderer(ir) 36 | } 37 | return new rendererCtor(ir) 38 | } 39 | -------------------------------------------------------------------------------- /website/docs/advanced/pre-block.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pre Block 3 | --- 4 | 5 | A pre block comes before actual diagram keyword, it starts with `@pre` and ends with `@endpre`. 6 | 7 | This allows for passing common information to the diagram, facilitating easier maintenance of grammar and parser across various types of diagrams. 8 | 9 | ```pintora play 10 | @pre 11 | @param entityBackground #61afef 12 | @title @pre block example 13 | @endpre 14 | 15 | classDiagram 16 | class Animal { 17 | } 18 | 19 | class Dog { 20 | void bark() 21 | } 22 | 23 | Animal <|-- Dog 24 | ``` 25 | 26 | ## Set title with `@title` 27 | 28 | You can set title with `@title`. It will be used as a part of the actual diagram title. 29 | 30 | If the diagram has its own title grammar, this will be overridden by the later value you provide. 31 | 32 | However, since title grammar may vary from one diagram to another, it is easier to remember to use the @title directive. 33 | 34 | ## Override config in pre block 35 | 36 | You can use `@param` and `@config` directive inside pre block to override config. Check the [Config documentation](configuration/config.md#override-config-by-directive) for available options. 37 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #ff8150; 11 | --ifm-color-primary-dark: #ff692f; 12 | --ifm-color-primary-darker: #ff5d1e; 13 | --ifm-color-primary-darkest: #eb4200; 14 | --ifm-color-primary-light: #ff9972; 15 | --ifm-color-primary-lighter: #ffa582; 16 | --ifm-color-primary-lightest: #ffc9b4; 17 | --ifm-code-font-size: 95%; 18 | --ifm-font-size-base: 16px; 19 | --ifm-font-family-monospace: "Source Code Pro", Menlo, Monaco, sans-serif; 20 | } 21 | 22 | .docusaurus-highlight-code-line { 23 | background-color: rgba(0, 0, 0, 0.1); 24 | display: block; 25 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 26 | padding: 0 var(--ifm-pre-padding); 27 | } 28 | 29 | html[data-theme='dark'] .docusaurus-highlight-code-line { 30 | background-color: rgba(0, 0, 0, 0.3); 31 | } 32 | 33 | code { 34 | color: var(--ifm-color-primary-darker); 35 | } 36 | -------------------------------------------------------------------------------- /website/i18n/zh-CN/docusaurus-theme-classic/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "link.title.Docs": { 3 | "message": "文档", 4 | "description": "The title of the footer links column with title=Docs in the footer" 5 | }, 6 | "link.title.More": { 7 | "message": "更多", 8 | "description": "The title of the footer links column with title=More in the footer" 9 | }, 10 | "link.item.label.Tutorial": { 11 | "message": "教程", 12 | "description": "The label of footer link with label=Tutorial linking to /docs/intro" 13 | }, 14 | "link.item.label.GitHub": { 15 | "message": "GitHub", 16 | "description": "The label of footer link with label=GitHub linking to https://github.com/hikerpig/pintora" 17 | }, 18 | "copyright": { 19 | "message": "Copyright © 2021 Hikerpig. Built with Docusaurus.", 20 | "description": "The footer copyright" 21 | }, 22 | "link.title.Community": { 23 | "message": "社区", 24 | "description": "The title of the footer links column with title=Community in the footer" 25 | }, 26 | "link.item.label.Discord": { 27 | "message": "Discord", 28 | "description": "The label of footer link with label=Discord linking to https://discord.gg/HcP4JSpRaz" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/er/__tests__/er-config.spec.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import { parse } from '../parser' 3 | import db from '../db' 4 | import { getConf } from '../config' 5 | 6 | describe('er config', () => { 7 | afterEach(() => { 8 | db.clear() 9 | }) 10 | it('can parse param clause', () => { 11 | const example = ` 12 | erDiagram 13 | @param fill #aabb00 14 | @param fontSize 16 15 | @param { 16 | layoutDirection LR 17 | } 18 | ORDER 19 | ` 20 | parse(example) 21 | const ir = db.getDiagramIR() 22 | const conf = getConf(ir) 23 | // console.log('ir', JSON.stringify(ir, null, 2)) 24 | // console.log(conf) 25 | expect(conf).toMatchObject({ 26 | fill: '#aabb00', 27 | fontSize: 16, 28 | }) 29 | }) 30 | 31 | it('can parse override clause', () => { 32 | const example = ` 33 | erDiagram 34 | @config({ 35 | "er": { 36 | "borderRadius": 4 37 | } 38 | }) 39 | ORDER 40 | ` 41 | parse(example) 42 | const ir = db.getDiagramIR() 43 | const conf = getConf(ir) 44 | // console.log('ir', JSON.stringify(ir, null, 2)) 45 | expect(conf).toMatchObject({ 46 | borderRadius: 4, 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /demo/src/const.ts: -------------------------------------------------------------------------------- 1 | export const DEMO_BASE_URL = '/demo/' 2 | 3 | export const GITHUB_URL = 'https://github.com/hikerpig/pintora' 4 | 5 | export const DOC_URL = '/' 6 | 7 | export const MIME_TYPES = { 8 | pintora: 'application/vnd.pintora+text', 9 | json: 'application/json', 10 | svg: 'image/svg+xml', 11 | png: 'image/png', 12 | jpg: 'image/jpeg', 13 | gif: 'image/gif', 14 | binary: 'application/octet-stream', 15 | } as const 16 | 17 | export enum EVENT { 18 | COPY = 'copy', 19 | PASTE = 'paste', 20 | CUT = 'cut', 21 | KEYDOWN = 'keydown', 22 | KEYUP = 'keyup', 23 | MOUSE_MOVE = 'mousemove', 24 | RESIZE = 'resize', 25 | UNLOAD = 'unload', 26 | FOCUS = 'focus', 27 | BLUR = 'blur', 28 | DRAG_OVER = 'dragover', 29 | DROP = 'drop', 30 | GESTURE_END = 'gestureend', 31 | BEFORE_UNLOAD = 'beforeunload', 32 | GESTURE_START = 'gesturestart', 33 | GESTURE_CHANGE = 'gesturechange', 34 | POINTER_MOVE = 'pointermove', 35 | POINTER_UP = 'pointerup', 36 | STATE_CHANGE = 'statechange', 37 | WHEEL = 'wheel', 38 | TOUCH_START = 'touchstart', 39 | TOUCH_END = 'touchend', 40 | HASHCHANGE = 'hashchange', 41 | VISIBILITY_CHANGE = 'visibilitychange', 42 | SCROLL = 'scroll', 43 | } 44 | -------------------------------------------------------------------------------- /packages/pintora-cli/examples/nodejs-render-example.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import { render, PintoraConfig } from '..' 3 | 4 | const buildSVG = async (code: string, config?: Partial) => { 5 | const str = await render({ 6 | code: code, 7 | pintoraConfig: config, 8 | mimeType: 'image/svg+xml', 9 | width: 1000, 10 | backgroundColor: '#fff', 11 | }) 12 | fs.writeFileSync('example.svg', str) 13 | } 14 | 15 | const buildPNG = async (code: string, config?: Partial) => { 16 | const buf = await render({ 17 | code: code, 18 | pintoraConfig: config, 19 | mimeType: 'image/png', 20 | width: 800, 21 | backgroundColor: '#fdfdfd', // use some other background color 22 | }) 23 | fs.writeFileSync('example.png', buf) 24 | } 25 | 26 | const code = ` 27 | activityDiagram 28 | start 29 | :render functionl called; 30 | if (is mimeType image/svg+xml ?) then 31 | :renderer svg; 32 | :render with jsdom; 33 | :generate string; 34 | else (no) 35 | :renderer canvas; 36 | :render with node-canvas; 37 | :generate image buffer by mimeType; 38 | endif 39 | 40 | :return result; 41 | 42 | end 43 | ` 44 | 45 | buildSVG(code) 46 | 47 | buildPNG(code) 48 | -------------------------------------------------------------------------------- /website/i18n/en/docusaurus-theme-classic/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "link.title.Docs": { 3 | "message": "Docs", 4 | "description": "The title of the footer links column with title=Docs in the footer" 5 | }, 6 | "link.title.More": { 7 | "message": "More", 8 | "description": "The title of the footer links column with title=More in the footer" 9 | }, 10 | "link.item.label.Tutorial": { 11 | "message": "Tutorial", 12 | "description": "The label of footer link with label=Tutorial linking to /docs/intro" 13 | }, 14 | "link.item.label.GitHub": { 15 | "message": "GitHub", 16 | "description": "The label of footer link with label=GitHub linking to https://github.com/hikerpig/pintora" 17 | }, 18 | "copyright": { 19 | "message": "Copyright © 2021 Hikerpig. Built with Docusaurus.", 20 | "description": "The footer copyright" 21 | }, 22 | "link.title.Community": { 23 | "message": "Community", 24 | "description": "The title of the footer links column with title=Community in the footer" 25 | }, 26 | "link.item.label.Discord": { 27 | "message": "Discord", 28 | "description": "The label of footer link with label=Discord linking to https://discord.gg/HcP4JSpRaz" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/event-recognizer.ts: -------------------------------------------------------------------------------- 1 | import { IDiagramEvent, IDiagramEventRecognizer, Mark, IGraphicEvent } from '@pintora/core' 2 | 3 | export type RecognizerRule = { 4 | match(m: Mark): boolean 5 | createDiagramEvent(e: IGraphicEvent, m: Mark, ir: D): IDiagramEvent 6 | } 7 | 8 | export class BaseEventRecognizer implements IDiagramEventRecognizer { 9 | rules: RecognizerRule[] = [] 10 | 11 | recognize(e: IGraphicEvent, ir: D): undefined | IDiagramEvent { 12 | let d: IDiagramEvent | undefined 13 | if (e.markPath) { 14 | for (const m of e.markPath) { 15 | if (m.itemId) { 16 | for (const rule of this.rules) { 17 | if (rule.match(m)) { 18 | d = rule.createDiagramEvent(e, m, ir) 19 | } 20 | if (d) break 21 | } 22 | } 23 | } 24 | } 25 | return d 26 | } 27 | 28 | addPatternRecognizerRule(pattern: RegExp, createDiagramEvent: RecognizerRule['createDiagramEvent']) { 29 | const rule = { 30 | match(m: Mark) { 31 | return pattern.test(m.itemId) 32 | }, 33 | createDiagramEvent, 34 | } 35 | this.rules.push(rule) 36 | return this 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/dagre-wrapper.ts: -------------------------------------------------------------------------------- 1 | import dagre from '@pintora/dagre' 2 | import { getGraphBounds, LayoutEdge, LayoutGraph, LayoutNode } from './graph' 3 | 4 | export interface IEdgeData { 5 | onLayout(data: LayoutEdge): void 6 | } 7 | 8 | /** 9 | * Some common methods dealing with dagre layout 10 | */ 11 | export class DagreWrapper { 12 | constructor(public g: LayoutGraph) {} 13 | 14 | doLayout() { 15 | dagre.layout(this.g) 16 | } 17 | 18 | callNodeOnLayout() { 19 | const graph = this.g 20 | graph.nodes().forEach(function (v) { 21 | const nodeData = graph.node(v) as unknown as N 22 | if (nodeData) { 23 | if (nodeData.onLayout) { 24 | nodeData.onLayout(nodeData) 25 | } 26 | } 27 | }) 28 | } 29 | 30 | callEdgeOnLayout() { 31 | const graph = this.g 32 | graph.edges().forEach(function (e) { 33 | const edgeData: LayoutEdge = graph.edge(e) 34 | if (edgeData) { 35 | if (edgeData.onLayout) { 36 | edgeData.onLayout(edgeData) 37 | } 38 | } 39 | }) 40 | } 41 | 42 | getGraphBounds() { 43 | return getGraphBounds(this.g) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/index.ts: -------------------------------------------------------------------------------- 1 | import { PintoraConfig } from '@pintora/core' 2 | import { sequenceDiagram, SequenceDiagramIR } from './sequence' 3 | import { erDiagram, ErDiagramIR } from './er' 4 | import { componentDiagram, ComponentDiagramIR } from './component' 5 | import { activityDiagram, ActivityDiagramIR } from './activity' 6 | import { dotDiagram, DotIR } from './dot' 7 | import { mindmap, MindmapIR } from './mindmap' 8 | import { gantt, GanttIR } from './gantt' 9 | import { classDiagram, ClassIR } from './class' 10 | import { BaseDiagramIR } from './util/ir' 11 | import * as PARSER_SHARED from './util/parser-shared' 12 | import './type' // type augmentation 13 | import './util/symbols' 14 | 15 | export type { PintoraConfig } 16 | 17 | export const DIAGRAMS = { 18 | erDiagram, 19 | sequenceDiagram, 20 | componentDiagram, 21 | activityDiagram, 22 | mindmap, 23 | gantt, 24 | dotDiagram, 25 | classDiagram, 26 | } 27 | 28 | export type { 29 | BaseDiagramIR, 30 | SequenceDiagramIR, 31 | ErDiagramIR, 32 | ComponentDiagramIR, 33 | ActivityDiagramIR, 34 | DotIR, 35 | MindmapIR, 36 | GanttIR, 37 | ClassIR, 38 | } 39 | export { sequenceDiagram, erDiagram, componentDiagram, dotDiagram, mindmap, gantt, classDiagram, PARSER_SHARED } 40 | -------------------------------------------------------------------------------- /packages/pintora-renderer/src/renderers/SvgRenderer.ts: -------------------------------------------------------------------------------- 1 | import { Mark } from '@pintora/core' 2 | import { BaseRenderer } from './base' 3 | import { Canvas, IShape } from '@antv/g-svg' 4 | 5 | export class SvgRenderer extends BaseRenderer { 6 | getCanvasClass() { 7 | return Canvas 8 | } 9 | 10 | preProcessMarkAttrs(mark: Mark) { 11 | if (mark.type === 'text') { 12 | return { 13 | ...mark.attrs, 14 | text: escapeHtml(mark.attrs.text), 15 | } 16 | } 17 | return mark.attrs! 18 | } 19 | 20 | onShapeAdd(shape: IShape, mark: Mark) { 21 | super.onShapeAdd(shape, mark) 22 | 23 | if (mark.class) { 24 | const el = shape.get('el') 25 | // TODO: some js dom implementation does not have classList 26 | if (el && el.classList) { 27 | mark.class.split(' ').forEach(cls => { 28 | if (cls) el.classList.add(cls) 29 | }) 30 | } 31 | } 32 | } 33 | } 34 | 35 | // https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript 36 | function escapeHtml(unsafe: string) { 37 | return unsafe 38 | .replace(/&/g, '&') 39 | .replace(//g, '>') 41 | .replace(/"/g, '"') 42 | .replace(/'/g, ''') 43 | } 44 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/Examples/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { EXAMPLES } from '@pintora/test-shared' 3 | import { actions } from 'src/live-editor/redux/slice' 4 | import Buttons from 'src/live-editor/components/Buttons' 5 | import store from '../../redux/store' 6 | import './Examples.less' 7 | 8 | interface ExamplesProps {} 9 | 10 | const Examples = ({}: ExamplesProps) => { 11 | const onExampleClick = useCallback((e: any) => { 12 | const key = e.currentTarget.dataset.key 13 | const example = (EXAMPLES as any)[key] 14 | if (example) { 15 | store.dispatch(actions.updateEditorCode({ code: example.code, syncToPreview: true })) 16 | } 17 | }, []) 18 | return ( 19 |
20 | 21 | {Object.entries(EXAMPLES).map(([key, example]) => { 22 | return ( 23 |
30 | {example.name} 31 |
32 | ) 33 | })} 34 |
35 |
36 | ) 37 | } 38 | 39 | export default Examples 40 | -------------------------------------------------------------------------------- /packages/development-kit/src/compie-grammar.ts: -------------------------------------------------------------------------------- 1 | import shellExec from 'shell-exec' 2 | import * as path from 'path' 3 | import * as fs from 'fs' 4 | 5 | type Options = { 6 | input: string 7 | output: string 8 | basePath?: string 9 | includePaths?: string[] 10 | /** 11 | * - npx 12 | * - pnpx 13 | * - yarn run 14 | */ 15 | executeCommand?: string 16 | } 17 | 18 | export async function compileGrammar(opts: Options) { 19 | const { input, output } = opts 20 | const basePath = opts.basePath || process.cwd() 21 | const outputPath = output 22 | const runCommand = opts.executeCommand || 'npx' 23 | let cmd = `${runCommand} nearleyc ${path.join(basePath, input)} -o ${outputPath}` 24 | if (opts.includePaths) { 25 | const includePaths = opts.includePaths || [] 26 | cmd += ` --include-paths ${includePaths.join(',')}` 27 | } 28 | await shellExec(cmd).then(console.log).catch(console.error) 29 | 30 | if (fs.existsSync(outputPath)) { 31 | const content = fs.readFileSync(outputPath).toString() 32 | // the 'declare var' that nearleyc generates may raise TS2300 error 'Duplicate identifier', so we need to ignore it 33 | const newContent = content.replace(/declare\svar/g, '//@ts-ignore\ndeclare var') 34 | fs.writeFileSync(outputPath, newContent) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/component/__tests__/component-config.spec.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import { parse } from '../parser' 3 | import db from '../db' 4 | import { getConf } from '../config' 5 | 6 | describe('componentDiagram config', () => { 7 | afterEach(() => { 8 | db.clear() 9 | }) 10 | 11 | it('can parse param clause', () => { 12 | const example = ` 13 | componentDiagram 14 | @param groupBackground #000000 15 | @param { 16 | componentPadding 20 17 | fontFamily serif 18 | } 19 | package "P_A" { 20 | [ContentA] 21 | } 22 | ` 23 | parse(example) 24 | const ir = db.getDiagramIR() 25 | const conf = getConf(ir) 26 | expect(conf).toMatchObject({ 27 | groupBackground: '#000000', 28 | componentPadding: 20, 29 | fontFamily: 'serif', 30 | }) 31 | }) 32 | 33 | it('can parse override clause', () => { 34 | const example = ` 35 | componentDiagram 36 | @config({ 37 | "component": { 38 | "componentPadding": 4, 39 | "textColor": "rgba(0, 0, 0, 1)" 40 | } 41 | }) 42 | ` 43 | parse(example) 44 | const ir = db.getDiagramIR() 45 | const conf = getConf(ir) 46 | expect(conf).toMatchObject({ 47 | componentPadding: 4, 48 | textColor: 'rgba(0, 0, 0, 1)', 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /packages/pintora-core/src/themes/base.ts: -------------------------------------------------------------------------------- 1 | export interface ITheme { 2 | /** 3 | * Indicate if this is a dark theme, if not specified, will be treat as a light theme 4 | */ 5 | isDark?: boolean 6 | /** 7 | * While toggling between light/dark theme, will switch to this if this is specified 8 | */ 9 | schemeOppsiteTheme?: string 10 | 11 | primaryColor: string 12 | secondaryColor: string 13 | teritaryColor: string 14 | 15 | primaryLineColor: string 16 | secondaryLineColor: string 17 | 18 | primaryBorderColor: string 19 | secondaryBorderColor: string 20 | 21 | textColor: string 22 | primaryTextColor: string 23 | secondaryTextColor: string 24 | teritaryTextColor: string 25 | 26 | /** 27 | * Background color for the canvas, by default, it will be transparent 28 | */ 29 | canvasBackground?: string 30 | groupBackground: string 31 | background1: string 32 | /** 33 | * Used in area that needs to display dark text, like erDiagram's atrributes 34 | */ 35 | lightestBackground?: string 36 | 37 | /** 38 | * Text color for note, by default, it will be the same with `textColor` 39 | */ 40 | noteTextColor?: string 41 | /** 42 | * Background color for note, by default, it will be slightly light yellow 43 | */ 44 | noteBackground?: string 45 | } 46 | -------------------------------------------------------------------------------- /website/plugins/docusaurus-theme-pintora/theme/PintoraPlay/highlight.ts: -------------------------------------------------------------------------------- 1 | import * as shiki from 'shiki' 2 | 3 | export const shikiLightTheme = 'material-theme' 4 | export const shikiDarkTheme = 'dracula' 5 | 6 | let globalHighlighter: shiki.Highlighter 7 | let globalHighlighterPromise: Promise 8 | 9 | export async function startShiki(opts: { isDarkMode?: boolean } = {}) { 10 | if (!globalHighlighter) { 11 | const syntaxJson: shiki.RawGrammar = await (await fetch('/data/pintora.tmLanguage.json')).json() 12 | 13 | const pintoraLanguage: shiki.LanguageRegistration = { 14 | name: 'pintora', 15 | scopeName: 'source.pintora', 16 | embeddedLangsLazy: ['json'], 17 | ...syntaxJson, 18 | } 19 | 20 | const theme = opts.isDarkMode ? shikiDarkTheme : shikiLightTheme 21 | 22 | if (!globalHighlighterPromise) { 23 | globalHighlighterPromise = shiki.getSingletonHighlighter({ 24 | langs: [pintoraLanguage, 'json'], 25 | themes: [theme], 26 | }) 27 | } 28 | 29 | const highlighter = await globalHighlighterPromise 30 | globalHighlighter = highlighter 31 | 32 | await highlighter.loadLanguage(pintoraLanguage) 33 | 34 | globalHighlighterPromise = null 35 | } 36 | 37 | return { highlighter: globalHighlighter } 38 | } 39 | -------------------------------------------------------------------------------- /packages/pintora-core/src/themes/palette.ts: -------------------------------------------------------------------------------- 1 | // ayu light 2 | export const AYU_LIGHT = { 3 | white: '#fff', 4 | lightDark: '#5E666D', 5 | normalDark: '#3b4044', 6 | neutralGray: '#f8f8f2', 7 | cyan: '#55b4d4', 8 | green: '#9c0', 9 | orange: '#fdb05e', // lightness 68 10 | // orangeLight1: '#fec88f', // +10 lightness 11 | // orangeLight2: '#fee0c1', // +20 lightness 12 | pink: '#f07171', 13 | purple: '#af71d0', 14 | red: '#e45649', 15 | yellow: '#f5f1be', 16 | } 17 | 18 | // modified dracula https://coolors.co/ad78f7-ff79c6-8be9fd-f1fa8c-ff5555 19 | export const DRACULA = { 20 | white: '#f8f8f2', 21 | normalDark: '#282a36', 22 | cyan: '#8be9fd', 23 | green: '#50fa7b', 24 | orange: '#ffb86c', 25 | pink: '#ff79c6', 26 | // purple: '#bd93f9', 27 | purple: '#AD78F7', // lightness 72 28 | purpleDark: '#9a58f5', // lightness 65 29 | red: '#ff5555', 30 | yellow: '#f1fa8c', 31 | } 32 | 33 | export const BLUE_LARK = { 34 | white: '#EDF4FF', 35 | normalDark: '#272827', 36 | cyan: '#A6D8F1', 37 | green: '#03B59C', 38 | blue: '#4492FD', 39 | darkBlue: '#143C9A', 40 | brightBlue: '#8BBBFD', 41 | orange: '#ffb86c', 42 | pink: '#F9DBD8', 43 | purple: '#AFBCF1', 44 | red: '#FD514D', 45 | yellow: '#FEF4D7', 46 | } 47 | 48 | export const NOTE_BACKGROUND = '#F8EA75' 49 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/Panel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren, useCallback, useState } from 'react' 2 | import PanelHeader, { Tabs, PanelHeaderProps } from '../PanelHeader' 3 | import './Panel.less' 4 | 5 | interface PanelProps { 6 | title: string 7 | tabs?: Tabs 8 | className?: string 9 | initialTab?: string 10 | onCurrentTabChange?(v: string): void 11 | headerAppendix?: PanelHeaderProps['appendix'] 12 | } 13 | 14 | const Panel: React.FC> = ({ 15 | className, 16 | title, 17 | tabs, 18 | initialTab, 19 | children, 20 | onCurrentTabChange, 21 | headerAppendix, 22 | }) => { 23 | const [currentTab, setCurrentTab] = useState(initialTab || tabs?.[0]?.key) 24 | const setCurrentTabProp = useCallback( 25 | (key: string) => { 26 | setCurrentTab(key) 27 | onCurrentTabChange && onCurrentTabChange(key) 28 | }, 29 | [tabs], 30 | ) 31 | 32 | return ( 33 |
34 | 41 |
{children}
42 |
43 | ) 44 | } 45 | 46 | export default Panel 47 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/activity/__tests__/activity-config.spec.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import { parse } from '../parser' 3 | import { db } from '../db' 4 | import { getConf } from '../config' 5 | 6 | describe('activityDiagram config', () => { 7 | afterEach(() => { 8 | db.clear() 9 | }) 10 | 11 | it('can parse param clause', () => { 12 | const example = ` 13 | activityDiagram 14 | @param { 15 | noteTextColor #ff0000 16 | noteMargin 15 17 | } 18 | group Init { 19 | if (diagram registered ?) then 20 | :get implementation; 21 | else (no) 22 | :print error; 23 | endif 24 | } 25 | ` 26 | parse(example) 27 | const ir = db.getDiagramIR() 28 | const conf = getConf(ir) 29 | expect(conf).toMatchObject({ 30 | noteTextColor: '#ff0000', 31 | noteMargin: 15, 32 | }) 33 | }) 34 | 35 | it('can parse override clause', () => { 36 | const example = ` 37 | activityDiagram 38 | @config({ 39 | "activity": { 40 | "edgesep": 4, 41 | "edgeColor": "rgba(0, 0, 0, 1)", 42 | "edgeType": "ortho" 43 | } 44 | }) 45 | ` 46 | parse(example) 47 | const ir = db.getDiagramIR() 48 | const conf = getConf(ir) 49 | expect(conf).toMatchObject({ 50 | edgesep: 4, 51 | edgeColor: 'rgba(0, 0, 0, 1)', 52 | edgeType: 'ortho', 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/style-engine/parser.ts: -------------------------------------------------------------------------------- 1 | import { type ActionHandler, type MakeAction } from '../base-db' 2 | import { StyleRule, type StyleSelector, BindRule } from './shared' 3 | 4 | export type ParserStyleRule = { 5 | selector: StyleSelector 6 | attrs: Array<{ 7 | key: string 8 | value: string 9 | }> 10 | } 11 | 12 | export type StylePayloads = { 13 | style: { 14 | rules: ParserStyleRule[] 15 | } 16 | bindClass: { 17 | nodes: string[] 18 | className: string 19 | } 20 | } 21 | 22 | export type Action = MakeAction 23 | 24 | export interface IStyleDb { 25 | styleRules: StyleRule[] 26 | bindRules: BindRule[] 27 | } 28 | 29 | export const STYLE_ACTION_HANDLERS: { 30 | [K in keyof StylePayloads]: ActionHandler 31 | } = { 32 | style(action) { 33 | const rules: StyleRule[] = [] 34 | action.rules.forEach(rule => { 35 | rules.push({ 36 | selector: rule.selector, 37 | attrs: rule.attrs.reduce((previous, current) => { 38 | previous[current.key] = current.value 39 | return previous 40 | }, {}), 41 | }) 42 | }) 43 | this.styleRules.push(...rules) 44 | }, 45 | bindClass(action) { 46 | this.bindRules.push({ 47 | nodes: action.nodes, 48 | className: action.className, 49 | }) 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/ConfigEditor/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { useDebounceCallback } from '@react-hook/debounce' 3 | import CodeMirrorEditor from 'src/live-editor/components/CodeMirrorEditor' 4 | import { useDispatch, connect } from 'react-redux' 5 | import { StoreState } from 'src/live-editor/redux/store' 6 | import { actions } from 'src/live-editor/redux/slice' 7 | 8 | interface Props { 9 | editorCode: string 10 | show: boolean 11 | } 12 | 13 | const CONFIG_EDITOR_OPTIONS = { 14 | language: 'json', 15 | } 16 | 17 | function ConfigEditor(props: Props) { 18 | const { editorCode, show } = props 19 | const dispatch = useDispatch() 20 | 21 | const onCodeChange = useDebounceCallback( 22 | useCallback((code: string) => { 23 | dispatch(actions.updateConfigCode({ code })) 24 | }, []), 25 | 500, 26 | ) 27 | 28 | const style = { 29 | display: show ? 'flex' : 'none', 30 | } 31 | return ( 32 |
33 | 34 |
35 | ) 36 | } 37 | 38 | export default connect((state: StoreState) => { 39 | return { 40 | editorCode: state.main.configEditor.code, 41 | show: state.main.currentEditor === 'config', 42 | } as Props 43 | })(ConfigEditor) 44 | -------------------------------------------------------------------------------- /packages/pintora-core/src/__tests__/config.spec.js: -------------------------------------------------------------------------------- 1 | import configApi from '../config' 2 | 3 | describe('pintora core configApi', () => { 4 | it('replace array instead of merging', () => { 5 | configApi.setConfig({ 6 | a: [1, 2], 7 | }) 8 | configApi.setConfig({ 9 | a: [3, 4], 10 | }) 11 | expect(configApi.getConfig()).toMatchObject({ a: [3, 4] }) 12 | }) 13 | 14 | describe('gnernateNewConfig(c)', () => { 15 | it('will get themeVariables from themeRegistry if themeConfig.theme is provided', () => { 16 | const newConfig = configApi.gnernateNewConfig({ 17 | themeConfig: { 18 | theme: 'dark', 19 | }, 20 | }) 21 | 22 | const currentConfig = configApi.getConfig() 23 | expect(newConfig).not.toBe(currentConfig) 24 | 25 | expect(newConfig.themeConfig.themeVariables.primaryColor).not.toEqual( 26 | currentConfig.themeConfig.themeVariables.primaryColor, 27 | ) 28 | }) 29 | 30 | it('will assign extra themeConfig.themeVariables', () => { 31 | const newConfig = configApi.gnernateNewConfig({ 32 | themeConfig: { 33 | theme: 'dark', 34 | themeVariables: { 35 | primaryColor: '#ffffff', 36 | }, 37 | }, 38 | }) 39 | expect(newConfig.themeConfig.themeVariables.primaryColor).toBe('#ffffff') 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /website/docs/getting-started/basic-syntax.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Basic Syntax 3 | --- 4 | 5 | ## Diagram Syntax Structure 6 | 7 | All Diagrams definitions begin with a declaration of the diagram type, followed by the definitions of the diagram and its contents. 8 | 9 | For example, the DSL below specify a [Component Diagram](/diagrams/component-diagram.mdx) by the `componentDiagram` declaration. 10 | 11 | ```pintora play 12 | componentDiagram 13 | [Pintora] --> [DiagramRegistry] : Get diagram by type 14 | ``` 15 | 16 | ## Comments 17 | 18 | You can add line comment with `%%` prefixed, the content in that line will not be rendered. 19 | 20 | ```pintora play 21 | sequenceDiagram 22 | %% here is line comment 23 | %% another line comment 24 | A-->B: Hi there! 25 | ``` 26 | 27 | ## Directives 28 | 29 | ### @param and @config 30 | 31 | You can override current diagram config with these directives. 32 | 33 | See [Config](/configuration/config.md) for more details. 34 | 35 | ```pintora play 36 | erDiagram 37 | @param fontSize 18 38 | @config({ 39 | "themeConfig": { 40 | "theme": "larkLight" 41 | }, 42 | "er": { 43 | "edgeType": "ortho" 44 | } 45 | }) 46 | CUSTOMER { 47 | int id PK 48 | int address FK 49 | } 50 | CUSTOMER ||--o{ ORDER : places 51 | ORDER ||--|{ LINE-ITEM : contains 52 | CUSTOMER }|..|{ DELIVERY-ADDRESS : uses 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/symbols/circle.ts: -------------------------------------------------------------------------------- 1 | import { symbolRegistry, safeAssign, ContentArea } from '@pintora/core' 2 | import { makeMark } from '../artist-util' 3 | 4 | symbolRegistry.register('circle', { 5 | type: 'factory', 6 | factory(contentArea, { mode }) { 7 | if (mode === 'container') { 8 | return makeContainer(contentArea) 9 | } 10 | }, 11 | styleMark(mark, def, attrs) { 12 | mark.children.forEach(child => { 13 | safeAssign(child.attrs, attrs) 14 | }) 15 | }, 16 | }) 17 | 18 | function makeContainer({ width, height, x, y }: ContentArea) { 19 | const halfWidth = width / 2 20 | const pad = 10 21 | const r = halfWidth + pad 22 | const mark = makeMark( 23 | 'group', 24 | {}, 25 | { 26 | children: [ 27 | makeMark('circle', { 28 | r, 29 | x: x, 30 | y: y, 31 | }), 32 | ], 33 | }, 34 | ) 35 | 36 | const leftX = x - pad 37 | const rightX = x + pad 38 | const outerWidth = width + 2 * pad 39 | const outerHeight = height + 2 * pad 40 | const sym = makeMark( 41 | 'symbol', 42 | {}, 43 | { 44 | mark, 45 | symbolBounds: { 46 | left: leftX, 47 | right: rightX, 48 | top: y - r, 49 | bottom: y + r, 50 | width: outerWidth, 51 | height: outerHeight, 52 | }, 53 | }, 54 | ) 55 | return sym 56 | } 57 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/Editor/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import CodeMirrorEditor from 'src/live-editor/components/CodeMirrorEditor' 3 | import { useDispatch, connect, ConnectedProps } from 'react-redux' 4 | import { StoreState } from 'src/live-editor/redux/store' 5 | import { actions } from 'src/live-editor/redux/slice' 6 | import './Editor.less' 7 | 8 | const CODE_EDITOR_OPTIONS = { 9 | language: 'pintora', 10 | } 11 | 12 | function Editor(props: Props) { 13 | const { editorCode, show, errorInfo } = props 14 | const dispatch = useDispatch() 15 | 16 | const onCodeChange = useCallback((code: string) => { 17 | dispatch(actions.updateEditorCode({ code })) 18 | }, []) 19 | 20 | const style = { 21 | display: show ? 'flex' : 'none', 22 | } 23 | return ( 24 |
25 | 31 |
32 | ) 33 | } 34 | 35 | const connector = connect((state: StoreState) => { 36 | return { 37 | editorCode: state.main.editor.code, 38 | errorInfo: state.main.editor.error, 39 | show: state.main.currentEditor === 'code', 40 | } 41 | }) 42 | 43 | type Props = ConnectedProps 44 | 45 | export default connector(Editor) 46 | -------------------------------------------------------------------------------- /demo/src/live-editor/main.tsx: -------------------------------------------------------------------------------- 1 | import { stripStartEmptyLines } from '@pintora/test-shared' 2 | import * as React from 'react' 3 | import { createRoot } from 'react-dom/client' 4 | import App from './App' 5 | import { isProd } from '../env' 6 | import '../styles/base.css' 7 | import './pwa' 8 | 9 | const container = document.getElementById('root') 10 | if (container) { 11 | const root = createRoot(container) 12 | root.render( 13 | 14 | 15 | , 16 | ) 17 | } 18 | 19 | function addAd() { 20 | window.addEventListener('load', () => { 21 | const script = document.createElement('script') 22 | script.async = true 23 | script.type = 'text/javascript' 24 | script.src = '//cdn.carbonads.com/carbon.js?serve=CEADPK77&placement=pintorajsvercelapp' 25 | script.id = '_carbonads_js' 26 | document.body.appendChild(script) 27 | 28 | const link = document.createElement('link') 29 | link.rel = 'stylesheet' 30 | link.href = '/style/carbon.css' // is hosted in website static/style 31 | // link.href = 'http://localhost:3000/style/carbon.css' 32 | document.body.appendChild(link) 33 | 34 | const style = document.createElement('style') 35 | style.innerHTML = stripStartEmptyLines(` 36 | #carbonads { 37 | bottom: 5px; 38 | } 39 | `) 40 | document.body.appendChild(style) 41 | }) 42 | } 43 | 44 | if (isProd) { 45 | addAd() 46 | } 47 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/__tests__/dot-artist.spec.js: -------------------------------------------------------------------------------- 1 | import { diagramRegistry } from '@pintora/core' 2 | import { EXAMPLES } from '@pintora/test-shared' 3 | import { testDraw, prepareDiagramConfig, stripDrawResultForSnapshot } from '../../__tests__/test-util' 4 | import { dotDiagram } from '../index' 5 | import '../../util/symbols' 6 | 7 | describe('dot-artist', () => { 8 | beforeAll(() => { 9 | prepareDiagramConfig() 10 | diagramRegistry.registerDiagram('dotDiagram', dotDiagram) 11 | }) 12 | 13 | it('match example snapshot', () => { 14 | expect(stripDrawResultForSnapshot(testDraw(EXAMPLES.dot.code))).toMatchSnapshot() 15 | }) 16 | 17 | it('renders different arrow type', () => { 18 | const code = ` 19 | dotDiagram 20 | digraph Test { 21 | bgcolor="lightyellow"; 22 | label="Graph Label"; 23 | 24 | a1 -> b1 [arrowhead="box"]; 25 | n1 -> end [arrowhead="odot"]; 26 | a2 -> end [arrowhead="open"]; 27 | } 28 | ` 29 | expect(stripDrawResultForSnapshot(testDraw(code))).toMatchSnapshot() 30 | }) 31 | 32 | it('renders different node type', () => { 33 | const code = ` 34 | dotDiagram 35 | @param fontWeight bold 36 | digraph { 37 | ellipse [shape="ellipse"]; 38 | circle [shape="circle"]; 39 | diamond [shape="diamond"]; 40 | plaintext [shape="plaintext"]; 41 | } 42 | ` 43 | expect(stripDrawResultForSnapshot(testDraw(code))).toMatchSnapshot() 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/PanelHeader/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import './PanelHeader.less' 3 | 4 | export type TabItem = { 5 | key: string 6 | label: string 7 | } 8 | 9 | export type Tabs = TabItem[] 10 | 11 | export interface PanelHeaderProps { 12 | title: string 13 | tabs: Tabs | undefined 14 | currentTab: string | undefined 15 | setCurrentTab(key: string): void 16 | appendix?: JSX.Element 17 | } 18 | 19 | const PanelHeader = ({ title, tabs, currentTab, setCurrentTab, appendix }: PanelHeaderProps) => { 20 | const onClickTab = useCallback( 21 | (key: string) => { 22 | setCurrentTab(key) 23 | }, 24 | [tabs], 25 | ) 26 | return ( 27 |
28 |
{title}
29 |
30 | {tabs && 31 | tabs.map(tabItem => { 32 | return ( 33 | onClickTab(tabItem.key)} 37 | > 38 | {tabItem.label} 39 | 40 | ) 41 | })} 42 |
43 | {appendix &&
{appendix}
} 44 |
45 | ) 46 | } 47 | 48 | export default PanelHeader 49 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/shared-grammars/style.ne: -------------------------------------------------------------------------------- 1 | @lexer lexer 2 | 3 | styleStatement -> 4 | "@style" %WS:* %L_BRACKET __ styleRules __ %R_BRACKET %WS:* %NL {% (d) => ({ type: 'style', rules: d[4] }) %} 5 | 6 | styleRules -> 7 | null {% null %} 8 | | styleRules __ styleRule {% 9 | (d) => { 10 | const rules = d[0] || [] 11 | if (d[2]) rules.push(d[2]) 12 | return rules 13 | } 14 | %} 15 | 16 | styleRule -> 17 | selector %WS:* %L_BRACKET __ styleAttrs __ %R_BRACKET %WS:* {% 18 | (d) => ({ 19 | type: 'styleRule', 20 | selector: d[0], 21 | attrs: d[4] 22 | }) 23 | %} 24 | 25 | selector -> 26 | %VALID_TEXT {% function(d) { 27 | const v = tv(d[0]) 28 | if (v[0] === '#') { 29 | return { type: 'id', target: v.slice(1) } 30 | } else if (v[0] === '.') { 31 | return { type: 'class', target: v.slice(1) } 32 | } 33 | } %} 34 | 35 | styleAttrs -> 36 | null {% null %} 37 | | styleAttrs styleAttr {% 38 | (d) => { 39 | const attrs = d[0] || [] 40 | if (d[1]) attrs.push(d[1]) 41 | return attrs 42 | } 43 | %} 44 | 45 | styleAttr -> 46 | %VALID_TEXT %WS:* %COLON %WS:* attrValue %WS:* %SEMICOLON __ {% 47 | (d) => ({ 48 | type: 'attr', 49 | key: tv(d[0]), 50 | value: d[4] 51 | }) 52 | %} 53 | 54 | attrValue -> 55 | %VALID_TEXT {% (d) => tv(d[0]) %} 56 | | %QUOTED_WORD {% (d) => tv(d[0]) %} 57 | -------------------------------------------------------------------------------- /demo/cypress/e2e/component/component.spec.ts: -------------------------------------------------------------------------------- 1 | import { stripStartEmptyLines } from '@pintora/test-shared' 2 | import { makeSnapshotCases } from '../test-utils/render' 3 | 4 | describe('Component Diagram', () => { 5 | makeSnapshotCases([ 6 | { 7 | description: 'Should render ortho edges and labels not fully overlapping with borders', 8 | code: stripStartEmptyLines(` 9 | componentDiagram 10 | @param edgeType ortho 11 | package "@pintora/core" { 12 | () GraphicsIR 13 | () IRenderer 14 | () IDiagram 15 | [Diagram Registry] as registry 16 | } 17 | 18 | package "@pintora/renderer" { 19 | () "render()" as renderFn 20 | [SVGRender] 21 | [SVGRender] --> IRenderer : implements 22 | IRenderer ..> GraphicsIR : accepts 23 | } 24 | package "@pintora/standalone" { 25 | [standalone] 26 | } 27 | [IDiagram] --> GraphicsIR : generate 28 | [standalone] --> registry : register all of @pintora/diagrams 29 | 30 | [standalone] --> renderFn : call with GraphicsIR 31 | `), 32 | onRender(c) { 33 | c.get('.component__type').should('exist') 34 | }, 35 | }, 36 | { 37 | description: 'Should pad group node if label is too wide', 38 | code: stripStartEmptyLines(` 39 | componentDiagram 40 | package "loooooooooooooong" { 41 | () "render()" as renderFn 42 | } 43 | folder "labbbbbbbbbbbbbbel" { 44 | [standalone] 45 | } 46 | `), 47 | onRender(c) { 48 | c.get('.component__type').should('exist') 49 | }, 50 | }, 51 | ]) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/symbols/ellipse.ts: -------------------------------------------------------------------------------- 1 | import { symbolRegistry, safeAssign, ContentArea } from '@pintora/core' 2 | import { makeMark } from '../artist-util' 3 | 4 | symbolRegistry.register('ellipse', { 5 | type: 'factory', 6 | factory(contentArea, { mode }) { 7 | if (mode === 'container') { 8 | return makeEllipseContainer(contentArea) 9 | } 10 | }, 11 | styleMark(mark, def, attrs) { 12 | mark.children.forEach(child => { 13 | safeAssign(child.attrs, attrs) 14 | }) 15 | }, 16 | }) 17 | 18 | function makeEllipseContainer({ width, height, x, y }: ContentArea) { 19 | const halfWidth = width / 2 20 | const padX = 10 21 | const padY = 8 22 | const rx = halfWidth + padX 23 | const ry = height / 2 + padY 24 | const mark = makeMark( 25 | 'group', 26 | {}, 27 | { 28 | children: [ 29 | makeMark('ellipse', { 30 | rx, 31 | ry, 32 | x: x, 33 | y: y, 34 | cx: x, // FIXME: is x 35 | cy: y, 36 | }), 37 | ], 38 | }, 39 | ) 40 | 41 | const leftX = x - padX 42 | const rightX = x + padX 43 | const outerWidth = width + 2 * padX 44 | const outerHeight = height + 2 * padY 45 | const sym = makeMark( 46 | 'symbol', 47 | {}, 48 | { 49 | mark, 50 | symbolBounds: { 51 | left: leftX, 52 | right: rightX, 53 | top: y - ry, 54 | bottom: y + ry, 55 | width: outerWidth, 56 | height: outerHeight, 57 | }, 58 | }, 59 | ) 60 | return sym 61 | } 62 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/style-engine/parser-test.ts: -------------------------------------------------------------------------------- 1 | import { BaseDb, type MakeAction } from '../base-db' 2 | import { genParserWithRules } from '../parser-util' 3 | import { BindRule, StyleRule, type StyleSelector } from './shared' 4 | import grammar from './grammar/style-test' 5 | import { StylePayloads, STYLE_ACTION_HANDLERS } from './parser' 6 | 7 | const parseFn = genParserWithRules(grammar, { 8 | dedupeAmbigousResults: true, 9 | }) 10 | 11 | export type ParserStyleRule = { 12 | selector: StyleSelector 13 | attrs: Array<{ 14 | key: string 15 | value: string 16 | }> 17 | } 18 | 19 | export type Action = MakeAction 20 | 21 | export type StyleData = { 22 | styleRules: StyleRule[] 23 | bindRules: BindRule[] 24 | } 25 | 26 | export class StyleDb extends BaseDb { 27 | styleRules: StyleRule[] = [] 28 | bindRules: BindRule[] = [] 29 | 30 | private ACTION_HANDLERS = STYLE_ACTION_HANDLERS 31 | 32 | apply(action: Action | Action[]) { 33 | if (!action) return 34 | if (Array.isArray(action)) { 35 | action.forEach(a => this.apply(a)) 36 | return 37 | } 38 | if (action.type in this.ACTION_HANDLERS) { 39 | this.ACTION_HANDLERS[action.type].call(this, action as any) 40 | } 41 | } 42 | 43 | getData() { 44 | return this.getBaseDiagramIR() 45 | } 46 | } 47 | 48 | export class StyleParse { 49 | static parse(text: string) { 50 | const actions = parseFn(text) 51 | const styleDb = new StyleDb() 52 | styleDb.apply(actions) 53 | return styleDb.getData() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # project 2 | packages/**/*/parser/*.[tj]s 3 | packages/**/*/grammar/*.[tj]s 4 | demo/dist/ 5 | demo/cypress/videos/ 6 | .idea/ 7 | 8 | # platform 9 | .DS_Store 10 | 11 | # Created by https://www.gitignore.io/api/node 12 | 13 | ### Node ### 14 | # Logs 15 | logs 16 | *.log 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | 33 | reports/* 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (http://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional REPL history 61 | .node_repl_history 62 | 63 | # Output of 'npm pack' 64 | *.tgz 65 | 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | 69 | # dotenv environment variables file 70 | .env 71 | .envrc 72 | 73 | 74 | # End of https://www.gitignore.io/api/node 75 | 76 | lib/ 77 | 78 | *.tsbuildinfo 79 | 80 | # turborepo 81 | .turbo/ 82 | 83 | # Local Netlify folder 84 | .netlify 85 | .turbo 86 | .vercel/ 87 | api/ 88 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/artist/style-context.ts: -------------------------------------------------------------------------------- 1 | export type StyleValue = { 2 | v: V 3 | } 4 | 5 | type ResolveResult = { 6 | resolved: boolean 7 | value: V 8 | } 9 | 10 | export class StyleContext { 11 | parent?: StyleContext 12 | protected values: Record = {} as any 13 | 14 | setParent(c: StyleContext) { 15 | this.parent = c 16 | } 17 | 18 | /** Spawn a child context */ 19 | spawn(): StyleContext { 20 | const child = new StyleContext() 21 | child.setParent(this) 22 | return child 23 | } 24 | 25 | getValue(key: K) { 26 | const result = this.resolve(key) 27 | if (result.resolved) { 28 | return result.value 29 | } 30 | return undefined 31 | } 32 | 33 | set(key: string, v: any) { 34 | this.values[key] = { v } 35 | } 36 | 37 | setValues(obj: Partial) { 38 | for (const [k, v] of Object.entries(obj)) { 39 | this.set(k, v) 40 | } 41 | } 42 | 43 | protected resolve(key: K): ResolveResult { 44 | // eslint-disable-next-line @typescript-eslint/no-this-alias 45 | let current: StyleContext | undefined = this 46 | while (current) { 47 | if (current.values[key]) { 48 | return { 49 | resolved: true, 50 | value: current.values[key].v as any, 51 | } 52 | } 53 | current = current.parent 54 | } 55 | 56 | return { 57 | resolved: false, 58 | value: undefined, 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "vscode-jest-tests", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", 12 | "args": [ 13 | "--runInBand", 14 | "--watchAll=false", 15 | "${fileBasenameNoExtension}" 16 | ], 17 | "cwd": "${workspaceFolder}/packages/pintora-diagrams", 18 | "console": "integratedTerminal", 19 | "internalConsoleOptions": "neverOpen", 20 | "disableOptimisticBPs": true, 21 | "outFiles": ["${workspaceFolder}/packages/pintora-diagrams/lib/**/*.js"], 22 | "resolveSourceMapLocations": ["${workspaceFolder}/packages/pintora-diagrams/lib/**/*.js"], 23 | "sourceMaps": true, 24 | "windows": { 25 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 26 | } 27 | }, 28 | { 29 | "name": "Launch pintora cli", 30 | "program": "${workspaceFolder}/packages/pintora-cli/bin/pintora", 31 | "request": "launch", 32 | "skipFiles": [ 33 | "/**" 34 | ], 35 | "type": "pwa-node" 36 | }, 37 | { 38 | "type": "pwa-chrome", 39 | "request": "launch", 40 | "name": "Open pintora demo", 41 | "url": "http://localhost:3000", 42 | "webRoot": "${workspaceFolder}" 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /website/i18n/zh-CN/docusaurus-plugin-content-docs/current/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | # 介绍 5 | 6 | Pintora 是一个可在浏览器和 Node.js 环境下运行的文字转示意图库。 7 | 8 | 受到 [Mermaid.js](https://mermaid-js.github.io/mermaid/#/) 和 [PlantUML](https://plantuml.com/) 的启发,帮助用户通过简单直观的语言来定义和绘制示意图。 9 | 10 | 通过标准化的图形展示,表达复杂的思想结构和意图,一图胜千言。 11 | 12 | ## 特性 13 | 14 | - 在浏览器端,支持 SVG 和 Canvas 输出 15 | - 在 Node.js 端,支持输出 PNG/JPG 位图和 SVG 文件 16 | - \[计划中\] 具有高度的可组合性,按需加载图表类型,核心代码可控制在较轻量级 17 | - 具有高度的可扩展性,开发者可扩展自己的图表,作为插件接入,详情请见 [实现你自己的图表](./advanced/write-a-custom-diagram.md) 18 | 19 | ## 支持图表 20 | 21 | - [时序图 Sequence Diagram](./diagrams/sequence-diagram.mdx) 22 | - [实体关系图 Entity Relationship Diagram](./diagrams/er-diagram.mdx) 23 | - [组件图 Component Diagram](./diagrams/component-diagram.mdx) 24 | - [活动图 Activity Diagram](./diagrams/activity-diagram.mdx) 25 | - [思维导图 Mind Map](./diagrams/mindmap.mdx) 实验中 26 | - [甘特图 Gantt Diagram](./diagrams/gantt-diagram.mdx) 实验中 27 | - [DOT Diagram](./diagrams/dot-diagram.mdx) 实验中 28 | 29 | ```pintora play 30 | mindmap 31 | @param layoutDirection TB 32 | * Pintora diagrams 33 | ** UML 图表 34 | *** 时序图 Sequence Diagram 35 | *** 活动图 Activity Diagram 36 | *** 组件图 Component Diagram 37 | ** Non-UML Diagrams 38 | *** 实体关系图 Entity Relationship Diagram 39 | *** 思维导图 Mind Map 40 | *** 甘特图 Diagram 41 | *** DOT Diagram 42 | ``` 43 | 44 | ## 💻 编辑器支持 45 | 46 | - VSCode 插件 [pintora-vscode](https://marketplace.visualstudio.com/items?itemName=hikerpig.pintora-vscode), 提供 `.pintora` 文件的语法高亮和 Markdown 代码块预览等功能。 47 | -------------------------------------------------------------------------------- /packages/pintora-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pintora/cli", 3 | "version": "0.8.1", 4 | "description": "A node.js version of pintora", 5 | "keywords": [ 6 | "pintora" 7 | ], 8 | "author": "hikerpig ", 9 | "homepage": "https://github.com/hikerpig/pintora#readme", 10 | "license": "MIT", 11 | "main": "lib/index.js", 12 | "directories": { 13 | "lib": "lib", 14 | "test": "__tests__" 15 | }, 16 | "files": [ 17 | "lib", 18 | "CHANGELOG.md" 19 | ], 20 | "bin": { 21 | "pintora": "./bin/pintora" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/hikerpig/pintora.git", 29 | "directory": "packages/pintora-cli" 30 | }, 31 | "scripts": { 32 | "compile": "tsc", 33 | "watch": "tsc -w", 34 | "test": "jest" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/hikerpig/pintora/issues" 38 | }, 39 | "dependencies": { 40 | "@pintora/core": "workspace:^0.8.1", 41 | "@pintora/renderer": "workspace:^0.8.1", 42 | "@pintora/standalone": "workspace:^0.8.1", 43 | "canvas": "^3.0.1", 44 | "consola": "^3.0.0", 45 | "jsdom": "^26.0.0", 46 | "mime-types": "^3.0.0", 47 | "yargs": "^17.7.2" 48 | }, 49 | "devDependencies": { 50 | "@pintora/test-shared": "workspace:^0.5.2", 51 | "@types/jsdom": "^21.0.0", 52 | "@types/mime-types": "^3.0.0", 53 | "@types/node": "^18.12.0", 54 | "@types/yargs": "^17.0.33", 55 | "typescript": "^5.9.0" 56 | }, 57 | "engines": { 58 | "node": ">=18.12.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/scripts/build-grammar.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path') 3 | const { compileGrammar } = require('@pintora/development-kit') 4 | 5 | const grammarFiles = [ 6 | { input: 'src/util/preproccesor/parser/preproccesor.ne', output: 'src/util/preproccesor/parser/preproccesor.ts' }, 7 | { input: 'src/util/style-engine/grammar/style-test.ne', output: 'src/util/style-engine/grammar/style-test.ts' }, 8 | { input: 'src/er/parser/erDiagram.ne', output: 'src/er/parser/erDiagram.ts' }, 9 | { input: 'src/sequence/parser/sequenceDiagram.ne', output: 'src/sequence/parser/sequenceDiagram.ts' }, 10 | { input: 'src/component/parser/componentDiagram.ne', output: 'src/component/parser/componentDiagram.ts' }, 11 | { input: 'src/activity/parser/activityDiagram.ne', output: 'src/activity/parser/activityDiagram.ts' }, 12 | { input: 'src/mindmap/parser/mindmap.ne', output: 'src/mindmap/parser/mindmap.ts' }, 13 | { input: 'src/gantt/parser/gantt.ne', output: 'src/gantt/parser/gantt.ts' }, 14 | { input: 'src/dot/parser/dotDiagram.ne', output: 'src/dot/parser/dotDiagram.ts' }, 15 | { input: 'src/class/parser/classDiagram.ne', output: 'src/class/parser/classDiagram.ts' }, 16 | ] 17 | 18 | grammarFiles.forEach(async ({ input, output }) => { 19 | const packagePath = path.join(__dirname, '..') 20 | const outputPath = path.join(__dirname, '..', output) 21 | const includePath = path.join(packagePath, 'shared-grammars/') 22 | compileGrammar({ 23 | input, 24 | output: outputPath, 25 | includePaths: [includePath], 26 | basePath: packagePath, 27 | executeCommand: 'pnpm exec', 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/font-config.ts: -------------------------------------------------------------------------------- 1 | import { MarkAttrs } from '@pintora/core' 2 | import { DEFAULT_FONT_FAMILY } from './text' 3 | 4 | /** 5 | * Base font configuration that should be used by all diagrams 6 | */ 7 | export interface BaseFontConfig { 8 | /** Font family, defaults to DEFAULT_FONT_FAMILY */ 9 | fontFamily: string 10 | /** Font size in pixels, defaults to 14 */ 11 | fontSize: number 12 | /** Font weight, defaults to 'normal' */ 13 | fontWeight: MarkAttrs['fontWeight'] 14 | /** Font style (normal, italic, oblique), defaults to 'normal' */ 15 | fontStyle: any 16 | // fontStyle: MarkAttrs['fontStyle'] 17 | } 18 | 19 | /** 20 | * Default font configuration that can be extended by diagrams 21 | */ 22 | export const defaultFontConfig: BaseFontConfig = { 23 | fontFamily: DEFAULT_FONT_FAMILY, 24 | fontSize: 14, 25 | fontWeight: 'normal', 26 | fontStyle: 'normal', 27 | } 28 | 29 | /** 30 | * Helper to get font config parameter rules 31 | */ 32 | export function getFontConfigRules() { 33 | return { 34 | fontFamily: { valueType: 'string' }, 35 | fontSize: { valueType: 'size' }, 36 | fontWeight: { valueType: 'fontWeight' }, 37 | fontStyle: { valueType: 'string' }, 38 | } as const 39 | } 40 | 41 | /** 42 | * Utility function for retrieving font configuration that can be shared between ER and Sequence artists. 43 | */ 44 | export function getFontConfig(conf: Partial, opts: Partial = {}): BaseFontConfig { 45 | return { 46 | fontFamily: opts.fontFamily || conf.fontFamily, 47 | fontSize: opts.fontSize || conf.fontSize, 48 | fontWeight: opts.fontWeight || conf.fontWeight, 49 | fontStyle: opts.fontStyle || conf.fontStyle, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /website/i18n/zh-CN/docusaurus-plugin-content-docs/current/diagrams/mindmap.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mind Map 思维导图 3 | --- 4 | 5 | Mind Map 语法基于 PlantUML 的 MindMap 语法。 6 | 7 | 实验中 现在还在实验期间,语法可能会改变。 8 | 9 | ## OrgMode 形式的层级语法 10 | 11 | 使用不同数量的 `*` 来表明节点深度,从 1 开始。 12 | 13 | ```pintora play 14 | mindmap 15 | * UML Diagrams 16 | ** Behavior Diagrams 17 | *** Sequence Diagram 18 | *** State Diagram 19 | ``` 20 | 21 | ## 使用 +/- 号表明节点方向 22 | 23 | ```pintora play 24 | mindmap 25 | + UML Diagrams 26 | ++ Behavior Diagrams 27 | +++ Sequence Diagram 28 | +++ State Diagram 29 | +++ Activity Diagram 30 | -- Structural Diagrams 31 | --- Class Diagram 32 | --- Component Diagram 33 | ``` 34 | 35 | ## 多行文本 36 | 37 | 节点的文本可以是多行文字,需要以 `:` 开始,以 `;` 结尾。 38 | 39 | ```pintora play 40 | mindmap 41 | * example 42 | ** :can have multiline 43 | text; 44 | ``` 45 | 46 | ## 具有多个根节点的导图 47 | 48 | 可以创建具有多个根节点的导图,每个深度为 1 的节点都会创建一棵树。 49 | 50 | ```pintora play 51 | mindmap 52 | * UML Diagram 53 | ** Sequence Diagram 54 | ** State Diagram 55 | ** Component Diagram 56 | * Non-UML Diagram 57 | ** Entity Relationship Diagram 58 | ** Mind Map 59 | ``` 60 | 61 | ## 覆盖设置 62 | 63 | 可以使用 `@param` 指令覆盖图表的部分设置。 64 | 65 | 可设置项的说明请见 [Config page](../configuration/config.md#mindmap). 66 | 67 | ```pintora play 68 | mindmap 69 | @param layoutDirection TB 70 | @param { 71 | l1NodeBgColor #2B7A5D 72 | l1NodeTextColor #fff 73 | l2NodeBgColor #26946C 74 | l2NodeTextColor #fff 75 | nodeBgColor #67B599 76 | textColor #fff 77 | } 78 | + UML Diagrams 79 | ++ Behavior Diagrams 80 | +++ Sequence Diagram 81 | +++ State Diagram 82 | +++ Activity Diagram 83 | ++ Structural Diagrams 84 | +++ Class Diagram 85 | +++ Component Diagram 86 | ``` 87 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/er/__tests__/er-artist.spec.ts: -------------------------------------------------------------------------------- 1 | import * as pintora from '@pintora/core' 2 | import { EXAMPLES } from '@pintora/test-shared' 3 | import { testDraw, prepareDiagramConfig, stripDrawResultForSnapshot } from '../../__tests__/test-util' 4 | import { erDiagram } from '../index' 5 | 6 | describe('er-artist', () => { 7 | beforeAll(() => { 8 | prepareDiagramConfig() 9 | pintora.diagramRegistry.registerDiagram('erDiagram', erDiagram) 10 | }) 11 | 12 | it('will not throw error', () => { 13 | expect(testDraw(EXAMPLES.erLarge.code).graphicIR).toBeTruthy() 14 | }) 15 | 16 | it('will process containerSize and @useMaxWidth', () => { 17 | const code = ` 18 | erDiagram 19 | @param useMaxWidth true 20 | artists { 21 | INTEGER ArtistId 22 | NVARCHAR Name 23 | } 24 | albums 25 | ` 26 | const result = testDraw(code, { containerSize: { width: 1000 } }) 27 | expect(Math.round(result.graphicIR.width)).toBe(1000) 28 | }) 29 | 30 | it('will draw inheritance', () => { 31 | const code = ` 32 | erDiagram 33 | person { 34 | int age 35 | string phone_number 36 | } 37 | 38 | customer inherit person 39 | deliverer inherit person 40 | 41 | customer { 42 | string address "deliver address" 43 | string id PK 44 | } 45 | ` 46 | expect(stripDrawResultForSnapshot(testDraw(code))).toMatchSnapshot() 47 | }) 48 | 49 | it('can parse and handle bindClass', () => { 50 | const code = ` 51 | erDiagram 52 | e1 { 53 | int age 54 | } 55 | e2 { 56 | string name 57 | } 58 | 59 | @bindClass entity-e1 test-class 60 | ` 61 | expect(stripDrawResultForSnapshot(testDraw(code))).toMatchSnapshot() 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /demo/src/live-editor/containers/AppSidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { useNavigate, useLocation } from 'react-router-dom' 3 | import classnames from 'classnames' 4 | import './AppSidebar.less' 5 | import { useDarkMode } from 'usehooks-ts' 6 | 7 | type Props = {} 8 | 9 | const SIDEBAR_ICONS = [ 10 | { 11 | name: 'editor' as const, 12 | label: 'Editor', 13 | icon: 'eva:file-text-outline', 14 | }, 15 | { 16 | name: 'theme-preview' as const, 17 | label: 'Theme Preview', 18 | icon: 'eva:color-palette-outline', 19 | }, 20 | ] 21 | 22 | type ArrayElement = T extends Array ? I : any 23 | 24 | type SidebarItem = ArrayElement 25 | 26 | const AppSidebar = ({}: Props) => { 27 | const { isDarkMode } = useDarkMode() 28 | const navigate = useNavigate() 29 | const currentLocation = useLocation() 30 | 31 | const bgColorCls = isDarkMode ? 'bg-transparent' : 'bg-warmGray-100' 32 | const cls = classnames(`AppSidebar ${bgColorCls} flex flex-col items-center`) 33 | 34 | const handleClick = useCallback((item: SidebarItem) => { 35 | navigate(item.name) 36 | }, []) 37 | 38 | return ( 39 |
40 | {SIDEBAR_ICONS.map(item => { 41 | const itemCls = classnames('AppSidebar__item btn btn-ghost btn-square mb-2', { 42 | 'btn-active': `/${item.name}` === currentLocation.pathname, 43 | }) 44 | return ( 45 |
handleClick(item)}> 46 | 47 |
48 |
49 | ) 50 | })} 51 |
52 | ) 53 | } 54 | 55 | export default AppSidebar 56 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/util/symbols/actor.ts: -------------------------------------------------------------------------------- 1 | import { symbolRegistry, safeAssign, ContentArea } from '@pintora/core' 2 | import { makeMark } from '../artist-util' 3 | 4 | symbolRegistry.register('actor', { 5 | type: 'factory', 6 | modes: ['icon'], 7 | factory(contentArea) { 8 | // has only icon mode 9 | return makeIcon(contentArea) 10 | }, 11 | styleMark(mark, def, attrs) { 12 | mark.children.forEach(child => { 13 | safeAssign(child.attrs, attrs) 14 | if (child.type === 'path') { 15 | child.attrs.fill = null 16 | } 17 | child.attrs.lineWidth = Math.max(attrs.lineWidth || 0, 1.5) 18 | }) 19 | }, 20 | }) 21 | 22 | function makeIcon({ width, height, x, y }: ContentArea) { 23 | const radius = Math.min(width, height) / 5 24 | const topY = y - height / 2 25 | const leftX = x - radius * 1.5 26 | const rightX = x + radius * 1.5 27 | const bodyHeight = radius * 1 28 | const legHeight = radius * 2 29 | const mark = makeMark( 30 | 'group', 31 | {}, 32 | { 33 | children: [ 34 | makeMark('circle', { 35 | r: radius, 36 | x, 37 | y: topY + radius, 38 | width, 39 | height, 40 | }), 41 | makeMark('path', { 42 | path: [ 43 | ['M', leftX, y + radius / 3], 44 | ['L', rightX, y + radius / 3], 45 | ['M', x, y - radius / 2], 46 | ['L', x, y + bodyHeight], 47 | ['l', -radius, legHeight], 48 | ['l', radius, -legHeight], 49 | ['l', radius, legHeight], 50 | ], 51 | }), 52 | ], 53 | }, 54 | ) 55 | const sym = makeMark( 56 | 'symbol', 57 | {}, 58 | { 59 | mark, 60 | }, 61 | ) 62 | return sym 63 | } 64 | -------------------------------------------------------------------------------- /demo/cypress/e2e/dot/dot.spec.ts: -------------------------------------------------------------------------------- 1 | import { stripStartEmptyLines } from '@pintora/test-shared' 2 | import { makeSnapshotCases } from '../test-utils/render' 3 | 4 | describe('DOT Diagram', () => { 5 | makeSnapshotCases([ 6 | { 7 | description: 'Should render arrow shapes', 8 | code: stripStartEmptyLines(` 9 | dotDiagram 10 | digraph { 11 | bgcolor="#faf5f5"; 12 | node [color="#111",bgcolor=orange] 13 | 14 | s1 -> e1 [arrowhead="box"] 15 | s2 -> e2 [arrowhead="obox"] 16 | s3 -> e3 [arrowhead="dot"] 17 | s4 -> e4 [arrowhead="odot"] 18 | s5 -> e5 [arrowhead="open"] 19 | s6 -> e6 [arrowhead="diamond"] 20 | s7 -> e7 [arrowhead="ediamond"] 21 | } 22 | `), 23 | }, 24 | { 25 | description: 'Should render node shapes', 26 | code: stripStartEmptyLines(` 27 | dotDiagram 28 | @param fontWeight bold 29 | digraph { 30 | bgcolor="#faf5f5"; 31 | node [color="#111",bgcolor=orange] 32 | 33 | ellipse [shape="ellipse"]; 34 | circle [shape="circle"]; 35 | diamond [shape="diamond"]; 36 | plaintext [shape="plaintext"]; 37 | } 38 | `), 39 | }, 40 | { 41 | description: 'Subgraph should be at least as wide as its label', 42 | code: stripStartEmptyLines(` 43 | dotDiagram 44 | graph { 45 | subgraph cluster_ground_floor { 46 | bgcolor="lightgreen" 47 | label="Ground Floor Long" 48 | Lounge 49 | Kitchen 50 | } 51 | 52 | subgraph cluster_top_floor { 53 | bgcolor="lightyellow" 54 | label="Top Floor" 55 | Bedroom 56 | } 57 | 58 | Lounge -- Kitchen 59 | } 60 | `), 61 | }, 62 | ]) 63 | }) 64 | -------------------------------------------------------------------------------- /demo/src/live-editor/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { DEMO_BASE_URL, GITHUB_URL, DOC_URL } from '../../const' 3 | import { useDarkMode } from 'usehooks-ts' 4 | import './Header.less' 5 | 6 | const LIVE_EDITOR_URL = `${DEMO_BASE_URL}live-editor/` 7 | 8 | function getPublicUrl(p: string) { 9 | return `${DEMO_BASE_URL}/${p.replace(/^\//, '')}` 10 | } 11 | 12 | const HEADER_MODE_ICON_STYLE: React.CSSProperties = { 13 | marginRight: '0.5em', 14 | position: 'relative', 15 | top: '2px', 16 | } 17 | 18 | export default function Header() { 19 | const { isDarkMode, enable, disable } = useDarkMode() 20 | const toggleDarkMode = React.useCallback( 21 | (e: React.ChangeEvent) => { 22 | if (e.target.checked) { 23 | enable() 24 | } else { 25 | disable() 26 | } 27 | }, 28 | [isDarkMode], 29 | ) 30 | 31 | const modeIcon = isDarkMode ? 'eva:moon-fill' : 'eva:sun-fill' 32 | 33 | return ( 34 | 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /packages/pintora-diagrams/src/dot/artist/draw-node.ts: -------------------------------------------------------------------------------- 1 | import { LayoutNode } from '../../util/graph' 2 | import { floorValues } from '../../util/bound' 3 | import { TRANSFORM_GRAPH } from '../../util/mark-positioner' 4 | import { DOTShapeType } from '../db' 5 | import { ContentArea, makeMark, MarkAttrs, symbolRegistry, TSize } from '@pintora/core' 6 | 7 | type DrawNodeContext = { 8 | data: LayoutNode 9 | shape: string 10 | markAttrs: MarkAttrs 11 | textDims: TSize 12 | } 13 | 14 | const SHAPE_MAP: Partial> = { 15 | ellipse: 'ellipse', 16 | circle: 'circle', 17 | } 18 | 19 | /** 20 | * Draw dot node, may be shape or just simple box 21 | */ 22 | export function drawNodeShape(context: DrawNodeContext) { 23 | const { data, shape, textDims, markAttrs } = context 24 | const flooredGeom = floorValues(TRANSFORM_GRAPH.graphNodeToRectStart(data)) 25 | 26 | if (shape) { 27 | const mappedShape = SHAPE_MAP[shape] 28 | const symbolDef = symbolRegistry.get(mappedShape || shape) 29 | if (symbolDef) { 30 | const contentArea: ContentArea = { 31 | x: data.x, 32 | y: data.y, 33 | width: textDims.width, 34 | height: textDims.height, 35 | } 36 | const sym = symbolRegistry.create(shape, { 37 | mode: 'container', 38 | attrs: markAttrs, 39 | contentArea, 40 | }) 41 | // console.log('draw shape', shape, sym) 42 | return { 43 | containerNode: sym, 44 | } 45 | } 46 | } 47 | 48 | const nodeRect = makeMark('rect', { 49 | ...flooredGeom, 50 | ...markAttrs, 51 | }) 52 | if (shape === 'plaintext') { 53 | nodeRect.attrs.fill = 'transparent' 54 | nodeRect.attrs.stroke = 'transparent' 55 | } 56 | 57 | return { 58 | containerNode: nodeRect, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/pintora-target-wintercg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pintora/target-wintercg", 3 | "version": "0.2.1", 4 | "description": "Pintora standalone target thast can run in WinterCG runtime", 5 | "homepage": "https://github.com/hikerpig/pintora#readme", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/hikerpig/pintora.git", 9 | "directory": "packages/pintora-target-wintercg" 10 | }, 11 | "license": "MIT", 12 | "author": "hikerpig ", 13 | "module": "./dist/runtime.esm.js", 14 | "types": "types/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "CHANGELOG.md", 18 | "README.md" 19 | ], 20 | "scripts": { 21 | "build-scripts": "tsc", 22 | "compile": "ts-node build/esbuild.ts" 23 | }, 24 | "dependencies": { 25 | "@pintora/standalone": "workspace:*" 26 | }, 27 | "devDependencies": { 28 | "@edge-runtime/types": "^4.0.0", 29 | "@pintora/renderer": "workspace:*", 30 | "@types/fontkit": "^2.0.6", 31 | "@types/node": "^16.11.7", 32 | "@types/svgdom": "^0.1.2", 33 | "base64-js": "^1.5.1", 34 | "buffer": "^6.0.3", 35 | "edge-runtime": "^4.0.0", 36 | "esbuild": "^0.27.0", 37 | "esbuild-visualizer": "^0.7.0", 38 | "events": "^3.3.0", 39 | "fontkit": "^2.0.2", 40 | "ieee754": "^1.2.1", 41 | "inherits": "^2.0.4", 42 | "path": "^0.12.7", 43 | "path-browserify": "^1.0.1", 44 | "punycode": "^2.3.1", 45 | "readable-stream": "^3.5.0", 46 | "signal-exit": "^4.1.0", 47 | "stream": "^0.0.3", 48 | "stream-browserify": "^3.0.0", 49 | "svgdom": "^0.1.19", 50 | "ts-node": "^10.9.2", 51 | "typescript": "^5.9.0", 52 | "url": "^0.11.3", 53 | "util": "^0.12.5" 54 | }, 55 | "publishConfig": { 56 | "access": "public" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /website/static/style/carbon.css: -------------------------------------------------------------------------------- 1 | #carbonads * { 2 | margin: initial; 3 | padding: initial; 4 | } 5 | 6 | #carbonads { 7 | display: flex; 8 | 9 | bottom: 100px; 10 | position: fixed; 11 | right: 10px; 12 | 13 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 14 | Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', Helvetica, Arial, 15 | sans-serif; 16 | font-size: 11px; 17 | overflow: hidden; 18 | max-width: 130px; 19 | border-radius: 4px; 20 | box-shadow: 21 | 0 0 1px hsla(0, 0%, 0%, .05), 22 | 0 0 2px hsla(0, 0%, 0%, .05), 23 | 0 0 4px hsla(0, 0%, 0%, .05); 24 | } 25 | 26 | #carbonads * { 27 | margin: initial; 28 | padding: initial; 29 | } 30 | 31 | #carbonads a { 32 | text-decoration: none; 33 | color: #111; 34 | } 35 | 36 | #carbonads a:hover { 37 | color: #111; 38 | } 39 | 40 | #carbonads .carbon-img { 41 | display: block; 42 | 43 | line-height: 1; 44 | max-width: 130px; 45 | } 46 | 47 | #carbonads .carbon-img img { 48 | display: block; 49 | margin: 0 auto; 50 | 51 | width: 130px; 52 | height: 100px; 53 | } 54 | 55 | #carbonads .carbon-text { 56 | display: block; 57 | padding: 8px 10px; 58 | 59 | line-height: 1.35; 60 | text-align: left; 61 | background-color: hsl(0, 0%, 98%); 62 | } 63 | 64 | #carbonads .carbon-poweredby { 65 | display: block; 66 | padding: 10px; 67 | 68 | font-size: 8px; 69 | font-weight: 600; 70 | line-height: 0; 71 | text-transform: uppercase; 72 | background: repeating-linear-gradient(-45deg, 73 | transparent, 74 | transparent 5px, 75 | hsla(0, 0%, 0%, .025) 5px, 76 | hsla(0, 0%, 0%, .025) 10px) hsla(203, 11%, 95%, .4); 77 | background-color: hsl(0, 0%, 98%); 78 | } 79 | 80 | @media only screen and (max-width: 1023px) { 81 | #carbonads { 82 | display: none; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pintora-website", 3 | "version": "0.7.2", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^3.7.0", 18 | "@docusaurus/preset-classic": "^3.7.0", 19 | "@docusaurus/theme-common": "^3.7.0", 20 | "@mdx-js/react": "^3.0.0", 21 | "@pintora/standalone": "workspace:^0.8.0", 22 | "@pintora/test-shared": "workspace:^0.5.2", 23 | "@svgr/webpack": "^8.0.0", 24 | "clsx": "^2.0.0", 25 | "docusaurus-plugin-less": "^2.0.2", 26 | "file-loader": "^6.2.0", 27 | "less-loader": "^12.0.0", 28 | "mdx-embed": "^1.0.0", 29 | "path-browserify": "^1.0.1", 30 | "prism-react-renderer": "^1.2.1", 31 | "react": "^18.0.0", 32 | "react-dom": "^18.0.0", 33 | "shiki": "^3.0.0", 34 | "url-loader": "^4.1.1" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.5%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "@docusaurus/faster": "^3.7.0", 50 | "@docusaurus/module-type-aliases": "^3.7.0", 51 | "@docusaurus/tsconfig": "^3.7.0", 52 | "@types/react": "^18.0.0", 53 | "@types/react-helmet": "^6.1.5", 54 | "@types/react-router-dom": "^5.3.2", 55 | "typescript": "^5.9.0" 56 | } 57 | } 58 | --------------------------------------------------------------------------------