@ng-doc/utils 15.10.0-beta.1

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 ADDED
@@ -0,0 +1,38 @@
1
+ [![GitHub Workflow Status][build-shield]][build-url]
2
+ [![NPM][npm-shield]][npm-url]
3
+ [![MIT License][license-shield]][license-url]
4
+
5
+ <!-- PROJECT LOGO -->
6
+ <br />
7
+ <div align="center">
8
+ <a href="https://github.com/skoropadas/ng-doc">
9
+ <img src="https://ng-doc.com/assets/images/ng-doc.svg" alt="Logo" height="100px">
10
+ </a>
11
+
12
+ <h3 align="center">NgDoc</h3>
13
+
14
+ <p align="center">
15
+ Create documentation faster than ever!
16
+ <br />
17
+ <a href="https://ng-doc.com/"><strong>Explore the docs »</strong></a>
18
+ <br />
19
+ <br />
20
+ <a href="https://github.com/skoropadas/ng-doc/issues">Report Bug</a>
21
+ ·
22
+ <a href="https://github.com/skoropadas/ng-doc/issues">Request Feature</a>
23
+ </p>
24
+ </div>
25
+
26
+ <!-- ABOUT THE PROJECT -->
27
+
28
+ ## About The Project
29
+
30
+ This library is a part of NgDoc project, that uses to create documentation
31
+ for Angular projects, please check our [repo](https://github.com/skoropadas/ng-doc) for more details.
32
+
33
+ [npm-shield]: https://img.shields.io/npm/v/@ng-doc/builder.svg?style=for-the-badge
34
+ [npm-url]: https://www.npmjs.com/package/@ng-doc/builder
35
+ [license-shield]: https://img.shields.io/github/license/skoropadas/ng-doc.svg?style=for-the-badge
36
+ [license-url]: https://github.com/othneildrew/Best-README-Template/blob/master/LICENSE.txt
37
+ [build-shield]: https://img.shields.io/github/actions/workflow/status/skoropadas/ng-doc/release.yml?style=for-the-badge&branch=release
38
+ [build-url]: https://github.com/skoropadas/ng-doc/actions
@@ -0,0 +1,7 @@
1
+ import { Element } from 'hast';
2
+ /**
3
+ *
4
+ * @param node
5
+ * @param cls
6
+ */
7
+ export declare function hasClass(node: Element, cls: string): boolean;
@@ -0,0 +1,6 @@
1
+ import { Element } from 'hast';
2
+ /**
3
+ *
4
+ * @param ancestors
5
+ */
6
+ export declare function hasLinkAncestor(ancestors: Element[]): boolean;
@@ -0,0 +1,3 @@
1
+ export * from './has-class';
2
+ export * from './has-link-ancestor';
3
+ export * from './is-code-node';
@@ -0,0 +1,6 @@
1
+ import { Element } from 'hast';
2
+ /**
3
+ *
4
+ * @param node
5
+ */
6
+ export declare function isCodeNode(node: Element): boolean;
@@ -0,0 +1,14 @@
1
+ import { NgDocHeading } from '@ng-doc/core';
2
+ import { AddUsedKeyword, GetKeyword } from './plugins/keywords.plugin';
3
+ export interface NgDocHtmlPostProcessorConfig {
4
+ headings?: NgDocHeading[];
5
+ route?: string;
6
+ addUsedKeyword?: AddUsedKeyword;
7
+ getKeyword?: GetKeyword;
8
+ }
9
+ /**
10
+ *
11
+ * @param html
12
+ * @param config
13
+ */
14
+ export declare function htmlPostProcessor(html: string, config: NgDocHtmlPostProcessorConfig): Promise<string>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ *
3
+ * @param options
4
+ * @param route
5
+ */
6
+ export default function autolinkHeading(route?: string): any;
@@ -0,0 +1,12 @@
1
+ import { NgDocKeyword } from '@ng-doc/core/interfaces';
2
+ import { Element } from 'hast';
3
+ export type AddUsedKeyword = (keyword: string) => void;
4
+ export type GetKeyword = (keyword: string) => NgDocKeyword | undefined;
5
+ /**
6
+ *
7
+ * @param entityStore
8
+ * @param entity
9
+ * @param addUsedKeyword
10
+ * @param getKeyword
11
+ */
12
+ export default function keywordsPlugin(addUsedKeyword?: AddUsedKeyword, getKeyword?: GetKeyword): (tree: Element) => void;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Add hljs class to `pre` elements that contain `code` elements
3
+ *
4
+ * We need this to allow customization of the code block style
5
+ *
6
+ * @param tree
7
+ * @param headings
8
+ */
9
+ export default function markCodeBlocksPlugin(): any;
@@ -0,0 +1,6 @@
1
+ /**
2
+ *
3
+ * @param tree
4
+ * @param headings
5
+ */
6
+ export default function markElementsPlugin(): any;
@@ -0,0 +1,6 @@
1
+ /**
2
+ *
3
+ * @param tree
4
+ * @param headings
5
+ */
6
+ export default function removeNotIndexableContentPlugin(): any;
@@ -0,0 +1,8 @@
1
+ import { NgDocHeading } from '@ng-doc/core';
2
+ import { Root } from 'hast';
3
+ /**
4
+ *
5
+ * @param tree
6
+ * @param headings
7
+ */
8
+ export default function sluggerPlugin(headings?: NgDocHeading[]): (tree: Root) => void;
@@ -0,0 +1,5 @@
1
+ /**
2
+ *
3
+ * @param html
4
+ */
5
+ export declare function removeNotIndexableContent(html: string): Promise<string>;
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './html/html-post-processor';
2
+ export * from './html/remove-not-indexable-content';
package/index.js ADDED
@@ -0,0 +1,276 @@
1
+ import twig from 'highlight.js/lib/languages/twig';
2
+ import { rehype } from 'rehype';
3
+ import rehypeFormat from 'rehype-format';
4
+ import rehypeHighlight from 'rehype-highlight';
5
+ import rehypeMinifyWhitespace from 'rehype-minify-whitespace';
6
+ import { isElement } from 'hast-util-is-element';
7
+ import { visit } from 'unist-util-visit';
8
+ import { NG_DOC_ELEMENT } from '@ng-doc/core/constants/defaults.js';
9
+ import chalk from 'chalk';
10
+ import { toString } from 'hast-util-to-string';
11
+ import { visitParents, SKIP } from 'unist-util-visit-parents';
12
+ import { asArray } from '@ng-doc/core/helpers/as-array.js';
13
+ import GithubSlugger from 'github-slugger';
14
+ import { hasProperty } from 'hast-util-has-property';
15
+ import { headingRank } from 'hast-util-heading-rank';
16
+ import { filter } from 'unist-util-filter';
17
+
18
+ /**
19
+ *
20
+ * @param node
21
+ * @param cls
22
+ */
23
+ function hasClass(node, cls) {
24
+ const className = node.properties?.['className']?.toString() ?? '';
25
+ return !!className && className.includes(cls);
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @param ancestors
31
+ */
32
+ function hasLinkAncestor(ancestors) {
33
+ return ancestors.some(ancestor => isElement(ancestor, 'a'));
34
+ }
35
+
36
+ /**
37
+ *
38
+ * @param node
39
+ */
40
+ function isCodeNode(node) {
41
+ return isElement(node, 'code') || isElement(node, 'ng-doc-code');
42
+ }
43
+
44
+ const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
45
+ const NO_ANCHOR_CLASS = 'no-anchor';
46
+ /**
47
+ *
48
+ * @param options
49
+ * @param route
50
+ */
51
+ function autolinkHeading(route) {
52
+ return tree => visit(tree, 'element', node => {
53
+ if (route && isElement(node, HEADINGS) && node.properties && node.properties['id'] && !hasClass(node, NO_ANCHOR_CLASS)) {
54
+ node.children.push({
55
+ type: 'element',
56
+ tagName: 'a',
57
+ properties: {
58
+ title: 'Link to heading',
59
+ className: ['ng-doc-header-link'],
60
+ href: `${route}#${node.properties['id']}`
61
+ },
62
+ children: [{
63
+ type: 'element',
64
+ tagName: 'ng-doc-icon',
65
+ properties: {
66
+ icon: 'link-2',
67
+ size: 16
68
+ },
69
+ children: []
70
+ }]
71
+ });
72
+ }
73
+ });
74
+ }
75
+
76
+ const languages = ['typescript'];
77
+ /**
78
+ *
79
+ * @param entityStore
80
+ * @param entity
81
+ * @param addUsedKeyword
82
+ * @param getKeyword
83
+ */
84
+ function keywordsPlugin(addUsedKeyword, getKeyword) {
85
+ return tree => visitParents(tree, 'element', (node, ancestors) => {
86
+ if (!isCodeNode(node)) {
87
+ return;
88
+ }
89
+ const isInlineCode = !isElement(ancestors[ancestors.length - 1], 'pre');
90
+ const lang = node.properties?.['lang']?.toString() || '';
91
+ if (isInlineCode || languages.includes(lang)) {
92
+ visitParents(node, 'text', (node, ancestors) => {
93
+ if (hasLinkAncestor(ancestors) || !getKeyword || !addUsedKeyword) {
94
+ return;
95
+ }
96
+ const parent = ancestors[ancestors.length - 1];
97
+ const index = parent.children.indexOf(node);
98
+ // Parse the text for words that we can convert to links
99
+ const nodes = getNodes(node, parent, isInlineCode, addUsedKeyword, getKeyword);
100
+ // Replace the text node with the links and leftover text nodes
101
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
102
+ // @ts-ignore
103
+ Array.prototype.splice.apply(parent.children, [index, 1].concat(nodes));
104
+ // Do not visit this node's children or the newly added nodes
105
+ return [SKIP, index + nodes.length];
106
+ });
107
+ }
108
+ });
109
+ }
110
+ /**
111
+ *
112
+ * @param node
113
+ * @param parent
114
+ * @param entityStore
115
+ * @param inlineLink
116
+ * @param e
117
+ * @param addUsedKeyword
118
+ * @param getKeyword
119
+ */
120
+ function getNodes(node, parent, inlineLink, addUsedKeyword, getKeyword) {
121
+ const KeywordRegExp = /([A-Za-z0-9_.-/*]+)/;
122
+ return toString(node).split(KeywordRegExp).filter(word => word.length).map(word => {
123
+ if (KeywordRegExp.test(word)) {
124
+ addUsedKeyword(word);
125
+ }
126
+ const keyword = getKeyword(word);
127
+ if (inlineLink && /^\*\w+/gm.test(word) && !keyword) {
128
+ console.log(`\n${chalk.blue('NgDoc:')} ${chalk.yellow(`Keyword "${word}" is missing.`)}`);
129
+ }
130
+ // Convert code tag to link if it's a link to the page entity
131
+ if (inlineLink && keyword?.isCodeLink === false) {
132
+ parent.tagName = 'a';
133
+ parent.properties = {
134
+ href: keyword.path
135
+ };
136
+ return {
137
+ type: 'text',
138
+ value: keyword.title
139
+ };
140
+ } else if (inlineLink && keyword && parent.properties) {
141
+ parent.properties['class'] = [NG_DOC_ELEMENT, 'ng-doc-code-with-link'];
142
+ }
143
+ // Add link inside the code if it's a link to the API entity
144
+ return keyword ? createLinkNode(inlineLink ? keyword.title : word, keyword.path, keyword.type) : {
145
+ type: 'text',
146
+ value: word
147
+ };
148
+ });
149
+ }
150
+ /**
151
+ *
152
+ * @param text
153
+ * @param href
154
+ * @param type
155
+ */
156
+ function createLinkNode(text, href, type) {
157
+ return {
158
+ type: 'element',
159
+ tagName: 'a',
160
+ properties: {
161
+ href: href,
162
+ class: ['ng-doc-code-anchor', NG_DOC_ELEMENT],
163
+ 'data-link-type': type
164
+ },
165
+ children: [{
166
+ type: 'text',
167
+ value: text
168
+ }]
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Add hljs class to `pre` elements that contain `code` elements
174
+ *
175
+ * We need this to allow customization of the code block style
176
+ *
177
+ * @param tree
178
+ * @param headings
179
+ */
180
+ function markCodeBlocksPlugin() {
181
+ return tree => {
182
+ visit(tree, 'element', node => {
183
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
184
+ // @ts-ignore
185
+ if (node.tagName === 'pre' && node.children?.some(child => child.tagName === 'code')) {
186
+ if (node.properties) {
187
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
188
+ // @ts-ignore
189
+ node.properties['className'] = [...(node.properties?.['className'] ?? []), 'hljs'];
190
+ }
191
+ }
192
+ });
193
+ };
194
+ }
195
+
196
+ /**
197
+ *
198
+ * @param tree
199
+ * @param headings
200
+ */
201
+ function markElementsPlugin() {
202
+ return tree => {
203
+ visit(tree, 'element', node => {
204
+ if (node.properties) {
205
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
206
+ // @ts-ignore
207
+ node.properties['className'] = [...asArray(node.properties.className), NG_DOC_ELEMENT];
208
+ }
209
+ });
210
+ };
211
+ }
212
+
213
+ /**
214
+ *
215
+ * @param tree
216
+ * @param headings
217
+ */
218
+ function sluggerPlugin(headings) {
219
+ headings = headings || ['h1', 'h2', 'h3', 'h4'];
220
+ const slugger = new GithubSlugger();
221
+ return tree => {
222
+ slugger.reset();
223
+ visit(tree, 'element', node => {
224
+ if (headingRank(node) && !hasProperty(node, 'id') && headings?.includes(node.tagName.toLowerCase())) {
225
+ if (node.properties) {
226
+ node.properties['id'] = slugger.slug(toString(node));
227
+ }
228
+ }
229
+ });
230
+ };
231
+ }
232
+
233
+ /**
234
+ *
235
+ * @param html
236
+ * @param config
237
+ */
238
+ async function htmlPostProcessor(html, config) {
239
+ return rehype().use(rehypeFormat).use(rehypeHighlight, {
240
+ ignoreMissing: true,
241
+ languages: {
242
+ twig
243
+ }
244
+ }).use(sluggerPlugin, config.headings).use(rehypeMinifyWhitespace).use(autolinkHeading, config.route).use(keywordsPlugin, config.addUsedKeyword, config.getKeyword).use(markElementsPlugin).use(markCodeBlocksPlugin).process(html).then(file => file.toString());
245
+ }
246
+
247
+ /**
248
+ *
249
+ * @param tree
250
+ * @param headings
251
+ */
252
+ function removeNotIndexableContentPlugin() {
253
+ return tree => {
254
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
255
+ // @ts-ignore
256
+ return filter(tree, {
257
+ cascade: false
258
+ }, node => {
259
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
260
+ // @ts-ignore
261
+ const preWithCode = node?.tagName === 'pre' && node?.children?.some(isCodeNode);
262
+ const notIndexable = node?.properties?.['indexable'] === 'false';
263
+ return !node?.tagName || !preWithCode && !notIndexable;
264
+ });
265
+ };
266
+ }
267
+
268
+ /**
269
+ *
270
+ * @param html
271
+ */
272
+ async function removeNotIndexableContent(html) {
273
+ return rehype().use(removeNotIndexableContentPlugin).process(html).then(file => file.toString());
274
+ }
275
+
276
+ export { htmlPostProcessor, removeNotIndexableContent };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@ng-doc/utils",
3
+ "version": "15.10.0-beta.1",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "rehype": "^12.0.1",
7
+ "rehype-format": "^4.0.1",
8
+ "rehype-highlight": "^6.0.0",
9
+ "github-slugger": "^2.0.0",
10
+ "hast-util-has-property": "^2.0.1",
11
+ "hast-util-heading-rank": "^2.1.1",
12
+ "hast-util-to-string": "^2.0.0",
13
+ "unist-util-visit": "^4.1.2",
14
+ "rehype-minify-whitespace": "^5.0.1",
15
+ "unist-util-filter": "^4.0.1",
16
+ "chalk": "^5.2.0"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "module": "./index.js",
22
+ "main": "./index.js",
23
+ "types": "./index.d.ts",
24
+ "keywords": [
25
+ "angular",
26
+ "documentation",
27
+ "generator",
28
+ "builder",
29
+ "guides",
30
+ "api",
31
+ "demo",
32
+ "ng-doc"
33
+ ],
34
+ "author": "Skoropad Aleksandr <alex@skoropad.dev>",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/skoropadas/ng-doc.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/skoropadas/ng-doc/issues"
41
+ },
42
+ "homepage": "https://ng-doc.com",
43
+ "license": "MIT"
44
+ }