@ng-doc/utils 16.3.6 → 16.4.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.
@@ -1,8 +1,6 @@
1
- import { NgDocHeading } from '@ng-doc/core';
2
1
  import { AddKeyword, GetKeyword } from './plugins/keywords.plugin';
3
2
  export interface NgDocHtmlPostProcessorConfig {
4
- headings?: NgDocHeading[];
5
- route?: string;
3
+ raiseError: (e: Error) => void;
6
4
  addUsedKeyword?: AddKeyword;
7
5
  addPotentialKeyword?: AddKeyword;
8
6
  getKeyword?: GetKeyword;
@@ -0,0 +1,13 @@
1
+ import { NgDocEntityAnchor, NgDocHeading } from '@ng-doc/core';
2
+ export interface NgDocHtmlProcessorConfig {
3
+ headings?: NgDocHeading[];
4
+ route?: string;
5
+ raiseError: (e: Error) => void;
6
+ addAnchor: (anchor: NgDocEntityAnchor) => void;
7
+ }
8
+ /**
9
+ *
10
+ * @param html
11
+ * @param config
12
+ */
13
+ export declare function htmlProcessor(html: string, config: NgDocHtmlProcessorConfig): Promise<string>;
@@ -1,13 +1,10 @@
1
1
  import { NgDocKeyword } from '@ng-doc/core/interfaces';
2
2
  import { Element } from 'hast';
3
+ import { NgDocHtmlPostProcessorConfig } from '../html-post-processor';
3
4
  export type AddKeyword = (keyword: string) => void;
4
5
  export type GetKeyword = (keyword: string) => NgDocKeyword | undefined;
5
6
  /**
6
7
  *
7
- * @param entityStore
8
- * @param entity
9
- * @param addUsedKeyword
10
- * @param addPotentialKeyword
11
- * @param getKeyword
8
+ * @param config
12
9
  */
13
- export default function keywordsPlugin(addUsedKeyword?: AddKeyword, addPotentialKeyword?: AddKeyword, getKeyword?: GetKeyword): (tree: Element) => void;
10
+ export default function keywordsPlugin(config: NgDocHtmlPostProcessorConfig): (tree: Element) => void;
@@ -1,8 +1,9 @@
1
- import { NgDocHeading } from '@ng-doc/core';
1
+ import { NgDocEntityAnchor, NgDocHeading } from '@ng-doc/core';
2
2
  import { Root } from 'hast';
3
3
  /**
4
4
  *
5
5
  * @param tree
6
+ * @param addAnchor
6
7
  * @param headings
7
8
  */
8
- export default function sluggerPlugin(headings?: NgDocHeading[]): (tree: Root) => void;
9
+ export default function sluggerPlugin(addAnchor: (anchor: NgDocEntityAnchor) => void, headings?: NgDocHeading[]): (tree: Root) => void;
package/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './html/html-post-processor';
2
+ export * from './html/html-processor';
2
3
  export * from './html/remove-not-indexable-content';
package/index.js CHANGED
@@ -1,363 +1,103 @@
1
- import rehypeFormat from 'rehype-format';
2
- import rehypeHighlight from 'rehype-highlight';
3
- import rehypeMinifyWhitespace from 'rehype-minify-whitespace';
4
- import { visit } from 'unist-util-visit';
5
1
  import { NG_DOC_ELEMENT } from '@ng-doc/core/constants/defaults.js';
6
- import chalk from 'chalk';
7
2
  import { toString } from 'hast-util-to-string';
3
+ import { visit } from 'unist-util-visit';
8
4
  import { asArray } from '@ng-doc/core/helpers/as-array.js';
9
- import GithubSlugger from 'github-slugger';
10
- import { hasProperty } from 'hast-util-has-property';
11
- import { headingRank } from 'hast-util-heading-rank';
12
5
  import proc from 'process';
13
6
  import { fileURLToPath } from 'url';
14
7
  import path from 'path';
8
+ import rehypeFormat from 'rehype-format';
9
+ import rehypeHighlight from 'rehype-highlight';
10
+ import rehypeMinifyWhitespace from 'rehype-minify-whitespace';
11
+ import GithubSlugger from 'github-slugger';
12
+ import { hasProperty } from 'hast-util-has-property';
13
+ import { headingRank } from 'hast-util-heading-rank';
15
14
  import { rehype } from 'rehype';
16
15
  import { filter } from 'unist-util-filter';
17
16
 
18
- /*
19
- Language: Twig
20
- Requires: xml.js
21
- Author: Luke Holder <lukemh@gmail.com>
22
- Description: Twig is a templating language for PHP
23
- Website: https://twig.symfony.com
24
- Category: template
25
- */
17
+ var unicode$3 = {};
26
18
 
27
- function twig(hljs) {
28
- const regex = hljs.regex;
29
- const FUNCTION_NAMES = [
30
- "absolute_url",
31
- "asset|0",
32
- "asset_version",
33
- "attribute",
34
- "block",
35
- "constant",
36
- "controller|0",
37
- "country_timezones",
38
- "csrf_token",
39
- "cycle",
40
- "date",
41
- "dump",
42
- "expression",
43
- "form|0",
44
- "form_end",
45
- "form_errors",
46
- "form_help",
47
- "form_label",
48
- "form_rest",
49
- "form_row",
50
- "form_start",
51
- "form_widget",
52
- "html_classes",
53
- "include",
54
- "is_granted",
55
- "logout_path",
56
- "logout_url",
57
- "max",
58
- "min",
59
- "parent",
60
- "path|0",
61
- "random",
62
- "range",
63
- "relative_path",
64
- "render",
65
- "render_esi",
66
- "source",
67
- "template_from_string",
68
- "url|0"
69
- ];
19
+ const UNDEFINED_CODE_POINTS = [
20
+ 0xfffe,
21
+ 0xffff,
22
+ 0x1fffe,
23
+ 0x1ffff,
24
+ 0x2fffe,
25
+ 0x2ffff,
26
+ 0x3fffe,
27
+ 0x3ffff,
28
+ 0x4fffe,
29
+ 0x4ffff,
30
+ 0x5fffe,
31
+ 0x5ffff,
32
+ 0x6fffe,
33
+ 0x6ffff,
34
+ 0x7fffe,
35
+ 0x7ffff,
36
+ 0x8fffe,
37
+ 0x8ffff,
38
+ 0x9fffe,
39
+ 0x9ffff,
40
+ 0xafffe,
41
+ 0xaffff,
42
+ 0xbfffe,
43
+ 0xbffff,
44
+ 0xcfffe,
45
+ 0xcffff,
46
+ 0xdfffe,
47
+ 0xdffff,
48
+ 0xefffe,
49
+ 0xeffff,
50
+ 0xffffe,
51
+ 0xfffff,
52
+ 0x10fffe,
53
+ 0x10ffff
54
+ ];
70
55
 
71
- const FILTERS = [
72
- "abs",
73
- "abbr_class",
74
- "abbr_method",
75
- "batch",
76
- "capitalize",
77
- "column",
78
- "convert_encoding",
79
- "country_name",
80
- "currency_name",
81
- "currency_symbol",
82
- "data_uri",
83
- "date",
84
- "date_modify",
85
- "default",
86
- "escape",
87
- "file_excerpt",
88
- "file_link",
89
- "file_relative",
90
- "filter",
91
- "first",
92
- "format",
93
- "format_args",
94
- "format_args_as_text",
95
- "format_currency",
96
- "format_date",
97
- "format_datetime",
98
- "format_file",
99
- "format_file_from_text",
100
- "format_number",
101
- "format_time",
102
- "html_to_markdown",
103
- "humanize",
104
- "inky_to_html",
105
- "inline_css",
106
- "join",
107
- "json_encode",
108
- "keys",
109
- "language_name",
110
- "last",
111
- "length",
112
- "locale_name",
113
- "lower",
114
- "map",
115
- "markdown",
116
- "markdown_to_html",
117
- "merge",
118
- "nl2br",
119
- "number_format",
120
- "raw",
121
- "reduce",
122
- "replace",
123
- "reverse",
124
- "round",
125
- "slice",
126
- "slug",
127
- "sort",
128
- "spaceless",
129
- "split",
130
- "striptags",
131
- "timezone_name",
132
- "title",
133
- "trans",
134
- "transchoice",
135
- "trim",
136
- "u|0",
137
- "upper",
138
- "url_encode",
139
- "yaml_dump",
140
- "yaml_encode"
141
- ];
56
+ unicode$3.REPLACEMENT_CHARACTER = '\uFFFD';
142
57
 
143
- let TAG_NAMES = [
144
- "apply",
145
- "autoescape",
146
- "block",
147
- "cache",
148
- "deprecated",
149
- "do",
150
- "embed",
151
- "extends",
152
- "filter",
153
- "flush",
154
- "for",
155
- "form_theme",
156
- "from",
157
- "if",
158
- "import",
159
- "include",
160
- "macro",
161
- "sandbox",
162
- "set",
163
- "stopwatch",
164
- "trans",
165
- "trans_default_domain",
166
- "transchoice",
167
- "use",
168
- "verbatim",
169
- "with"
170
- ];
58
+ unicode$3.CODE_POINTS = {
59
+ EOF: -1,
60
+ NULL: 0x00,
61
+ TABULATION: 0x09,
62
+ CARRIAGE_RETURN: 0x0d,
63
+ LINE_FEED: 0x0a,
64
+ FORM_FEED: 0x0c,
65
+ SPACE: 0x20,
66
+ EXCLAMATION_MARK: 0x21,
67
+ QUOTATION_MARK: 0x22,
68
+ NUMBER_SIGN: 0x23,
69
+ AMPERSAND: 0x26,
70
+ APOSTROPHE: 0x27,
71
+ HYPHEN_MINUS: 0x2d,
72
+ SOLIDUS: 0x2f,
73
+ DIGIT_0: 0x30,
74
+ DIGIT_9: 0x39,
75
+ SEMICOLON: 0x3b,
76
+ LESS_THAN_SIGN: 0x3c,
77
+ EQUALS_SIGN: 0x3d,
78
+ GREATER_THAN_SIGN: 0x3e,
79
+ QUESTION_MARK: 0x3f,
80
+ LATIN_CAPITAL_A: 0x41,
81
+ LATIN_CAPITAL_F: 0x46,
82
+ LATIN_CAPITAL_X: 0x58,
83
+ LATIN_CAPITAL_Z: 0x5a,
84
+ RIGHT_SQUARE_BRACKET: 0x5d,
85
+ GRAVE_ACCENT: 0x60,
86
+ LATIN_SMALL_A: 0x61,
87
+ LATIN_SMALL_F: 0x66,
88
+ LATIN_SMALL_X: 0x78,
89
+ LATIN_SMALL_Z: 0x7a,
90
+ REPLACEMENT_CHARACTER: 0xfffd
91
+ };
171
92
 
172
- TAG_NAMES = TAG_NAMES.concat(TAG_NAMES.map(t => `end${t}`));
173
-
174
- const STRING = {
175
- scope: 'string',
176
- variants: [
177
- {
178
- begin: /'/,
179
- end: /'/
180
- },
181
- {
182
- begin: /"/,
183
- end: /"/
184
- },
185
- ]
186
- };
187
-
188
- const NUMBER = {
189
- scope: "number",
190
- match: /\d+/
191
- };
192
-
193
- const PARAMS = {
194
- begin: /\(/,
195
- end: /\)/,
196
- excludeBegin: true,
197
- excludeEnd: true,
198
- contains: [
199
- STRING,
200
- NUMBER
201
- ]
202
- };
203
-
204
-
205
- const FUNCTIONS = {
206
- beginKeywords: FUNCTION_NAMES.join(" "),
207
- keywords: { name: FUNCTION_NAMES },
208
- relevance: 0,
209
- contains: [ PARAMS ]
210
- };
211
-
212
- const FILTER = {
213
- match: /\|(?=[A-Za-z_]+:?)/,
214
- beginScope: "punctuation",
215
- relevance: 0,
216
- contains: [
217
- {
218
- match: /[A-Za-z_]+:?/,
219
- keywords: FILTERS
220
- },
221
- ]
222
- };
223
-
224
- const tagNamed = (tagnames, { relevance }) => {
225
- return {
226
- beginScope: {
227
- 1: 'template-tag',
228
- 3: 'name'
229
- },
230
- relevance: relevance || 2,
231
- endScope: 'template-tag',
232
- begin: [
233
- /\{%/,
234
- /\s*/,
235
- regex.either(...tagnames)
236
- ],
237
- end: /%\}/,
238
- keywords: "in",
239
- contains: [
240
- FILTER,
241
- FUNCTIONS,
242
- STRING,
243
- NUMBER
244
- ]
245
- };
246
- };
247
-
248
- const CUSTOM_TAG_RE = /[a-z_]+/;
249
- const TAG = tagNamed(TAG_NAMES, { relevance: 2 });
250
- const CUSTOM_TAG = tagNamed([ CUSTOM_TAG_RE ], { relevance: 1 });
251
-
252
- return {
253
- name: 'Twig',
254
- aliases: [ 'craftcms' ],
255
- case_insensitive: true,
256
- subLanguage: 'xml',
257
- contains: [
258
- hljs.COMMENT(/\{#/, /#\}/),
259
- TAG,
260
- CUSTOM_TAG,
261
- {
262
- className: 'template-variable',
263
- begin: /\{\{/,
264
- end: /\}\}/,
265
- contains: [
266
- 'self',
267
- FILTER,
268
- FUNCTIONS,
269
- STRING,
270
- NUMBER
271
- ]
272
- }
273
- ]
274
- };
275
- }
276
-
277
- var unicode$3 = {};
278
-
279
- const UNDEFINED_CODE_POINTS = [
280
- 0xfffe,
281
- 0xffff,
282
- 0x1fffe,
283
- 0x1ffff,
284
- 0x2fffe,
285
- 0x2ffff,
286
- 0x3fffe,
287
- 0x3ffff,
288
- 0x4fffe,
289
- 0x4ffff,
290
- 0x5fffe,
291
- 0x5ffff,
292
- 0x6fffe,
293
- 0x6ffff,
294
- 0x7fffe,
295
- 0x7ffff,
296
- 0x8fffe,
297
- 0x8ffff,
298
- 0x9fffe,
299
- 0x9ffff,
300
- 0xafffe,
301
- 0xaffff,
302
- 0xbfffe,
303
- 0xbffff,
304
- 0xcfffe,
305
- 0xcffff,
306
- 0xdfffe,
307
- 0xdffff,
308
- 0xefffe,
309
- 0xeffff,
310
- 0xffffe,
311
- 0xfffff,
312
- 0x10fffe,
313
- 0x10ffff
314
- ];
315
-
316
- unicode$3.REPLACEMENT_CHARACTER = '\uFFFD';
317
-
318
- unicode$3.CODE_POINTS = {
319
- EOF: -1,
320
- NULL: 0x00,
321
- TABULATION: 0x09,
322
- CARRIAGE_RETURN: 0x0d,
323
- LINE_FEED: 0x0a,
324
- FORM_FEED: 0x0c,
325
- SPACE: 0x20,
326
- EXCLAMATION_MARK: 0x21,
327
- QUOTATION_MARK: 0x22,
328
- NUMBER_SIGN: 0x23,
329
- AMPERSAND: 0x26,
330
- APOSTROPHE: 0x27,
331
- HYPHEN_MINUS: 0x2d,
332
- SOLIDUS: 0x2f,
333
- DIGIT_0: 0x30,
334
- DIGIT_9: 0x39,
335
- SEMICOLON: 0x3b,
336
- LESS_THAN_SIGN: 0x3c,
337
- EQUALS_SIGN: 0x3d,
338
- GREATER_THAN_SIGN: 0x3e,
339
- QUESTION_MARK: 0x3f,
340
- LATIN_CAPITAL_A: 0x41,
341
- LATIN_CAPITAL_F: 0x46,
342
- LATIN_CAPITAL_X: 0x58,
343
- LATIN_CAPITAL_Z: 0x5a,
344
- RIGHT_SQUARE_BRACKET: 0x5d,
345
- GRAVE_ACCENT: 0x60,
346
- LATIN_SMALL_A: 0x61,
347
- LATIN_SMALL_F: 0x66,
348
- LATIN_SMALL_X: 0x78,
349
- LATIN_SMALL_Z: 0x7a,
350
- REPLACEMENT_CHARACTER: 0xfffd
351
- };
352
-
353
- unicode$3.CODE_POINT_SEQUENCES = {
354
- DASH_DASH_STRING: [0x2d, 0x2d], //--
355
- DOCTYPE_STRING: [0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45], //DOCTYPE
356
- CDATA_START_STRING: [0x5b, 0x43, 0x44, 0x41, 0x54, 0x41, 0x5b], //[CDATA[
357
- SCRIPT_STRING: [0x73, 0x63, 0x72, 0x69, 0x70, 0x74], //script
358
- PUBLIC_STRING: [0x50, 0x55, 0x42, 0x4c, 0x49, 0x43], //PUBLIC
359
- SYSTEM_STRING: [0x53, 0x59, 0x53, 0x54, 0x45, 0x4d] //SYSTEM
360
- };
93
+ unicode$3.CODE_POINT_SEQUENCES = {
94
+ DASH_DASH_STRING: [0x2d, 0x2d], //--
95
+ DOCTYPE_STRING: [0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45], //DOCTYPE
96
+ CDATA_START_STRING: [0x5b, 0x43, 0x44, 0x41, 0x54, 0x41, 0x5b], //[CDATA[
97
+ SCRIPT_STRING: [0x73, 0x63, 0x72, 0x69, 0x70, 0x74], //script
98
+ PUBLIC_STRING: [0x50, 0x55, 0x42, 0x4c, 0x49, 0x43], //PUBLIC
99
+ SYSTEM_STRING: [0x53, 0x59, 0x53, 0x54, 0x45, 0x4d] //SYSTEM
100
+ };
361
101
 
362
102
  //Surrogates
363
103
  unicode$3.isSurrogate = function(cp) {
@@ -8096,14 +7836,14 @@ function increment() {
8096
7836
  }
8097
7837
 
8098
7838
  var types = /*#__PURE__*/Object.freeze({
8099
- __proto__: null,
8100
- boolean: boolean,
8101
- booleanish: booleanish,
8102
- overloadedBoolean: overloadedBoolean,
8103
- number: number,
8104
- spaceSeparated: spaceSeparated,
8105
- commaSeparated: commaSeparated,
8106
- commaOrSpaceSeparated: commaOrSpaceSeparated
7839
+ __proto__: null,
7840
+ boolean: boolean,
7841
+ booleanish: booleanish,
7842
+ overloadedBoolean: overloadedBoolean,
7843
+ number: number,
7844
+ spaceSeparated: spaceSeparated,
7845
+ commaSeparated: commaSeparated,
7846
+ commaOrSpaceSeparated: commaOrSpaceSeparated
8107
7847
  });
8108
7848
 
8109
7849
  /** @type {Array<keyof types>} */
@@ -14622,64 +14362,6 @@ function element(node) {
14622
14362
  )
14623
14363
  }
14624
14364
 
14625
- /**
14626
- *
14627
- * @param node
14628
- * @param cls
14629
- */
14630
- function hasClass(node, cls) {
14631
- const className = node.properties?.['className']?.toString() ?? '';
14632
- return !!className && className.includes(cls);
14633
- }
14634
-
14635
- /**
14636
- *
14637
- * @param ancestors
14638
- */
14639
- function hasLinkAncestor(ancestors) {
14640
- return ancestors.some(ancestor => isElement(ancestor, 'a'));
14641
- }
14642
-
14643
- /**
14644
- *
14645
- * @param node
14646
- */
14647
- function isCodeNode(node) {
14648
- return isElement(node, 'code') || isElement(node, 'ng-doc-code');
14649
- }
14650
-
14651
- const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
14652
- const NO_ANCHOR_CLASS = 'no-anchor';
14653
- /**
14654
- *
14655
- * @param options
14656
- * @param route
14657
- */
14658
- function autolinkHeading(route) {
14659
- return tree => visit(tree, 'element', node => {
14660
- if (route && isElement(node, HEADINGS) && node.properties && node.properties['id'] && !hasClass(node, NO_ANCHOR_CLASS)) {
14661
- node.children.push({
14662
- type: 'element',
14663
- tagName: 'a',
14664
- properties: {
14665
- title: 'Link to heading',
14666
- className: ['ng-doc-header-link'],
14667
- href: `${route}#${node.properties['id']}`
14668
- },
14669
- children: [{
14670
- type: 'element',
14671
- tagName: 'ng-doc-icon',
14672
- properties: {
14673
- icon: 'link-2',
14674
- size: 16
14675
- },
14676
- children: []
14677
- }]
14678
- });
14679
- }
14680
- });
14681
- }
14682
-
14683
14365
  /**
14684
14366
  * @typedef {import('unist').Node} Node
14685
14367
  * @typedef {import('unist').Parent} Parent
@@ -14988,190 +14670,542 @@ const visitParents =
14988
14670
  return subresult
14989
14671
  }
14990
14672
 
14991
- offset =
14992
- typeof subresult[1] === 'number' ? subresult[1] : offset + step;
14993
- }
14994
- }
14673
+ offset =
14674
+ typeof subresult[1] === 'number' ? subresult[1] : offset + step;
14675
+ }
14676
+ }
14677
+
14678
+ return result
14679
+ }
14680
+ }
14681
+ }
14682
+ );
14683
+
14684
+ /**
14685
+ * Turn a return value into a clean result.
14686
+ *
14687
+ * @param {VisitorResult} value
14688
+ * Valid return values from visitors.
14689
+ * @returns {ActionTuple}
14690
+ * Clean result.
14691
+ */
14692
+ function toResult(value) {
14693
+ if (Array.isArray(value)) {
14694
+ return value
14695
+ }
14696
+
14697
+ if (typeof value === 'number') {
14698
+ return [CONTINUE, value]
14699
+ }
14700
+
14701
+ return [value]
14702
+ }
14703
+
14704
+ /**
14705
+ *
14706
+ * @param node
14707
+ * @param cls
14708
+ */
14709
+ function hasClass(node, cls) {
14710
+ const className = node.properties?.['className']?.toString() ?? '';
14711
+ return !!className && className.includes(cls);
14712
+ }
14713
+
14714
+ /**
14715
+ *
14716
+ * @param ancestors
14717
+ */
14718
+ function hasLinkAncestor(ancestors) {
14719
+ return ancestors.some(ancestor => isElement(ancestor, 'a'));
14720
+ }
14721
+
14722
+ /**
14723
+ *
14724
+ * @param node
14725
+ */
14726
+ function isCodeNode(node) {
14727
+ return isElement(node, 'code') || isElement(node, 'ng-doc-code');
14728
+ }
14729
+
14730
+ const languages = ['typescript', 'ts'];
14731
+ /**
14732
+ *
14733
+ * @param config
14734
+ */
14735
+ function keywordsPlugin(config) {
14736
+ const {
14737
+ addUsedKeyword,
14738
+ addPotentialKeyword,
14739
+ getKeyword
14740
+ } = config;
14741
+ return tree => visitParents(tree, 'element', (node, ancestors) => {
14742
+ if (!isCodeNode(node)) {
14743
+ return;
14744
+ }
14745
+ const isInlineCode = !isElement(ancestors[ancestors.length - 1], 'pre');
14746
+ const lang = node.properties?.['lang']?.toString() || '';
14747
+ if (isInlineCode || languages.includes(lang)) {
14748
+ visitParents(node, 'text', (node, ancestors) => {
14749
+ if (hasLinkAncestor(ancestors) || !getKeyword || !addUsedKeyword || !addPotentialKeyword) {
14750
+ return;
14751
+ }
14752
+ const parent = ancestors[ancestors.length - 1];
14753
+ const index = parent.children.indexOf(node);
14754
+ // Parse the text for words that we can convert to links
14755
+ const nodes = getNodes(node, parent, isInlineCode, config);
14756
+ // Replace the text node with the links and leftover text nodes
14757
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14758
+ // @ts-ignore
14759
+ Array.prototype.splice.apply(parent.children, [index, 1].concat(nodes));
14760
+ // Do not visit this node's children or the newly added nodes
14761
+ return [SKIP, index + nodes.length];
14762
+ });
14763
+ }
14764
+ });
14765
+ }
14766
+ /**
14767
+ *
14768
+ * @param node
14769
+ * @param parent
14770
+ * @param inlineLink
14771
+ * @param config
14772
+ */
14773
+ function getNodes(node, parent, inlineLink, config) {
14774
+ const KeywordRegExp = /([A-Za-z0-9_\-*]+[.#]?[A-Za-z0-9_\-*$]+)/g;
14775
+ const keywordAnchorRegexp = /^(?<keyword>[A-Za-z0-9_\-*]+)((?<delimiter>[.#])(?<anchor>[A-Za-z0-9_\-*$]+))?$/;
14776
+ const {
14777
+ addUsedKeyword,
14778
+ addPotentialKeyword,
14779
+ getKeyword,
14780
+ raiseError
14781
+ } = config;
14782
+ if (!getKeyword || !addUsedKeyword || !addPotentialKeyword) {
14783
+ throw new Error('Missing config');
14784
+ }
14785
+ return toString(node).split(KeywordRegExp).filter(word => word.length).map(word => {
14786
+ const match = word.match(keywordAnchorRegexp);
14787
+ const formattedWord = match ? `${match.groups?.['keyword']}${match.groups?.['delimiter'] || ''}${match.groups?.['anchor']?.toLowerCase() || ''}` : word;
14788
+ const keyword = getKeyword(formattedWord);
14789
+ const rootKeyword = getKeyword(match?.groups?.['keyword'] || '');
14790
+ if (keywordAnchorRegexp.test(word)) {
14791
+ keyword ? addUsedKeyword(word) : addPotentialKeyword(word);
14792
+ }
14793
+ const notFoundGuideKeyword = /^\*\w+/gm.test(word) && !keyword;
14794
+ const notFoundApiKeyword = !!rootKeyword && !!match?.groups?.['anchor'] && !keyword;
14795
+ if (inlineLink && (notFoundGuideKeyword || notFoundApiKeyword)) {
14796
+ raiseError(new Error(`Route with keyword "${word}" is missing.`));
14797
+ }
14798
+ // Convert code tag to link if it's a link to the page entity
14799
+ if (inlineLink && keyword?.isCodeLink === false) {
14800
+ parent.tagName = 'a';
14801
+ parent.properties = {
14802
+ href: keyword.path
14803
+ };
14804
+ return {
14805
+ type: 'text',
14806
+ value: keyword.title
14807
+ };
14808
+ } else if (inlineLink && keyword && parent.properties) {
14809
+ parent.properties['class'] = [NG_DOC_ELEMENT, 'ng-doc-code-with-link'];
14810
+ }
14811
+ // Add link inside the code if it's a link to the API entity
14812
+ return keyword ? createLinkNode(inlineLink ? keyword.title : word, keyword.path, keyword.type) : {
14813
+ type: 'text',
14814
+ value: word
14815
+ };
14816
+ });
14817
+ }
14818
+ /**
14819
+ *
14820
+ * @param text
14821
+ * @param href
14822
+ * @param type
14823
+ * @param anchor
14824
+ */
14825
+ function createLinkNode(text, href, type) {
14826
+ return {
14827
+ type: 'element',
14828
+ tagName: 'a',
14829
+ properties: {
14830
+ href: href,
14831
+ class: ['ng-doc-code-anchor', NG_DOC_ELEMENT],
14832
+ 'data-link-type': type
14833
+ },
14834
+ children: [{
14835
+ type: 'text',
14836
+ value: text
14837
+ }]
14838
+ };
14839
+ }
14840
+
14841
+ /**
14842
+ * Add hljs class to `pre` elements that contain `code` elements
14843
+ *
14844
+ * We need this to allow customization of the code block style
14845
+ *
14846
+ * @param tree
14847
+ * @param headings
14848
+ */
14849
+ function markCodeBlocksPlugin() {
14850
+ return tree => {
14851
+ visit(tree, 'element', node => {
14852
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14853
+ // @ts-ignore
14854
+ if (node.tagName === 'pre' && node.children?.some(child => child.tagName === 'code')) {
14855
+ if (node.properties) {
14856
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14857
+ // @ts-ignore
14858
+ node.properties['className'] = [...(node.properties?.['className'] ?? []), 'hljs'];
14859
+ }
14860
+ }
14861
+ });
14862
+ };
14863
+ }
14864
+
14865
+ /**
14866
+ *
14867
+ * @param tree
14868
+ * @param headings
14869
+ */
14870
+ function markElementsPlugin() {
14871
+ return tree => {
14872
+ visit(tree, 'element', node => {
14873
+ if (node.properties) {
14874
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14875
+ // @ts-ignore
14876
+ node.properties['className'] = [...asArray(node.properties.className), NG_DOC_ELEMENT];
14877
+ }
14878
+ });
14879
+ };
14880
+ }
14881
+
14882
+ /**
14883
+ *
14884
+ * @param html
14885
+ * @param config
14886
+ */
14887
+ async function htmlPostProcessor(html, config) {
14888
+ return unified().use(rehypeParse, {
14889
+ fragment: true
14890
+ }).use(rehypeStringify).use(keywordsPlugin, config).use(markElementsPlugin).use(markCodeBlocksPlugin).process(html).then(file => file.toString());
14891
+ }
14892
+
14893
+ /*
14894
+ Language: Twig
14895
+ Requires: xml.js
14896
+ Author: Luke Holder <lukemh@gmail.com>
14897
+ Description: Twig is a templating language for PHP
14898
+ Website: https://twig.symfony.com
14899
+ Category: template
14900
+ */
14901
+
14902
+ function twig(hljs) {
14903
+ const regex = hljs.regex;
14904
+ const FUNCTION_NAMES = [
14905
+ "absolute_url",
14906
+ "asset|0",
14907
+ "asset_version",
14908
+ "attribute",
14909
+ "block",
14910
+ "constant",
14911
+ "controller|0",
14912
+ "country_timezones",
14913
+ "csrf_token",
14914
+ "cycle",
14915
+ "date",
14916
+ "dump",
14917
+ "expression",
14918
+ "form|0",
14919
+ "form_end",
14920
+ "form_errors",
14921
+ "form_help",
14922
+ "form_label",
14923
+ "form_rest",
14924
+ "form_row",
14925
+ "form_start",
14926
+ "form_widget",
14927
+ "html_classes",
14928
+ "include",
14929
+ "is_granted",
14930
+ "logout_path",
14931
+ "logout_url",
14932
+ "max",
14933
+ "min",
14934
+ "parent",
14935
+ "path|0",
14936
+ "random",
14937
+ "range",
14938
+ "relative_path",
14939
+ "render",
14940
+ "render_esi",
14941
+ "source",
14942
+ "template_from_string",
14943
+ "url|0"
14944
+ ];
14945
+
14946
+ const FILTERS = [
14947
+ "abs",
14948
+ "abbr_class",
14949
+ "abbr_method",
14950
+ "batch",
14951
+ "capitalize",
14952
+ "column",
14953
+ "convert_encoding",
14954
+ "country_name",
14955
+ "currency_name",
14956
+ "currency_symbol",
14957
+ "data_uri",
14958
+ "date",
14959
+ "date_modify",
14960
+ "default",
14961
+ "escape",
14962
+ "file_excerpt",
14963
+ "file_link",
14964
+ "file_relative",
14965
+ "filter",
14966
+ "first",
14967
+ "format",
14968
+ "format_args",
14969
+ "format_args_as_text",
14970
+ "format_currency",
14971
+ "format_date",
14972
+ "format_datetime",
14973
+ "format_file",
14974
+ "format_file_from_text",
14975
+ "format_number",
14976
+ "format_time",
14977
+ "html_to_markdown",
14978
+ "humanize",
14979
+ "inky_to_html",
14980
+ "inline_css",
14981
+ "join",
14982
+ "json_encode",
14983
+ "keys",
14984
+ "language_name",
14985
+ "last",
14986
+ "length",
14987
+ "locale_name",
14988
+ "lower",
14989
+ "map",
14990
+ "markdown",
14991
+ "markdown_to_html",
14992
+ "merge",
14993
+ "nl2br",
14994
+ "number_format",
14995
+ "raw",
14996
+ "reduce",
14997
+ "replace",
14998
+ "reverse",
14999
+ "round",
15000
+ "slice",
15001
+ "slug",
15002
+ "sort",
15003
+ "spaceless",
15004
+ "split",
15005
+ "striptags",
15006
+ "timezone_name",
15007
+ "title",
15008
+ "trans",
15009
+ "transchoice",
15010
+ "trim",
15011
+ "u|0",
15012
+ "upper",
15013
+ "url_encode",
15014
+ "yaml_dump",
15015
+ "yaml_encode"
15016
+ ];
15017
+
15018
+ let TAG_NAMES = [
15019
+ "apply",
15020
+ "autoescape",
15021
+ "block",
15022
+ "cache",
15023
+ "deprecated",
15024
+ "do",
15025
+ "embed",
15026
+ "extends",
15027
+ "filter",
15028
+ "flush",
15029
+ "for",
15030
+ "form_theme",
15031
+ "from",
15032
+ "if",
15033
+ "import",
15034
+ "include",
15035
+ "macro",
15036
+ "sandbox",
15037
+ "set",
15038
+ "stopwatch",
15039
+ "trans",
15040
+ "trans_default_domain",
15041
+ "transchoice",
15042
+ "use",
15043
+ "verbatim",
15044
+ "with"
15045
+ ];
15046
+
15047
+ TAG_NAMES = TAG_NAMES.concat(TAG_NAMES.map(t => `end${t}`));
15048
+
15049
+ const STRING = {
15050
+ scope: 'string',
15051
+ variants: [
15052
+ {
15053
+ begin: /'/,
15054
+ end: /'/
15055
+ },
15056
+ {
15057
+ begin: /"/,
15058
+ end: /"/
15059
+ },
15060
+ ]
15061
+ };
14995
15062
 
14996
- return result
14997
- }
14998
- }
14999
- }
15000
- );
15063
+ const NUMBER = {
15064
+ scope: "number",
15065
+ match: /\d+/
15066
+ };
15001
15067
 
15002
- /**
15003
- * Turn a return value into a clean result.
15004
- *
15005
- * @param {VisitorResult} value
15006
- * Valid return values from visitors.
15007
- * @returns {ActionTuple}
15008
- * Clean result.
15009
- */
15010
- function toResult(value) {
15011
- if (Array.isArray(value)) {
15012
- return value
15013
- }
15068
+ const PARAMS = {
15069
+ begin: /\(/,
15070
+ end: /\)/,
15071
+ excludeBegin: true,
15072
+ excludeEnd: true,
15073
+ contains: [
15074
+ STRING,
15075
+ NUMBER
15076
+ ]
15077
+ };
15014
15078
 
15015
- if (typeof value === 'number') {
15016
- return [CONTINUE, value]
15017
- }
15018
15079
 
15019
- return [value]
15020
- }
15080
+ const FUNCTIONS = {
15081
+ beginKeywords: FUNCTION_NAMES.join(" "),
15082
+ keywords: { name: FUNCTION_NAMES },
15083
+ relevance: 0,
15084
+ contains: [ PARAMS ]
15085
+ };
15021
15086
 
15022
- const languages = ['typescript', 'ts'];
15023
- /**
15024
- *
15025
- * @param entityStore
15026
- * @param entity
15027
- * @param addUsedKeyword
15028
- * @param addPotentialKeyword
15029
- * @param getKeyword
15030
- */
15031
- function keywordsPlugin(addUsedKeyword, addPotentialKeyword, getKeyword) {
15032
- return tree => visitParents(tree, 'element', (node, ancestors) => {
15033
- if (!isCodeNode(node)) {
15034
- return;
15035
- }
15036
- const isInlineCode = !isElement(ancestors[ancestors.length - 1], 'pre');
15037
- const lang = node.properties?.['lang']?.toString() || '';
15038
- if (isInlineCode || languages.includes(lang)) {
15039
- visitParents(node, 'text', (node, ancestors) => {
15040
- if (hasLinkAncestor(ancestors) || !getKeyword || !addUsedKeyword || !addPotentialKeyword) {
15041
- return;
15042
- }
15043
- const parent = ancestors[ancestors.length - 1];
15044
- const index = parent.children.indexOf(node);
15045
- // Parse the text for words that we can convert to links
15046
- const nodes = getNodes(node, parent, isInlineCode, addUsedKeyword, addPotentialKeyword, getKeyword);
15047
- // Replace the text node with the links and leftover text nodes
15048
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15049
- // @ts-ignore
15050
- Array.prototype.splice.apply(parent.children, [index, 1].concat(nodes));
15051
- // Do not visit this node's children or the newly added nodes
15052
- return [SKIP, index + nodes.length];
15053
- });
15054
- }
15055
- });
15056
- }
15057
- /**
15058
- *
15059
- * @param node
15060
- * @param parent
15061
- * @param entityStore
15062
- * @param inlineLink
15063
- * @param e
15064
- * @param addUsedKeyword
15065
- * @param addPotentialKeyword
15066
- * @param getKeyword
15067
- */
15068
- function getNodes(node, parent, inlineLink, addUsedKeyword, addPotentialKeyword, getKeyword) {
15069
- const KeywordRegExp = /([A-Za-z0-9_.-/*]+)/;
15070
- return toString(node).split(KeywordRegExp).filter(word => word.length).map(word => {
15071
- const keyword = getKeyword(word);
15072
- if (KeywordRegExp.test(word)) {
15073
- keyword ? addUsedKeyword(word) : addPotentialKeyword(word);
15074
- }
15075
- if (inlineLink && /^\*\w+/gm.test(word) && !keyword) {
15076
- console.log(`\n${chalk.blue('NgDoc:')} ${chalk.yellow(`Keyword "${word}" is missing.`)}`);
15077
- }
15078
- // Convert code tag to link if it's a link to the page entity
15079
- if (inlineLink && keyword?.isCodeLink === false) {
15080
- parent.tagName = 'a';
15081
- parent.properties = {
15082
- href: keyword.path
15083
- };
15084
- return {
15085
- type: 'text',
15086
- value: keyword.title
15087
- };
15088
- } else if (inlineLink && keyword && parent.properties) {
15089
- parent.properties['class'] = [NG_DOC_ELEMENT, 'ng-doc-code-with-link'];
15090
- }
15091
- // Add link inside the code if it's a link to the API entity
15092
- return keyword ? createLinkNode(inlineLink ? keyword.title : word, keyword.path, keyword.type) : {
15093
- type: 'text',
15094
- value: word
15087
+ const FILTER = {
15088
+ match: /\|(?=[A-Za-z_]+:?)/,
15089
+ beginScope: "punctuation",
15090
+ relevance: 0,
15091
+ contains: [
15092
+ {
15093
+ match: /[A-Za-z_]+:?/,
15094
+ keywords: FILTERS
15095
+ },
15096
+ ]
15097
+ };
15098
+
15099
+ const tagNamed = (tagnames, { relevance }) => {
15100
+ return {
15101
+ beginScope: {
15102
+ 1: 'template-tag',
15103
+ 3: 'name'
15104
+ },
15105
+ relevance: relevance || 2,
15106
+ endScope: 'template-tag',
15107
+ begin: [
15108
+ /\{%/,
15109
+ /\s*/,
15110
+ regex.either(...tagnames)
15111
+ ],
15112
+ end: /%\}/,
15113
+ keywords: "in",
15114
+ contains: [
15115
+ FILTER,
15116
+ FUNCTIONS,
15117
+ STRING,
15118
+ NUMBER
15119
+ ]
15095
15120
  };
15096
- });
15097
- }
15098
- /**
15099
- *
15100
- * @param text
15101
- * @param href
15102
- * @param type
15103
- */
15104
- function createLinkNode(text, href, type) {
15105
- return {
15106
- type: 'element',
15107
- tagName: 'a',
15108
- properties: {
15109
- href: href,
15110
- class: ['ng-doc-code-anchor', NG_DOC_ELEMENT],
15111
- 'data-link-type': type
15112
- },
15113
- children: [{
15114
- type: 'text',
15115
- value: text
15116
- }]
15117
15121
  };
15118
- }
15119
15122
 
15120
- /**
15121
- * Add hljs class to `pre` elements that contain `code` elements
15122
- *
15123
- * We need this to allow customization of the code block style
15124
- *
15125
- * @param tree
15126
- * @param headings
15127
- */
15128
- function markCodeBlocksPlugin() {
15129
- return tree => {
15130
- visit(tree, 'element', node => {
15131
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15132
- // @ts-ignore
15133
- if (node.tagName === 'pre' && node.children?.some(child => child.tagName === 'code')) {
15134
- if (node.properties) {
15135
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15136
- // @ts-ignore
15137
- node.properties['className'] = [...(node.properties?.['className'] ?? []), 'hljs'];
15138
- }
15123
+ const CUSTOM_TAG_RE = /[a-z_]+/;
15124
+ const TAG = tagNamed(TAG_NAMES, { relevance: 2 });
15125
+ const CUSTOM_TAG = tagNamed([ CUSTOM_TAG_RE ], { relevance: 1 });
15126
+
15127
+ return {
15128
+ name: 'Twig',
15129
+ aliases: [ 'craftcms' ],
15130
+ case_insensitive: true,
15131
+ subLanguage: 'xml',
15132
+ contains: [
15133
+ hljs.COMMENT(/\{#/, /#\}/),
15134
+ TAG,
15135
+ CUSTOM_TAG,
15136
+ {
15137
+ className: 'template-variable',
15138
+ begin: /\{\{/,
15139
+ end: /\}\}/,
15140
+ contains: [
15141
+ 'self',
15142
+ FILTER,
15143
+ FUNCTIONS,
15144
+ STRING,
15145
+ NUMBER
15146
+ ]
15139
15147
  }
15140
- });
15148
+ ]
15141
15149
  };
15142
15150
  }
15143
15151
 
15152
+ const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
15153
+ const NO_ANCHOR_CLASS = 'no-anchor';
15144
15154
  /**
15145
15155
  *
15146
- * @param tree
15147
- * @param headings
15156
+ * @param options
15157
+ * @param route
15148
15158
  */
15149
- function markElementsPlugin() {
15150
- return tree => {
15151
- visit(tree, 'element', node => {
15152
- if (node.properties) {
15153
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15154
- // @ts-ignore
15155
- node.properties['className'] = [...asArray(node.properties.className), NG_DOC_ELEMENT];
15156
- }
15157
- });
15158
- };
15159
+ function autolinkHeading(route) {
15160
+ return tree => visit(tree, 'element', node => {
15161
+ if (route && isElement(node, HEADINGS) && node.properties && node.properties['id'] && !hasClass(node, NO_ANCHOR_CLASS)) {
15162
+ node.children.push({
15163
+ type: 'element',
15164
+ tagName: 'a',
15165
+ properties: {
15166
+ title: 'Link to heading',
15167
+ className: ['ng-doc-header-link'],
15168
+ href: `${route}#${node.properties['id']}`
15169
+ },
15170
+ children: [{
15171
+ type: 'element',
15172
+ tagName: 'ng-doc-icon',
15173
+ properties: {
15174
+ icon: 'link-2',
15175
+ size: 16
15176
+ },
15177
+ children: []
15178
+ }]
15179
+ });
15180
+ }
15181
+ });
15159
15182
  }
15160
15183
 
15161
15184
  /**
15162
15185
  *
15163
15186
  * @param tree
15187
+ * @param addAnchor
15164
15188
  * @param headings
15165
15189
  */
15166
- function sluggerPlugin(headings) {
15190
+ function sluggerPlugin(addAnchor, headings) {
15167
15191
  headings = headings || ['h1', 'h2', 'h3', 'h4'];
15168
15192
  const slugger = new GithubSlugger();
15169
15193
  return tree => {
15170
15194
  slugger.reset();
15171
15195
  visit(tree, 'element', node => {
15172
- if (headingRank(node) && !hasProperty(node, 'id') && headings?.includes(node.tagName.toLowerCase())) {
15196
+ const isHeading = headingRank(node) && !hasProperty(node, 'id') && headings?.includes(node.tagName.toLowerCase());
15197
+ const isSlugAttribute = hasProperty(node, 'dataslug');
15198
+ const dataToSlug = isHeading ? toString(node) : isSlugAttribute ? String(node.properties?.['dataslug']) : undefined;
15199
+ if (dataToSlug) {
15173
15200
  if (node.properties) {
15174
- node.properties['id'] = slugger.slug(toString(node));
15201
+ const slug = slugger.slug(dataToSlug);
15202
+ const slugTitle = node.properties?.['dataslugtitle'] ? String(node.properties['dataslugtitle']) : undefined;
15203
+ node.properties['id'] = slug;
15204
+ addAnchor({
15205
+ anchor: slug,
15206
+ title: slugTitle || dataToSlug,
15207
+ type: isHeading && node.properties?.['dataslugtype'] !== 'member' || isSlugAttribute && node.properties?.['dataslugtype'] === 'heading' ? 'heading' : 'member'
15208
+ });
15175
15209
  }
15176
15210
  }
15177
15211
  });
@@ -15183,7 +15217,7 @@ function sluggerPlugin(headings) {
15183
15217
  * @param html
15184
15218
  * @param config
15185
15219
  */
15186
- async function htmlPostProcessor(html, config) {
15220
+ async function htmlProcessor(html, config) {
15187
15221
  return unified().use(rehypeParse, {
15188
15222
  fragment: true
15189
15223
  }).use(rehypeStringify).use(rehypeFormat).use(rehypeHighlight, {
@@ -15191,7 +15225,7 @@ async function htmlPostProcessor(html, config) {
15191
15225
  languages: {
15192
15226
  twig
15193
15227
  }
15194
- }).use(sluggerPlugin, config.headings).use(rehypeMinifyWhitespace).use(autolinkHeading, config.route).use(keywordsPlugin, config.addUsedKeyword, config.addPotentialKeyword, config.getKeyword).use(markElementsPlugin).use(markCodeBlocksPlugin).process(html).then(file => file.toString());
15228
+ }).use(sluggerPlugin, config.addAnchor, config.headings).use(rehypeMinifyWhitespace).use(autolinkHeading, config.route).process(html).then(file => file.toString());
15195
15229
  }
15196
15230
 
15197
15231
  /**
@@ -15223,4 +15257,4 @@ async function removeNotIndexableContent(html) {
15223
15257
  return rehype().use(removeNotIndexableContentPlugin).process(html).then(file => file.toString());
15224
15258
  }
15225
15259
 
15226
- export { htmlPostProcessor, removeNotIndexableContent };
15260
+ export { htmlPostProcessor, htmlProcessor, removeNotIndexableContent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-doc/utils",
3
- "version": "16.3.6",
3
+ "version": "16.4.0",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "rehype": "12.0.1",