From 62aca302866daca422c0f27e4c524222159df8ba Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sun, 21 Mar 2021 11:07:34 +0000 Subject: [PATCH] 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 --- .pullapprove.yml | 3 +- aio/content/special-elements/README.md | 28 ++++++++++ .../special-elements/core/ng-container.md | 1 + .../special-elements/core/ng-content.md | 5 ++ .../special-elements/core/ng-template.md | 3 ++ aio/firebase.json | 10 ++-- aio/ngsw-config.json | 2 +- .../custom-elements/api/api-list.component.ts | 3 +- aio/src/styles/_constants.scss | 5 ++ .../transforms/angular-api-package/index.js | 52 ++++++++++++++----- .../processors/processPackages.js | 1 + .../processors/processSpecialElements.js | 24 +++++++++ .../angular-api-package/readers/element.js | 29 +++++++++++ .../tag-defs/elementAttribute.js | 24 +++++++++ .../templates/api/base.template.html | 2 +- .../templates/api/element.template.html | 8 +++ .../templates/api/export-base.template.html | 4 +- .../api/includes/element-attributes.html | 21 ++++++++ .../templates/api/package.template.html | 3 +- 19 files changed, 202 insertions(+), 26 deletions(-) create mode 100644 aio/content/special-elements/README.md create mode 100644 aio/content/special-elements/core/ng-container.md create mode 100644 aio/content/special-elements/core/ng-content.md create mode 100644 aio/content/special-elements/core/ng-template.md create mode 100644 aio/tools/transforms/angular-api-package/processors/processSpecialElements.js create mode 100644 aio/tools/transforms/angular-api-package/readers/element.js create mode 100644 aio/tools/transforms/angular-api-package/tag-defs/elementAttribute.js create mode 100644 aio/tools/transforms/templates/api/element.template.html create mode 100644 aio/tools/transforms/templates/api/includes/element-attributes.html diff --git a/.pullapprove.yml b/.pullapprove.yml index 2130e2ae30..41de32cd10 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -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: diff --git a/aio/content/special-elements/README.md b/aio/content/special-elements/README.md new file mode 100644 index 0000000000..b42812af50 --- /dev/null +++ b/aio/content/special-elements/README.md @@ -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. +``` diff --git a/aio/content/special-elements/core/ng-container.md b/aio/content/special-elements/core/ng-container.md new file mode 100644 index 0000000000..72c712a807 --- /dev/null +++ b/aio/content/special-elements/core/ng-container.md @@ -0,0 +1 @@ +The `` can be used to hold directives without creating an HTML element. diff --git a/aio/content/special-elements/core/ng-content.md b/aio/content/special-elements/core/ng-content.md new file mode 100644 index 0000000000..8f402f9115 --- /dev/null +++ b/aio/content/special-elements/core/ng-content.md @@ -0,0 +1,5 @@ +The `` 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`. diff --git a/aio/content/special-elements/core/ng-template.md b/aio/content/special-elements/core/ng-template.md new file mode 100644 index 0000000000..f4718f2cb3 --- /dev/null +++ b/aio/content/special-elements/core/ng-template.md @@ -0,0 +1,3 @@ +Angular's `` element defines a template that doesn't render anything by default. + +With ``, you can render the content manually for full control over how the content displays. diff --git a/aio/firebase.json b/aio/firebase.json index 5e3775a2e9..96fa8850d7 100644 --- a/aio/firebase.json +++ b/aio/firebase.json @@ -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[^/]+)/(?P[^/]+)-\\D*$", "destination": "/api/:package/:api"}, - {"type": 301, "regex": "^/api/(?P[^/]+)/testing/index/(?P[^/]+)$", "destination": "/api/:package/testing/:api"}, - {"type": 301, "regex": "^/api/(?P[^/]+)/testing/(?P[^/]+)-\\D*$", "destination": "/api/:package/testing/:api"}, - {"type": 301, "regex": "^/api/upgrade/(?P[^/]+)/index/(?P[^/]+)$", "destination": "/api/upgrade/:package/:api"}, - {"type": 301, "regex": "^/api/upgrade/(?P[^/]+)/(?P[^/]+)-\\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"}, diff --git a/aio/ngsw-config.json b/aio/ngsw-config.json index 2d21d1e166..474841f153 100644 --- a/aio/ngsw-config.json +++ b/aio/ngsw-config.json @@ -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*", diff --git a/aio/src/app/custom-elements/api/api-list.component.ts b/aio/src/app/custom-elements/api/api-list.component.ts index 0950f4f29a..50e8d2e8a3 100644 --- a/aio/src/app/custom-elements/api/api-list.component.ts +++ b/aio/src/app/custom-elements/api/api-list.component.ts @@ -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[] = [ diff --git a/aio/src/styles/_constants.scss b/aio/src/styles/_constants.scss index 4e0745141f..80c113e241 100755 --- a/aio/src/styles/_constants.scss +++ b/aio/src/styles/_constants.scss @@ -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 diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index b846c60168..013582f291 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -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({ diff --git a/aio/tools/transforms/angular-api-package/processors/processPackages.js b/aio/tools/transforms/angular-api-package/processors/processPackages.js index d9af8ac3a1..99d0d060ee 100644 --- a/aio/tools/transforms/angular-api-package/processors/processPackages.js +++ b/aio/tools/transforms/angular-api-package/processors/processPackages.js @@ -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.'; } diff --git a/aio/tools/transforms/angular-api-package/processors/processSpecialElements.js b/aio/tools/transforms/angular-api-package/processors/processSpecialElements.js new file mode 100644 index 0000000000..052601f37b --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/processSpecialElements.js @@ -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); + } + }); + } + }; +}; diff --git a/aio/tools/transforms/angular-api-package/readers/element.js b/aio/tools/transforms/angular-api-package/readers/element.js new file mode 100644 index 0000000000..2d9350f98c --- /dev/null +++ b/aio/tools/transforms/angular-api-package/readers/element.js @@ -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 `` or ``, 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, + }]; + } + }; +}; \ No newline at end of file diff --git a/aio/tools/transforms/angular-api-package/tag-defs/elementAttribute.js b/aio/tools/transforms/angular-api-package/tag-defs/elementAttribute.js new file mode 100644 index 0000000000..81d36f6ddd --- /dev/null +++ b/aio/tools/transforms/angular-api-package/tag-defs/elementAttribute.js @@ -0,0 +1,24 @@ +/** + * Documents attributes that can appear on "special elements", such as `select` on ``. + * + * 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}; + } + }; +}; diff --git a/aio/tools/transforms/templates/api/base.template.html b/aio/tools/transforms/templates/api/base.template.html index a30f8c3862..19e7e0bd86 100644 --- a/aio/tools/transforms/templates/api/base.template.html +++ b/aio/tools/transforms/templates/api/base.template.html @@ -20,7 +20,7 @@ {% block header %}
-

{$ doc.name $}

+

{$ doc.name | escape $}

{% if doc.global %}{% endif %} {% if doc.deprecated !== undefined %}{% endif %} diff --git a/aio/tools/transforms/templates/api/element.template.html b/aio/tools/transforms/templates/api/element.template.html new file mode 100644 index 0000000000..75a4e9f16b --- /dev/null +++ b/aio/tools/transforms/templates/api/element.template.html @@ -0,0 +1,8 @@ +{% extends 'export-base.template.html' -%} + +{% block overview %} +{% include "includes/element-attributes.html" %} +{% endblock %} +{% block details %} +{% include "includes/description.html" %} +{% endblock %} \ No newline at end of file diff --git a/aio/tools/transforms/templates/api/export-base.template.html b/aio/tools/transforms/templates/api/export-base.template.html index 9b94d5a312..bba146b49b 100644 --- a/aio/tools/transforms/templates/api/export-base.template.html +++ b/aio/tools/transforms/templates/api/export-base.template.html @@ -1,10 +1,10 @@ {% extends 'base.template.html' -%} {% block body %} -
+
{% block shortDescription %} {$ doc.shortDescription | marked $} {% if doc.description %}

See more...

{% endif %} -
+ {% endblock %}
{% include "includes/security-notes.html" %} {% include "includes/deprecation.html" %} {% block overview %}{% endblock %} diff --git a/aio/tools/transforms/templates/api/includes/element-attributes.html b/aio/tools/transforms/templates/api/includes/element-attributes.html new file mode 100644 index 0000000000..8de4930f99 --- /dev/null +++ b/aio/tools/transforms/templates/api/includes/element-attributes.html @@ -0,0 +1,21 @@ +{%- if doc.attributes %} +
+

Attributes

+ + + + + + + + + {%- for attribute in doc.attributes %} + + + + + {% endfor %} + +
NameDescription
{$ attribute.name $}{$ attribute.description | marked $}
+
+{% endif %} diff --git a/aio/tools/transforms/templates/api/package.template.html b/aio/tools/transforms/templates/api/package.template.html index 4d0952ac8d..6e9ab02de4 100644 --- a/aio/tools/transforms/templates/api/package.template.html +++ b/aio/tools/transforms/templates/api/package.template.html @@ -8,7 +8,7 @@ {% for item in filteredItems %} - +
{$ item.name $}{$ item.name | escape $} {% 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 %}