feat(docs-infra): add support for "special elements" (#41299)
This commit adds support for generating pages that document special Angular elements, such as `ng-content` and `ng-template`, which have special behavior in Angular but are not directives nor components. Resolves #41273 PR Close #41299
This commit is contained in:
parent
2d1347b2ce
commit
62aca30286
|
@ -412,7 +412,8 @@ groups:
|
|||
'aio/content/images/guide/user-input/**',
|
||||
'aio/content/guide/view-encapsulation.md',
|
||||
'aio/content/examples/view-encapsulation/**',
|
||||
'aio/content/images/guide/view-encapsulation/**'
|
||||
'aio/content/images/guide/view-encapsulation/**',
|
||||
'aio/content/special-elements/**'
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# Special Elements
|
||||
|
||||
Each sub-directory below this contains documentation that describes "special elements".
|
||||
These are elements that can appear in templates that have special meaning and behaviour in the Angular framework.
|
||||
|
||||
Each element should have a markdown file with the same file name as the element's tag name, e.g. `ng-container.md`.
|
||||
The file should be stored in a directory whose name is that of the Angular package under which this element should appear in the docs (usually `core`).
|
||||
|
||||
## Short description
|
||||
|
||||
The file should contain a "short description" of the element. This is the first paragraph in the file.
|
||||
|
||||
## Long description
|
||||
|
||||
All the paragraphs after the short description are collected as an additional longer description.
|
||||
|
||||
## Element attributes
|
||||
|
||||
If the special element accepts one or more attributes that have special meaning to Angular, then these should be documented using the `@elementAttribute` tag.
|
||||
These tags should come after the description.
|
||||
|
||||
The format of this tag is:
|
||||
|
||||
```
|
||||
@elementAttribute attr="value"
|
||||
|
||||
Description of the attribute and value.
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
The `<ng-container>` can be used to hold directives without creating an HTML element.
|
|
@ -0,0 +1,5 @@
|
|||
The `<ng-content>` element specifies where to project content inside a component template.
|
||||
|
||||
@elementAttribute select="selector"
|
||||
|
||||
Only select elements from the projected content that match the given CSS `selector`.
|
|
@ -0,0 +1,3 @@
|
|||
Angular's `<ng-template>` element defines a template that doesn't render anything by default.
|
||||
|
||||
With `<ng-template>`, you can render the content manually for full control over how the content displays.
|
|
@ -94,11 +94,11 @@
|
|||
|
||||
// URLs that use the old scheme of adding the type to the end (e.g. `SomeClass-class`)
|
||||
// (Exclude disambiguated URLs that might be suffixed with `-\d+` (e.g. `SomeClass-1`))
|
||||
{"type": 301, "regex": "^/api/(?P<package>[^/]+)/(?P<api>[^/]+)-\\D*$", "destination": "/api/:package/:api"},
|
||||
{"type": 301, "regex": "^/api/(?P<package>[^/]+)/testing/index/(?P<api>[^/]+)$", "destination": "/api/:package/testing/:api"},
|
||||
{"type": 301, "regex": "^/api/(?P<package>[^/]+)/testing/(?P<api>[^/]+)-\\D*$", "destination": "/api/:package/testing/:api"},
|
||||
{"type": 301, "regex": "^/api/upgrade/(?P<package>[^/]+)/index/(?P<api>[^/]+)$", "destination": "/api/upgrade/:package/:api"},
|
||||
{"type": 301, "regex": "^/api/upgrade/(?P<package>[^/]+)/(?P<api>[^/]+)-\\D*$", "destination": "/api/upgrade/:package/:api"},
|
||||
{"type": 301, "source": "/api/:package/:api-@(class|decorator|directive|function|interface|let|pipe|type|type-alias|var)", "destination": "/api/:package/:api"},
|
||||
{"type": 301, "source": "/api/:package/testing/index/:api", "destination": "/api/:package/testing/:api"},
|
||||
{"type": 301, "source": "/api/:package/testing/:api-@(class|decorator|directive|function|interface|let|pipe|type|type-alias|var)", "destination": "/api/:package/testing/:api"},
|
||||
{"type": 301, "source": "/api/upgrade/:package/index/:api", "destination": "/api/upgrade/:package/:api"},
|
||||
{"type": 301, "source": "/api/upgrade/:package/:api-@(class|decorator|directive|function|interface|let|pipe|type|type-alias|var)", "destination": "/api/upgrade/:package/:api"},
|
||||
|
||||
// URLs that use the old scheme before we moved the docs to the angular/angular repo
|
||||
{"type": 301, "source": "/docs/*/latest", "destination": "/docs"},
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
"!/**/*__*/**",
|
||||
"!/**/stackblitz",
|
||||
"!/**/stackblitz.html",
|
||||
"!/api/*/**/*-\\D{0,}",
|
||||
"!/api/*/**/*-(class|directive|var|interface|function|pipe|let|type-alias|decorator)",
|
||||
"!/api/**/AnimationStateDeclarationMetadata*",
|
||||
"!/api/**/CORE_DIRECTIVES*",
|
||||
"!/api/**/DirectiveMetadata*",
|
||||
|
|
|
@ -46,13 +46,14 @@ export class ApiListComponent implements OnInit {
|
|||
{ value: 'const', title: 'Const'},
|
||||
{ value: 'decorator', title: 'Decorator' },
|
||||
{ value: 'directive', title: 'Directive' },
|
||||
{ value: 'element', title: 'Element'},
|
||||
{ value: 'enum', title: 'Enum' },
|
||||
{ value: 'function', title: 'Function' },
|
||||
{ value: 'interface', title: 'Interface' },
|
||||
{ value: 'package', title: 'Package'},
|
||||
{ value: 'pipe', title: 'Pipe'},
|
||||
{ value: 'ngmodule', title: 'NgModule'},
|
||||
{ value: 'type-alias', title: 'Type alias' },
|
||||
{ value: 'package', title: 'Package'}
|
||||
];
|
||||
|
||||
statuses: Option[] = [
|
||||
|
|
|
@ -38,6 +38,7 @@ $darkorange: #940;
|
|||
$anti-pattern: $brightred;
|
||||
|
||||
// API & CODE COLORS
|
||||
$amber-200: #AA3000;
|
||||
$amber-700: #FFA000;
|
||||
$blue-400: #42A5F5;
|
||||
$blue-500: #2196F3;
|
||||
|
@ -121,6 +122,10 @@ $api-symbols: (
|
|||
content: 'P',
|
||||
background: $blue-grey-600
|
||||
),
|
||||
element: (
|
||||
content: 'El',
|
||||
background: $amber-200
|
||||
),
|
||||
type-alias: (
|
||||
content: 'T',
|
||||
background: $light-green-600
|
||||
|
|
|
@ -9,7 +9,8 @@ const Package = require('dgeni').Package;
|
|||
|
||||
const basePackage = require('../angular-base-package');
|
||||
const typeScriptPackage = require('dgeni-packages/typescript');
|
||||
const {API_SOURCE_PATH, API_TEMPLATES_PATH, requireFolder} = require('../config');
|
||||
const {API_SOURCE_PATH, API_TEMPLATES_PATH, requireFolder, CONTENTS_PATH} = require('../config');
|
||||
const API_SEGMENT = 'api';
|
||||
|
||||
module.exports =
|
||||
new Package('angular-api', [basePackage, typeScriptPackage])
|
||||
|
@ -37,6 +38,7 @@ module.exports =
|
|||
.processor(require('./processors/simplifyMemberAnchors'))
|
||||
.processor(require('./processors/computeStability'))
|
||||
.processor(require('./processors/removeInjectableConstructors'))
|
||||
.processor(require('./processors/processSpecialElements'))
|
||||
.processor(require('./processors/collectPackageContentDocs'))
|
||||
.processor(require('./processors/processPackages'))
|
||||
.processor(require('./processors/processNgModuleDocs'))
|
||||
|
@ -50,7 +52,7 @@ module.exports =
|
|||
* more Angular specific API types, such as decorators and directives.
|
||||
*/
|
||||
.factory(function API_DOC_TYPES_TO_RENDER(EXPORT_DOC_TYPES) {
|
||||
return EXPORT_DOC_TYPES.concat(['decorator', 'directive', 'ngmodule', 'pipe', 'package']);
|
||||
return EXPORT_DOC_TYPES.concat(['decorator', 'directive', 'ngmodule', 'pipe', 'package', 'element']);
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -72,11 +74,12 @@ module.exports =
|
|||
})
|
||||
|
||||
.factory(require('./readers/package-content'))
|
||||
.factory(require('./readers/element'))
|
||||
|
||||
// Where do we get the source files?
|
||||
.config(function(
|
||||
readTypeScriptModules, readFilesProcessor, collectExamples, tsParser,
|
||||
packageContentFileReader) {
|
||||
packageContentFileReader, specialElementFileReader) {
|
||||
// Tell TypeScript how to load modules that start with with `@angular`
|
||||
tsParser.options.paths = {'@angular/*': [API_SOURCE_PATH + '/*']};
|
||||
tsParser.options.baseUrl = '.';
|
||||
|
@ -122,24 +125,47 @@ module.exports =
|
|||
'upgrade/static/testing/index.ts',
|
||||
];
|
||||
|
||||
readFilesProcessor.fileReaders.push(packageContentFileReader);
|
||||
|
||||
// API Examples
|
||||
// Special elements and packages docs are not extracted directly from TS code.
|
||||
readFilesProcessor.fileReaders.push(packageContentFileReader, specialElementFileReader);
|
||||
readFilesProcessor.sourceFiles = [
|
||||
{
|
||||
basePath: API_SOURCE_PATH,
|
||||
include: API_SOURCE_PATH + '/examples/**/*',
|
||||
fileReader: 'exampleFileReader'
|
||||
},
|
||||
{
|
||||
basePath: API_SOURCE_PATH,
|
||||
include: API_SOURCE_PATH + '/**/PACKAGE.md',
|
||||
fileReader: 'packageContentFileReader'
|
||||
},
|
||||
{
|
||||
basePath: CONTENTS_PATH + '/special-elements',
|
||||
include: CONTENTS_PATH + '/special-elements/*/**/*.md',
|
||||
fileReader: 'specialElementFileReader'
|
||||
},
|
||||
{
|
||||
basePath: API_SOURCE_PATH,
|
||||
include: API_SOURCE_PATH + '/examples/**/*',
|
||||
fileReader: 'exampleFileReader'
|
||||
}
|
||||
];
|
||||
|
||||
collectExamples.exampleFolders.push('examples');
|
||||
})
|
||||
|
||||
// Configure element ids and paths
|
||||
.config(function(computeIdsProcessor, computePathsProcessor) {
|
||||
computeIdsProcessor.idTemplates.push({
|
||||
docTypes: ['element'],
|
||||
getId(doc) {
|
||||
// path should not have a suffix
|
||||
return doc.fileInfo.relativePath.replace(/\.\w*$/, '');
|
||||
},
|
||||
getAliases(doc) { return [doc.name, doc.id]; }
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['element'],
|
||||
pathTemplate: API_SEGMENT + '/${id}',
|
||||
outputPathTemplate: '${path}.json'
|
||||
});
|
||||
})
|
||||
|
||||
// Configure jsdoc-style tag parsing
|
||||
.config(function(parseTagsProcessor, getInjectables, tsHost) {
|
||||
// Load up all the tag definitions in the tag-defs folder
|
||||
|
@ -154,7 +180,7 @@ module.exports =
|
|||
API_DOC_TYPES_TO_RENDER, API_DOC_TYPES) {
|
||||
computeStability.docTypes = API_DOC_TYPES_TO_RENDER;
|
||||
// Only split the description on the API docs
|
||||
splitDescription.docTypes = API_DOC_TYPES.concat(['package-content']);
|
||||
splitDescription.docTypes = API_DOC_TYPES.concat(['package-content', 'element']);
|
||||
addNotYetDocumentedProperty.docTypes = API_DOC_TYPES;
|
||||
})
|
||||
|
||||
|
@ -186,8 +212,6 @@ module.exports =
|
|||
|
||||
|
||||
.config(function(computePathsProcessor, EXPORT_DOC_TYPES, generateApiListDoc) {
|
||||
const API_SEGMENT = 'api';
|
||||
|
||||
generateApiListDoc.outputFolder = API_SEGMENT;
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
|
|
|
@ -27,6 +27,7 @@ module.exports = function processPackages(collectPackageContentDocsProcessor) {
|
|||
doc.directives = publicExports.filter(doc => doc.docType === 'directive').sort(byId);
|
||||
doc.pipes = publicExports.filter(doc => doc.docType === 'pipe').sort(byId);
|
||||
doc.types = publicExports.filter(doc => doc.docType === 'type-alias' || doc.docType === 'const').sort(byId);
|
||||
doc.elements = publicExports.filter(doc => doc.docType === 'element').sort(byId);
|
||||
if (doc.hasPublicExports && publicExports.every(doc => !!doc.deprecated)) {
|
||||
doc.deprecated = 'all exports of this entry point are deprecated.';
|
||||
}
|
||||
|
|
24
aio/tools/transforms/angular-api-package/processors/processSpecialElements.js
vendored
Normal file
24
aio/tools/transforms/angular-api-package/processors/processSpecialElements.js
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
const path = require('canonical-path');
|
||||
|
||||
module.exports = function processSpecialElements() {
|
||||
return {
|
||||
$runAfter: ['tags-extracted'],
|
||||
$runBefore: ['collectPackageContentDocsProcessor'],
|
||||
$process(docs) {
|
||||
const moduleDocs = {};
|
||||
docs.forEach(doc => {
|
||||
if (doc.docType === 'module') {
|
||||
moduleDocs[doc.id] = doc;
|
||||
}
|
||||
});
|
||||
|
||||
docs.forEach(doc => {
|
||||
// Wire up each 'element' doc to its containing module/package.
|
||||
if (doc.docType === 'element') {
|
||||
doc.moduleDoc = moduleDocs[path.dirname(doc.fileInfo.relativePath)];
|
||||
doc.moduleDoc.exports.push(doc);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @dgService
|
||||
* @description
|
||||
* This file reader will pull the contents from a text file that will be used
|
||||
* as the description of a "special element", such as `<ng-content>` or `<ng-template>`, etc.
|
||||
*
|
||||
* The doc will initially have the form:
|
||||
* ```
|
||||
* {
|
||||
* docType: 'element',
|
||||
* name: 'some-name',
|
||||
* content: 'the content of the file',
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
module.exports = function specialElementFileReader() {
|
||||
return {
|
||||
name: 'specialElementFileReader',
|
||||
defaultPattern: /\.md$/,
|
||||
getDocs: function(fileInfo) {
|
||||
// We return a single element array because element files only contain one document
|
||||
return [{
|
||||
docType: 'element',
|
||||
name: `<${fileInfo.baseName}>`,
|
||||
content: fileInfo.content,
|
||||
}];
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Documents attributes that can appear on "special elements", such as `select` on `<ng-content>`.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```
|
||||
* @elementAttribute select="selector"
|
||||
*
|
||||
* Only select elements from the projected content that match the given CSS `selector`.
|
||||
* ```
|
||||
*/
|
||||
module.exports = function() {
|
||||
return {
|
||||
name: 'elementAttribute',
|
||||
docProperty: 'attributes',
|
||||
multi: true,
|
||||
transforms(doc, tag, value) {
|
||||
const startOfDescription = value.indexOf('\n');
|
||||
const name = value.substring(0, startOfDescription).trim();
|
||||
const description = value.substring(startOfDescription).trim();
|
||||
return {name, description};
|
||||
}
|
||||
};
|
||||
};
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
{% block header %}
|
||||
<header class="api-header">
|
||||
<h1>{$ doc.name $}</h1>
|
||||
<h1>{$ doc.name | escape $}</h1>
|
||||
{% if doc.global %}<label class="api-type-label global">global</label>{% endif %}
|
||||
<label class="api-type-label {$ doc.docType $}">{$ doc.docType $}</label>
|
||||
{% if doc.deprecated !== undefined %}<label class="api-status-label deprecated">deprecated</label>{% endif %}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{% extends 'export-base.template.html' -%}
|
||||
|
||||
{% block overview %}
|
||||
{% include "includes/element-attributes.html" %}
|
||||
{% endblock %}
|
||||
{% block details %}
|
||||
{% include "includes/description.html" %}
|
||||
{% endblock %}
|
|
@ -1,10 +1,10 @@
|
|||
{% extends 'base.template.html' -%}
|
||||
|
||||
{% block body %}
|
||||
<section class="short-description">
|
||||
<section class="short-description">{% block shortDescription %}
|
||||
{$ doc.shortDescription | marked $}
|
||||
{% if doc.description %}<p><a href="#description">See more...</a></p>{% endif %}
|
||||
</section>
|
||||
{% endblock %}</section>
|
||||
{% include "includes/security-notes.html" %}
|
||||
{% include "includes/deprecation.html" %}
|
||||
{% block overview %}{% endblock %}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{%- if doc.attributes %}
|
||||
<section class="element-attributes">
|
||||
<h2>Attributes</h2>
|
||||
<table class="is-full-width list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- for attribute in doc.attributes %}
|
||||
<tr class="element-attribute">
|
||||
<td><code>{$ attribute.name $}</code></td>
|
||||
<td>{$ attribute.description | marked $}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endif %}
|
|
@ -8,7 +8,7 @@
|
|||
<table class="is-full-width list-table">
|
||||
{% for item in filteredItems %}
|
||||
<tr>
|
||||
<td><code class="code-anchor{% if item.deprecated %} deprecated-api-item{% endif %}"><a href="{$ overridePath or item.path $}"{%- if item.deprecated != undefined %} class="deprecated-api-item"{% endif %}>{$ item.name $}</a></code></td>
|
||||
<td><code class="code-anchor{% if item.deprecated %} deprecated-api-item{% endif %}"><a href="{$ overridePath or item.path $}"{%- if item.deprecated != undefined %} class="deprecated-api-item"{% endif %}>{$ item.name | escape $}</a></code></td>
|
||||
<td>
|
||||
{% if item.deprecated !== undefined %}{$ ('**Deprecated:** ' + item.deprecated) | marked $}{% endif %}
|
||||
{% if item.shortDescription %}{$ item.shortDescription | marked $}{% endif %}
|
||||
|
@ -53,6 +53,7 @@
|
|||
{$ listItems(doc.functions, 'Functions') $}
|
||||
{$ listItems(doc.structures, 'Structures') $}
|
||||
{$ listItems(doc.directives, 'Directives') $}
|
||||
{$ listItems(doc.elements, 'Elements') $}
|
||||
{$ listItems(doc.pipes, 'Pipes') $}
|
||||
{$ listItems(doc.types, 'Types') $}
|
||||
{%- endblock %}
|
||||
|
|
Loading…
Reference in New Issue