@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 +38 -0
- package/html/helpers/has-class.d.ts +7 -0
- package/html/helpers/has-link-ancestor.d.ts +6 -0
- package/html/helpers/index.d.ts +3 -0
- package/html/helpers/is-code-node.d.ts +6 -0
- package/html/html-post-processor.d.ts +14 -0
- package/html/plugins/autolink-headings.plugin.d.ts +6 -0
- package/html/plugins/keywords.plugin.d.ts +12 -0
- package/html/plugins/mark-code-blocks.plugin.d.ts +9 -0
- package/html/plugins/mark-elements.plugin.d.ts +6 -0
- package/html/plugins/remove-not-indexable-content.plugin.d.ts +6 -0
- package/html/plugins/slugger.plugin.d.ts +8 -0
- package/html/remove-not-indexable-content.d.ts +5 -0
- package/index.d.ts +2 -0
- package/index.js +276 -0
- package/package.json +44 -0
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,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,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;
|
package/index.d.ts
ADDED
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
|
+
}
|