├── .nvmrc ├── packages ├── docs │ ├── yarn.lock │ ├── static │ │ ├── favicon.ico │ │ ├── media │ │ │ ├── hero.jpg │ │ │ ├── concepts.png │ │ │ ├── kicker.jpg │ │ │ └── extensions │ │ │ │ ├── khr-materials-sheen.png │ │ │ │ ├── khr-materials-unlit.png │ │ │ │ ├── khr-materials-variants.jpg │ │ │ │ ├── khr-materials-volume.png │ │ │ │ ├── khr-materials-clearcoat.png │ │ │ │ └── khr-material-pbr-specular-glossiness.png │ │ └── main.css │ ├── src │ │ ├── routes │ │ │ ├── installation │ │ │ │ └── +page.svx │ │ │ ├── +page.svelte │ │ │ ├── +page.server.ts │ │ │ ├── modules │ │ │ │ └── [module] │ │ │ │ │ └── [kind] │ │ │ │ │ └── [slug] │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ └── +page.svelte │ │ │ ├── +layout.server.ts │ │ │ └── +layout.svelte │ │ ├── app.d.ts │ │ ├── app.html │ │ └── lib │ │ │ └── server │ │ │ └── model │ │ │ └── index.ts │ ├── README.md │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── vite.config.ts │ ├── tsconfig.json │ ├── svelte.config.js │ └── package.json ├── parse │ ├── yarn.lock │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── utils │ │ │ ├── format.ts │ │ │ ├── sort.ts │ │ │ └── no-case.ts │ │ ├── constants.ts │ │ ├── types.ts │ │ ├── Parser.ts │ │ └── Encoder.ts │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── test │ │ └── index.test.ts ├── svelte │ ├── .npmrc │ ├── static │ │ └── favicon.png │ ├── README.md │ ├── .gitignore │ ├── src │ │ ├── lib │ │ │ ├── Comment.svelte │ │ │ ├── Reference.svelte │ │ │ ├── index.ts │ │ │ ├── Sources.svelte │ │ │ ├── Enum.svelte │ │ │ ├── Tags.svelte │ │ │ ├── EnumMember.svelte │ │ │ ├── Property.svelte │ │ │ ├── Interface.svelte │ │ │ ├── Function.svelte │ │ │ ├── Method.svelte │ │ │ ├── Constructor.svelte │ │ │ └── Class.svelte │ │ ├── app.html │ │ ├── app.d.ts │ │ └── routes │ │ │ └── +page.svelte │ ├── vite.config.ts │ ├── svelte.config.js │ ├── tsconfig.json │ └── package.json └── README.md ├── .npmrc ├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── lerna.json ├── .prettierrc ├── .prettierignore ├── renovate.json ├── nx.json ├── README.md ├── package.json └── LICENSE.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /packages/docs/yarn.lock: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/parse/yarn.lock: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .nx/cache -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [donmccurdy] 2 | -------------------------------------------------------------------------------- /packages/svelte/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /packages/parse/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | -------------------------------------------------------------------------------- /packages/docs/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/favicon.ico -------------------------------------------------------------------------------- /packages/svelte/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/svelte/static/favicon.png -------------------------------------------------------------------------------- /packages/docs/static/media/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/hero.jpg -------------------------------------------------------------------------------- /packages/docs/static/media/concepts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/concepts.png -------------------------------------------------------------------------------- /packages/docs/static/media/kicker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/kicker.jpg -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "npmClient": "yarn", 4 | "version": "0.4.1" 5 | } 6 | -------------------------------------------------------------------------------- /packages/docs/src/routes/installation/+page.svx: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Lorem ipsum dolorem... 4 | 5 | ```html 6 | lorem ipsum 7 | ``` -------------------------------------------------------------------------------- /packages/parse/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Parser'; 2 | export * from './Encoder'; 3 | export * from './types'; 4 | export * from './utils/sort'; 5 | -------------------------------------------------------------------------------- /packages/docs/README.md: -------------------------------------------------------------------------------- 1 | # 🌿 @greendoc/docs 2 | 3 | > WORK IN PROGRESS / NOT READY FOR USE 4 | 5 | _Documentation for, and example of, the greendoc system._ 6 | -------------------------------------------------------------------------------- /packages/docs/static/media/extensions/khr-materials-sheen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/extensions/khr-materials-sheen.png -------------------------------------------------------------------------------- /packages/docs/static/media/extensions/khr-materials-unlit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/extensions/khr-materials-unlit.png -------------------------------------------------------------------------------- /packages/svelte/README.md: -------------------------------------------------------------------------------- 1 | # 🌿 @greendoc/svelte 2 | 3 | > WORK IN PROGRESS / NOT READY FOR USE 4 | 5 | _Renders the API model in HTML, using [Svelte](https://svelte.dev/)._ 6 | -------------------------------------------------------------------------------- /packages/docs/static/media/extensions/khr-materials-variants.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/extensions/khr-materials-variants.jpg -------------------------------------------------------------------------------- /packages/docs/static/media/extensions/khr-materials-volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/extensions/khr-materials-volume.png -------------------------------------------------------------------------------- /packages/parse/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ES2022"], 4 | "module": "ES2022", 5 | "moduleResolution": "node", 6 | "target": "ES2022" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/docs/static/media/extensions/khr-materials-clearcoat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/extensions/khr-materials-clearcoat.png -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /packages/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /dist 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /packages/docs/static/media/extensions/khr-material-pbr-specular-glossiness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/greendoc/main/packages/docs/static/media/extensions/khr-material-pbr-specular-glossiness.png -------------------------------------------------------------------------------- /packages/parse/README.md: -------------------------------------------------------------------------------- 1 | # 🌿 @greendoc/parse 2 | 3 | > WORK IN PROGRESS / NOT READY FOR USE 4 | 5 | _Parses TS/JS (with [API Extractor](https://api-extractor.com/)) and builds a strongly-typed API model in JSON._ 6 | -------------------------------------------------------------------------------- /packages/docs/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |

🌿 greendoc

