@ng-prism/plugin-jsdoc 22.6.0 → 22.7.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -15
- package/dist/api-page/api-page.helpers.d.ts +79 -0
- package/dist/api-page/api-page.helpers.d.ts.map +1 -0
- package/dist/api-page/api-page.helpers.js +133 -0
- package/dist/api-page/api-page.model.d.ts +62 -0
- package/dist/api-page/api-page.model.d.ts.map +1 -0
- package/dist/api-page/api-page.model.js +122 -0
- package/dist/api-page/component-usage.d.ts +14 -0
- package/dist/api-page/component-usage.d.ts.map +1 -0
- package/dist/api-page/component-usage.js +120 -0
- package/dist/api-page/doc-links.d.ts +15 -0
- package/dist/api-page/doc-links.d.ts.map +1 -0
- package/dist/api-page/doc-links.js +45 -0
- package/dist/api-page/jsdoc-api-page.component.d.ts +98 -0
- package/dist/api-page/jsdoc-api-page.component.d.ts.map +1 -0
- package/dist/api-page/jsdoc-api-page.component.js +1458 -0
- package/dist/api-page/pipe-usage.d.ts +13 -0
- package/dist/api-page/pipe-usage.d.ts.map +1 -0
- package/dist/api-page/pipe-usage.js +35 -0
- package/dist/api-page/service-usage.d.ts +31 -0
- package/dist/api-page/service-usage.d.ts.map +1 -0
- package/dist/api-page/service-usage.js +66 -0
- package/dist/jsdoc-extractor.d.ts.map +1 -1
- package/dist/jsdoc-extractor.js +17 -10
- package/dist/jsdoc-panel.component.d.ts +8 -25
- package/dist/jsdoc-panel.component.d.ts.map +1 -1
- package/dist/jsdoc-panel.component.js +34 -474
- package/dist/jsdoc-plugin.browser.d.ts.map +1 -1
- package/dist/jsdoc-plugin.browser.js +2 -1
- package/dist/jsdoc-plugin.d.ts.map +1 -1
- package/dist/jsdoc-plugin.js +5 -1
- package/dist/service-page/api-page.model.d.ts +35 -0
- package/dist/service-page/api-page.model.d.ts.map +1 -0
- package/dist/service-page/api-page.model.js +54 -0
- package/dist/service-page/jsdoc-service-page.component.d.ts +82 -0
- package/dist/service-page/jsdoc-service-page.component.d.ts.map +1 -0
- package/dist/service-page/jsdoc-service-page.component.js +1178 -0
- package/dist/service-page/pipe-usage.d.ts +13 -0
- package/dist/service-page/pipe-usage.d.ts.map +1 -0
- package/dist/service-page/pipe-usage.js +35 -0
- package/dist/service-page/service-page.model.d.ts +78 -0
- package/dist/service-page/service-page.model.d.ts.map +1 -0
- package/dist/service-page/service-page.model.js +130 -0
- package/dist/service-page/service-usage.d.ts +28 -0
- package/dist/service-page/service-usage.d.ts.map +1 -0
- package/dist/service-page/service-usage.js +61 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -12,13 +12,18 @@ npm install @ng-prism/plugin-jsdoc
|
|
|
12
12
|
|
|
13
13
|
### Peer Dependencies
|
|
14
14
|
|
|
15
|
-
| Package
|
|
16
|
-
|
|
|
17
|
-
| `@ng-prism/core`
|
|
18
|
-
| `@angular/
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
|
|
|
15
|
+
| Package | Version |
|
|
16
|
+
| ---------------------------- | --------------------- |
|
|
17
|
+
| `@ng-prism/core` | `^<plugin version>-0` |
|
|
18
|
+
| `@angular/common` | `>=20.0.0` |
|
|
19
|
+
| `@angular/core` | `>=20.0.0` |
|
|
20
|
+
| `@angular/platform-browser` | `>=20.0.0` |
|
|
21
|
+
| `@angular-devkit/schematics` | `>=20.0.0` |
|
|
22
|
+
| `typescript` | `>=5.5.0` |
|
|
23
|
+
| `highlight.js` | `>=11.0.0` |
|
|
24
|
+
| `ngx-highlightjs` | `>=14.0.0` |
|
|
25
|
+
|
|
26
|
+
Each release requires the `@ng-prism/core` of its own version (`^22.7.0-0` for 22.7.0), so keep the core and the plugin on the same version.
|
|
22
27
|
|
|
23
28
|
## Setup
|
|
24
29
|
|
|
@@ -36,11 +41,9 @@ export default defineConfig({
|
|
|
36
41
|
|
|
37
42
|
- Extracts class-level JSDoc description
|
|
38
43
|
- Extracts per-input and per-output JSDoc comments
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- Outputs table (name, description)
|
|
43
|
-
- Supported tags: `@deprecated`, `@since`, `@see`, `@example`
|
|
44
|
+
- Draws the API page of every component and directive with `@Showcase`, the same page a service or a pipe gets (since 22.7.0): a generated usage snippet, the examples, the inputs with their types linked to type cards, the outputs with their payload, the documented methods, the types they reach and an "On this page" list. `{@link}` and `@see` jump to a type card, and `@deprecated`, `@since` and `@example` are supported, see [Components and Directives](https://ng-prism.github.io/ng-prism/#/plugins/jsdoc?id=components-and-directives)
|
|
45
|
+
- Draws the API page of every service with `@Showcase` (since 22.7.0): its properties, methods and setup, a generated usage snippet and the types it reaches, see [Services](https://ng-prism.github.io/ng-prism/#/plugins/jsdoc?id=services)
|
|
46
|
+
- Draws the API page of every pipe with `@Showcase` (since 22.7.0): the template name, purity, every `transform()` signature and setup, a generated usage snippet and the types it reaches, see [Pipes](https://ng-prism.github.io/ng-prism/#/plugins/jsdoc?id=pipes)
|
|
44
47
|
|
|
45
48
|
## Markdown Support
|
|
46
49
|
|
|
@@ -98,13 +101,13 @@ export class ButtonComponent {
|
|
|
98
101
|
}
|
|
99
102
|
```
|
|
100
103
|
|
|
101
|
-
The API
|
|
104
|
+
The API tab shows the class description, a usage snippet, the inputs with their types, defaults and JSDoc, and all supported tags.
|
|
102
105
|
|
|
103
106
|
## How It Works
|
|
104
107
|
|
|
105
|
-
**Build time:** The `onComponentScanned` hook uses `ts.createSourceFile` with `setParentNodes: true` and `ts.getJSDocTags()` to extract JSDoc data. The extracted data is injected into `showcaseConfig.meta.jsdoc`.
|
|
108
|
+
**Build time:** The plugin sets `componentApi: true`, which has the core scanner read `ScannedComponent.api` for every component; a config without a plugin that sets it skips that work. The `onComponentScanned` hook uses `ts.createSourceFile` with `setParentNodes: true` and `ts.getJSDocTags()` to extract JSDoc data, keeping each parsed file while its mtime and size stay the same, so a rebuild of `serve` only parses the files that changed. The extracted data is injected into `showcaseConfig.meta.jsdoc`.
|
|
106
109
|
|
|
107
|
-
**Runtime:** The `JsDocPanelComponent`
|
|
110
|
+
**Runtime:** The `JsDocPanelComponent` is lazy-loaded via `loadComponent`. It builds the API page from `ScannedComponent.api`, which the core scanner fills with the types, payloads, methods and links, and from the JSDoc data in `showcaseConfig.meta.jsdoc`, and renders it. Services and pipes get the same page from their own scanned data.
|
|
108
111
|
|
|
109
112
|
## License
|
|
110
113
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ApiParam, ApiSetupEntry, ApiTypeRef } from '@ng-prism/core/plugin';
|
|
2
|
+
import type { ApiPageModel } from './api-page.model.js';
|
|
3
|
+
/** One entry of the API page's "On this page" list. `id` names the section, `level` 2 is a type below "Types". */
|
|
4
|
+
export interface TocEntry {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
level: 1 | 2;
|
|
8
|
+
}
|
|
9
|
+
/** A piece of a type as written; `link` is the TOC id of the documented type the piece names. */
|
|
10
|
+
export interface TypeSegment {
|
|
11
|
+
text: string;
|
|
12
|
+
link?: string;
|
|
13
|
+
}
|
|
14
|
+
/** The TOC id of a documented type's card. */
|
|
15
|
+
export declare function typeAnchor(typeId: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* The sections of an API page in the order the page shows them. Usage is
|
|
18
|
+
* always there, being generated; any other section only when it has content.
|
|
19
|
+
* A component or directive has inputs, outputs and methods, a service
|
|
20
|
+
* properties and methods, a pipe its transform signatures.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildApiPageToc(page: ApiPageModel): TocEntry[];
|
|
23
|
+
/** Where the page is scrolled to, and where each anchor of the "On this page" list sits on it. */
|
|
24
|
+
export interface ScrollSpyState {
|
|
25
|
+
/** The TOC id of each anchor in page order, with its offset from the top of the scrolled content. */
|
|
26
|
+
anchors: readonly {
|
|
27
|
+
id: string;
|
|
28
|
+
top: number;
|
|
29
|
+
}[];
|
|
30
|
+
scrollTop: number;
|
|
31
|
+
clientHeight: number;
|
|
32
|
+
scrollHeight: number;
|
|
33
|
+
/** The entry a jump is heading for, for as long as the jump lasts. */
|
|
34
|
+
jumpTarget: string | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The entry the "On this page" list marks. During a jump, the one the reader
|
|
38
|
+
* jumped to, whatever the jump scrolls past. Otherwise the last section whose
|
|
39
|
+
* heading has come up to a line a quarter down the view, except at the bottom
|
|
40
|
+
* of the page: the sections there can never come up to the line, so the last
|
|
41
|
+
* one in view is active.
|
|
42
|
+
*/
|
|
43
|
+
export declare function activeTocId({ anchors, scrollTop, clientHeight, scrollHeight, jumpTarget }: ScrollSpyState): string;
|
|
44
|
+
/**
|
|
45
|
+
* Cut a type as written into plain text and the names of documented types, so
|
|
46
|
+
* the page can turn those names into links to their cards.
|
|
47
|
+
*
|
|
48
|
+
* `ref.links` holds type ids, and an id is the type's name, or
|
|
49
|
+
* `${name}@${importPath}` when two reachable types share a name. An identifier
|
|
50
|
+
* in the text is matched against the name part of each id, the id that is the
|
|
51
|
+
* name itself first. Known limitation: a type imported under another name
|
|
52
|
+
* (`import { Item as OtherItem }`) appears in the text by its local name, which
|
|
53
|
+
* no id carries, so it stays unlinked.
|
|
54
|
+
*/
|
|
55
|
+
export declare function typeSegments(ref: ApiTypeRef): TypeSegment[];
|
|
56
|
+
/** A parameter as a signature writes it, `config?: ToastConfig` or `...features: Feature[]`, with its type linked. */
|
|
57
|
+
export declare function paramSegments(param: ApiParam): TypeSegment[];
|
|
58
|
+
/**
|
|
59
|
+
* A parameter as its declaration writes it, default included:
|
|
60
|
+
* `config: ToastConfig = {}`. A default makes the parameter optional, so it
|
|
61
|
+
* goes without the `?`; one without a default reads as in a signature.
|
|
62
|
+
*/
|
|
63
|
+
export declare function declaredParamSegments(param: ApiParam): TypeSegment[];
|
|
64
|
+
/** Whether a parameter list says more than its signature: a `@param` text or a default. */
|
|
65
|
+
export declare function documentsParams(params: readonly ApiParam[] | undefined): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* What follows a setup entry's name on its signature line: the parameters and
|
|
68
|
+
* the return type of a function, the type of a token.
|
|
69
|
+
*/
|
|
70
|
+
export declare function setupSignature(entry: ApiSetupEntry): TypeSegment[];
|
|
71
|
+
/**
|
|
72
|
+
* Split a type's `reachedFrom` (`parameter of show()`, `via options.action`)
|
|
73
|
+
* into its words and the code they end in, so the page can set the code apart.
|
|
74
|
+
*/
|
|
75
|
+
export declare function splitReachedFrom(reachedFrom: string): {
|
|
76
|
+
text: string;
|
|
77
|
+
code?: string;
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=api-page.helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-page.helpers.d.ts","sourceRoot":"","sources":["../../src/api-page/api-page.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,kHAAkH;AAClH,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;CAChB;AAED,iGAAiG;AACjG,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,EAAE,CAgB9D;AAKD,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC3B,qGAAqG;IACrG,OAAO,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,cAAc,GAAG,MAAM,CAYlH;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,EAAE,CAsB3D;AAED,sHAAsH;AACtH,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,EAAE,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,EAAE,CAGpE;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,WAAW,EAAE,CAMlE;AAiBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAKrF"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/** The TOC id of a documented type's card. */
|
|
2
|
+
export function typeAnchor(typeId) {
|
|
3
|
+
return `type-${typeId}`;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* The sections of an API page in the order the page shows them. Usage is
|
|
7
|
+
* always there, being generated; any other section only when it has content.
|
|
8
|
+
* A component or directive has inputs, outputs and methods, a service
|
|
9
|
+
* properties and methods, a pipe its transform signatures.
|
|
10
|
+
*/
|
|
11
|
+
export function buildApiPageToc(page) {
|
|
12
|
+
const sections = [
|
|
13
|
+
['usage', 'Usage', true],
|
|
14
|
+
['setup', 'Setup', page.setup.length > 0],
|
|
15
|
+
['examples', 'Examples', page.examples.length > 0],
|
|
16
|
+
['inputs', 'Inputs', !!page.inputs?.length],
|
|
17
|
+
['outputs', 'Outputs', !!page.outputs?.length],
|
|
18
|
+
['properties', 'Properties', !!page.properties?.length],
|
|
19
|
+
['methods', 'Methods', !!page.methods?.length],
|
|
20
|
+
['transform', 'Transform', !!page.transform?.length],
|
|
21
|
+
['types', 'Types', page.types.length > 0]
|
|
22
|
+
];
|
|
23
|
+
const toc = sections.filter(([, , present]) => present).map(([id, label]) => ({ id, label, level: 1 }));
|
|
24
|
+
for (const type of page.types)
|
|
25
|
+
toc.push({ id: typeAnchor(type.id), label: type.name, level: 2 });
|
|
26
|
+
return toc;
|
|
27
|
+
}
|
|
28
|
+
/** How far down the view, as a share of its height, a section's heading has to come to make the section active. */
|
|
29
|
+
const ACTIVATION_LINE = 0.25;
|
|
30
|
+
/**
|
|
31
|
+
* The entry the "On this page" list marks. During a jump, the one the reader
|
|
32
|
+
* jumped to, whatever the jump scrolls past. Otherwise the last section whose
|
|
33
|
+
* heading has come up to a line a quarter down the view, except at the bottom
|
|
34
|
+
* of the page: the sections there can never come up to the line, so the last
|
|
35
|
+
* one in view is active.
|
|
36
|
+
*/
|
|
37
|
+
export function activeTocId({ anchors, scrollTop, clientHeight, scrollHeight, jumpTarget }) {
|
|
38
|
+
if (jumpTarget)
|
|
39
|
+
return jumpTarget;
|
|
40
|
+
if (scrollTop + clientHeight >= scrollHeight - 1) {
|
|
41
|
+
const inView = anchors.filter((anchor) => anchor.top >= scrollTop && anchor.top < scrollTop + clientHeight).at(-1);
|
|
42
|
+
if (inView)
|
|
43
|
+
return inView.id;
|
|
44
|
+
}
|
|
45
|
+
const line = scrollTop + clientHeight * ACTIVATION_LINE;
|
|
46
|
+
return (anchors.filter((anchor) => anchor.top <= line).at(-1) ?? anchors[0])?.id ?? 'usage';
|
|
47
|
+
}
|
|
48
|
+
const IDENTIFIER = /[A-Za-z_$][\w$]*/g;
|
|
49
|
+
/**
|
|
50
|
+
* Cut a type as written into plain text and the names of documented types, so
|
|
51
|
+
* the page can turn those names into links to their cards.
|
|
52
|
+
*
|
|
53
|
+
* `ref.links` holds type ids, and an id is the type's name, or
|
|
54
|
+
* `${name}@${importPath}` when two reachable types share a name. An identifier
|
|
55
|
+
* in the text is matched against the name part of each id, the id that is the
|
|
56
|
+
* name itself first. Known limitation: a type imported under another name
|
|
57
|
+
* (`import { Item as OtherItem }`) appears in the text by its local name, which
|
|
58
|
+
* no id carries, so it stays unlinked.
|
|
59
|
+
*/
|
|
60
|
+
export function typeSegments(ref) {
|
|
61
|
+
const links = ref.links ?? [];
|
|
62
|
+
const segments = [];
|
|
63
|
+
let plain = '';
|
|
64
|
+
let last = 0;
|
|
65
|
+
for (const match of ref.text.matchAll(IDENTIFIER)) {
|
|
66
|
+
const name = match[0];
|
|
67
|
+
const id = links.find((candidate) => candidate === name) ?? links.find((candidate) => candidate.split('@')[0] === name);
|
|
68
|
+
if (!id)
|
|
69
|
+
continue;
|
|
70
|
+
plain += ref.text.slice(last, match.index);
|
|
71
|
+
if (plain)
|
|
72
|
+
segments.push({ text: plain });
|
|
73
|
+
segments.push({ text: name, link: typeAnchor(id) });
|
|
74
|
+
plain = '';
|
|
75
|
+
last = match.index + name.length;
|
|
76
|
+
}
|
|
77
|
+
plain += ref.text.slice(last);
|
|
78
|
+
if (plain || !segments.length)
|
|
79
|
+
segments.push({ text: plain });
|
|
80
|
+
return segments;
|
|
81
|
+
}
|
|
82
|
+
/** A parameter as a signature writes it, `config?: ToastConfig` or `...features: Feature[]`, with its type linked. */
|
|
83
|
+
export function paramSegments(param) {
|
|
84
|
+
return joinSegments(`${param.rest ? '...' : ''}${param.name}${param.optional ? '?' : ''}: `, typeSegments(param.type));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A parameter as its declaration writes it, default included:
|
|
88
|
+
* `config: ToastConfig = {}`. A default makes the parameter optional, so it
|
|
89
|
+
* goes without the `?`; one without a default reads as in a signature.
|
|
90
|
+
*/
|
|
91
|
+
export function declaredParamSegments(param) {
|
|
92
|
+
if (param.defaultValue === undefined)
|
|
93
|
+
return paramSegments(param);
|
|
94
|
+
return joinSegments(`${param.rest ? '...' : ''}${param.name}: `, typeSegments(param.type), ` = ${param.defaultValue}`);
|
|
95
|
+
}
|
|
96
|
+
/** Whether a parameter list says more than its signature: a `@param` text or a default. */
|
|
97
|
+
export function documentsParams(params) {
|
|
98
|
+
return !!params?.some((param) => param.description || param.defaultValue !== undefined);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* What follows a setup entry's name on its signature line: the parameters and
|
|
102
|
+
* the return type of a function, the type of a token.
|
|
103
|
+
*/
|
|
104
|
+
export function setupSignature(entry) {
|
|
105
|
+
if (entry.kind === 'token')
|
|
106
|
+
return entry.tokenType ? joinSegments(': ', typeSegments(entry.tokenType)) : [];
|
|
107
|
+
const params = (entry.params ?? []).flatMap((param, index) => (index ? [', ', paramSegments(param)] : [paramSegments(param)]));
|
|
108
|
+
return joinSegments('(', ...params, ')', ...(entry.returnType ? [': ', typeSegments(entry.returnType)] : []));
|
|
109
|
+
}
|
|
110
|
+
/** Concatenate text and segments, merging neighbouring plain text into one segment. */
|
|
111
|
+
function joinSegments(...parts) {
|
|
112
|
+
const joined = [];
|
|
113
|
+
for (const segment of parts.flatMap((part) => (typeof part === 'string' ? [{ text: part }] : part))) {
|
|
114
|
+
const previous = joined.at(-1);
|
|
115
|
+
if (!segment.text)
|
|
116
|
+
continue;
|
|
117
|
+
if (previous && !previous.link && !segment.link)
|
|
118
|
+
previous.text += segment.text;
|
|
119
|
+
else
|
|
120
|
+
joined.push({ ...segment });
|
|
121
|
+
}
|
|
122
|
+
return joined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Split a type's `reachedFrom` (`parameter of show()`, `via options.action`)
|
|
126
|
+
* into its words and the code they end in, so the page can set the code apart.
|
|
127
|
+
*/
|
|
128
|
+
export function splitReachedFrom(reachedFrom) {
|
|
129
|
+
const space = reachedFrom.lastIndexOf(' ');
|
|
130
|
+
if (space < 0)
|
|
131
|
+
return { text: reachedFrom };
|
|
132
|
+
return { text: reachedFrom.slice(0, space + 1), code: reachedFrom.slice(space + 1) };
|
|
133
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ApiDocTags, ApiMethod, ApiProperty, ApiSetupEntry, ApiTypeDoc, ApiTypeRef, ScannedComponent, ScannedPipe, ScannedService } from '@ng-prism/core/plugin';
|
|
2
|
+
/** A chip under the description, `Since: 1.2.0` or a bare `Pure`; the deprecation is a badge of its own. */
|
|
3
|
+
export interface ApiPageChip {
|
|
4
|
+
label: string;
|
|
5
|
+
value?: string;
|
|
6
|
+
deprecated?: true;
|
|
7
|
+
}
|
|
8
|
+
/** What an API page documents. */
|
|
9
|
+
export type ApiPageKind = 'component' | 'directive' | 'service' | 'pipe';
|
|
10
|
+
/** An input of a component or directive, by the name a template binds it by. */
|
|
11
|
+
export interface ApiInputRow {
|
|
12
|
+
name: string;
|
|
13
|
+
type: ApiTypeRef;
|
|
14
|
+
/** As the code writes it: a string in quotes. */
|
|
15
|
+
defaultValue?: string;
|
|
16
|
+
required: boolean;
|
|
17
|
+
description?: string;
|
|
18
|
+
tags: ApiDocTags;
|
|
19
|
+
}
|
|
20
|
+
/** An output of a component or directive: an `output()`, or the change event of a `model()`. */
|
|
21
|
+
export interface ApiOutputRow {
|
|
22
|
+
name: string;
|
|
23
|
+
/** What the event emits. Absent when the scanner did not read it. */
|
|
24
|
+
payload?: ApiTypeRef;
|
|
25
|
+
description?: string;
|
|
26
|
+
tags: ApiDocTags;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What the API page shows, for a component, a directive, a service or a pipe.
|
|
30
|
+
* The page renders this alone, so all four kinds share one look; the sections
|
|
31
|
+
* a kind does not have are left out.
|
|
32
|
+
*
|
|
33
|
+
* The entries of `properties`, `methods`, `transform` and `types` are the
|
|
34
|
+
* scanned objects themselves, not copies: the page caches what it derives from
|
|
35
|
+
* them by object. The rows of `inputs` and `outputs`, and the `methods` of a
|
|
36
|
+
* component without `api`, are built anew by every call.
|
|
37
|
+
*/
|
|
38
|
+
export interface ApiPageModel {
|
|
39
|
+
kind: ApiPageKind;
|
|
40
|
+
className: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
chips: ApiPageChip[];
|
|
43
|
+
see: string[];
|
|
44
|
+
usage: string;
|
|
45
|
+
setup: ApiSetupEntry[];
|
|
46
|
+
examples: string[];
|
|
47
|
+
/** Components and directives only. */
|
|
48
|
+
inputs?: ApiInputRow[];
|
|
49
|
+
/** Components and directives only. */
|
|
50
|
+
outputs?: ApiOutputRow[];
|
|
51
|
+
/** Services only. */
|
|
52
|
+
properties?: ApiProperty[];
|
|
53
|
+
/** Services, components and directives: the public methods. */
|
|
54
|
+
methods?: ApiMethod[];
|
|
55
|
+
/** Pipes: one entry per transform signature. */
|
|
56
|
+
transform?: ApiMethod[];
|
|
57
|
+
types: ApiTypeDoc[];
|
|
58
|
+
}
|
|
59
|
+
export declare function servicePage(service: ScannedService): ApiPageModel;
|
|
60
|
+
export declare function pipePage(pipe: ScannedPipe): ApiPageModel;
|
|
61
|
+
export declare function componentApiPage(component: ScannedComponent): ApiPageModel;
|
|
62
|
+
//# sourceMappingURL=api-page.model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-page.model.d.ts","sourceRoot":"","sources":["../../src/api-page/api-page.model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,UAAU,EACV,UAAU,EAEV,gBAAgB,EAChB,WAAW,EACX,cAAc,EACjB,MAAM,uBAAuB,CAAC;AAM/B,4GAA4G;AAC5G,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,IAAI,CAAC;CACrB;AAED,kCAAkC;AAClC,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzE,gFAAgF;AAChF,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;CACpB;AAED,gGAAgG;AAChG,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,sCAAsC;IACtC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,sCAAsC;IACtC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,qBAAqB;IACrB,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,gDAAgD;IAChD,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB,KAAK,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,CAqBjE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,YAAY,CAsBxD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,gBAAgB,GAAG,YAAY,CAiD1E"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { generateComponentUsage } from './component-usage.js';
|
|
2
|
+
import { generatePipeUsage } from './pipe-usage.js';
|
|
3
|
+
import { generateServiceUsage } from './service-usage.js';
|
|
4
|
+
export function servicePage(service) {
|
|
5
|
+
const { className, importPath, provision, api } = service;
|
|
6
|
+
return {
|
|
7
|
+
kind: 'service',
|
|
8
|
+
className,
|
|
9
|
+
description: api.description,
|
|
10
|
+
chips: [
|
|
11
|
+
...deprecationChip(api.tags),
|
|
12
|
+
...valueChip('Since', api.tags.since),
|
|
13
|
+
{ label: 'Provided in', value: provision === 'auto' ? 'root/automatically' : 'providers' },
|
|
14
|
+
...valueChip('Entry point', importPath)
|
|
15
|
+
],
|
|
16
|
+
see: api.tags.see ?? [],
|
|
17
|
+
usage: generateServiceUsage(service),
|
|
18
|
+
setup: api.setup,
|
|
19
|
+
examples: api.tags.example ?? [],
|
|
20
|
+
properties: api.properties,
|
|
21
|
+
methods: api.methods,
|
|
22
|
+
types: api.types
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function pipePage(pipe) {
|
|
26
|
+
const { className, importPath, pipeName, pure, api } = pipe;
|
|
27
|
+
return {
|
|
28
|
+
kind: 'pipe',
|
|
29
|
+
className,
|
|
30
|
+
description: api.description,
|
|
31
|
+
chips: [
|
|
32
|
+
...deprecationChip(api.tags),
|
|
33
|
+
{ label: 'Pipe', value: pipeName },
|
|
34
|
+
// Unreadable purity is no reason to guess: the chip goes.
|
|
35
|
+
...(pure === undefined ? [] : [{ label: pure ? 'Pure' : 'Impure' }]),
|
|
36
|
+
...valueChip('Since', api.tags.since),
|
|
37
|
+
...valueChip('Entry point', importPath)
|
|
38
|
+
],
|
|
39
|
+
see: api.tags.see ?? [],
|
|
40
|
+
usage: generatePipeUsage(pipe),
|
|
41
|
+
setup: api.setup,
|
|
42
|
+
examples: api.tags.example ?? [],
|
|
43
|
+
transform: api.transform,
|
|
44
|
+
types: api.types
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function componentApiPage(component) {
|
|
48
|
+
const { className, importPath, inputs, outputs, componentMeta, showcaseConfig, api } = component;
|
|
49
|
+
// The jsdoc plugin's own data, for a component that has no `api`: one a plugin hook built, or one from the manifest of an older core.
|
|
50
|
+
const jsdoc = showcaseConfig.meta?.['jsdoc'];
|
|
51
|
+
const description = api ? api.description : jsdoc?.classDescription;
|
|
52
|
+
const tags = api ? api.tags : (jsdoc?.classTags ?? {});
|
|
53
|
+
const memberTags = jsdoc?.memberTags ?? {};
|
|
54
|
+
const outputRows = outputs.map((output) => ({
|
|
55
|
+
name: output.alias ?? output.name,
|
|
56
|
+
payload: api?.outputTypes[output.name],
|
|
57
|
+
description: output.doc,
|
|
58
|
+
tags: memberTags[output.name] ?? {}
|
|
59
|
+
}));
|
|
60
|
+
// Angular adds the change event of a `model()` by itself, so the scanner has no output for it.
|
|
61
|
+
for (const change of api?.modelChanges ?? []) {
|
|
62
|
+
const input = inputs.find((candidate) => candidate.name === change.input);
|
|
63
|
+
const publicName = input ? (input.alias ?? input.name) : change.input;
|
|
64
|
+
outputRows.push({
|
|
65
|
+
name: change.event,
|
|
66
|
+
payload: change.type,
|
|
67
|
+
description: `Emits when \`${publicName}\` changes; bind both ways with \`[(${publicName})]\`.`,
|
|
68
|
+
tags: memberTags[change.input] ?? {}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
kind: componentMeta.isDirective ? 'directive' : 'component',
|
|
73
|
+
className,
|
|
74
|
+
description,
|
|
75
|
+
chips: [...deprecationChip(tags), ...valueChip('Version', tags.version), ...valueChip('Since', tags.since), ...valueChip('Entry point', importPath)],
|
|
76
|
+
see: tags.see ?? [],
|
|
77
|
+
usage: generateComponentUsage(component),
|
|
78
|
+
setup: [],
|
|
79
|
+
examples: tags.example ?? [],
|
|
80
|
+
inputs: inputs.map((input) => ({
|
|
81
|
+
name: input.alias ?? input.name,
|
|
82
|
+
type: api?.inputTypes[input.name] ?? { text: input.rawType ?? input.type },
|
|
83
|
+
defaultValue: input.defaultValue === undefined ? undefined : formatDefault(input.defaultValue),
|
|
84
|
+
required: input.required,
|
|
85
|
+
description: input.doc,
|
|
86
|
+
tags: memberTags[input.name] ?? {}
|
|
87
|
+
})),
|
|
88
|
+
outputs: outputRows,
|
|
89
|
+
methods: api ? api.methods : (jsdoc?.methods ?? []).map(methodSignature),
|
|
90
|
+
types: api?.types ?? []
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** The deprecation leads the chips, with its message when the tag has one. */
|
|
94
|
+
function deprecationChip({ deprecated }) {
|
|
95
|
+
if (!deprecated)
|
|
96
|
+
return [];
|
|
97
|
+
return [{ label: 'Deprecated', ...(typeof deprecated === 'string' ? { value: deprecated } : {}), deprecated: true }];
|
|
98
|
+
}
|
|
99
|
+
function valueChip(label, value) {
|
|
100
|
+
return value ? [{ label, value }] : [];
|
|
101
|
+
}
|
|
102
|
+
/** As the code writes a default: a string in quotes, an array or object as JSON (`String()` would give `a,b` and `[object Object]`), anything else by its `String()`. */
|
|
103
|
+
function formatDefault(value) {
|
|
104
|
+
if (typeof value === 'string')
|
|
105
|
+
return `'${value}'`;
|
|
106
|
+
return typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value);
|
|
107
|
+
}
|
|
108
|
+
/** A method of the jsdoc plugin's data as a signature: it knows no more of the types than their names. */
|
|
109
|
+
function methodSignature(method) {
|
|
110
|
+
return {
|
|
111
|
+
name: method.name,
|
|
112
|
+
params: method.params.map((param) => ({
|
|
113
|
+
name: param.name,
|
|
114
|
+
type: { text: param.type ?? 'unknown' },
|
|
115
|
+
optional: false,
|
|
116
|
+
description: param.description
|
|
117
|
+
})),
|
|
118
|
+
returnType: { text: method.returnType ?? 'void' },
|
|
119
|
+
description: method.description,
|
|
120
|
+
tags: method.tags
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ScannedComponent } from '@ng-prism/core/plugin';
|
|
2
|
+
/**
|
|
3
|
+
* The snippet that shows how to use a component or directive: the import, the
|
|
4
|
+
* `imports` entry of the component that uses it, and one line of template.
|
|
5
|
+
*
|
|
6
|
+
* It is generated from the scanned selector and inputs, not taken from the
|
|
7
|
+
* author, so it shows the minimum that works: the required inputs, bound to a
|
|
8
|
+
* member of the same name, and nothing else. A directive goes where the
|
|
9
|
+
* showcase puts it: on the `host` element, or on a plain element of its own.
|
|
10
|
+
* A `{ template }` host is the exception: the snippet shows the subject inside
|
|
11
|
+
* that markup, as the first variant that has one renders it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateComponentUsage(component: ScannedComponent): string;
|
|
14
|
+
//# sourceMappingURL=component-usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-usage.d.ts","sourceRoot":"","sources":["../../src/api-page/component-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAS9H;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAc1E"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { COMPOSITION_SUBJECT_SLOT } from '@ng-prism/core/plugin';
|
|
2
|
+
import { importLines, joinUsageBlocks } from './service-usage.js';
|
|
3
|
+
/** Elements that have no closing tag. */
|
|
4
|
+
const VOID_ELEMENTS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
|
|
5
|
+
/**
|
|
6
|
+
* The snippet that shows how to use a component or directive: the import, the
|
|
7
|
+
* `imports` entry of the component that uses it, and one line of template.
|
|
8
|
+
*
|
|
9
|
+
* It is generated from the scanned selector and inputs, not taken from the
|
|
10
|
+
* author, so it shows the minimum that works: the required inputs, bound to a
|
|
11
|
+
* member of the same name, and nothing else. A directive goes where the
|
|
12
|
+
* showcase puts it: on the `host` element, or on a plain element of its own.
|
|
13
|
+
* A `{ template }` host is the exception: the snippet shows the subject inside
|
|
14
|
+
* that markup, as the first variant that has one renders it.
|
|
15
|
+
*/
|
|
16
|
+
export function generateComponentUsage(component) {
|
|
17
|
+
const { className, componentMeta, showcaseConfig } = component;
|
|
18
|
+
const configured = showcaseConfig.host;
|
|
19
|
+
const composed = isHostTemplate(configured) ? firstCase(component) : undefined;
|
|
20
|
+
// A component renders itself; only a directive needs an element to sit on.
|
|
21
|
+
const host = componentMeta.isDirective && !isHostTemplate(configured) ? configured : undefined;
|
|
22
|
+
const hostImport = host && typeof host !== 'string' ? host.import : undefined;
|
|
23
|
+
const extraImports = composed ? (component.composition?.imports ?? []) : hostImport ? [hostImport] : [];
|
|
24
|
+
return joinUsageBlocks([
|
|
25
|
+
importLines(component, extraImports),
|
|
26
|
+
['// in the component that uses it', `@Component({ imports: [${[className, ...extraImports.map(({ name }) => name)].join(', ')}], … })`],
|
|
27
|
+
['// in its template', composed ? compositionLine(component, composed) : templateLine(component, host)]
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
/** `null` is no host, though `typeof null` is `'object'`: a config from untyped code may carry one. */
|
|
31
|
+
function isHostTemplate(host) {
|
|
32
|
+
return !!host && typeof host === 'object' && 'template' in host;
|
|
33
|
+
}
|
|
34
|
+
/** The case of the first variant that has a composition: what the styleguide shows first. */
|
|
35
|
+
function firstCase({ composition }) {
|
|
36
|
+
const index = composition?.variantCases.find((candidate) => candidate >= 0);
|
|
37
|
+
return index === undefined ? undefined : composition?.cases[index];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The template of a case as its author wrote it, with the required inputs on the
|
|
41
|
+
* subject element in place of the slot. A required input the template sets
|
|
42
|
+
* itself is not bound a second time.
|
|
43
|
+
*/
|
|
44
|
+
function compositionLine({ inputs }, { template, unbound }) {
|
|
45
|
+
const owned = new Set(unbound.filter(({ reason }) => reason === 'template').map(({ name }) => name));
|
|
46
|
+
const bindings = requiredInputs(inputs)
|
|
47
|
+
.filter(({ member }) => !owned.has(member))
|
|
48
|
+
.map(binding);
|
|
49
|
+
// Split and join, not replace: a member name may hold `$`, which `replace` would read as a pattern.
|
|
50
|
+
return template.split(COMPOSITION_SUBJECT_SLOT).join(bindings.length ? ` ${bindings.join(' ')}` : '');
|
|
51
|
+
}
|
|
52
|
+
function templateLine({ inputs, componentMeta }, host) {
|
|
53
|
+
const selector = parseSelector(componentMeta.selector);
|
|
54
|
+
const hostElement = typeof host === 'string' ? parseHostString(host) : host && { element: host.selector, attributes: hostAttributes(host.inputs) };
|
|
55
|
+
const element = hostElement?.element ?? selector.element ?? 'div';
|
|
56
|
+
const required = requiredInputs(inputs);
|
|
57
|
+
// A binding takes the place of the bare attribute the selector asks for.
|
|
58
|
+
const selectorAttributes = selector.attributes.filter((attribute) => !required.some(({ publicName }) => publicName === attribute.name));
|
|
59
|
+
const attributes = [
|
|
60
|
+
...(hostElement?.attributes ?? []),
|
|
61
|
+
...selectorAttributes.map(({ name, value }) => (value === undefined ? name : `${name}="${value}"`)),
|
|
62
|
+
...required.map(binding)
|
|
63
|
+
];
|
|
64
|
+
const opening = [element, ...attributes].join(' ');
|
|
65
|
+
return element.includes('-') || VOID_ELEMENTS.has(element) ? `<${opening} />` : `<${opening}></${element}>`;
|
|
66
|
+
}
|
|
67
|
+
/** The required inputs in source order, each by the name a template binds it under and the member it is bound to. */
|
|
68
|
+
function requiredInputs(inputs) {
|
|
69
|
+
return inputs.filter((input) => input.required).map((input) => ({ publicName: input.alias ?? input.name, member: input.name }));
|
|
70
|
+
}
|
|
71
|
+
function binding({ publicName, member }) {
|
|
72
|
+
return `[${publicName}]="${member}"`;
|
|
73
|
+
}
|
|
74
|
+
const SELECTOR_PART = /\[([\w-]+)(?:=(["']?)([^\]"']*)\2)?]|\.([\w-]+)/g;
|
|
75
|
+
/**
|
|
76
|
+
* The first selector of a list as an element and attributes. A class becomes
|
|
77
|
+
* the `class` attribute; negations, other attribute operators and pseudo
|
|
78
|
+
* classes say nothing about what to write, so they are left out.
|
|
79
|
+
*/
|
|
80
|
+
function parseSelector(selector) {
|
|
81
|
+
const first = selector
|
|
82
|
+
.split(',')[0]
|
|
83
|
+
.trim()
|
|
84
|
+
.replace(/:not\([^)]*\)/g, '');
|
|
85
|
+
const element = first.match(/^[a-zA-Z][\w-]*/)?.[0];
|
|
86
|
+
const attributes = [];
|
|
87
|
+
for (const [, name, , value, className] of first.slice(element?.length ?? 0).matchAll(SELECTOR_PART)) {
|
|
88
|
+
const classAttribute = attributes.find((attribute) => attribute.name === 'class');
|
|
89
|
+
if (className === undefined)
|
|
90
|
+
attributes.push({ name, value });
|
|
91
|
+
else if (classAttribute)
|
|
92
|
+
classAttribute.value += ` ${className}`;
|
|
93
|
+
else
|
|
94
|
+
attributes.push({ name: 'class', value: className });
|
|
95
|
+
}
|
|
96
|
+
return { element, attributes };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The opening tag a string host starts with and its attributes as written;
|
|
100
|
+
* undefined when the string does not start with a tag. A host may carry
|
|
101
|
+
* content and a closing tag (`<button class="btn">Hover me</button>`): the
|
|
102
|
+
* showcase renders all of it, the snippet only needs the element to put the
|
|
103
|
+
* directive on.
|
|
104
|
+
*/
|
|
105
|
+
function parseHostString(host) {
|
|
106
|
+
const match = host.match(/^\s*<\s*([a-zA-Z][\w-]*)([^>]*?)\s*\/?\s*>/);
|
|
107
|
+
if (!match)
|
|
108
|
+
return undefined;
|
|
109
|
+
return { element: match[1], attributes: match[2].trim() ? [match[2].trim()] : [] };
|
|
110
|
+
}
|
|
111
|
+
/** Strings are attributes; numbers and booleans need a binding to stay what they are; anything else is bound to a member of that name. */
|
|
112
|
+
function hostAttributes(inputs) {
|
|
113
|
+
return Object.entries(inputs ?? {}).map(([name, value]) => {
|
|
114
|
+
if (typeof value === 'string')
|
|
115
|
+
return `${name}="${value.replace(/&/g, '&').replace(/"/g, '"')}"`;
|
|
116
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
117
|
+
return `[${name}]="${value}"`;
|
|
118
|
+
return `[${name}]="${name}"`;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ApiTypeDoc } from '@ng-prism/core/plugin';
|
|
2
|
+
/** Marks the button of a link, so the page can find it in rendered `innerHTML` that Angular does not bind. */
|
|
3
|
+
export declare const TYPE_LINK_ATTRIBUTE = "data-prism-type-link";
|
|
4
|
+
/**
|
|
5
|
+
* The TOC anchor of the type card a doc reference names: by id, then by name;
|
|
6
|
+
* `X.y` and `X#y` name `X`. A page holds two types of one name only under
|
|
7
|
+
* different ids, so an exact id is what tells them apart.
|
|
8
|
+
*/
|
|
9
|
+
export declare function typeLinkTarget(types: readonly ApiTypeDoc[], target: string): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Rewrites `{@link}`, `{@linkcode}` and `{@linkplain}` into what the Markdown renderer shows: a button to the type card
|
|
12
|
+
* `resolve` names, a Markdown link for a URL target, inline code (or the link text) otherwise. Code spans stay as written.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveDocLinks(text: string, resolve: (target: string) => string | undefined): string;
|
|
15
|
+
//# sourceMappingURL=doc-links.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-links.d.ts","sourceRoot":"","sources":["../../src/api-page/doc-links.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAWxD,8GAA8G;AAC9G,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK/F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,MAAM,CAUrG"}
|