6 | 7 | An adaptable system for generating documentation of TypeScript APIs. 8 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Comment.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | {@html data} 8 |
9 |
10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/docs/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /packages/docs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/docs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()], 6 | server: { port: 4000 } 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /packages/svelte/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()], 6 | server: { port: 4001 } 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | packages/*/build 4 | packages/*/dist 5 | packages/*/.svelte-kit 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | 15 | /.nx/cache -------------------------------------------------------------------------------- /packages/docs/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | // and what to do when importing types 4 | declare namespace App { 5 | // interface Locals {} 6 | // interface PageData {} 7 | // interface Error {} 8 | // interface Platform {} 9 | } 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>donmccurdy/renovate-config"], 3 | "packageRules": [ 4 | { 5 | "description": "TypeScript support missing in marked-highlight. Also see https://github.com/donmccurdy/greendoc/issues/19.", 6 | "matchPackageNames": ["marked"], 7 | "enabled": false 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/docs/src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad } from './$types'; 2 | 3 | export const load: PageServerLoad = async ({ params }) => { 4 | return { 5 | metadata: { 6 | title: 'greendoc', 7 | snippet: 'An adaptable system for generating documentation of TypeScript APIs.' 8 | } 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: preprocess(), 7 | 8 | kit: { 9 | adapter: adapter() 10 | } 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /packages/svelte/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/svelte/src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#app 4 | // for information about these interfaces 5 | // and what to do when importing types 6 | declare namespace App { 7 | // interface Locals {} 8 | // interface PageData {} 9 | // interface Error {} 10 | // interface Platform {} 11 | } 12 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Reference.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | {#if data.path} 8 | {data.name} 9 | {:else} 10 | {data.name} 11 | {/if} 12 | 13 | -------------------------------------------------------------------------------- /packages/parse/src/utils/format.ts: -------------------------------------------------------------------------------- 1 | import { marked } from 'marked'; 2 | import hljs from 'highlight.js'; 3 | 4 | marked.setOptions({ 5 | highlight: (code, lang) => { 6 | const language = hljs.getLanguage(lang) ? lang : 'plaintext'; 7 | return hljs.highlight(code, { language }).value; 8 | } 9 | }); 10 | 11 | export function markedFormatter(md: string): string { 12 | return marked.parse(md); 13 | } 14 | -------------------------------------------------------------------------------- /packages/parse/src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * JSDoc and TSDoc tags included in API serialization. Currently this list 3 | * is chosen to represent presentation-related tags, like alpha/beta/public 4 | * release status, rather than type-related tags. 5 | */ 6 | export const SUPPORTED_TAGS = new Set([ 7 | 'alpha', 8 | 'beta', 9 | 'experimental', 10 | 'public', 11 | 'deprecated', 12 | 'category' // mine 13 | ]); 14 | -------------------------------------------------------------------------------- /packages/docs/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %sveltekit.head% 10 | 11 | 12 | 13 | 14 |
%sveltekit.body%
15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 | # 🌿 greendoc → packages 2 | 3 | > WORK IN PROGRESS / NOT READY FOR USE 4 | 5 | _An adaptable system for generating documentation of TypeScript and JavaScript APIs._ 6 | 7 | ## Packages 8 | 9 | - [`@greendoc/docs`](./docs): Documentation for, and example of, the greendoc system 10 | - [`@greendoc/parse`](./parse): Parses TS/JS (with [TS Morph](https://ts-morph.com/)) and builds a strongly-typed API model in JSON 11 | - [`@greendoc/svelte`](./svelte): Renders the API model in HTML, using [Svelte](https://svelte.dev/) 12 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "targetDefaults": { 3 | "build": { 4 | "dependsOn": ["^build"], 5 | "outputs": ["{projectRoot}/dist", "{projectRoot}/build"], 6 | "cache": true 7 | }, 8 | "check": { 9 | "cache": true 10 | }, 11 | "check:watch": { 12 | "cache": true 13 | }, 14 | "lint": { 15 | "cache": true 16 | }, 17 | "format": { 18 | "cache": true 19 | }, 20 | "test": { 21 | "cache": true 22 | } 23 | }, 24 | "namedInputs": { 25 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 26 | "sharedGlobals": [], 27 | "production": ["default"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /packages/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /packages/docs/src/routes/modules/[module]/[kind]/[slug]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { error } from '@sveltejs/kit'; 2 | import type { PageServerLoad } from './$types'; 3 | import { parser, encoder, getMetadata } from '$lib/server/model'; 4 | import type { GD } from '@greendoc/parse'; 5 | 6 | export const load: PageServerLoad<{ export: GD.ApiItem }> = async ({ params }) => { 7 | const slug = params.slug.replace(/\.html$/, ''); 8 | const item = parser.getItemBySlug(slug); 9 | const encodedItem = encoder.encodeItem(item); 10 | if (item && encodedItem) { 11 | return { 12 | metadata: getMetadata(encodedItem), 13 | export: encodedItem 14 | }; 15 | } 16 | error(404, 'Not found'); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/docs/svelte.config.js: -------------------------------------------------------------------------------- 1 | // import adapter from '@sveltejs/adapter-auto'; 2 | import adapter from '@sveltejs/adapter-static'; 3 | import preprocess from 'svelte-preprocess'; 4 | import { mdsvex } from 'mdsvex'; 5 | 6 | /** @type {import('@sveltejs/kit').Config} */ 7 | const config = { 8 | extensions: ['.svelte', '.md', '.svx'], 9 | 10 | // Consult https://github.com/sveltejs/svelte-preprocess 11 | // for more information about preprocessors 12 | preprocess: [preprocess(), mdsvex({ extensions: ['.md', '.svx'] })], 13 | 14 | kit: { 15 | adapter: adapter(), 16 | prerender: { 17 | handleHttpError: 'warn', 18 | handleMissingId: 'warn' 19 | } 20 | } 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@greendocs/docs", 3 | "version": "0.4.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --force", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --check src", 12 | "format": "prettier --write src", 13 | "clean": "rimraf build/*" 14 | }, 15 | "dependencies": { 16 | "@greendoc/parse": "^0.4.1", 17 | "@greendoc/svelte": "^0.4.1", 18 | "@types/he": "^1.2.3", 19 | "he": "^1.2.0", 20 | "ts-morph": "^23.0.0" 21 | }, 22 | "type": "module" 23 | } 24 | -------------------------------------------------------------------------------- /packages/svelte/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |

Welcome to your library project

7 |

Create your package using @sveltejs/package and preview/showcase your work with SvelteKit

8 |

Visit kit.svelte.dev to read the documentation

9 | 24 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // Reexport your entry components here 2 | export { default as Class } from './Class.svelte'; 3 | export { default as Constructor } from './Constructor.svelte'; 4 | export { default as Comment } from './Comment.svelte'; 5 | export { default as Enum } from './Enum.svelte'; 6 | export { default as EnumMember } from './EnumMember.svelte'; 7 | export { default as Function } from './Function.svelte'; 8 | export { default as Interface } from './Interface.svelte'; 9 | export { default as Method } from './Method.svelte'; 10 | export { default as Property } from './Property.svelte'; 11 | export { default as Reference } from './Reference.svelte'; 12 | export { default as Sources } from './Sources.svelte'; 13 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Sources.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 28 | -------------------------------------------------------------------------------- /packages/docs/src/routes/modules/[module]/[kind]/[slug]/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |

{data.export.name}

9 |
10 | 11 | {#if data.export.kind === 'Class'} 12 | 13 | {:else if data.export.kind === 'Interface'} 14 | 15 | {:else if data.export.kind === 'Enum'} 16 | 17 | {:else if data.export.kind === 'Function'} 18 | 19 | {:else} 20 |

Error: Unknown kind, "${data.export.kind}"

21 | {/if} 22 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Enum.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | {#if data.tags} 11 | 12 | {/if} 13 | {#if data.comment} 14 | 15 | {/if} 16 |
17 | 18 | {#if data.members.length} 19 |
20 |

Members

21 | {#each data.members as member} 22 | 23 | {/each} 24 |
25 | {/if} 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Tags.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | {#if hasVisibleTag()} 24 |
    25 | {#each Object.keys(data) as tag (tag)} 26 | {#if DISPLAYED_TAGS.has(tag)} 27 |
  • 28 | {tag} 29 |
  • 30 | {/if} 31 | {/each} 32 |
33 | {/if} 34 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/EnumMember.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 | 13 |
14 | {data.name}{#if typeof data.type === 'string'}: 15 | {data.type}{:else if data.type}: {/if} 18 |
19 | {#if data.tags} 20 | 21 | {/if} 22 | {#if data.comment} 23 | 24 | {/if} 25 |
26 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Property.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 | 13 |
14 | {data.name}{#if typeof data.type === 'string'}: 15 | {data.type}{:else if data.type}: {/if} 18 |
19 | {#if data.tags} 20 | 21 | {/if} 22 | {#if data.comment} 23 | 24 | {/if} 25 | 26 |
27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | node-version: [20.x] 14 | os: [ubuntu-latest, windows-latest] 15 | runs-on: ${{ matrix.os }} 16 | env: 17 | CI: true 18 | LINT: ${{ matrix.os == 'ubuntu-latest' && true || false }} 19 | 20 | steps: 21 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | # https://github.com/yarnpkg/yarn/issues/4890 28 | - run: yarn install --frozen-lockfile 29 | if: matrix.os == 'ubuntu-latest' 30 | - run: yarn install --frozen-lockfile --network-timeout 100000 31 | if: matrix.os == 'windows-latest' 32 | 33 | - run: yarn build 34 | - run: yarn test 35 | - run: yarn lint 36 | if: env.LINT == 'true' 37 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Interface.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | {#if data.tags} 12 | 13 | {/if} 14 | {#if data.comment} 15 | 16 | {/if} 17 |
18 | 19 | {#if data.properties.length} 20 |
21 |

Properties

22 | {#each data.properties as property} 23 | 24 | {/each} 25 |
26 | {/if} 27 | 28 | {#if data.methods.length} 29 |
30 |

Methods

31 | {#each data.methods as method} 32 | 33 | {/each} 34 |
35 | {/if} 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/parse/src/utils/sort.ts: -------------------------------------------------------------------------------- 1 | import { noCase } from './no-case'; 2 | import { GD } from '../types'; 3 | 4 | export type SortFn = (a: GD.ApiItemBase, b: GD.ApiItemBase) => number; 5 | 6 | /** 7 | * Defines default sort order, strictly alphabetical. 8 | */ 9 | export function createDefaultSort(): SortFn { 10 | return (a, b) => (a.name > b.name ? 1 : -1); 11 | } 12 | 13 | const DEFAULT_PREFIX_LIST = ['get', 'set', 'add', 'remove', 'delete', 'list', 'to', 'from']; 14 | 15 | /** 16 | * Defines a prefix-based sort order, using the provided prefix list. This 17 | * sorting method is designed to keep items like `getA` and `setA` next to one 18 | * another, sorting first with the prefix omitted from each item, and then 19 | * by prefix. 20 | */ 21 | export function createPrefixSort(prefixList = DEFAULT_PREFIX_LIST): SortFn { 22 | const prefixSet = new Set(prefixList); 23 | return (a, b) => { 24 | const tokensA = noCase(a.name); 25 | const tokensB = noCase(b.name); 26 | 27 | const prefixA = prefixSet.has(tokensA[0]) ? tokensA.shift() : ''; 28 | const prefixB = prefixSet.has(tokensB[0]) ? tokensB.shift() : ''; 29 | 30 | const nameA = tokensA.join(''); 31 | const nameB = tokensB.join(''); 32 | 33 | return (nameA !== nameB ? nameA > nameB : prefixA > prefixB) ? 1 : -1; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌿 greendoc 2 | 3 | [![Latest NPM release](https://img.shields.io/npm/v/@greendoc/parse.svg)](https://www.npmjs.com/package/@greendoc/parse) 4 | [![License](https://img.shields.io/badge/license-BlueOak--1.0.0-007ec6.svg)](https://github.com/donmccurdy/greendoc/blob/main/LICENSE.md) 5 | [![Build Status](https://github.com/donmccurdy/greendoc/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/donmccurdy/greendoc/actions?query=workflow%3ACI) 6 | 7 | _An adaptable system for generating documentation of TypeScript APIs._ 8 | 9 | > ⚠️ **NOTICE:** WORK IN PROGRESS / NOT READY FOR USE 10 | 11 | ## Packages 12 | 13 | - [`@greendoc/docs`](./packages/docs): _Documentation for, and example of, the greendoc system._ 14 | - [`@greendoc/parse`](./packages/parse): _Parses TypeScript and builds a strongly-typed API model in JSON._ 15 | - `@greendoc/html`: _Renders the API model in HTML._ 16 | - [`@greendoc/svelte`](./packages/svelte): _Renders the API model in HTML, using [Svelte](https://svelte.dev/)._ 17 | - `@greendoc/react`: _Renders the API model in HTML, using [React](https://reactjs.org/)._ 18 | - `@greendoc/i18n`: _Provides [internationalization (i18n)](https://web.dev/learn/design/internationalization/) support._ 19 | 20 | ## License 21 | 22 | Licensed under [Blue Oak Model License 1.0.0](/LICENSE.md). 23 | -------------------------------------------------------------------------------- /packages/parse/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@greendoc/parse", 3 | "version": "0.4.1", 4 | "type": "module", 5 | "sideEffects": false, 6 | "source": "./src/index.ts", 7 | "types": "./dist/index.d.ts", 8 | "main": "./dist/greendoc-parse.cjs", 9 | "module": "./dist/greendoc-parse.esm.js", 10 | "exports": { 11 | "types": "./dist/index.d.ts", 12 | "require": "./dist/greendoc-parse.cjs", 13 | "default": "./dist/greendoc-parse.modern.js" 14 | }, 15 | "files": [ 16 | "dist/" 17 | ], 18 | "repository": "https://github.com/donmccurdy/greendoc", 19 | "author": "Don McCurdy ", 20 | "license": "BlueOak-1.0.0", 21 | "scripts": { 22 | "build": "microbundle --format cjs,esm,modern --no-compress --define PACKAGE_VERSION=$npm_package_version", 23 | "test": "ava --no-worker-threads test/*.test.ts", 24 | "preversion": "yarn build && yarn test", 25 | "version": "rimraf dist/* && yarn build && git add -u", 26 | "postversion": "git push && git push --tags && npm publish", 27 | "clean": "rimraf dist/*" 28 | }, 29 | "dependencies": { 30 | "highlight.js": "^11.9.0", 31 | "marked": "^4.2.3", 32 | "ts-morph": "^23.0.0" 33 | }, 34 | "ava": { 35 | "extensions": { 36 | "ts": "module" 37 | }, 38 | "nodeArguments": [ 39 | "--import=tsx" 40 | ] 41 | }, 42 | "gitHead": "276870f41d0e92060bb04b138d1ee8fbe643d1d3" 43 | } 44 | -------------------------------------------------------------------------------- /packages/docs/src/lib/server/model/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { Encoder, GD, Parser } from '@greendoc/parse'; 4 | import * as TS from 'ts-morph'; 5 | import he from 'he'; 6 | 7 | const ROOT_DELTA = '../../../../../../'; 8 | const ROOT_PATH = resolve(dirname(fileURLToPath(import.meta.url)), ROOT_DELTA); 9 | 10 | const entryPath = resolve(ROOT_PATH, 'packages/parse/src/index.ts'); 11 | 12 | const project = new TS.Project({ 13 | compilerOptions: { 14 | paths: { 15 | '@greendoc/parse': [entryPath] 16 | } 17 | } 18 | }); 19 | 20 | export const parser = new Parser(project) 21 | .addModule({ name: '@greendoc/parse', slug: 'parse', entry: entryPath }) 22 | .setRootPath(ROOT_PATH) 23 | .setBaseURL('https://github.com/donmccurdy/greendoc/tree/main') 24 | .init(); 25 | 26 | export const encoder = new Encoder(parser); 27 | 28 | export function getMetadata(item: GD.ApiItem): { 29 | title: string; 30 | snippet: string; 31 | } { 32 | return { 33 | title: item.name, 34 | snippet: (item as any).comment ? getSnippet((item as any).comment) : '' 35 | }; 36 | } 37 | 38 | export function getSnippet(html: string): string { 39 | const text = he.decode(html.replace(/(<([^>]+)>)/gi, '')); 40 | const words = text.split(/\s+/); 41 | if (words.length < 50) return text; 42 | return words.slice(0, 50).join(' ') + '…'; 43 | } 44 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Function.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 | 13 |
    14 |
  • 15 | {data.name}({#each data.params as param, i}{param.name}{#if param.optional}?{/if}{#if typeof param.type === 'string'}: 16 | {param.type}{:else if param.type}: {/if}{#if i < data.params.length - 1}, 19 | {/if}{/each}): 20 | {#if typeof data.returns === 'string'}{data.returns}{:else}{/if} 23 |
  • 24 |
25 | {#if data.tags} 26 | 27 | {/if} 28 |
    29 |
  • 30 | {#if data.comment} 31 | 32 | {/if} 33 | 34 |
  • 35 |
36 |
37 | -------------------------------------------------------------------------------- /packages/parse/src/utils/no-case.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * no-case 3 | * 4 | * Based on https://github.com/blakeembrey/change-case/blob/master/packages/no-case/src/index.ts, 5 | * released under MIT License. 6 | */ 7 | 8 | // Support camel case ("camelCase" -> "camel Case" and "CAMELCase" -> "CAMEL Case"). 9 | const DEFAULT_SPLIT_REGEXP = [/([a-z0-9])([A-Z])/g, /([A-Z])([A-Z][a-z])/g]; 10 | 11 | // Remove all non-word characters. 12 | const DEFAULT_STRIP_REGEXP = /[^A-Z0-9]+/gi; 13 | 14 | /** Normalize the string into an array of 'words'. */ 15 | export function noCase(input: string): string[] { 16 | const splitRegexp = DEFAULT_SPLIT_REGEXP; 17 | const stripRegexp = DEFAULT_STRIP_REGEXP; 18 | const transform = (s: string) => s.toLowerCase(); 19 | const delimiter = ' '; 20 | 21 | let result = replace(replace(input, splitRegexp, '$1\0$2'), stripRegexp, '\0'); 22 | let start = 0; 23 | let end = result.length; 24 | 25 | // Trim the delimiter from around the output string. 26 | while (result.charAt(start) === '\0') start++; 27 | while (result.charAt(end - 1) === '\0') end--; 28 | 29 | // Transform each token independently. 30 | return result.slice(start, end).split('\0').map(transform); 31 | } 32 | 33 | /** Replace `re` in the input string with the replacement value. */ 34 | function replace(input: string, re: RegExp | RegExp[], value: string) { 35 | if (re instanceof RegExp) return input.replace(re, value); 36 | return re.reduce((input, re) => input.replace(re, value), input); 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "type": "module", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "dev": "concurrently 'yarn build:watch' 'lerna run dev'", 10 | "build": "lerna run build", 11 | "build:watch": "lerna watch --scope=$LERNA_PACKAGE_NAME --include-dependents --no-private -- lerna run build", 12 | "test": "lerna run test", 13 | "test:watch": "lerna watch --scope=$LERNA_PACKAGE_NAME -- lerna run test", 14 | "clean": "lerna run clean", 15 | "lint": "lerna run lint", 16 | "preversion": "yarn clean && yarn build && yarn test", 17 | "postpublish": "git push && git push --tags" 18 | }, 19 | "devDependencies": { 20 | "@sveltejs/adapter-auto": "^3.0.0", 21 | "@sveltejs/adapter-static": "^3.0.0", 22 | "@sveltejs/kit": "^2.0.0", 23 | "@sveltejs/package": "^2.3.1", 24 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 25 | "@types/node": "^22.0.0", 26 | "ava": "^6.1.2", 27 | "concurrently": "^9.0.0", 28 | "lerna": "^8.1.2", 29 | "mdsvex": "^0.12.0", 30 | "microbundle": "^0.15.1", 31 | "prettier": "^3.2.5", 32 | "prettier-plugin-svelte": "^3.2.3", 33 | "rimraf": "^6.0.0", 34 | "svelte": "^4.2.15", 35 | "svelte-check": "^4.0.0", 36 | "svelte-preprocess": "^6.0.0", 37 | "ts-morph": "^23.0.0", 38 | "tsx": "^4.7.3", 39 | "typescript": "^5.4.5", 40 | "vite": "^5.2.10" 41 | }, 42 | "ava": { 43 | "extensions": { 44 | "ts": "module" 45 | }, 46 | "nodeArguments": [ 47 | "--import=tsx" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Method.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
14 | 15 | 16 |
    20 |
  • 21 | {data.name}({#each data.params as param, i}{param.name}{#if param.optional}?{/if}{#if typeof param.type === 'string'}: 22 | {param.type}{:else if param.type}: {/if}{#if i < data.params.length - 1}, 25 | {/if}{/each}): 26 | {#if typeof data.returns === 'string'}{data.returns}{:else}{/if} 29 |
  • 30 |
31 | {#if data.tags} 32 | 33 | {/if} 34 |
    35 |
  • 36 | {#if data.comment} 37 | 38 | {/if} 39 | 40 |
  • 41 |
42 |
43 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Constructor.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
14 | 15 | 16 |
    20 |
  • 21 | {data.name}({#each data.params as param, i}{param.name}{#if param.optional}?{/if}{#if typeof param.type === 'string'}: 22 | {param.type}{:else if param.type}: {/if}{#if i < data.params.length - 1}, 25 | {/if}{/each}): 26 | {#if typeof data.returns === 'string'}{data.returns}{:else}{/if} 29 |
  • 30 |
31 | {#if data.tags} 32 | 33 | {/if} 34 |
    35 |
  • 36 | {#if data.comment} 37 | 38 | {/if} 39 | 40 |
  • 41 |
42 |
43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Blue Oak Model License 2 | 3 | Version 1.0.0 4 | 5 | ## Purpose 6 | 7 | This license gives everyone as much permission to work with 8 | this software as possible, while protecting contributors 9 | from liability. 10 | 11 | ## Acceptance 12 | 13 | In order to receive this license, you must agree to its 14 | rules. The rules of this license are both obligations 15 | under that agreement and conditions to your license. 16 | You must not do anything with this software that triggers 17 | a rule that you cannot or will not follow. 18 | 19 | ## Copyright 20 | 21 | Each contributor licenses you to do everything with this 22 | software that would otherwise infringe that contributor's 23 | copyright in it. 24 | 25 | ## Notices 26 | 27 | You must ensure that everyone who gets a copy of 28 | any part of this software from you, with or without 29 | changes, also gets the text of this license or a link to 30 | . 31 | 32 | ## Excuse 33 | 34 | If anyone notifies you in writing that you have not 35 | complied with [Notices](#notices), you can keep your 36 | license by taking all practical steps to comply within 30 37 | days after the notice. If you do not do so, your license 38 | ends immediately. 39 | 40 | ## Patent 41 | 42 | Each contributor licenses you to do everything with this 43 | software that would otherwise infringe any patent claims 44 | they can license or become able to license. 45 | 46 | ## Reliability 47 | 48 | No contributor can revoke this license. 49 | 50 | ## No Liability 51 | 52 | ***As far as the law allows, this software comes as is, 53 | without any warranty or condition, and no contributor 54 | will be liable to anyone for any damages related to this 55 | software or this license, under any kind of legal claim.*** 56 | -------------------------------------------------------------------------------- /packages/docs/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types'; 2 | import { parser } from '$lib/server/model'; 3 | import type { Node } from 'ts-morph'; 4 | 5 | export const prerender = true; 6 | 7 | const parseExports = parser 8 | .getModuleExports('@greendoc/parse') 9 | .map(createExport) 10 | .sort((a: Export, b: Export) => (a.text > b.text ? 1 : -1)); 11 | 12 | interface Export { 13 | text: string; 14 | href: string; 15 | kind: string; 16 | category?: string; 17 | external?: boolean; 18 | tags?: Record; 19 | } 20 | 21 | interface Section { 22 | title: string; 23 | items?: Export[]; 24 | subsections?: Subsection[]; 25 | } 26 | 27 | interface Subsection { 28 | title: string; 29 | items?: Export[]; 30 | } 31 | 32 | function createExport(item: Node): Export { 33 | return { 34 | text: parser.getName(item), 35 | href: parser.getPath(item)!, 36 | kind: item.getKindName(), 37 | category: parser.getTag(item, 'category') || undefined, 38 | tags: parser.getTags(item) || undefined 39 | }; 40 | } 41 | 42 | export const load: LayoutServerLoad = () => { 43 | return { 44 | metadata: { 45 | title: 'greendoc', 46 | snippet: '' 47 | }, 48 | navigation: { 49 | sections: [ 50 | { 51 | title: 'Getting Started', 52 | items: [ 53 | { text: 'Home', href: '/' }, 54 | { text: 'Installation', href: '/installation' }, 55 | { 56 | text: 'GitHub', 57 | external: true, 58 | href: 'https://github.com/donmccurdy/greendoc' 59 | }, 60 | { 61 | text: 'NPM', 62 | external: true, 63 | href: 'https://www.npmjs.com/search?q=%40greendoc' 64 | } 65 | ], 66 | subsections: [] 67 | }, 68 | { 69 | title: '@greendoc/parse', 70 | items: parseExports 71 | } 72 | ] as Section[] 73 | } 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /packages/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@greendoc/svelte", 3 | "version": "0.4.1", 4 | "repository": "https://github.com/donmccurdy/greendoc", 5 | "author": "Don McCurdy ", 6 | "license": "BlueOak-1.0.0", 7 | "type": "module", 8 | "sideEffects": false, 9 | "exports": { 10 | ".": { 11 | "types": "./dist/index.d.ts", 12 | "svelte": "./dist/index.js" 13 | } 14 | }, 15 | "typesVersions": { 16 | ">4.0": { 17 | "Class.svelte": [ 18 | "./dist/Class.d.ts" 19 | ], 20 | "Constructor.svelte": [ 21 | "./dist/Constructor.d.ts" 22 | ], 23 | "Comment.svelte": [ 24 | "./dist/Comment.d.ts" 25 | ], 26 | "Enum.svelte": [ 27 | "./dist/Enum.d.ts" 28 | ], 29 | "EnumMember.svelte": [ 30 | "./dist/EnumMember.d.ts" 31 | ], 32 | "Function.svelte": [ 33 | "./dist/Function.d.ts" 34 | ], 35 | "Interface.svelte": [ 36 | "./dist/Interface.d.ts" 37 | ], 38 | "Method.svelte": [ 39 | "./dist/Method.d.ts" 40 | ], 41 | "Property.svelte": [ 42 | "./dist/Property.d.ts" 43 | ], 44 | "Reference.svelte": [ 45 | "./dist/Reference.d.ts" 46 | ], 47 | "Sources.svelte": [ 48 | "./dist/Sources.d.ts" 49 | ], 50 | "index": [ 51 | "./dist/index.d.ts" 52 | ] 53 | } 54 | }, 55 | "scripts": { 56 | "dev:disabled": "vite dev --force", 57 | "build": "svelte-kit sync && svelte-package -o dist", 58 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 59 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 60 | "lint": "prettier --check src", 61 | "format": "prettier --write src", 62 | "test": "echo \"Tests not yet implemented\"", 63 | "preversion": "yarn build && yarn test", 64 | "version": "rimraf dist/* && yarn build && git add -u", 65 | "postversion": "git push && git push --tags && npm publish --access public", 66 | "clean": "rimraf dist/*" 67 | }, 68 | "dependencies": { 69 | "@greendoc/parse": "^0.4.1" 70 | }, 71 | "peerDependencies": { 72 | "svelte": "^4.0.0" 73 | }, 74 | "files": [ 75 | "dist" 76 | ], 77 | "gitHead": "276870f41d0e92060bb04b138d1ee8fbe643d1d3" 78 | } 79 | -------------------------------------------------------------------------------- /packages/svelte/src/lib/Class.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | {#if data.tags} 14 | 15 | {/if} 16 | {#if data.comment} 17 | 18 | {/if} 19 |
20 | 21 | {#if data.extendsTypes.length} 22 |
23 |

Hierarchy

24 |
    25 | {#each data.extendsTypes as extendsType} 26 |
  • 27 | 28 |
  • 29 | {/each} 30 |
  • 31 | 32 |
  • 33 |
34 |
35 | {/if} 36 | 37 | {#if data.constructor} 38 |
39 |

Constructor

40 | 41 |
42 | {/if} 43 | 44 | {#if data.staticProperties.length} 45 |
46 |

Static properties

47 | {#each data.staticProperties as property} 48 | {#if !property.isProtected} 49 | 50 | {/if} 51 | {/each} 52 |
53 | {/if} 54 | 55 | {#if data.staticMethods.length} 56 |
57 |

Static methods

58 | {#each data.staticMethods as method} 59 | {#if !method.isProtected} 60 | 61 | {/if} 62 | {/each} 63 |
64 | {/if} 65 | 66 | {#if data.properties.length} 67 |
68 |

Properties

69 | {#each data.properties as property} 70 | {#if !property.isProtected} 71 | 72 | {/if} 73 | {/each} 74 |
75 | {/if} 76 | 77 | {#if data.methods.length} 78 |
79 |

Methods

80 | {#each data.methods as method} 81 | {#if !method.isProtected} 82 | 83 | {/if} 84 | {/each} 85 |
86 | {/if} 87 | 88 | 89 | -------------------------------------------------------------------------------- /packages/docs/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | {$page.data.metadata.title} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | 59 |
60 | 63 |
64 |
65 |
66 |
67 |
68 | 114 | 115 |
116 |
117 | 118 |
119 |
120 | -------------------------------------------------------------------------------- /packages/parse/src/types.ts: -------------------------------------------------------------------------------- 1 | // TODO(design): Clean up API from an end-user perspective. 2 | // TODO(design): Consider reading through the ECMAScript type annotations proposal, 3 | // and fitting this design to its goals. If a feature is supported in TypeScript but 4 | // not in JavaScript, I might consider omitting it here. 5 | // TODO(design): Consider removing 'Api' prefix. 6 | export namespace GD { 7 | // export enum ApiExportKind { 8 | // CLASS = 'Class', 9 | // INTERFACE = 'Interface', 10 | // ENUM = 'Enum', 11 | // FUNCTION = 'Function', 12 | // VARIABLE = 'Variable' 13 | // } 14 | 15 | export enum ApiItemKind { 16 | CLASS = 'Class', 17 | INTERFACE = 'Interface', 18 | ENUM = 'Enum', 19 | ENUM_MEMBER = 'EnumMember', 20 | FUNCTION = 'Function', 21 | VARIABLE = 'Variable', 22 | CONSTRUCTOR = 'Constructor', 23 | METHOD = 'Method', 24 | METHOD_SIGNATURE = 'MethodSignature', 25 | PROPERTY = 'Property', 26 | PROPERTY_SIGNATURE = 'PropertySignature' 27 | } 28 | 29 | export type ApiItem = 30 | | ApiClass 31 | | ApiInterface 32 | | ApiFunction 33 | | ApiMember 34 | | ApiMethod 35 | | ApiEnum 36 | | ApiEnumMember 37 | | ApiVariable; 38 | 39 | export interface ApiItemBase { 40 | name: string; 41 | kind: ApiItemKind; 42 | source?: Source; 43 | tags?: Record; 44 | } 45 | 46 | export interface ApiClass extends ApiItemBase { 47 | // TODO: resolved & unresolved generics? 48 | // TODO: interfaces implemented? 49 | kind: ApiItemKind.CLASS; 50 | comment?: string; // → IntlText 51 | extendsTypes: Reference[]; 52 | constructor?: ApiConstructor; 53 | properties: ApiProperty[]; 54 | methods: ApiMethod[]; 55 | // TODO: overloads? 56 | staticProperties: ApiProperty[]; 57 | staticMethods: ApiMethod[]; 58 | } 59 | 60 | export interface ApiInterface extends ApiItemBase { 61 | kind: ApiItemKind.INTERFACE; 62 | comment?: string; 63 | extendsTypes: Reference[]; 64 | properties: ApiProperty[]; 65 | methods: ApiMethod[]; 66 | } 67 | 68 | export interface ApiFunction extends ApiItemBase { 69 | kind: ApiItemKind.FUNCTION; 70 | comment?: string; 71 | params: ApiParameter[]; 72 | returns: ApiReturnType; 73 | returnsComment?: string; 74 | } 75 | 76 | export interface ApiMember extends ApiItemBase { 77 | kind: ApiItemKind.CONSTRUCTOR | ApiItemKind.METHOD | ApiItemKind.PROPERTY; 78 | isStatic?: boolean; 79 | isProtected?: boolean; 80 | isOptional?: boolean; 81 | overwrite?: Reference; 82 | comment?: string; 83 | } 84 | 85 | export interface ApiMethod extends ApiMember { 86 | kind: ApiItemKind.METHOD; 87 | params: ApiParameter[]; 88 | returns: ApiReturnType; 89 | returnsComment?: string; 90 | } 91 | 92 | export type ApiParameter = { 93 | name: string; 94 | type?: Token; 95 | optional?: boolean; 96 | }; 97 | 98 | export type ApiReturnType = Token; 99 | 100 | export interface ApiConstructor extends ApiMember { 101 | kind: ApiItemKind.CONSTRUCTOR; 102 | isStatic: false; 103 | name: 'constructor'; 104 | params: ApiParameter[]; 105 | returns: ApiReturnType; 106 | } 107 | 108 | export interface ApiProperty extends ApiMember { 109 | kind: ApiItemKind.PROPERTY; 110 | type?: Token; 111 | isReadonly: boolean; 112 | } 113 | 114 | export interface ApiEnum extends ApiItemBase { 115 | kind: ApiItemKind.ENUM; 116 | members: ApiEnumMember[]; 117 | comment?: string; 118 | } 119 | 120 | export interface ApiEnumMember extends ApiItemBase { 121 | kind: ApiItemKind.ENUM_MEMBER; 122 | type?: Token; 123 | comment?: string; 124 | } 125 | 126 | export interface ApiVariable extends ApiItemBase { 127 | type?: Token; 128 | comment?: string; // not yet supported by ts-morph 129 | } 130 | 131 | export type Token = string | Reference; 132 | 133 | export interface Reference { 134 | name: string; 135 | kind: ApiItemKind; 136 | path?: string; 137 | } 138 | 139 | export interface Source { 140 | text: string; 141 | url: string; 142 | } 143 | 144 | export interface ApiTypeAlias extends ApiItemBase {} 145 | } 146 | -------------------------------------------------------------------------------- /packages/parse/src/Parser.ts: -------------------------------------------------------------------------------- 1 | import * as TS from 'ts-morph'; 2 | import { markedFormatter } from './utils/format'; 3 | import { SUPPORTED_TAGS } from './constants'; 4 | 5 | type $StringLike = { toString: () => string }; 6 | 7 | interface Module { 8 | name: string; 9 | slug: string; 10 | rootDirectory: string; 11 | entry: TS.SourceFile; 12 | } 13 | 14 | export interface ModuleConfig { 15 | name: string; 16 | slug: string; 17 | entry: TS.SourceFile | string; 18 | } 19 | 20 | export class Parser { 21 | readonly project: TS.Project; 22 | readonly modules: Module[] = []; 23 | readonly itemToSlug = new Map(); 24 | readonly slugToItem = new Map(); 25 | readonly exportToItem = new Map(); 26 | // TODO(design): Clarify if/that this is a URL path, not a path on disk. 27 | private rootPath: string = ''; 28 | private baseURL: string = ''; 29 | private formatter: (md: string) => string = markedFormatter; 30 | 31 | constructor(project = new TS.Project()) { 32 | this.project = project; 33 | } 34 | 35 | public init(): this { 36 | return this; 37 | } 38 | 39 | public setRootPath(path: string) { 40 | this.rootPath = path; 41 | return this; 42 | } 43 | 44 | public setBaseURL(url: string) { 45 | this.baseURL = url; 46 | return this; 47 | } 48 | 49 | public setMarkdownRenderer(formatter: ((md: string) => string) | null): this { 50 | this.formatter = formatter || markedFormatter; 51 | return this; 52 | } 53 | 54 | public addModule(config: ModuleConfig): this { 55 | let entry: TS.SourceFile; 56 | if (config.entry instanceof TS.SourceFile) { 57 | entry = config.entry; 58 | } else { 59 | entry = this.project.addSourceFileAtPath(config.entry); 60 | } 61 | 62 | const module: Module = { 63 | name: config.name, 64 | slug: config.slug, 65 | rootDirectory: fs.dirname(entry.getFilePath()), 66 | entry: entry 67 | }; 68 | this.modules.push(module); 69 | 70 | for (const [name, items] of module.entry.getExportedDeclarations()) { 71 | for (const item of items) { 72 | if (this.isHidden(item)) continue; 73 | 74 | const slug = name; 75 | this.itemToSlug.set(item, slug); 76 | this.slugToItem.set(slug, item); 77 | this.exportToItem.set(name, item); 78 | } 79 | } 80 | return this; 81 | } 82 | 83 | public getModuleExports(name: string): TS.Node[] { 84 | const module = this.modules.find((module) => module.name === name); 85 | const exports = []; 86 | for (const [name, items] of module.entry.getExportedDeclarations()) { 87 | for (const item of items) { 88 | if (this.isHidden(item)) continue; 89 | exports.push(item); 90 | } 91 | } 92 | return exports; 93 | } 94 | 95 | /** @internal */ 96 | getItemBySlug(slug: string): TS.Node { 97 | const item = this.slugToItem.get(slug); 98 | if (item) return item; 99 | throw new Error(`Item for "${slug}" not found`); 100 | } 101 | 102 | /** @internal */ 103 | getItemByExportName(name: string): TS.Node { 104 | const item = this.exportToItem.get(name); 105 | if (item) return item; 106 | throw new Error(`Item for "${name}" not found`); 107 | } 108 | 109 | /** @internal */ 110 | hasItem(item: TS.Node): boolean { 111 | return this.itemToSlug.has(item); 112 | } 113 | 114 | /** @internal */ 115 | getSlug(item: TS.Node): string { 116 | const slug = this.itemToSlug.get(item); 117 | if (slug) return slug; 118 | 119 | throw new Error( 120 | `Slug for "${item.getKindName()}" from "${item.getSourceFile().getBaseName()}" not found` 121 | ); 122 | } 123 | 124 | // TODO(design): URL paths should be an application-level decision. 125 | /** @internal */ 126 | getPath(item: TS.Node): string | null { 127 | const module = this.getModule(item); 128 | if (!module) return null; 129 | 130 | if (this.isHidden(item)) return null; 131 | 132 | switch (item.getKind()) { 133 | case TS.SyntaxKind.ClassDeclaration: 134 | return `/modules/${module.slug}/classes/${this.getSlug(item)}`; 135 | case TS.SyntaxKind.InterfaceDeclaration: 136 | return `/modules/${module.slug}/interfaces/${this.getSlug(item)}`; 137 | case TS.SyntaxKind.EnumDeclaration: 138 | return `/modules/${module.slug}/enums/${this.getSlug(item)}`; 139 | case TS.SyntaxKind.FunctionDeclaration: 140 | return `/modules/${module.slug}/functions/${this.getSlug(item)}`; 141 | // case TS.SyntaxKind.VariableDeclaration: 142 | // return `/modules/${module.slug}/constants/${this.getSlug(item)}`; 143 | default: 144 | return null; 145 | } 146 | } 147 | 148 | getModule(item: TS.Node): Module | null { 149 | const file = item.getSourceFile(); 150 | if (file.isFromExternalLibrary()) return null; 151 | if (file.isDeclarationFile()) return null; 152 | 153 | const filePath = file.getFilePath(); 154 | for (const module of this.modules) { 155 | if (filePath.startsWith(module.rootDirectory)) { 156 | return module; 157 | } 158 | } 159 | 160 | throw new Error(`Module not found for path "${filePath}".`); 161 | } 162 | 163 | getTags(item: TS.Node): Record | null { 164 | const tags: Record = {}; 165 | let tagCount = 0; 166 | if ((item as unknown as TS.JSDocableNode).getJsDocs) { 167 | for (const doc of (item as unknown as TS.JSDocableNode).getJsDocs()) { 168 | for (const tag of doc.getTags()) { 169 | const tagName = tag.getTagName(); 170 | if (SUPPORTED_TAGS.has(tagName)) { 171 | tags[tagName] = tag.getCommentText() || true; 172 | tagCount++; 173 | } 174 | } 175 | } 176 | } 177 | return tagCount > 0 ? tags : null; 178 | } 179 | 180 | getTag(item: TS.Node, tagName: string): string | null { 181 | if ((item as unknown as TS.JSDocableNode).getJsDocs) { 182 | for (const doc of (item as unknown as TS.JSDocableNode).getJsDocs()) { 183 | for (const tag of doc.getTags()) { 184 | if (tag.getTagName() === tagName) { 185 | return tag.getCommentText(); 186 | } 187 | } 188 | } 189 | } 190 | return null; 191 | } 192 | 193 | getName(item: TS.Node): string { 194 | if ((item as any).getName) return (item as any).getName(); 195 | return ''; 196 | } 197 | 198 | getSourceText(item: TS.Node): string { 199 | const file = item.getSourceFile(); 200 | if (file.isFromExternalLibrary()) return 'external'; 201 | if (file.isDeclarationFile()) return 'external'; 202 | const url = file.getFilePath() as string; 203 | if (url.startsWith(this.rootPath)) { 204 | return url.replace(this.rootPath + '/', ''); 205 | } 206 | return url; 207 | } 208 | 209 | getSourceURL(item: TS.Node): string { 210 | const file = item.getSourceFile(); 211 | if (file.isFromExternalLibrary()) return ''; 212 | if (file.isDeclarationFile()) return ''; 213 | let url = file.getFilePath() as string; 214 | if (url.startsWith(this.rootPath)) { 215 | url = this.baseURL + url.replace(this.rootPath, ''); 216 | } 217 | return url; 218 | } 219 | 220 | isHidden(item: TS.Node): boolean { 221 | if ((item as unknown as TS.JSDocableNode).getJsDocs) { 222 | for (const doc of (item as unknown as TS.JSDocableNode).getJsDocs()) { 223 | for (const tag of doc.getTags()) { 224 | if (tag.getTagName() === 'hidden') return true; 225 | if (tag.getTagName() === 'internal') return true; 226 | } 227 | } 228 | } 229 | return false; 230 | } 231 | 232 | renderMarkdown(md: string): string { 233 | return this.formatter(md); 234 | } 235 | } 236 | 237 | const fs = { 238 | basename(uri: string): string { 239 | const fileName = uri.split(/[\\/]/).pop()!; 240 | return fileName.substring(0, fileName.lastIndexOf('.')); 241 | }, 242 | dirname(uri: string): string { 243 | return uri.match(/(.*)[\/\\]/)[1] || ''; 244 | } 245 | }; 246 | -------------------------------------------------------------------------------- /packages/parse/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Encoder, GD, Parser, createDefaultSort, createPrefixSort } from '@greendoc/parse'; 3 | import * as TS from 'ts-morph'; 4 | 5 | const MOCK_ROOT_PATH = '/path/to'; 6 | const MOCK_SOURCE_PATH = '/path/to/source.ts'; 7 | const MOCK_BASE_URL = 'https://example.com/api'; 8 | 9 | test('parser.modules', (t) => { 10 | const parser = createParser( 11 | 'my-package', 12 | ` 13 | export class Animal {} 14 | export class Dog {} 15 | export class Snake {} 16 | export class Freddy {} 17 | ` 18 | ); 19 | const pkg = parser.modules[0]; 20 | t.is(pkg.name, 'my-package', 'package name'); 21 | t.deepEqual( 22 | parser.getModuleExports(pkg.name).map((item) => (item as any).getName()), 23 | ['Animal', 'Dog', 'Snake', 'Freddy'], 24 | 'package exports' 25 | ); 26 | }); 27 | 28 | test('parser.modules - tags', (t) => { 29 | const parser = createParser( 30 | 'my-package', 31 | ` 32 | /** 33 | * Description. 34 | * @type {string} name 35 | * @alpha 36 | * @beta 37 | * @deprecated Use something else. 38 | * @experimental 39 | */ 40 | export function myFunction() {} 41 | 42 | /** 43 | * @experimental 44 | */ 45 | export class MyClass {} 46 | ` 47 | ); 48 | 49 | const exports = parser.getModuleExports('my-package'); 50 | 51 | t.deepEqual( 52 | exports.map((item) => [parser.getName(item), parser.getTags(item)]), 53 | [ 54 | [ 55 | 'myFunction', 56 | { 57 | alpha: true, 58 | beta: true, 59 | deprecated: 'Use something else.', 60 | experimental: true 61 | } 62 | ], 63 | ['MyClass', { experimental: true }] 64 | ], 65 | 'tags' 66 | ); 67 | }); 68 | 69 | test('class serialization', (t) => { 70 | const parser = createParser( 71 | 'my-package', 72 | ` 73 | /** Description of Animal. */ 74 | export class Animal { 75 | private name: string; 76 | /** Description of Animal#getName. */ 77 | getName(): string { 78 | return this.name; 79 | } 80 | } 81 | 82 | /** Description of Dog. */ 83 | export class Dog extends Animal { 84 | /** Description of Dog#getLegs. */ 85 | getLegs(): number { 86 | return 4; 87 | } 88 | } 89 | 90 | /** Description of Cat. */ 91 | export class Snake extends Animal { 92 | /** Description of Snake#getFangs. */ 93 | getFangs(): number { 94 | return 2; 95 | } 96 | } 97 | 98 | export class Freddy extends Dog { 99 | getName(): string { 100 | return 'Freddy'; 101 | } 102 | } 103 | ` 104 | ); 105 | 106 | const encoder = new Encoder(parser); 107 | const dog = parser.getItemBySlug('Dog') as TS.ClassDeclaration; 108 | const encodedDog = trim(encoder.encodeItem(dog)); 109 | t.deepEqual( 110 | encodedDog, 111 | { 112 | kind: 'Class', 113 | name: 'Dog', 114 | source: { 115 | text: 'source.ts', 116 | url: 'https://example.com/api/source.ts' 117 | }, 118 | comment: '

Description of Dog.

\n', 119 | extendsTypes: [ 120 | { 121 | path: '/modules/my-package/classes/Animal', 122 | name: 'Animal', 123 | kind: 'Class' 124 | } 125 | ], 126 | staticProperties: [], 127 | properties: [], 128 | staticMethods: [], 129 | methods: [ 130 | { 131 | comment: '

Description of Dog#getLegs.

\n', 132 | kind: GD.ApiItemKind.METHOD, 133 | name: 'getLegs', 134 | params: [], 135 | returns: 'number', 136 | source: { 137 | text: 'source.ts', 138 | url: 'https://example.com/api/source.ts' 139 | } 140 | }, 141 | { 142 | comment: '

Description of Animal#getName.

\n', 143 | kind: GD.ApiItemKind.METHOD, 144 | name: 'getName', 145 | params: [], 146 | returns: 'string', 147 | source: { 148 | text: 'source.ts', 149 | url: 'https://example.com/api/source.ts' 150 | } 151 | } 152 | ] 153 | }, 154 | 'encoded class' 155 | ); 156 | }); 157 | 158 | test('constructors', (t) => { 159 | const parser = createParser( 160 | 'my-package', 161 | ` 162 | export class A { 163 | constructor(a: string, b: string) { 164 | // ... 165 | } 166 | } 167 | ` 168 | ); 169 | 170 | const encoder = new Encoder(parser); 171 | const classA = parser.getItemBySlug('A') as TS.ClassDeclaration; 172 | const encodedClassA = trim(encoder.encodeItem(classA)); 173 | 174 | t.deepEqual( 175 | encodedClassA.constructor, 176 | { 177 | kind: GD.ApiItemKind.CONSTRUCTOR, 178 | name: 'constructor', 179 | params: [ 180 | { name: 'a', type: 'string' }, 181 | { name: 'b', type: 'string' } 182 | ], 183 | returns: { kind: 'Class', name: 'A', path: '/modules/my-package/classes/A' }, 184 | source: { text: 'source.ts', url: 'https://example.com/api/source.ts' } 185 | }, 186 | 'a.constructor' 187 | ); 188 | }); 189 | 190 | test('inherited members', (t) => { 191 | const parser = createParser( 192 | 'my-package', 193 | ` 194 | export class A { 195 | static parentStaticProperty = 1; 196 | parentProperty = 2; 197 | static parentStaticMethod (): number { return 3; } 198 | parentMethod (): number { return 4; } 199 | } 200 | 201 | export class B extends A { 202 | static childStaticProperty = 5; 203 | childProperty = 6; 204 | static childStaticMethod (): number { return 7; } 205 | childMethod (): number { return 8; } 206 | } 207 | ` 208 | ); 209 | 210 | const encoder = new Encoder(parser); 211 | const a = encoder.encodeItem(parser.getItemBySlug('A') as TS.ClassDeclaration); 212 | const b = encoder.encodeItem(parser.getItemBySlug('B') as TS.ClassDeclaration); 213 | 214 | // methods 215 | t.deepEqual(a.methods.map(toName), ['parentMethod'], 'a.methods'); 216 | t.deepEqual(b.methods.map(toName), ['childMethod', 'parentMethod'], 'b.methods'); 217 | 218 | // static methods 219 | t.deepEqual(a.staticMethods.map(toName), ['parentStaticMethod'], 'a.staticMethods'); 220 | t.deepEqual( 221 | b.staticMethods.map(toName), 222 | ['childStaticMethod', 'parentStaticMethod'], 223 | 'b.staticMethods' 224 | ); 225 | 226 | // properties 227 | t.deepEqual(a.properties.map(toName), ['parentProperty'], 'a.properties'); 228 | t.deepEqual(b.properties.map(toName), ['childProperty', 'parentProperty'], 'b.properties'); 229 | 230 | // static properties 231 | t.deepEqual(a.staticProperties.map(toName), ['parentStaticProperty'], 'a.staticProperties'); 232 | t.deepEqual( 233 | b.staticProperties.map(toName), 234 | ['childStaticProperty', 'parentStaticProperty'], 235 | 'b.staticProperties' 236 | ); 237 | }); 238 | 239 | test('custom sort', (t) => { 240 | const parser = createParser( 241 | 'my-package', 242 | ` 243 | export class A { 244 | a() {} 245 | getA() {} 246 | setA() {} 247 | listA() {} 248 | b() {} 249 | getB() {} 250 | setB() {} 251 | listB() {} 252 | } 253 | ` 254 | ); 255 | 256 | const encoder = new Encoder(parser); 257 | const a = parser.getItemBySlug('A') as TS.ClassDeclaration; 258 | 259 | let encodedA = encoder.encodeItem(a); 260 | t.deepEqual( 261 | encodedA.methods.map(toName), 262 | ['a', 'b', 'getA', 'getB', 'listA', 'listB', 'setA', 'setB'], 263 | 'default sort' 264 | ); 265 | 266 | encodedA = encoder.setSort(createPrefixSort()).encodeItem(a); 267 | t.deepEqual( 268 | encodedA.methods.map(toName), 269 | ['a', 'getA', 'listA', 'setA', 'b', 'getB', 'listB', 'setB'], 270 | 'prefix sort' 271 | ); 272 | 273 | encodedA = encoder.setSort(createDefaultSort()).encodeItem(a); 274 | t.deepEqual( 275 | encodedA.methods.map(toName), 276 | ['a', 'b', 'getA', 'getB', 'listA', 'listB', 'setA', 'setB'], 277 | 'default sort (reset)' 278 | ); 279 | }); 280 | 281 | test('tsdoc comments, params, and returns', (t) => { 282 | const parser = createParser( 283 | 'my-package', 284 | ` 285 | /** 286 | * Concatenates two string arguments. 287 | * @param a First argument. 288 | * @param b Second argument. 289 | * @returns Concatenation of first and second arguments. 290 | */ 291 | export function concat(a: string, b: string): string { 292 | return a + b; 293 | } 294 | 295 | /** Class that adds numbers. */ 296 | export class AddingMachine { 297 | /** 298 | * Sums two numbers. 299 | * @param a First addend. 300 | * @param b Second addend. 301 | * @returns Sum of first and second addends. 302 | */ 303 | add(a: number, b: number): number { 304 | return a + b; 305 | } 306 | } 307 | ` 308 | ); 309 | 310 | const encoder = new Encoder(parser); 311 | const concat = parser.getItemBySlug('concat') as TS.FunctionDeclaration; 312 | const encodedConcat = trim(encoder.encodeItem(concat)); 313 | 314 | t.deepEqual( 315 | encodedConcat, 316 | { 317 | kind: 'Function', 318 | name: 'concat', 319 | source: { text: 'source.ts', url: 'https://example.com/api/source.ts' }, 320 | params: [ 321 | { name: 'a', type: 'string', comment: '

First argument.

\n' }, 322 | { name: 'b', type: 'string', comment: '

Second argument.

\n' } 323 | ], 324 | returns: 'string', 325 | returnsComment: '

Concatenation of first and second arguments.

\n', 326 | comment: '

Concatenates two string arguments.

\n' 327 | }, 328 | 'function' 329 | ); 330 | 331 | const addingMachine = parser.getItemBySlug('AddingMachine') as TS.ClassDeclaration; 332 | const encodedAddingMachine = trim(encoder.encodeItem(addingMachine)); 333 | 334 | t.deepEqual( 335 | encodedAddingMachine, 336 | { 337 | kind: 'Class', 338 | name: 'AddingMachine', 339 | comment: '

Class that adds numbers.

\n', 340 | source: { text: 'source.ts', url: 'https://example.com/api/source.ts' }, 341 | methods: [ 342 | { 343 | kind: 'Method', 344 | name: 'add', 345 | source: { text: 'source.ts', url: 'https://example.com/api/source.ts' }, 346 | comment: '

Sums two numbers.

\n', 347 | params: [ 348 | { name: 'a', type: 'number', comment: '

First addend.

\n' }, 349 | { name: 'b', type: 'number', comment: '

Second addend.

\n' } 350 | ], 351 | returns: 'number', 352 | returnsComment: '

Sum of first and second addends.

\n' 353 | } 354 | ], 355 | extendsTypes: [], 356 | staticProperties: [], 357 | properties: [], 358 | staticMethods: [] 359 | }, 360 | 'class' 361 | ); 362 | }); 363 | 364 | test('item tags', (t) => { 365 | const parser = createParser( 366 | 'my-package', 367 | ` 368 | /** 369 | * My class. 370 | * @public 371 | */ 372 | export class AddingMachine { 373 | /** @deprecated Do not use this. */ 374 | public myProperty = 25; 375 | /** 376 | * Sums two numbers. 377 | * @param a First addend. 378 | * @param b Second addend. 379 | * @returns Sum of first and second addends. 380 | * @public 381 | */ 382 | add(a: number, b: number): number { 383 | return a + b; 384 | } 385 | /** @deprecated */ 386 | subtract(a: number, b: number): number { 387 | return a - b; 388 | } 389 | /** @alpha */ 390 | multiply(a: number, b: number): number { 391 | return a * b; 392 | } 393 | } 394 | ` 395 | ); 396 | 397 | const encoder = new Encoder(parser); 398 | const addingMachine = parser.getItemBySlug('AddingMachine') as TS.ClassDeclaration; 399 | const encodedAddingMachine = trim(encoder.encodeItem(addingMachine)); 400 | const addMethod = encodedAddingMachine.methods.find(({ name }) => name === 'add'); 401 | const subtractMethod = encodedAddingMachine.methods.find(({ name }) => name === 'subtract'); 402 | const multiplyMethod = encodedAddingMachine.methods.find(({ name }) => name === 'multiply'); 403 | 404 | t.deepEqual(encodedAddingMachine.tags, { public: true }, 'class.tags'); 405 | t.deepEqual( 406 | encodedAddingMachine.properties[0].tags, 407 | { deprecated: 'Do not use this.' }, 408 | 'class.property.tags' 409 | ); 410 | t.deepEqual(addMethod.tags, { public: true }, 'class#add.tags'); 411 | t.deepEqual(subtractMethod.tags, { deprecated: true }, 'class#subtract.tags'); 412 | t.deepEqual(multiplyMethod.tags, { alpha: true }, 'class#multiply.tags'); 413 | }); 414 | 415 | test('variables', (t) => { 416 | const parser = createParser( 417 | 'my-package', 418 | ` 419 | /** 420 | * Description. 421 | * @type {string} name 422 | */ 423 | export let name = 'hello'; 424 | ` 425 | ); 426 | 427 | const encoder = new Encoder(parser); 428 | const encodedVariable = trim( 429 | encoder.encodeItem(parser.getItemBySlug('name') as TS.VariableDeclaration) 430 | ); 431 | 432 | t.deepEqual( 433 | encodedVariable, 434 | { 435 | kind: 'Variable', 436 | name: 'name', 437 | source: { text: 'source.ts', url: 'https://example.com/api/source.ts' }, 438 | type: 'string' 439 | // comment: '

Description.

\n' // not yet supported by ts-morph 440 | }, 441 | 'variable' 442 | ); 443 | }); 444 | 445 | //////////////////////// UTILITIES //////////////////////// 446 | 447 | /** Trims properties that JSON serialization would exclude. */ 448 | function trim(object: T): T { 449 | return JSON.parse(JSON.stringify(object)); 450 | } 451 | 452 | function toName(object: { name: string }): string { 453 | return object.name; 454 | } 455 | 456 | function createParser(name: string, source: string) { 457 | const project = new TS.Project(); 458 | const file = project.createSourceFile(MOCK_SOURCE_PATH, source); 459 | return new Parser(project) 460 | .setRootPath(MOCK_ROOT_PATH) 461 | .setBaseURL(MOCK_BASE_URL) 462 | .addModule({ name: name, slug: name, entry: file }) 463 | .init(); 464 | } 465 | -------------------------------------------------------------------------------- /packages/parse/src/Encoder.ts: -------------------------------------------------------------------------------- 1 | import { GD } from './types'; 2 | import { Parser } from './Parser'; 3 | import * as TS from 'ts-morph'; 4 | import { SortFn, createDefaultSort } from './utils/sort'; 5 | 6 | /** 7 | * Encodes a serialized representation of an exported item in the API, such as 8 | * a class, enum, or function. 9 | */ 10 | export class Encoder { 11 | private _sort = createDefaultSort(); 12 | private readonly _parser: Parser; 13 | 14 | /** 15 | * Creates a reusable Encoder instance, bound to the given {@link Parser}. 16 | */ 17 | constructor(parser: Parser) { 18 | this._parser = parser; 19 | } 20 | 21 | encodeItem(item: TS.EnumDeclaration): GD.ApiEnum; 22 | encodeItem(item: TS.InterfaceDeclaration): GD.ApiInterface; 23 | encodeItem(item: TS.ClassDeclaration): GD.ApiClass; 24 | encodeItem(item: TS.EnumDeclaration): GD.ApiEnum; 25 | encodeItem(item: TS.EnumMember): GD.ApiEnumMember; 26 | encodeItem(item: TS.TypeAliasDeclaration): GD.ApiTypeAlias; 27 | encodeItem(item: TS.FunctionDeclaration): GD.ApiFunction; 28 | encodeItem(item: TS.Node): GD.ApiItem; 29 | encodeItem(item: TS.Node): GD.ApiItem { 30 | switch (item.getKind()) { 31 | case TS.SyntaxKind.ClassDeclaration: 32 | return this._encodeClass(item as TS.ClassDeclaration); 33 | case TS.SyntaxKind.InterfaceDeclaration: 34 | return this._encodeInterface(item as TS.InterfaceDeclaration); 35 | case TS.SyntaxKind.EnumDeclaration: 36 | return this._encodeEnum(item as TS.EnumDeclaration); 37 | case TS.SyntaxKind.FunctionDeclaration: 38 | return this._encodeFunction(item as TS.FunctionDeclaration); 39 | case TS.SyntaxKind.EnumMember: 40 | return this._encodeEnumMember(item as TS.EnumMember); 41 | case TS.SyntaxKind.TypeAliasDeclaration: 42 | return this._encodeTypeAlias(item as TS.TypeAliasDeclaration) as any; // TODO(cleanup) 43 | case TS.SyntaxKind.PropertyDeclaration: 44 | case TS.SyntaxKind.MethodDeclaration: 45 | throw new Error(`Unexpected detached type, "${item.getKindName()}"`); 46 | case TS.SyntaxKind.VariableDeclaration: 47 | return this._encodeVariable(item as TS.VariableDeclaration); 48 | default: 49 | console.log(item); 50 | throw new Error(`Unsupported encoded type, "${item.getKindName()}"`); 51 | } 52 | } 53 | 54 | setSort(sort: SortFn): this { 55 | this._sort = sort; 56 | return this; 57 | } 58 | 59 | protected _encodeItem(item: TS.Node): GD.ApiItem { 60 | return { 61 | kind: this._encodeKind(item.getKind()) as any, // TODO(cleanup) 62 | name: (item as any).getName ? (item as any).getName() : '', 63 | source: { 64 | text: this._parser.getSourceText(item), 65 | url: this._parser.getSourceURL(item) 66 | }, 67 | tags: this._parser.getTags(item) || undefined 68 | }; 69 | } 70 | 71 | protected _encodeKind(kind: TS.SyntaxKind): GD.ApiItemKind { 72 | switch (kind) { 73 | case TS.SyntaxKind.ClassDeclaration: 74 | return GD.ApiItemKind.CLASS; 75 | case TS.SyntaxKind.InterfaceDeclaration: 76 | return GD.ApiItemKind.INTERFACE; 77 | case TS.SyntaxKind.EnumDeclaration: 78 | return GD.ApiItemKind.ENUM; 79 | case TS.SyntaxKind.EnumMember: 80 | return GD.ApiItemKind.ENUM_MEMBER; 81 | case TS.SyntaxKind.FunctionDeclaration: 82 | return GD.ApiItemKind.FUNCTION; 83 | case TS.SyntaxKind.Constructor: 84 | case TS.SyntaxKind.ConstructSignature: 85 | return GD.ApiItemKind.CONSTRUCTOR; 86 | case TS.SyntaxKind.MethodDeclaration: 87 | return GD.ApiItemKind.METHOD; 88 | case TS.SyntaxKind.MethodSignature: 89 | return GD.ApiItemKind.METHOD_SIGNATURE; 90 | case TS.SyntaxKind.PropertyDeclaration: 91 | return GD.ApiItemKind.PROPERTY; 92 | case TS.SyntaxKind.PropertySignature: 93 | return GD.ApiItemKind.PROPERTY_SIGNATURE; 94 | case TS.SyntaxKind.VariableDeclaration: 95 | return GD.ApiItemKind.VARIABLE; 96 | default: 97 | throw new Error(`SyntaxKind.${getKindName(kind)} not implemented.`); 98 | } 99 | } 100 | 101 | protected _encodeClass(item: TS.ClassDeclaration): GD.ApiClass { 102 | const parser = this._parser; 103 | const constructor = getInheritedConstructor(item); 104 | const data = { 105 | ...this._encodeItem(item), 106 | comment: this._encodeComment(item.getJsDocs().pop()), 107 | extendsTypes: [], 108 | constructor: constructor ? this._encodeConstructor(constructor, item) : undefined, 109 | staticProperties: getInheritedMembers(item, TS.SyntaxKind.PropertyDeclaration, true) 110 | .filter((prop) => !parser.isHidden(prop)) 111 | .map((prop) => this._encodeProperty(prop as TS.PropertyDeclaration)) 112 | .sort(this._sort), 113 | properties: getInheritedMembers(item, TS.SyntaxKind.PropertyDeclaration, false) 114 | .filter((prop) => prop.getScope() !== TS.Scope.Private) 115 | .filter((prop) => !parser.isHidden(prop)) 116 | .map((prop) => this._encodeProperty(prop as TS.PropertyDeclaration)) 117 | .sort(this._sort), 118 | staticMethods: getInheritedMembers(item, TS.SyntaxKind.MethodDeclaration, true) 119 | .filter((method) => !parser.isHidden(method)) 120 | .map((method) => this._encodeMethod(method)) 121 | .sort(this._sort), 122 | methods: getInheritedMembers(item, TS.SyntaxKind.MethodDeclaration, false) 123 | .filter((method) => method.getScope() !== TS.Scope.Private) 124 | .filter((method) => !parser.isHidden(method)) 125 | .map((method) => this._encodeMethod(method)) 126 | .sort(this._sort) 127 | } as GD.ApiClass; 128 | 129 | let base = item; 130 | while ((base = base.getBaseClass())) { 131 | data.extendsTypes.push(this._encodeReference(base)); 132 | } 133 | data.extendsTypes.reverse(); 134 | 135 | return data; 136 | } 137 | 138 | protected _encodeInterface(item: TS.InterfaceDeclaration): GD.ApiInterface { 139 | return { 140 | ...this._encodeItem(item), 141 | comment: this._encodeComment(item.getJsDocs().pop()), 142 | extendsTypes: [], // item.extendsTypes.map(({ excerpt }) => this._encodeExcerpt(excerpt)), 143 | properties: item 144 | .getProperties() 145 | .filter((prop) => !this._parser.isHidden(prop)) 146 | .map((prop) => this._encodeProperty(prop as any)) 147 | .sort(this._sort), 148 | methods: item 149 | .getMethods() 150 | .filter((method) => !this._parser.isHidden(method)) 151 | .map((method) => this._encodeMethod(method as any)) 152 | .sort(this._sort) 153 | } as GD.ApiInterface; 154 | } 155 | 156 | protected _encodeEnum(item: TS.EnumDeclaration): GD.ApiEnum { 157 | const data = { 158 | ...this._encodeItem(item), 159 | members: item 160 | .getMembers() 161 | .filter((item) => !this._parser.isHidden(item)) 162 | .map((item) => this._encodeEnumMember(item)) 163 | .sort(this._sort) 164 | } as GD.ApiEnum; 165 | 166 | const comment = item.getJsDocs().pop(); 167 | if (comment) data.comment = this._encodeComment(comment); 168 | 169 | return data; 170 | } 171 | protected _encodeFunction(item: TS.FunctionDeclaration): GD.ApiFunction { 172 | const comment = item.getJsDocs().pop(); 173 | const data = { 174 | ...this._encodeItem(item), 175 | kind: GD.ApiItemKind.FUNCTION, 176 | params: item.getParameters().map((param) => ({ 177 | name: param.getName(), 178 | type: this._encodeType(param.getType(), param.getTypeNode()), 179 | comment: comment ? this._encodeParamComment(comment, param.getName()) : undefined, 180 | optional: param.isOptional() || undefined 181 | })), 182 | returns: this._encodeType(item.getReturnType(), item.getReturnTypeNode()), 183 | returnsComment: comment ? this._encodeReturnsComment(comment) : undefined 184 | } as Partial; 185 | 186 | if (comment) data.comment = this._encodeComment(comment); 187 | 188 | return data as GD.ApiFunction; 189 | } 190 | protected _encodeEnumMember(item: TS.EnumMember): GD.ApiEnumMember { 191 | return { 192 | ...this._encodeItem(item), 193 | type: this._encodeType(item.getType()), 194 | comment: this._encodeComment(item.getJsDocs().pop()) 195 | } as GD.ApiEnumMember; 196 | } 197 | protected _encodeTypeAlias(item: TS.TypeAliasDeclaration): GD.ApiTypeAlias { 198 | return this._encodeItem(item) as GD.ApiTypeAlias; 199 | } 200 | 201 | protected _encodeVariable(item: TS.VariableDeclaration): GD.ApiVariable { 202 | return { 203 | ...this._encodeItem(item), 204 | type: this._encodeType(item.getType()) 205 | }; 206 | } 207 | 208 | protected _encodeReference(item: TS.Node): GD.Reference | null { 209 | return { 210 | path: this._parser.getPath(item), 211 | name: (item as any).getName ? (item as any).getName() : '', 212 | kind: this._encodeKind(item.getKind()) 213 | }; 214 | } 215 | 216 | protected _encodeType(type: TS.Type, typeNode?: TS.TypeNode): GD.Token { 217 | const symbol = type.getSymbol(); 218 | if (symbol) { 219 | for (const decl of symbol.getDeclarations()) { 220 | if (!this._parser.hasItem(decl)) continue; 221 | const ref = this._encodeReference(decl); 222 | if (ref) return ref; 223 | } 224 | } 225 | if (typeNode) return typeNode.getText(); 226 | if (type.isAnonymous()) return 'unknown'; 227 | return type.getText(); 228 | } 229 | 230 | protected _encodeComment(comment?: TS.JSDoc): string { 231 | if (!comment) return ''; 232 | 233 | let md = comment.getCommentText(); 234 | if (!md) return ''; 235 | 236 | md = md.replaceAll(/{@link ([\S]+)\s*(\S+)?\s*}/g, (_, anchorRef, anchorText) => { 237 | const [exportName, fragment] = anchorRef.split('.'); 238 | const item = this._parser.getItemByExportName(exportName); 239 | const text = anchorText || anchorRef; 240 | const href = this._parser.getPath(item) + (fragment ? `#${fragment}` : ''); 241 | return `[${text}](${href})`; 242 | }); 243 | 244 | return this._parser.renderMarkdown(md) || ''; 245 | } 246 | 247 | protected _encodeParamComment(comment: TS.JSDoc, name: string): string | undefined { 248 | for (const tag of comment.getTags()) { 249 | const tagName = tag.getTagName(); 250 | if (tagName === 'param' || tagName === 'arg' || tagName === 'argument') { 251 | const paramTag = tag as TS.JSDocParameterTag; 252 | if (paramTag.getName() === name) { 253 | return this._encodeComment(paramTag as unknown as TS.JSDoc); 254 | } 255 | } 256 | } 257 | return undefined; 258 | } 259 | 260 | protected _encodeReturnsComment(comment?: TS.JSDoc): string | undefined { 261 | for (const tag of comment.getTags()) { 262 | const tagName = tag.getTagName(); 263 | if (tagName === 'returns' || tagName === 'return') { 264 | return this._encodeComment(tag as unknown as TS.JSDoc); 265 | } 266 | } 267 | return undefined; 268 | } 269 | 270 | protected _encodeMember( 271 | item: 272 | | TS.ConstructorDeclaration 273 | | TS.MethodDeclaration 274 | | TS.MethodSignature 275 | | TS.PropertyDeclaration 276 | | TS.PropertySignature 277 | ): Omit { 278 | const data = { 279 | ...this._encodeItem(item) 280 | // overwrite?: Reference, 281 | } as Partial; 282 | 283 | if (item instanceof TS.MethodDeclaration || item instanceof TS.PropertyDeclaration) { 284 | if (item.isStatic()) data.isStatic = true; 285 | if (item.getScope() === TS.Scope.Protected) data.isProtected = true; 286 | } 287 | 288 | const comment = item.getJsDocs().pop(); 289 | if (comment) data.comment = this._encodeComment(comment); 290 | 291 | return data as GD.ApiMember; 292 | } 293 | 294 | protected _encodeConstructor( 295 | item: TS.ConstructorDeclaration, 296 | parent: TS.ClassDeclaration 297 | ): GD.ApiConstructor { 298 | return { 299 | ...this._encodeMember(item), 300 | kind: GD.ApiItemKind.CONSTRUCTOR, 301 | isStatic: undefined, 302 | name: 'constructor', 303 | params: item.getParameters().map((param) => ({ 304 | name: param.getName(), 305 | type: this._encodeType(param.getType(), param.getTypeNode()), 306 | optional: param.isOptional() || undefined 307 | })), 308 | returns: this._encodeType(parent.getType()) 309 | }; 310 | } 311 | 312 | protected _encodeMethod(item: TS.MethodDeclaration | TS.MethodSignature): GD.ApiMethod { 313 | const comment = item.getJsDocs().pop(); 314 | return { 315 | ...this._encodeMember(item), 316 | kind: GD.ApiItemKind.METHOD, 317 | params: item.getParameters().map((param) => ({ 318 | name: param.getName(), 319 | type: this._encodeType(param.getType(), param.getTypeNode()), 320 | comment: comment ? this._encodeParamComment(comment, param.getName()) : undefined, 321 | optional: param.isOptional() || undefined 322 | })), 323 | returns: this._encodeType(item.getReturnType(), item.getReturnTypeNode()), 324 | returnsComment: comment ? this._encodeReturnsComment(comment) : undefined 325 | }; 326 | } 327 | 328 | protected _encodeProperty(item: TS.PropertyDeclaration | TS.PropertySignature): GD.ApiProperty { 329 | return { 330 | ...this._encodeMember(item), 331 | kind: GD.ApiItemKind.PROPERTY, 332 | type: this._encodeType(item.getType(), item.getTypeNode()), 333 | isReadonly: item.isReadonly() 334 | }; 335 | } 336 | } 337 | 338 | function getInheritedConstructor(child: TS.ClassDeclaration): TS.ConstructorDeclaration | null { 339 | // TODO: Traverse inheritance tree. 340 | return child.getConstructors()[0] || null; 341 | } 342 | 343 | function getInheritedMembers( 344 | child: TS.ClassDeclaration, 345 | kind: TS.SyntaxKind.MethodDeclaration, 346 | _static: boolean 347 | ): T[]; 348 | function getInheritedMembers( 349 | child: TS.ClassDeclaration, 350 | kind: TS.SyntaxKind.PropertyDeclaration, 351 | _static: boolean 352 | ): T[]; 353 | function getInheritedMembers( 354 | child: TS.ClassDeclaration, 355 | kind: TS.SyntaxKind.MethodDeclaration | TS.SyntaxKind.PropertyDeclaration, 356 | _static: boolean 357 | ): T[] { 358 | const members: T[] = []; 359 | const memberSet = new Set(); 360 | 361 | const baseMembers = 362 | kind === TS.SyntaxKind.MethodDeclaration ? child.getMethods() : child.getProperties(); 363 | for (const member of baseMembers) { 364 | if (member.isStatic() !== _static) continue; 365 | memberSet.add(member.getName()); 366 | members.push(member as T); 367 | } 368 | 369 | let current = child; 370 | while ((current = current.getBaseClass())) { 371 | const inheritedMembers = 372 | kind === TS.SyntaxKind.MethodDeclaration ? current.getMethods() : current.getProperties(); 373 | for (const member of inheritedMembers) { 374 | if (member.isStatic() !== _static) continue; 375 | if (memberSet.has(member.getName())) continue; 376 | memberSet.add(member.getName()); 377 | members.push(member as T); 378 | } 379 | } 380 | 381 | return members.sort((a, b) => (a.getName() > b.getName() ? 1 : -1)); 382 | } 383 | 384 | let _kindNameCache: Record | undefined; 385 | function getKindName(kind: TS.SyntaxKind): string { 386 | if (!_kindNameCache) { 387 | _kindNameCache = {}; 388 | for (const key in TS.SyntaxKind) { 389 | _kindNameCache[TS.SyntaxKind[key]] = key; 390 | } 391 | } 392 | return _kindNameCache[kind]; 393 | } 394 | -------------------------------------------------------------------------------- /packages/docs/static/main.css: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | * * Base 3 | * * ========================================================================== */ 4 | /** 5 | * * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using 6 | * * `em` units. 7 | * * 2. Prevent iOS text size adjust after orientation change, without disabling 8 | * * user zoom. */ 9 | html { 10 | font-size: 100%; 11 | /* 1 */ 12 | -ms-text-size-adjust: 100%; 13 | /* 2 */ 14 | -webkit-text-size-adjust: 100%; 15 | /* 2 */ 16 | font-family: sans-serif; 17 | } 18 | 19 | /** 20 | * * Address `font-family` inconsistency between `textarea` and other form 21 | * * elements. */ 22 | button, 23 | input, 24 | select, 25 | textarea { 26 | font-family: sans-serif; 27 | } 28 | 29 | /** 30 | * * Address margins handled incorrectly in IE 6/7. */ 31 | body { 32 | margin: 0; 33 | } 34 | 35 | /* ========================================================================== 36 | * * Links 37 | * * ========================================================================== */ 38 | /** 39 | * * Address `outline` inconsistency between Chrome and other browsers. */ 40 | a:focus { 41 | outline: thin dotted; 42 | } 43 | a:active, 44 | a:hover { 45 | outline: 0; 46 | } 47 | 48 | /** 49 | * * Improve readability when focused and also mouse hovered in all browsers. */ 50 | /* ========================================================================== 51 | * * Typography 52 | * * ========================================================================== */ 53 | /** 54 | * * Address font sizes and margins set differently in IE 6/7. 55 | * * Address font sizes within `section` and `article` in Firefox 4+, Safari 5, 56 | * * and Chrome. */ 57 | h1 { 58 | font-size: 2em; 59 | margin: 0.67em 0; 60 | } 61 | 62 | h2 { 63 | font-size: 1.5em; 64 | margin: 2em 0 0.83em; 65 | } 66 | 67 | h3 { 68 | font-size: 1.17em; 69 | margin: 1em 0; 70 | } 71 | 72 | h4, 73 | .greendoc-index-panel h3 { 74 | font-size: 1em; 75 | margin: 1.33em 0; 76 | } 77 | 78 | h5 { 79 | font-size: 0.83em; 80 | margin: 1.67em 0; 81 | } 82 | 83 | h6 { 84 | font-size: 0.67em; 85 | margin: 2.33em 0; 86 | } 87 | 88 | /** 89 | * * Address styling not present in IE 7/8/9, Safari 5, and Chrome. */ 90 | abbr[title] { 91 | border-bottom: 1px dotted; 92 | } 93 | 94 | /** 95 | * * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. */ 96 | b, 97 | strong { 98 | font-weight: bold; 99 | } 100 | 101 | blockquote { 102 | margin: 1em 40px; 103 | } 104 | 105 | /** 106 | * * Address styling not present in Safari 5 and Chrome. */ 107 | dfn { 108 | font-style: italic; 109 | } 110 | 111 | /** 112 | * * Address differences between Firefox and other browsers. 113 | * * Known issue: no IE 6/7 normalization. */ 114 | hr { 115 | box-sizing: content-box; 116 | height: 0; 117 | } 118 | 119 | /** 120 | * * Address styling not present in IE 6/7/8/9. */ 121 | mark { 122 | background: #ff8; 123 | color: #000; 124 | } 125 | 126 | /** 127 | * * Address margins set differently in IE 6/7. */ 128 | p, 129 | pre { 130 | margin: 1em 0; 131 | } 132 | 133 | /** 134 | * * Correct font family set oddly in IE 6, Safari 4/5, and Chrome. */ 135 | code, 136 | kbd, 137 | pre, 138 | samp { 139 | font-family: monospace, serif; 140 | _font-family: 'courier new', monospace; 141 | font-size: 1em; 142 | } 143 | 144 | /** 145 | * * Improve readability of pre-formatted text in all browsers. */ 146 | pre { 147 | white-space: pre; 148 | white-space: pre-wrap; 149 | word-wrap: break-word; 150 | } 151 | 152 | /** 153 | * * Address CSS quotes not supported in IE 6/7. */ 154 | q { 155 | quotes: none; 156 | } 157 | q:before, 158 | q:after { 159 | content: ''; 160 | content: none; 161 | } 162 | 163 | /** 164 | * * Address `quotes` property not supported in Safari 4. */ 165 | /** 166 | * * Address inconsistent and variable font size in all browsers. */ 167 | small { 168 | font-size: 80%; 169 | } 170 | 171 | /** 172 | * * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ 173 | sub { 174 | font-size: 75%; 175 | line-height: 0; 176 | position: relative; 177 | vertical-align: baseline; 178 | } 179 | 180 | sup { 181 | font-size: 75%; 182 | line-height: 0; 183 | position: relative; 184 | vertical-align: baseline; 185 | top: -0.5em; 186 | } 187 | 188 | sub { 189 | bottom: -0.25em; 190 | } 191 | 192 | /* ========================================================================== 193 | * * Lists 194 | * * ========================================================================== */ 195 | /** 196 | * * Address margins set differently in IE 6/7. */ 197 | dl, 198 | menu, 199 | ol, 200 | ul { 201 | margin: 1em 0; 202 | } 203 | 204 | dd { 205 | margin: 0 0 0 40px; 206 | } 207 | 208 | /** 209 | * * Address paddings set differently in IE 6/7. */ 210 | menu, 211 | ol, 212 | ul { 213 | padding: 0 0 0 40px; 214 | } 215 | 216 | /** 217 | * * Correct list images handled incorrectly in IE 7. */ 218 | nav ul, 219 | nav ol { 220 | list-style: none; 221 | list-style-image: none; 222 | } 223 | 224 | /* ========================================================================== 225 | * * Embedded content 226 | * * ========================================================================== */ 227 | /** 228 | * * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. 229 | * * 2. Improve image quality when scaled in IE 7. */ 230 | img { 231 | border: 0; 232 | /* 1 */ 233 | -ms-interpolation-mode: bicubic; 234 | } 235 | 236 | /* 2 */ 237 | /** 238 | * * Correct overflow displayed oddly in IE 9. */ 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* ========================================================================== 244 | * * Figures 245 | * * ========================================================================== */ 246 | /** 247 | * * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. */ 248 | figure, 249 | form { 250 | margin: 0; 251 | } 252 | 253 | /* ========================================================================== 254 | * * Forms 255 | * * ========================================================================== */ 256 | /** 257 | * * Correct margin displayed oddly in IE 6/7. */ 258 | /** 259 | * * Define consistent border, margin, and padding. */ 260 | fieldset { 261 | border: 1px solid #c0c0c0; 262 | margin: 0 2px; 263 | padding: 0.35em 0.625em 0.75em; 264 | } 265 | 266 | /** 267 | * * 1. Correct color not being inherited in IE 6/7/8/9. 268 | * * 2. Correct text not wrapping in Firefox 3. 269 | * * 3. Correct alignment displayed oddly in IE 6/7. */ 270 | legend { 271 | border: 0; 272 | /* 1 */ 273 | padding: 0; 274 | white-space: normal; 275 | /* 2 */ 276 | *margin-left: -7px; 277 | } 278 | 279 | /* 3 */ 280 | /** 281 | * * 1. Correct font size not being inherited in all browsers. 282 | * * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, 283 | * * and Chrome. 284 | * * 3. Improve appearance and consistency in all browsers. */ 285 | button, 286 | input, 287 | select, 288 | textarea { 289 | font-size: 100%; 290 | /* 1 */ 291 | margin: 0; 292 | /* 2 */ 293 | vertical-align: baseline; 294 | /* 3 */ 295 | *vertical-align: middle; 296 | } 297 | 298 | /* 3 */ 299 | /** 300 | * * Address Firefox 3+ setting `line-height` on `input` using `!important` in 301 | * * the UA stylesheet. */ 302 | button, 303 | input { 304 | line-height: normal; 305 | } 306 | 307 | /** 308 | * * Address inconsistent `text-transform` inheritance for `button` and `select`. 309 | * * All other form control elements do not inherit `text-transform` values. 310 | * * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. 311 | * * Correct `select` style inheritance in Firefox 4+ and Opera. */ 312 | button, 313 | select { 314 | text-transform: none; 315 | } 316 | 317 | /** 318 | * * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 319 | * * and `video` controls. 320 | * * 2. Correct inability to style clickable `input` types in iOS. 321 | * * 3. Improve usability and consistency of cursor style between image-type 322 | * * `input` and others. 323 | * * 4. Remove inner spacing in IE 7 without affecting normal text inputs. 324 | * * Known issue: inner spacing remains in IE 6. */ 325 | button, 326 | html input[type='button'] { 327 | -webkit-appearance: button; 328 | /* 2 */ 329 | cursor: pointer; 330 | /* 3 */ 331 | *overflow: visible; 332 | } 333 | 334 | /* 4 */ 335 | input[type='reset'], 336 | input[type='submit'] { 337 | -webkit-appearance: button; 338 | /* 2 */ 339 | cursor: pointer; 340 | /* 3 */ 341 | *overflow: visible; 342 | } 343 | 344 | /* 4 */ 345 | /** 346 | * * Re-set default cursor for disabled elements. */ 347 | button[disabled], 348 | html input[disabled] { 349 | cursor: default; 350 | } 351 | 352 | /** 353 | * * 1. Address box sizing set to content-box in IE 8/9. 354 | * * 2. Remove excess padding in IE 8/9. 355 | * * 3. Remove excess padding in IE 7. 356 | * * Known issue: excess padding remains in IE 6. */ 357 | input { 358 | /* 3 */ 359 | } 360 | input[type='checkbox'], 361 | input[type='radio'] { 362 | box-sizing: border-box; 363 | /* 1 */ 364 | padding: 0; 365 | /* 2 */ 366 | *height: 13px; 367 | /* 3 */ 368 | *width: 13px; 369 | } 370 | input[type='search'] { 371 | -webkit-appearance: textfield; 372 | /* 1 */ 373 | /* 2 */ 374 | box-sizing: content-box; 375 | } 376 | input[type='search']::-webkit-search-cancel-button, 377 | input[type='search']::-webkit-search-decoration { 378 | -webkit-appearance: none; 379 | } 380 | 381 | /** 382 | * * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 383 | * * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 384 | * * (include `-moz` to future-proof). */ 385 | /** 386 | * * Remove inner padding and search cancel button in Safari 5 and Chrome 387 | * * on OS X. */ 388 | /** 389 | * * Remove inner padding and border in Firefox 3+. */ 390 | button::-moz-focus-inner, 391 | input::-moz-focus-inner { 392 | border: 0; 393 | padding: 0; 394 | } 395 | 396 | /** 397 | * * 1. Remove default vertical scrollbar in IE 6/7/8/9. 398 | * * 2. Improve readability and alignment in all browsers. */ 399 | textarea { 400 | overflow: auto; 401 | /* 1 */ 402 | vertical-align: top; 403 | } 404 | 405 | /* 2 */ 406 | /* ========================================================================== 407 | * * Tables 408 | * * ========================================================================== */ 409 | /** 410 | * * Remove most spacing between table cells. */ 411 | table { 412 | border-collapse: collapse; 413 | border-spacing: 0; 414 | } 415 | 416 | /* * 417 | * *Visual Studio-like style based on original C# coloring by Jason Diamond */ 418 | .hljs { 419 | display: inline-block; 420 | padding: 0.5em; 421 | background: white; 422 | color: black; 423 | } 424 | 425 | .hljs-comment, 426 | .hljs-annotation, 427 | .hljs-template_comment, 428 | .diff .hljs-header, 429 | .hljs-chunk, 430 | .apache .hljs-cbracket { 431 | color: #008000; 432 | } 433 | 434 | .hljs-keyword, 435 | .hljs-id, 436 | .hljs-built_in, 437 | .css .smalltalk .hljs-class, 438 | .hljs-winutils, 439 | .bash .hljs-variable, 440 | .tex .hljs-command, 441 | .hljs-request, 442 | .hljs-status, 443 | .nginx .hljs-title { 444 | color: #00f; 445 | } 446 | 447 | .xml .hljs-tag { 448 | color: #00f; 449 | } 450 | .xml .hljs-tag .hljs-value { 451 | color: #00f; 452 | } 453 | 454 | .hljs-string, 455 | .hljs-title, 456 | .hljs-parent, 457 | .hljs-tag .hljs-value, 458 | .hljs-rules .hljs-value { 459 | color: #a31515; 460 | } 461 | 462 | .ruby .hljs-symbol { 463 | color: #a31515; 464 | } 465 | .ruby .hljs-symbol .hljs-string { 466 | color: #a31515; 467 | } 468 | 469 | .hljs-template_tag, 470 | .django .hljs-variable, 471 | .hljs-addition, 472 | .hljs-flow, 473 | .hljs-stream, 474 | .apache .hljs-tag, 475 | .hljs-date, 476 | .tex .hljs-formula, 477 | .coffeescript .hljs-attribute { 478 | color: #a31515; 479 | } 480 | 481 | .ruby .hljs-string, 482 | .hljs-decorator, 483 | .hljs-filter .hljs-argument, 484 | .hljs-localvars, 485 | .hljs-array, 486 | .hljs-attr_selector, 487 | .hljs-pseudo, 488 | .hljs-pi, 489 | .hljs-doctype, 490 | .hljs-deletion, 491 | .hljs-envvar, 492 | .hljs-shebang, 493 | .hljs-preprocessor, 494 | .hljs-pragma, 495 | .userType, 496 | .apache .hljs-sqbracket, 497 | .nginx .hljs-built_in, 498 | .tex .hljs-special, 499 | .hljs-prompt { 500 | color: #2b91af; 501 | } 502 | 503 | .hljs-phpdoc, 504 | .hljs-javadoc, 505 | .hljs-xmlDocTag { 506 | color: #808080; 507 | } 508 | 509 | .vhdl .hljs-typename { 510 | font-weight: bold; 511 | } 512 | .vhdl .hljs-string { 513 | color: #666666; 514 | } 515 | .vhdl .hljs-literal { 516 | color: #a31515; 517 | } 518 | .vhdl .hljs-attribute { 519 | color: #00b0e8; 520 | } 521 | 522 | .xml .hljs-attribute { 523 | color: #f00; 524 | } 525 | 526 | ul.greendoc-descriptions > li > :first-child, 527 | .greendoc-panel > :first-child, 528 | .col > :first-child, 529 | .col-11 > :first-child, 530 | .col-10 > :first-child, 531 | .col-9 > :first-child, 532 | .col-8 > :first-child, 533 | .col-7 > :first-child, 534 | .col-6 > :first-child, 535 | .col-5 > :first-child, 536 | .col-4 > :first-child, 537 | .col-3 > :first-child, 538 | .col-2 > :first-child, 539 | .col-1 > :first-child, 540 | ul.greendoc-descriptions > li > :first-child > :first-child, 541 | .greendoc-panel > :first-child > :first-child, 542 | .col > :first-child > :first-child, 543 | .col-11 > :first-child > :first-child, 544 | .col-10 > :first-child > :first-child, 545 | .col-9 > :first-child > :first-child, 546 | .col-8 > :first-child > :first-child, 547 | .col-7 > :first-child > :first-child, 548 | .col-6 > :first-child > :first-child, 549 | .col-5 > :first-child > :first-child, 550 | .col-4 > :first-child > :first-child, 551 | .col-3 > :first-child > :first-child, 552 | .col-2 > :first-child > :first-child, 553 | .col-1 > :first-child > :first-child, 554 | ul.greendoc-descriptions > li > :first-child > :first-child > :first-child, 555 | .greendoc-panel > :first-child > :first-child > :first-child, 556 | .col > :first-child > :first-child > :first-child, 557 | .col-11 > :first-child > :first-child > :first-child, 558 | .col-10 > :first-child > :first-child > :first-child, 559 | .col-9 > :first-child > :first-child > :first-child, 560 | .col-8 > :first-child > :first-child > :first-child, 561 | .col-7 > :first-child > :first-child > :first-child, 562 | .col-6 > :first-child > :first-child > :first-child, 563 | .col-5 > :first-child > :first-child > :first-child, 564 | .col-4 > :first-child > :first-child > :first-child, 565 | .col-3 > :first-child > :first-child > :first-child, 566 | .col-2 > :first-child > :first-child > :first-child, 567 | .col-1 > :first-child > :first-child > :first-child { 568 | margin-top: 0; 569 | } 570 | ul.greendoc-descriptions > li > :last-child, 571 | .greendoc-panel > :last-child, 572 | .col > :last-child, 573 | .col-11 > :last-child, 574 | .col-10 > :last-child, 575 | .col-9 > :last-child, 576 | .col-8 > :last-child, 577 | .col-7 > :last-child, 578 | .col-6 > :last-child, 579 | .col-5 > :last-child, 580 | .col-4 > :last-child, 581 | .col-3 > :last-child, 582 | .col-2 > :last-child, 583 | .col-1 > :last-child, 584 | ul.greendoc-descriptions > li > :last-child > :last-child, 585 | .greendoc-panel > :last-child > :last-child, 586 | .col > :last-child > :last-child, 587 | .col-11 > :last-child > :last-child, 588 | .col-10 > :last-child > :last-child, 589 | .col-9 > :last-child > :last-child, 590 | .col-8 > :last-child > :last-child, 591 | .col-7 > :last-child > :last-child, 592 | .col-6 > :last-child > :last-child, 593 | .col-5 > :last-child > :last-child, 594 | .col-4 > :last-child > :last-child, 595 | .col-3 > :last-child > :last-child, 596 | .col-2 > :last-child > :last-child, 597 | .col-1 > :last-child > :last-child, 598 | ul.greendoc-descriptions > li > :last-child > :last-child > :last-child, 599 | .greendoc-panel > :last-child > :last-child > :last-child, 600 | .col > :last-child > :last-child > :last-child, 601 | .col-11 > :last-child > :last-child > :last-child, 602 | .col-10 > :last-child > :last-child > :last-child, 603 | .col-9 > :last-child > :last-child > :last-child, 604 | .col-8 > :last-child > :last-child > :last-child, 605 | .col-7 > :last-child > :last-child > :last-child, 606 | .col-6 > :last-child > :last-child > :last-child, 607 | .col-5 > :last-child > :last-child > :last-child, 608 | .col-4 > :last-child > :last-child > :last-child, 609 | .col-3 > :last-child > :last-child > :last-child, 610 | .col-2 > :last-child > :last-child > :last-child, 611 | .col-1 > :last-child > :last-child > :last-child { 612 | margin-bottom: 0; 613 | } 614 | 615 | .container { 616 | max-width: 1200px; 617 | margin: 0 auto; 618 | padding: 0 40px; 619 | } 620 | @media (max-width: 640px) { 621 | .container { 622 | padding: 0 20px; 623 | } 624 | } 625 | 626 | .container-main { 627 | padding-bottom: 200px; 628 | } 629 | 630 | .row { 631 | display: -ms-flexbox; 632 | display: flex; 633 | position: relative; 634 | margin: 0 -10px; 635 | } 636 | .row:after { 637 | visibility: hidden; 638 | display: block; 639 | content: ''; 640 | clear: both; 641 | height: 0; 642 | } 643 | 644 | .col, 645 | .col-11, 646 | .col-10, 647 | .col-9, 648 | .col-8, 649 | .col-7, 650 | .col-6, 651 | .col-5, 652 | .col-4, 653 | .col-3, 654 | .col-2, 655 | .col-1 { 656 | box-sizing: border-box; 657 | float: left; 658 | padding: 0 10px; 659 | } 660 | 661 | .col-1 { 662 | width: 8.3333333333%; 663 | } 664 | 665 | .offset-1 { 666 | margin-left: 8.3333333333%; 667 | } 668 | 669 | .col-2 { 670 | width: 16.6666666667%; 671 | } 672 | 673 | .offset-2 { 674 | margin-left: 16.6666666667%; 675 | } 676 | 677 | .col-3 { 678 | width: 25%; 679 | } 680 | 681 | .offset-3 { 682 | margin-left: 25%; 683 | } 684 | 685 | .col-4 { 686 | width: 33.3333333333%; 687 | } 688 | 689 | .offset-4 { 690 | margin-left: 33.3333333333%; 691 | } 692 | 693 | .col-5 { 694 | width: 41.6666666667%; 695 | } 696 | 697 | .offset-5 { 698 | margin-left: 41.6666666667%; 699 | } 700 | 701 | .col-6 { 702 | width: 50%; 703 | } 704 | 705 | .offset-6 { 706 | margin-left: 50%; 707 | } 708 | 709 | .col-7 { 710 | width: 58.3333333333%; 711 | } 712 | 713 | .offset-7 { 714 | margin-left: 58.3333333333%; 715 | } 716 | 717 | .col-8 { 718 | width: 66.6666666667%; 719 | } 720 | 721 | .offset-8 { 722 | margin-left: 66.6666666667%; 723 | } 724 | 725 | .col-9 { 726 | width: 75%; 727 | } 728 | 729 | .offset-9 { 730 | margin-left: 75%; 731 | } 732 | 733 | .col-10 { 734 | width: 83.3333333333%; 735 | } 736 | 737 | .offset-10 { 738 | margin-left: 83.3333333333%; 739 | } 740 | 741 | .col-11 { 742 | width: 91.6666666667%; 743 | } 744 | 745 | .offset-11 { 746 | margin-left: 91.6666666667%; 747 | } 748 | 749 | @keyframes fade-in { 750 | from { 751 | opacity: 0; 752 | } 753 | to { 754 | opacity: 1; 755 | } 756 | } 757 | @keyframes fade-out { 758 | from { 759 | opacity: 1; 760 | visibility: visible; 761 | } 762 | to { 763 | opacity: 0; 764 | } 765 | } 766 | @keyframes fade-in-delayed { 767 | 0% { 768 | opacity: 0; 769 | } 770 | 33% { 771 | opacity: 0; 772 | } 773 | 100% { 774 | opacity: 1; 775 | } 776 | } 777 | @keyframes fade-out-delayed { 778 | 0% { 779 | opacity: 1; 780 | visibility: visible; 781 | } 782 | 66% { 783 | opacity: 0; 784 | } 785 | 100% { 786 | opacity: 0; 787 | } 788 | } 789 | @keyframes shift-to-left { 790 | from { 791 | transform: translate(0, 0); 792 | } 793 | to { 794 | transform: translate(-25%, 0); 795 | } 796 | } 797 | @keyframes unshift-to-left { 798 | from { 799 | transform: translate(-25%, 0); 800 | } 801 | to { 802 | transform: translate(0, 0); 803 | } 804 | } 805 | @keyframes pop-in-from-right { 806 | from { 807 | transform: translate(100%, 0); 808 | } 809 | to { 810 | transform: translate(0, 0); 811 | } 812 | } 813 | @keyframes pop-out-to-right { 814 | from { 815 | transform: translate(0, 0); 816 | visibility: visible; 817 | } 818 | to { 819 | transform: translate(100%, 0); 820 | } 821 | } 822 | body { 823 | background: #fdfdfd; 824 | font-family: 'Segoe UI', sans-serif; 825 | font-size: 16px; 826 | color: #222; 827 | } 828 | 829 | a { 830 | color: #4da6ff; 831 | text-decoration: none; 832 | } 833 | a:hover { 834 | text-decoration: underline; 835 | } 836 | 837 | code, 838 | pre { 839 | font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; 840 | padding: 0.2em; 841 | margin: 0; 842 | font-size: 14px; 843 | background-color: rgba(0, 0, 0, 0.04); 844 | } 845 | 846 | pre { 847 | padding: 2em; 848 | } 849 | pre code { 850 | padding: 0; 851 | font-size: 100%; 852 | background-color: transparent; 853 | } 854 | 855 | .greendoc-typography { 856 | line-height: 1.333em; 857 | } 858 | .greendoc-typography ul { 859 | list-style: square; 860 | padding: 0 0 0 20px; 861 | margin: 0; 862 | } 863 | .greendoc-typography h4, 864 | .greendoc-typography .greendoc-index-panel h3, 865 | .greendoc-index-panel .greendoc-typography h3, 866 | .greendoc-typography h5, 867 | .greendoc-typography h6 { 868 | font-size: 1em; 869 | margin: 0; 870 | } 871 | .greendoc-typography h5, 872 | .greendoc-typography h6 { 873 | font-weight: normal; 874 | } 875 | .greendoc-typography p, 876 | .greendoc-typography ul, 877 | .greendoc-typography ol { 878 | margin: 1em 0; 879 | } 880 | 881 | @media (min-width: 901px) and (max-width: 1024px) { 882 | html.default .col-content { 883 | width: 72%; 884 | } 885 | html.default .col-menu { 886 | width: 28%; 887 | } 888 | html.default .greendoc-navigation { 889 | padding-left: 10px; 890 | } 891 | } 892 | @media (max-width: 900px) { 893 | html.default .col-content { 894 | float: none; 895 | width: 100%; 896 | } 897 | html.default .col-menu { 898 | position: fixed !important; 899 | overflow: auto; 900 | -webkit-overflow-scrolling: touch; 901 | z-index: 1024; 902 | top: 0 !important; 903 | bottom: 0 !important; 904 | left: auto !important; 905 | right: 0 !important; 906 | width: 100%; 907 | padding: 20px 20px 0 0; 908 | max-width: 450px; 909 | visibility: hidden; 910 | background-color: #fff; 911 | transform: translate(100%, 0); 912 | } 913 | html.default .col-menu > *:last-child { 914 | padding-bottom: 20px; 915 | } 916 | html.default .overlay { 917 | content: ''; 918 | display: block; 919 | position: fixed; 920 | z-index: 1023; 921 | top: 0; 922 | left: 0; 923 | right: 0; 924 | bottom: 0; 925 | background-color: rgba(0, 0, 0, 0.75); 926 | visibility: hidden; 927 | } 928 | html.default.to-has-menu .overlay { 929 | animation: fade-in 0.4s; 930 | } 931 | html.default.to-has-menu header, 932 | html.default.to-has-menu footer, 933 | html.default.to-has-menu .col-content { 934 | animation: shift-to-left 0.4s; 935 | } 936 | html.default.to-has-menu .col-menu { 937 | animation: pop-in-from-right 0.4s; 938 | } 939 | html.default.from-has-menu .overlay { 940 | animation: fade-out 0.4s; 941 | } 942 | html.default.from-has-menu header, 943 | html.default.from-has-menu footer, 944 | html.default.from-has-menu .col-content { 945 | animation: unshift-to-left 0.4s; 946 | } 947 | html.default.from-has-menu .col-menu { 948 | animation: pop-out-to-right 0.4s; 949 | } 950 | html.default.has-menu body { 951 | overflow: hidden; 952 | } 953 | html.default.has-menu .overlay { 954 | visibility: visible; 955 | } 956 | html.default.has-menu header, 957 | html.default.has-menu footer, 958 | html.default.has-menu .col-content { 959 | transform: translate(-25%, 0); 960 | } 961 | html.default.has-menu .col-menu { 962 | visibility: visible; 963 | transform: translate(0, 0); 964 | } 965 | } 966 | 967 | .greendoc-page-title { 968 | padding: 70px 0 20px 0; 969 | margin: 0 0 40px 0; 970 | background: #fff; 971 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.35); 972 | } 973 | .greendoc-page-title h1 { 974 | margin: 0; 975 | } 976 | 977 | .greendoc-breadcrumb { 978 | margin: 0; 979 | padding: 0; 980 | color: #808080; 981 | } 982 | .greendoc-breadcrumb a { 983 | color: #808080; 984 | text-decoration: none; 985 | } 986 | .greendoc-breadcrumb a:hover { 987 | text-decoration: underline; 988 | } 989 | .greendoc-breadcrumb li { 990 | display: inline; 991 | } 992 | .greendoc-breadcrumb li:after { 993 | content: ' / '; 994 | } 995 | 996 | html.minimal .container { 997 | margin: 0; 998 | } 999 | html.minimal .container-main { 1000 | padding-top: 50px; 1001 | padding-bottom: 0; 1002 | } 1003 | html.minimal .content-wrap { 1004 | padding-left: 300px; 1005 | } 1006 | html.minimal .greendoc-navigation { 1007 | position: fixed !important; 1008 | overflow: auto; 1009 | -webkit-overflow-scrolling: touch; 1010 | box-sizing: border-box; 1011 | z-index: 1; 1012 | left: 0; 1013 | top: 40px; 1014 | bottom: 0; 1015 | width: 300px; 1016 | padding: 20px; 1017 | margin: 0; 1018 | } 1019 | html.minimal .greendoc-member .greendoc-member { 1020 | margin-left: 0; 1021 | } 1022 | html.minimal .greendoc-page-toolbar { 1023 | position: fixed; 1024 | z-index: 2; 1025 | } 1026 | html.minimal footer { 1027 | background-color: transparent; 1028 | } 1029 | html.minimal footer .container { 1030 | padding: 0; 1031 | } 1032 | html.minimal .greendoc-generator { 1033 | padding: 0; 1034 | } 1035 | @media (max-width: 900px) { 1036 | html.minimal .greendoc-navigation { 1037 | display: none; 1038 | } 1039 | html.minimal .content-wrap { 1040 | padding-left: 0; 1041 | } 1042 | } 1043 | 1044 | dl.greendoc-comment-tags { 1045 | overflow: hidden; 1046 | } 1047 | dl.greendoc-comment-tags dt { 1048 | float: left; 1049 | padding: 1px 5px; 1050 | margin: 0 10px 0 0; 1051 | border-radius: 4px; 1052 | border: 1px solid #808080; 1053 | color: #808080; 1054 | font-size: 0.8em; 1055 | font-weight: normal; 1056 | } 1057 | dl.greendoc-comment-tags dd { 1058 | margin: 0 0 10px 0; 1059 | } 1060 | dl.greendoc-comment-tags dd:before, 1061 | dl.greendoc-comment-tags dd:after { 1062 | display: table; 1063 | content: ' '; 1064 | } 1065 | dl.greendoc-comment-tags dd pre, 1066 | dl.greendoc-comment-tags dd:after { 1067 | clear: both; 1068 | } 1069 | dl.greendoc-comment-tags p { 1070 | margin: 0; 1071 | } 1072 | 1073 | /** TODO: Merge with above? */ 1074 | .tags { 1075 | list-style: none; 1076 | margin: 0; 1077 | padding: 0; 1078 | } 1079 | .tag { 1080 | display: inline-block; 1081 | padding: 0.2em 0.5em; 1082 | background: rgba(0, 0, 0, 0.04); 1083 | height: 14px; 1084 | font-size: 0.8em; 1085 | font-family: monospace; 1086 | border: 1px solid #c0c0c0; 1087 | border-radius: 7px; 1088 | margin-right: 0.2em; 1089 | } 1090 | .tag[data-gd-tag='alpha'], 1091 | .tag[data-gd-tag='experimental'] { 1092 | background-color: orangered; 1093 | color: #fff; 1094 | } 1095 | .tag[data-gd-tag='beta'] { 1096 | background-color: goldenrod; 1097 | color: #fff; 1098 | } 1099 | .tag[data-gd-tag='deprecated'] { 1100 | background-color: crimson; 1101 | color: #fff; 1102 | } 1103 | 1104 | .greendoc-panel.greendoc-comment .lead { 1105 | font-size: 1.1em; 1106 | line-height: 1.333em; 1107 | margin-bottom: 2em; 1108 | } 1109 | .greendoc-panel.greendoc-comment .lead:last-child { 1110 | margin-bottom: 0; 1111 | } 1112 | 1113 | .toggle-protected .greendoc-is-private { 1114 | display: none; 1115 | } 1116 | 1117 | .toggle-public .greendoc-is-private, 1118 | .toggle-public .greendoc-is-protected, 1119 | .toggle-public .greendoc-is-private-protected { 1120 | display: none; 1121 | } 1122 | 1123 | .toggle-inherited .greendoc-is-inherited { 1124 | display: none; 1125 | } 1126 | 1127 | .toggle-only-exported .greendoc-is-not-exported { 1128 | display: none; 1129 | } 1130 | 1131 | .toggle-externals .greendoc-is-external { 1132 | display: none; 1133 | } 1134 | 1135 | footer { 1136 | border-top: 1px solid #eee; 1137 | background-color: #fff; 1138 | } 1139 | footer.with-border-bottom { 1140 | border-bottom: 1px solid #eee; 1141 | } 1142 | footer .greendoc-legend-group { 1143 | font-size: 0; 1144 | } 1145 | footer .greendoc-legend { 1146 | display: inline-block; 1147 | width: 25%; 1148 | padding: 0; 1149 | font-size: 16px; 1150 | list-style: none; 1151 | line-height: 1.333em; 1152 | vertical-align: top; 1153 | } 1154 | @media (max-width: 900px) { 1155 | footer .greendoc-legend { 1156 | width: 50%; 1157 | } 1158 | } 1159 | 1160 | .greendoc-hierarchy { 1161 | list-style: square; 1162 | padding: 0 0 0 20px; 1163 | margin: 0; 1164 | } 1165 | .greendoc-hierarchy .target { 1166 | font-weight: bold; 1167 | } 1168 | 1169 | .greendoc-index-panel .greendoc-index-content { 1170 | margin-bottom: -30px !important; 1171 | } 1172 | .greendoc-index-panel .greendoc-index-section { 1173 | margin-bottom: 30px !important; 1174 | } 1175 | .greendoc-index-panel h3 { 1176 | margin: 0 -20px 10px -20px; 1177 | padding: 0 20px 10px 20px; 1178 | border-bottom: 1px solid #eee; 1179 | } 1180 | .greendoc-index-panel ul.greendoc-index-list { 1181 | -moz-column-count: 3; 1182 | -ms-column-count: 3; 1183 | -o-column-count: 3; 1184 | column-count: 3; 1185 | -moz-column-gap: 20px; 1186 | -ms-column-gap: 20px; 1187 | -o-column-gap: 20px; 1188 | column-gap: 20px; 1189 | padding: 0; 1190 | list-style: none; 1191 | line-height: 1.333em; 1192 | } 1193 | @media (max-width: 900px) { 1194 | .greendoc-index-panel ul.greendoc-index-list { 1195 | -moz-column-count: 1; 1196 | -ms-column-count: 1; 1197 | -o-column-count: 1; 1198 | column-count: 1; 1199 | } 1200 | } 1201 | @media (min-width: 901px) and (max-width: 1024px) { 1202 | .greendoc-index-panel ul.greendoc-index-list { 1203 | -moz-column-count: 2; 1204 | -ms-column-count: 2; 1205 | -o-column-count: 2; 1206 | column-count: 2; 1207 | } 1208 | } 1209 | .greendoc-index-panel ul.greendoc-index-list li { 1210 | -webkit-page-break-inside: avoid; 1211 | -moz-page-break-inside: avoid; 1212 | -ms-page-break-inside: avoid; 1213 | -o-page-break-inside: avoid; 1214 | page-break-inside: avoid; 1215 | } 1216 | .greendoc-index-panel a, 1217 | .greendoc-index-panel .greendoc-parent-kind-module a { 1218 | color: #9600ff; 1219 | } 1220 | .greendoc-index-panel .greendoc-parent-kind-interface a { 1221 | color: #7da01f; 1222 | } 1223 | .greendoc-index-panel .greendoc-parent-kind-enum a { 1224 | color: #cc9900; 1225 | } 1226 | .greendoc-index-panel .greendoc-parent-kind-class a { 1227 | color: #4da6ff; 1228 | } 1229 | .greendoc-index-panel .greendoc-kind-module a { 1230 | color: #9600ff; 1231 | } 1232 | .greendoc-index-panel .greendoc-kind-interface a { 1233 | color: #7da01f; 1234 | } 1235 | .greendoc-index-panel .greendoc-kind-enum a { 1236 | color: #cc9900; 1237 | } 1238 | .greendoc-index-panel .greendoc-kind-class a { 1239 | color: #4da6ff; 1240 | } 1241 | .greendoc-index-panel .greendoc-is-private a { 1242 | color: #808080; 1243 | } 1244 | 1245 | .greendoc-flag { 1246 | display: inline-block; 1247 | padding: 1px 5px; 1248 | border-radius: 4px; 1249 | color: #fff; 1250 | background-color: #808080; 1251 | text-indent: 0; 1252 | font-size: 14px; 1253 | font-weight: normal; 1254 | } 1255 | 1256 | .greendoc-anchor { 1257 | position: absolute; 1258 | top: -100px; 1259 | } 1260 | 1261 | .greendoc-member { 1262 | position: relative; 1263 | } 1264 | .greendoc-member .greendoc-anchor + h3 { 1265 | margin-top: 0; 1266 | margin-bottom: 0; 1267 | border-bottom: none; 1268 | } 1269 | 1270 | .greendoc-navigation { 1271 | margin: 0 0 0 40px; 1272 | } 1273 | .greendoc-navigation a { 1274 | display: block; 1275 | padding-top: 2px; 1276 | padding-bottom: 2px; 1277 | border-left: 2px solid transparent; 1278 | color: #222; 1279 | text-decoration: none; 1280 | transition: border-left-color 0.1s; 1281 | } 1282 | .greendoc-navigation a:hover { 1283 | text-decoration: underline; 1284 | } 1285 | .greendoc-navigation ul { 1286 | margin: 0; 1287 | padding: 0; 1288 | list-style: none; 1289 | } 1290 | .greendoc-navigation li { 1291 | padding: 0; 1292 | } 1293 | .greendoc-navigation > section:not(:last-child) { 1294 | padding-bottom: 1em; 1295 | border-bottom: 1px solid #ccc; 1296 | } 1297 | 1298 | .greendoc-navigation.primary { 1299 | padding-bottom: 40px; 1300 | } 1301 | .greendoc-navigation.primary a { 1302 | display: block; 1303 | padding-top: 6px; 1304 | padding-bottom: 6px; 1305 | } 1306 | .greendoc-navigation.primary ul li a { 1307 | padding-left: 5px; 1308 | } 1309 | .greendoc-navigation.primary ul li li a { 1310 | padding-left: 25px; 1311 | } 1312 | .greendoc-navigation.primary ul li li li a { 1313 | padding-left: 45px; 1314 | } 1315 | .greendoc-navigation.primary ul li li li li a { 1316 | padding-left: 65px; 1317 | } 1318 | .greendoc-navigation.primary ul li li li li li a { 1319 | padding-left: 85px; 1320 | } 1321 | .greendoc-navigation.primary ul li li li li li li a { 1322 | padding-left: 105px; 1323 | } 1324 | .greendoc-navigation.primary > ul { 1325 | border-bottom: 1px solid #eee; 1326 | } 1327 | .greendoc-navigation.primary li { 1328 | border-top: 1px solid #eee; 1329 | } 1330 | .greendoc-navigation.primary li.current > a { 1331 | font-weight: bold; 1332 | } 1333 | .greendoc-navigation.primary li.label span { 1334 | display: block; 1335 | padding: 20px 0 6px 5px; 1336 | color: #808080; 1337 | } 1338 | .greendoc-navigation.primary li.globals + li > span, 1339 | .greendoc-navigation.primary li.globals + li > a { 1340 | padding-top: 20px; 1341 | } 1342 | 1343 | .greendoc-navigation.secondary { 1344 | max-height: calc(100vh - 1rem - 40px); 1345 | overflow: auto; 1346 | position: -webkit-sticky; 1347 | position: sticky; 1348 | top: calc(0.5rem + 40px); 1349 | transition: 0.3s; 1350 | } 1351 | .greendoc-navigation.secondary.greendoc-navigation--toolbar-hide { 1352 | max-height: calc(100vh - 1rem); 1353 | top: 0.5rem; 1354 | } 1355 | .greendoc-navigation.secondary ul { 1356 | transition: opacity 0.2s; 1357 | } 1358 | .greendoc-navigation.secondary ul li a { 1359 | padding-left: 25px; 1360 | } 1361 | .greendoc-navigation.secondary ul li li a { 1362 | padding-left: 45px; 1363 | } 1364 | .greendoc-navigation.secondary ul li li li a { 1365 | padding-left: 65px; 1366 | } 1367 | .greendoc-navigation.secondary ul li li li li a { 1368 | padding-left: 85px; 1369 | } 1370 | .greendoc-navigation.secondary ul li li li li li a { 1371 | padding-left: 105px; 1372 | } 1373 | .greendoc-navigation.secondary ul li li li li li li a { 1374 | padding-left: 125px; 1375 | } 1376 | .greendoc-navigation.secondary ul.current a { 1377 | border-left-color: #eee; 1378 | } 1379 | .greendoc-navigation.secondary li.focus > a, 1380 | .greendoc-navigation.secondary ul.current li.focus > a { 1381 | border-left-color: #000; 1382 | } 1383 | .greendoc-navigation.secondary li.current { 1384 | margin-top: 20px; 1385 | margin-bottom: 20px; 1386 | border-left-color: #eee; 1387 | } 1388 | .greendoc-navigation.secondary li.current > a { 1389 | font-weight: bold; 1390 | } 1391 | 1392 | @media (min-width: 901px) { 1393 | .menu-sticky-wrap { 1394 | position: static; 1395 | } 1396 | } 1397 | 1398 | .greendoc-panel { 1399 | margin: 20px 0; 1400 | padding: 20px; 1401 | background-color: #fff; 1402 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); 1403 | } 1404 | .greendoc-panel:empty { 1405 | display: none; 1406 | } 1407 | .greendoc-panel > h1, 1408 | .greendoc-panel > h2, 1409 | .greendoc-panel > h3 { 1410 | margin: 1.5em -20px 10px -20px; 1411 | padding: 0 20px 10px 20px; 1412 | border-bottom: 1px solid #eee; 1413 | } 1414 | .greendoc-panel > h1.greendoc-before-signature, 1415 | .greendoc-panel > h2.greendoc-before-signature, 1416 | .greendoc-panel > h3.greendoc-before-signature { 1417 | margin-bottom: 0; 1418 | border-bottom: 0; 1419 | } 1420 | .greendoc-panel table { 1421 | display: block; 1422 | width: 100%; 1423 | overflow: auto; 1424 | margin-top: 10px; 1425 | word-break: normal; 1426 | word-break: keep-all; 1427 | } 1428 | .greendoc-panel table th { 1429 | font-weight: bold; 1430 | } 1431 | .greendoc-panel table th, 1432 | .greendoc-panel table td { 1433 | padding: 6px 13px; 1434 | border: 1px solid #ddd; 1435 | } 1436 | .greendoc-panel table tr { 1437 | background-color: #fff; 1438 | border-top: 1px solid #ccc; 1439 | } 1440 | .greendoc-panel table tr:nth-child(2n) { 1441 | background-color: #f8f8f8; 1442 | } 1443 | 1444 | .greendoc-panel-group { 1445 | margin: 60px 0; 1446 | } 1447 | .greendoc-panel-group > h1, 1448 | .greendoc-panel-group > h2, 1449 | .greendoc-panel-group > h3 { 1450 | padding-left: 20px; 1451 | padding-right: 20px; 1452 | } 1453 | 1454 | #greendoc-search { 1455 | transition: background-color 0.2s; 1456 | } 1457 | #greendoc-search .title { 1458 | position: relative; 1459 | z-index: 2; 1460 | } 1461 | #greendoc-search .field { 1462 | position: absolute; 1463 | left: 0; 1464 | top: 0; 1465 | right: 40px; 1466 | height: 40px; 1467 | } 1468 | #greendoc-search .field input { 1469 | box-sizing: border-box; 1470 | position: relative; 1471 | top: -50px; 1472 | z-index: 1; 1473 | width: 100%; 1474 | padding: 0 10px; 1475 | opacity: 0; 1476 | outline: 0; 1477 | border: 0; 1478 | background: transparent; 1479 | color: #222; 1480 | } 1481 | #greendoc-search .field label { 1482 | position: absolute; 1483 | overflow: hidden; 1484 | right: -40px; 1485 | } 1486 | #greendoc-search .field input, 1487 | #greendoc-search .title { 1488 | transition: opacity 0.2s; 1489 | } 1490 | #greendoc-search .results { 1491 | position: absolute; 1492 | visibility: hidden; 1493 | top: 40px; 1494 | width: 100%; 1495 | margin: 0; 1496 | padding: 0; 1497 | list-style: none; 1498 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); 1499 | } 1500 | #greendoc-search .results li { 1501 | padding: 0 10px; 1502 | background-color: #fdfdfd; 1503 | } 1504 | #greendoc-search .results li:nth-child(even) { 1505 | background-color: #fff; 1506 | } 1507 | #greendoc-search .results li.state { 1508 | display: none; 1509 | } 1510 | #greendoc-search .results li.current, 1511 | #greendoc-search .results li:hover { 1512 | background-color: #eee; 1513 | } 1514 | #greendoc-search .results a { 1515 | display: block; 1516 | } 1517 | #greendoc-search .results a:before { 1518 | top: 10px; 1519 | } 1520 | #greendoc-search .results span.parent { 1521 | color: #808080; 1522 | font-weight: normal; 1523 | } 1524 | #greendoc-search.has-focus { 1525 | background-color: #eee; 1526 | } 1527 | #greendoc-search.has-focus .field input { 1528 | top: 0; 1529 | opacity: 1; 1530 | } 1531 | #greendoc-search.has-focus .title { 1532 | z-index: 0; 1533 | opacity: 0; 1534 | } 1535 | #greendoc-search.has-focus .results { 1536 | visibility: visible; 1537 | } 1538 | #greendoc-search.loading .results li.state.loading { 1539 | display: block; 1540 | } 1541 | #greendoc-search.failure .results li.state.failure { 1542 | display: block; 1543 | } 1544 | 1545 | .greendoc-signature { 1546 | margin: 0 0 1em 0; 1547 | padding: 10px; 1548 | border: 1px solid #eee; 1549 | font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; 1550 | font-size: 14px; 1551 | overflow-x: auto; 1552 | } 1553 | .greendoc-kind-namespace a { 1554 | text-overflow: ellipsis; 1555 | overflow: hidden; 1556 | } 1557 | .greendoc-signature.greendoc-kind-icon { 1558 | padding-left: 30px; 1559 | } 1560 | .greendoc-signature.greendoc-kind-icon:before { 1561 | top: 10px; 1562 | left: 10px; 1563 | } 1564 | .greendoc-panel > .greendoc-signature { 1565 | margin-left: -20px; 1566 | margin-right: -20px; 1567 | border-width: 1px 0; 1568 | } 1569 | .greendoc-panel > .greendoc-signature.greendoc-kind-icon { 1570 | padding-left: 40px; 1571 | } 1572 | .greendoc-panel > .greendoc-signature.greendoc-kind-icon:before { 1573 | left: 20px; 1574 | } 1575 | 1576 | .greendoc-signature-symbol { 1577 | color: #808080; 1578 | font-weight: normal; 1579 | } 1580 | 1581 | .greendoc-signature-type { 1582 | font-style: italic; 1583 | font-weight: normal; 1584 | } 1585 | 1586 | .greendoc-signatures { 1587 | padding: 0; 1588 | margin: 0 0 1em 0; 1589 | border: 1px solid #eee; 1590 | } 1591 | .greendoc-signatures .greendoc-signature { 1592 | margin: 0; 1593 | border-width: 1px 0 0 0; 1594 | transition: background-color 0.1s; 1595 | } 1596 | .greendoc-signatures .greendoc-signature:first-child { 1597 | border-top-width: 0; 1598 | } 1599 | .greendoc-signatures .greendoc-signature.current { 1600 | background-color: #eee; 1601 | } 1602 | .greendoc-signatures.active > .greendoc-signature { 1603 | cursor: pointer; 1604 | } 1605 | .greendoc-panel > .greendoc-signatures { 1606 | margin-top: -20px; 1607 | margin-left: -20px; 1608 | margin-right: -20px; 1609 | border-width: 1px 0; 1610 | } 1611 | .greendoc-panel > .greendoc-signatures .greendoc-signature.greendoc-kind-icon { 1612 | padding-left: 40px; 1613 | } 1614 | .greendoc-panel > .greendoc-signatures .greendoc-signature.greendoc-kind-icon:before { 1615 | left: 20px; 1616 | } 1617 | .greendoc-panel > a.anchor + .greendoc-signatures { 1618 | border-top-width: 0; 1619 | margin-top: -20px; 1620 | } 1621 | 1622 | ul.greendoc-descriptions { 1623 | position: relative; 1624 | overflow: hidden; 1625 | padding: 0; 1626 | list-style: none; 1627 | } 1628 | ul.greendoc-descriptions.active > .greendoc-description { 1629 | display: none; 1630 | } 1631 | ul.greendoc-descriptions.active > .greendoc-description.current { 1632 | display: block; 1633 | } 1634 | ul.greendoc-descriptions.active > .greendoc-description.fade-in { 1635 | animation: fade-in-delayed 0.3s; 1636 | } 1637 | ul.greendoc-descriptions.active > .greendoc-description.fade-out { 1638 | animation: fade-out-delayed 0.3s; 1639 | position: absolute; 1640 | display: block; 1641 | top: 0; 1642 | left: 0; 1643 | right: 0; 1644 | opacity: 0; 1645 | visibility: hidden; 1646 | } 1647 | ul.greendoc-descriptions h4, 1648 | ul.greendoc-descriptions .greendoc-index-panel h3, 1649 | .greendoc-index-panel ul.greendoc-descriptions h3 { 1650 | font-size: 16px; 1651 | margin: 1em 0 0.5em 0; 1652 | } 1653 | 1654 | ul.greendoc-parameters, 1655 | ul.greendoc-type-parameters { 1656 | list-style: square; 1657 | margin: 0; 1658 | padding-left: 20px; 1659 | } 1660 | ul.greendoc-parameters > li.greendoc-parameter-signature, 1661 | ul.greendoc-type-parameters > li.greendoc-parameter-signature { 1662 | list-style: none; 1663 | margin-left: -20px; 1664 | } 1665 | ul.greendoc-parameters h5, 1666 | ul.greendoc-type-parameters h5 { 1667 | font-size: 16px; 1668 | margin: 1em 0 0.5em 0; 1669 | } 1670 | ul.greendoc-parameters .greendoc-comment, 1671 | ul.greendoc-type-parameters .greendoc-comment { 1672 | margin-top: -0.5em; 1673 | } 1674 | 1675 | .greendoc-sources { 1676 | font-size: 14px; 1677 | color: #808080; 1678 | margin: 0 0 1em 0; 1679 | } 1680 | .greendoc-sources a { 1681 | color: #808080; 1682 | text-decoration: underline; 1683 | } 1684 | .greendoc-sources ul, 1685 | .greendoc-sources p { 1686 | margin: 0 !important; 1687 | } 1688 | .greendoc-sources ul { 1689 | list-style: none; 1690 | padding: 0; 1691 | } 1692 | 1693 | .greendoc-page-toolbar { 1694 | position: fixed; 1695 | z-index: 1; 1696 | top: 0; 1697 | left: 0; 1698 | width: 100%; 1699 | height: 40px; 1700 | color: #333; 1701 | background: #fff; 1702 | border-bottom: 1px solid #eee; 1703 | transition: transform 0.3s linear; 1704 | } 1705 | .greendoc-page-toolbar a { 1706 | color: #333; 1707 | text-decoration: none; 1708 | } 1709 | .greendoc-page-toolbar a.title { 1710 | font-weight: bold; 1711 | } 1712 | .greendoc-page-toolbar a.title:hover { 1713 | text-decoration: underline; 1714 | } 1715 | .greendoc-page-toolbar .table-wrap { 1716 | display: table; 1717 | width: 100%; 1718 | height: 40px; 1719 | } 1720 | .greendoc-page-toolbar .table-cell { 1721 | display: table-cell; 1722 | position: relative; 1723 | white-space: nowrap; 1724 | line-height: 40px; 1725 | } 1726 | .greendoc-page-toolbar .table-cell:first-child { 1727 | width: 100%; 1728 | } 1729 | 1730 | .greendoc-page-toolbar--hide { 1731 | transform: translateY(-100%); 1732 | } 1733 | 1734 | .greendoc-select .greendoc-select-list li:before, 1735 | .greendoc-select .greendoc-select-label:before, 1736 | .greendoc-widget:before { 1737 | content: ''; 1738 | display: inline-block; 1739 | width: 40px; 1740 | height: 40px; 1741 | margin: 0 -8px 0 0; 1742 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAoCAQAAAAlSeuiAAABp0lEQVR4Ae3aUa3jQAyF4QNhIBTCQiiEQlgIhRAGhTAQBkIgBEIgDITZZGXNjZTePiSWYqn/54dGfbAq+SiTutWXAgAAAAAAAAAAAAA8NCz1UFSD2lKDS5d3NVzZj/BVNasaLoRZRUmj2lLrVVHWMUntQ13Wj/i1pWa9lprX6xMRnH4dx6Rjsn26+v+12ms+EcB37P0r+qH+DNQGXgMFcHzbregQ78B8eQCTJk0e979ZW7PdA2O49ceDsYexKgUNoI3EKYDWL3D8miaPh/uXtl6BHqEHFQvgXau/FsCiIWAAbST2fpQRT0sl70j3z5ZiBdD7CG5WZX8kxwmgjbiP5GQA9/3O2XaxnnHi53AEE0AbRh+JQwC3/fzC4hcb6xPvS4i3QaMdwX+0utsRPEY6gm2wNhKHAG77eUi7SIcK4G4NY4GMIan2u2Cxqzncl5DUn7Q8ArjvZ8JFOsl/Ed0jyBom+BomQKSto+9PcblHMM4iuu4X0QQw5hrGQY/gUxFkjZuf4m4alXVU+1De/VhEn5CvDSB/RsBzqWgAAAAAAAAAAAAAAACAfyyYJ5nhVuwIAAAAAElFTkSuQmCC); 1743 | background-repeat: no-repeat; 1744 | text-indent: -1024px; 1745 | vertical-align: bottom; 1746 | } 1747 | @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { 1748 | .greendoc-select .greendoc-select-list li:before, 1749 | .greendoc-select .greendoc-select-label:before, 1750 | .greendoc-widget:before { 1751 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAABQCAMAAAC+sjQXAAAAM1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjBUbJAAAAEXRSTlMA3/+/UCBw7xCPYIBAMM+vn1qYQ7QAAALCSURBVHgB7MGBAAAAAICg/akXqQIAAAAAAAAAAAAAAAAAAJids9mdE4bhoDNZCITP93/aSmhV/9uwPWyi8jtkblws2IxsYpz9LwSAaJW8AreE16PxOsMYE6Q4DiYKF7X+8ZHXc/E608xv5snEyIuZrVwMZjbnujR6T3gsXmcLOIRNzD+Ig2UuVtt2+NbAiX/wVLzOlviD9L2BOfGBlL/3D1I+uDjGBJArBPxU3x+K15kCQFo2s21JAOHrKpz4SPrWv4IKA+uFaR6vMwMcb+emA2DWEfDglrkLqEBOKVslA8Dx14oPMiV4CtywWxdQgAwkq2QE0uTXUwJGk2G9s3mTFNBzAkC7HKPsX72AEVjMnAWIpsPCRRjXdQxcjCYpoOcEgHY5Rtk/slWSgM3M2aSeeVgjAOeVpKcdgGMdNAXMuIAqOcZzqF8L+WcAsi8wkTeheCWMegL6mgCorHHyEJ5TVfxrLWDrTUjZdhnhjYqAnlN8TaoELOLVC0gucmoz/3RKcPs2jAs4+J5ET8AEZF+TSgGLeC1V8YuGQQU2IV1Asq9JCwE9XitZVPxr34bpJRj8PqsFLOK108W9aVrWZRrR7Sm2HL4JCToCujHZ6gUs4jUz0P1TEvD+U5wMa363YeziBODIq1YbJrsv9QKW8Ry1nNp+GAHvuingRTfmYcjBf0QpAS37bdUL6PFKtHJq63EsZ5cxcKMkDVIClu1dAK1PcJ5TFQ0M9wZKDCPs3BD7MIJGTs3WfiTfDVQYx5q5ZekCauTU3P5Q0ukGCgh49oFURdobWBY9N/CxEuwGjpGLuPhTdwH1x7HqDDxNgRP2zQ8lraFyF/yJ9vH6QGqtgSbBOU8/j2VORz+Wqfle2d5Ae4R+ML0z7Y+W4P7XHN3AU+tzyK/24EAGAAAAYJC/9T2+CgAAAAAAAAAAAAAAAAAAAADgJpfzHyIKFFBKAAAAAElFTkSuQmCC); 1752 | background-size: 320px 40px; 1753 | } 1754 | } 1755 | 1756 | .greendoc-widget { 1757 | display: inline-block; 1758 | overflow: hidden; 1759 | opacity: 0.6; 1760 | height: 40px; 1761 | transition: 1762 | opacity 0.1s, 1763 | background-color 0.2s; 1764 | vertical-align: bottom; 1765 | cursor: pointer; 1766 | } 1767 | .greendoc-widget:hover { 1768 | opacity: 0.8; 1769 | } 1770 | .greendoc-widget.active { 1771 | opacity: 1; 1772 | background-color: #eee; 1773 | } 1774 | .greendoc-widget.no-caption { 1775 | width: 40px; 1776 | } 1777 | .greendoc-widget.no-caption:before { 1778 | margin: 0; 1779 | } 1780 | .greendoc-widget.search:before { 1781 | background-position: 0 0; 1782 | } 1783 | .greendoc-widget.menu:before { 1784 | background-position: -40px 0; 1785 | } 1786 | .greendoc-widget.options:before { 1787 | background-position: -80px 0; 1788 | } 1789 | .greendoc-widget.options, 1790 | .greendoc-widget.menu { 1791 | display: none; 1792 | background: none; 1793 | outline: none; 1794 | border: none; 1795 | } 1796 | @media (max-width: 900px) { 1797 | .greendoc-widget.options, 1798 | .greendoc-widget.menu { 1799 | display: inline-block; 1800 | } 1801 | } 1802 | input[type='checkbox'] + .greendoc-widget:before { 1803 | background-position: -120px 0; 1804 | } 1805 | input[type='checkbox']:checked + .greendoc-widget:before { 1806 | background-position: -160px 0; 1807 | } 1808 | 1809 | .greendoc-select { 1810 | position: relative; 1811 | display: inline-block; 1812 | height: 40px; 1813 | transition: 1814 | opacity 0.1s, 1815 | background-color 0.2s; 1816 | vertical-align: bottom; 1817 | cursor: pointer; 1818 | } 1819 | .greendoc-select .greendoc-select-label { 1820 | opacity: 0.6; 1821 | transition: opacity 0.2s; 1822 | } 1823 | .greendoc-select .greendoc-select-label:before { 1824 | background-position: -240px 0; 1825 | } 1826 | .greendoc-select.active .greendoc-select-label { 1827 | opacity: 0.8; 1828 | } 1829 | .greendoc-select.active .greendoc-select-list { 1830 | visibility: visible; 1831 | opacity: 1; 1832 | transition-delay: 0s; 1833 | } 1834 | .greendoc-select .greendoc-select-list { 1835 | position: absolute; 1836 | visibility: hidden; 1837 | top: 40px; 1838 | left: 0; 1839 | margin: 0; 1840 | padding: 0; 1841 | opacity: 0; 1842 | list-style: none; 1843 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); 1844 | transition: 1845 | visibility 0s 0.2s, 1846 | opacity 0.2s; 1847 | } 1848 | .greendoc-select .greendoc-select-list li { 1849 | padding: 0 20px 0 0; 1850 | background-color: #fdfdfd; 1851 | } 1852 | .greendoc-select .greendoc-select-list li:before { 1853 | background-position: 40px 0; 1854 | } 1855 | .greendoc-select .greendoc-select-list li:nth-child(even) { 1856 | background-color: #fff; 1857 | } 1858 | .greendoc-select .greendoc-select-list li:hover { 1859 | background-color: #eee; 1860 | } 1861 | .greendoc-select .greendoc-select-list li.selected:before { 1862 | background-position: -200px 0; 1863 | } 1864 | @media (max-width: 900px) { 1865 | .greendoc-select .greendoc-select-list { 1866 | top: 0; 1867 | left: auto; 1868 | right: 100%; 1869 | margin-right: -5px; 1870 | } 1871 | .greendoc-select .greendoc-select-label:before { 1872 | background-position: -280px 0; 1873 | } 1874 | } 1875 | 1876 | img { 1877 | max-width: 100%; 1878 | } 1879 | 1880 | /* ========================================================================== 1881 | * * Customizations 1882 | * * ========================================================================== */ 1883 | 1884 | body { 1885 | font-family: 1886 | Iowan Old Style, 1887 | Apple Garamond, 1888 | Palatino Linotype, 1889 | Times New Roman, 1890 | 'Droid Serif', 1891 | Times, 1892 | serif; 1893 | } 1894 | 1895 | .header-badge img { 1896 | vertical-align: sub; 1897 | margin-left: 0.5em; 1898 | } 1899 | 1900 | .greendoc-typography { 1901 | line-height: 1.6em; 1902 | } 1903 | 1904 | .greendoc-navigation h4 { 1905 | margin-bottom: 0.5em; 1906 | } 1907 | 1908 | .greendoc-navigation.secondary ul li a { 1909 | padding-left: 1em; 1910 | } 1911 | 1912 | .greendoc-navigation.secondary ul li a.active { 1913 | text-decoration: underline; 1914 | } 1915 | 1916 | .greendoc-panel { 1917 | box-shadow: none; 1918 | } 1919 | 1920 | pre { 1921 | padding: 20px; 1922 | white-space: pre; 1923 | overflow: auto; 1924 | } 1925 | 1926 | .greendoc-signature { 1927 | color: #4da6ff; 1928 | } 1929 | 1930 | html.minimal .greendoc-generator, 1931 | .greendoc-panel > .greendoc-signature.greendoc-kind-icon, 1932 | .greendoc-panel > .greendoc-signatures .greendoc-signature.greendoc-kind-icon { 1933 | padding-left: 20px; 1934 | } 1935 | 1936 | .greendoc-select .greendoc-select-label, 1937 | .greendoc-widget { 1938 | opacity: 0.8; 1939 | } 1940 | 1941 | .greendoc-select.active .greendoc-select-label, 1942 | .greendoc-widget:hover { 1943 | opacity: 1; 1944 | } 1945 | 1946 | .greendoc-navigation a[target='_blank']::after { 1947 | content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==); 1948 | margin: 0 3px 0 5px; 1949 | } 1950 | 1951 | /** Enum styles are too sprawling. */ 1952 | .greendoc-parent-kind-object-literal { 1953 | padding-bottom: 0; 1954 | margin-bottom: 0; 1955 | margin-top: 0; 1956 | padding-top: 0; 1957 | } 1958 | 1959 | .greendoc-parent-kind-object-literal .greendoc-sources { 1960 | display: none; 1961 | } 1962 | 1963 | .greendoc-parent-kind-object-literal .greendoc-signature.greendoc-kind-icon { 1964 | border-bottom: none; 1965 | padding-bottom: 0; 1966 | } 1967 | 1968 | .greendoc-parent-kind-object-literal .greendoc-comment.greendoc-typography p { 1969 | margin-bottom: 0.3em; 1970 | } 1971 | 1972 | blockquote { 1973 | border-left: 6px solid #ccc; 1974 | padding-left: 1em; 1975 | margin-left: 0; 1976 | color: #606060; 1977 | font-style: italic; 1978 | } 1979 | 1980 | html.minimal .greendoc-generator { 1981 | text-align: center; 1982 | padding-left: 0; 1983 | } 1984 | 1985 | .greendoc-generator img { 1986 | max-width: 40%; 1987 | } 1988 | 1989 | details[open] { 1990 | padding-left: 1em; 1991 | margin-bottom: 1em; 1992 | } 1993 | 1994 | details[open] > summary { 1995 | margin-left: -1em; 1996 | } 1997 | 1998 | details > summary { 1999 | cursor: pointer; 2000 | } 2001 | 2002 | @media (max-width: 900px) { 2003 | html.minimal .greendoc-navigation { 2004 | display: block; 2005 | top: 0; 2006 | left: -305px; /* Additional 5px for box-shadow blur. */ 2007 | max-height: none; 2008 | padding-top: 40px; 2009 | background: #fff; 2010 | box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.16); 2011 | transition: left 0.2s ease; 2012 | } 2013 | 2014 | html.minimal .toggle-nav .greendoc-navigation { 2015 | left: 0; 2016 | } 2017 | 2018 | .greendoc-generator img { 2019 | max-width: 60%; 2020 | } 2021 | 2022 | .greendoc-generator .line { 2023 | display: block; 2024 | line-height: 2em; 2025 | } 2026 | 2027 | .greendoc-generator .divider { 2028 | display: none; 2029 | } 2030 | } 2031 | --------------------------------------------------------------------------------