rehype-highlight-code-lines 0.0.1 → 0.0.3

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 CHANGED
@@ -8,7 +8,7 @@
8
8
  [![typescript][badge-typescript]][typescript-url]
9
9
  [![License][badge-license]][github-license-url]
10
10
 
11
- This package is a [unified][unified] ([rehype][rehype]) plugin **to add wrapper to each line in a code block, allowing numbering of the code block and highlighting desired lines of code**.
11
+ This package is a [unified][unified] ([rehype][rehype]) plugin **to add container to each line in a code block, allowing numbering of the code block and highlighting desired lines of code**.
12
12
 
13
13
  **[unified][unified]** is a project that transforms content with abstract syntax trees (ASTs) using the new parser **[micromark][micromark]**. **[remark][remark]** adds support for markdown to unified. **[mdast][mdast]** is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree. "**[rehype][rehype]**" is a tool that transforms HTML with plugins. "**[hast][hast]**" stands for HTML Abstract Syntax Tree (HAST) that rehype uses.
14
14
 
@@ -131,18 +131,49 @@ Without `rehype-highlight-code-lines`, the lines of code wouldn't be in a `div`.
131
131
  </pre>
132
132
  ```
133
133
 
134
+ ***Note:** `hljs` prefix comes from `rehype-highlight`*.
135
+
134
136
  ## Options
135
137
 
136
- There is one **boolean** option.
138
+ All options are **optional** and have **default values**.
137
139
 
138
140
  ```typescript
139
141
  type HighlightLinesOptions = {
140
- showLineNumbers?: boolean; // the default is "false"
142
+ showLineNumbers?: boolean; // default is "false"
143
+ lineContainerTagName?: "div" | "span"; // default is "span"
141
144
  };
142
145
 
143
146
  use(rehypeHighlightLines, HighlightLinesOptions);
144
147
  ```
145
148
 
149
+ #### `showLineNumbers`
150
+
151
+ It is a **boolean** option which is for all code blocks will be numbered.
152
+
153
+ By default, it is `false`.
154
+
155
+ ```javascript
156
+ use(rehypeHighlightLines, {
157
+ showLineNumbers: true,
158
+ });
159
+ ```
160
+
161
+ Now, all code blocks will become numbered by line.
162
+
163
+ #### `lineContainerTagName`
164
+
165
+ It is a **union** type option which is **"div" | "span"** for providing custom HTML tag name for code lines.
166
+
167
+ By default, it is `span` which is **inline** level container. If you set it as `div`, the container will be **block** level, in perspective of CSS.
168
+
169
+ ```javascript
170
+ use(rehypeHighlightLines, {
171
+ lineContainerTagName: "div",
172
+ });
173
+ ```
174
+
175
+ Now, the code line container's tag name will be `div`.
176
+
146
177
  ### Examples:
147
178
 
148
179
  ```typescript
@@ -2,6 +2,7 @@ import type { Plugin } from "unified";
2
2
  import type { Root } from "hast";
3
3
  export type HighlightLinesOptions = {
4
4
  showLineNumbers?: boolean;
5
+ lineContainerTagName?: "div" | "span";
5
6
  };
6
7
  export declare function clsx(arr: (string | false | null | undefined | 0)[]): string[];
7
8
  /**
package/dist/esm/index.js CHANGED
@@ -2,6 +2,7 @@ import { visit, CONTINUE } from "unist-util-visit";
2
2
  import rangeParser from "parse-numeric-range";
3
3
  const DEFAULT_SETTINGS = {
4
4
  showLineNumbers: false,
5
+ lineContainerTagName: "span",
5
6
  };
6
7
  // a simple util for our use case, like clsx package
7
8
  export function clsx(arr) {
@@ -14,25 +15,55 @@ export function clsx(arr) {
14
15
  */
15
16
  const plugin = (options) => {
16
17
  const settings = Object.assign({}, DEFAULT_SETTINGS, options);
18
+ /**
19
+ *
20
+ * flatten deep nodes
21
+ *
22
+ */
23
+ function flattenCodeTree(children, className = [], newTree = []) {
24
+ for (let i = 0; i < children.length; i++) {
25
+ const node = children[i];
26
+ if (node.type !== "element") {
27
+ newTree = newTree.concat([node]);
28
+ }
29
+ else {
30
+ // @ts-expect-error
31
+ // /* v8 ignore next */
32
+ const classNames = className.concat(node.properties?.className || []);
33
+ if (node.children.length === 1 && node.children[0].type !== "element") {
34
+ node.properties.className = classNames;
35
+ newTree = newTree.concat([node]);
36
+ }
37
+ else {
38
+ newTree = newTree.concat(flattenCodeTree(node.children, classNames));
39
+ }
40
+ }
41
+ }
42
+ return newTree;
43
+ }
17
44
  /**
18
45
  *
19
46
  * constructs the line element
20
47
  *
21
48
  */
22
- const createLine = (children, lineNumber, classNames) => {
49
+ const createLine = (children, lineNumber, showLineNumbers, linesToBeHighlighted) => {
23
50
  return {
24
51
  type: "element",
25
- tagName: "div",
52
+ tagName: settings.lineContainerTagName,
26
53
  children,
27
54
  properties: {
28
- className: classNames,
29
- ...(lineNumber && { "data-line-number": lineNumber }),
55
+ className: clsx([
56
+ "code-line",
57
+ showLineNumbers && "numbered-code-line",
58
+ linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
59
+ ]),
60
+ dataLineNumber: showLineNumbers ? lineNumber : undefined,
30
61
  },
31
62
  };
32
63
  };
33
64
  // match all common types of line breaks
34
65
  const RE = /\r?\n|\r/g;
35
- function starryNightGutter(tree, showLineNumbers, linesToBeHighlighted) {
66
+ function gutter(tree, showLineNumbers, linesToBeHighlighted) {
36
67
  const replacement = [];
37
68
  let index = -1;
38
69
  let start = 0;
@@ -44,7 +75,7 @@ const plugin = (options) => {
44
75
  let textStart = 0;
45
76
  let match = RE.exec(child.value);
46
77
  while (match) {
47
- // Nodes in this line.
78
+ // Nodes in this line. (current child is exclusive)
48
79
  const line = tree.children.slice(start, index);
49
80
  /* v8 ignore start */
50
81
  // Prepend text from a partial matched earlier text.
@@ -55,21 +86,16 @@ const plugin = (options) => {
55
86
  /* v8 ignore end */
56
87
  // Append text from this text.
57
88
  if (match.index > textStart) {
58
- line.push({
59
- type: "text",
60
- value: child.value.slice(textStart, match.index),
61
- });
89
+ const value = child.value.slice(textStart, match.index);
90
+ line.push({ type: "text", value });
62
91
  }
63
- // Add a line, and the eol.
92
+ // Add a line
64
93
  lineNumber += 1;
65
- replacement.push(createLine(line, showLineNumbers ? lineNumber : undefined, clsx([
66
- "code-line",
67
- showLineNumbers && "numbered-code-line",
68
- linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
69
- ])), {
70
- type: "text",
71
- value: match[0],
72
- });
94
+ replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
95
+ // Add eol if the tag name is "span"
96
+ if (settings.lineContainerTagName === "span") {
97
+ replacement.push({ type: "text", value: match[0] });
98
+ }
73
99
  start = index + 1;
74
100
  textStart = match.index + match[0].length;
75
101
  match = RE.exec(child.value);
@@ -89,11 +115,7 @@ const plugin = (options) => {
89
115
  }
90
116
  if (line.length > 0) {
91
117
  lineNumber += 1;
92
- replacement.push(createLine(line, showLineNumbers ? lineNumber : undefined, clsx([
93
- "code-line",
94
- showLineNumbers && "numbered-code-line",
95
- linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
96
- ])));
118
+ replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
97
119
  }
98
120
  /* v8 ignore end */
99
121
  // Replace children with new array.
@@ -127,24 +149,34 @@ const plugin = (options) => {
127
149
  const language = className.slice(9).toLowerCase();
128
150
  if (language.startsWith("{") || language.startsWith("showlinenumbers")) {
129
151
  meta = meta ? language + meta : language;
130
- const index = code.properties.className.findIndex(testingFunction);
131
- if (index > -1) {
132
- code.properties.className[index] = "language-unknown";
152
+ const idx = code.properties.className.findIndex(testingFunction);
153
+ if (idx > -1) {
154
+ code.properties.className[idx] = "language-unknown";
133
155
  }
134
156
  }
135
157
  }
136
158
  }
159
+ if (settings.showLineNumbers) {
160
+ if (!meta) {
161
+ meta = "showlinenumbers";
162
+ }
163
+ else if (!meta.includes("showlinenumbers")) {
164
+ meta = meta + " showlinenumbers";
165
+ }
166
+ }
137
167
  if (!meta)
138
168
  return;
139
- const showLineNumbers = settings.showLineNumbers || meta.includes("showlinenumbers");
169
+ const showLineNumbers = meta.includes("showlinenumbers");
140
170
  // find number range string within curly braces and parse it
141
171
  const RE = /{(?<lines>[\d\s,-]+)}/g;
142
172
  const strLineNumbers = RE.exec(meta)?.groups?.lines?.replace(/\s/g, "");
143
173
  const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
144
174
  if (!showLineNumbers && linesToBeHighlighted.length === 0)
145
175
  return;
176
+ // flatten deeper nodes into first level <span>s an texts
177
+ code.children = flattenCodeTree(code.children);
146
178
  // add wrapper for each line mutating the code element
147
- starryNightGutter(code, showLineNumbers, linesToBeHighlighted);
179
+ gutter(code, showLineNumbers, linesToBeHighlighted);
148
180
  });
149
181
  };
150
182
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rehype-highlight-code-lines",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Rehype plugin to add line numbers to code blocks and allow highlighting of desired code lines",
5
5
  "type": "module",
6
6
  "exports": "./dist/esm/index.js",
package/src/index.ts CHANGED
@@ -3,14 +3,26 @@ import type { Root, Element, ElementContent, ElementData } from "hast";
3
3
  import { type VisitorResult, visit, CONTINUE } from "unist-util-visit";
4
4
  import rangeParser from "parse-numeric-range";
5
5
 
6
+ // eslint-disable-next-line @typescript-eslint/ban-types
7
+ type Prettify<T> = { [K in keyof T]: T[K] } & {};
8
+
9
+ // eslint-disable-next-line @typescript-eslint/ban-types
10
+ type PartiallyRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
11
+
6
12
  export type HighlightLinesOptions = {
7
13
  showLineNumbers?: boolean;
14
+ lineContainerTagName?: "div" | "span";
8
15
  };
9
16
 
10
17
  const DEFAULT_SETTINGS: HighlightLinesOptions = {
11
18
  showLineNumbers: false,
19
+ lineContainerTagName: "span",
12
20
  };
13
21
 
22
+ type PartiallyRequiredHighlightLinesOptions = Prettify<
23
+ PartiallyRequired<HighlightLinesOptions, "showLineNumbers" | "lineContainerTagName">
24
+ >;
25
+
14
26
  type CodeData = ElementData & {
15
27
  meta?: string;
16
28
  };
@@ -26,7 +38,41 @@ export function clsx(arr: (string | false | null | undefined | 0)[]): string[] {
26
38
  *
27
39
  */
28
40
  const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
29
- const settings = Object.assign({}, DEFAULT_SETTINGS, options);
41
+ const settings = Object.assign(
42
+ {},
43
+ DEFAULT_SETTINGS,
44
+ options,
45
+ ) as PartiallyRequiredHighlightLinesOptions;
46
+
47
+ /**
48
+ *
49
+ * flatten deep nodes
50
+ *
51
+ */
52
+ function flattenCodeTree(
53
+ children: ElementContent[],
54
+ className: string[] = [],
55
+ newTree: ElementContent[] = [],
56
+ ): ElementContent[] {
57
+ for (let i = 0; i < children.length; i++) {
58
+ const node = children[i];
59
+ if (node.type !== "element") {
60
+ newTree = newTree.concat([node]);
61
+ } else {
62
+ // @ts-expect-error
63
+ // /* v8 ignore next */
64
+ const classNames = className.concat(node.properties?.className || []);
65
+
66
+ if (node.children.length === 1 && node.children[0].type !== "element") {
67
+ node.properties.className = classNames;
68
+ newTree = newTree.concat([node]);
69
+ } else {
70
+ newTree = newTree.concat(flattenCodeTree(node.children, classNames));
71
+ }
72
+ }
73
+ }
74
+ return newTree;
75
+ }
30
76
 
31
77
  /**
32
78
  *
@@ -35,16 +81,21 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
35
81
  */
36
82
  const createLine = (
37
83
  children: ElementContent[],
38
- lineNumber: number | undefined,
39
- classNames: string[],
84
+ lineNumber: number,
85
+ showLineNumbers: boolean,
86
+ linesToBeHighlighted: number[],
40
87
  ): Element => {
41
88
  return {
42
89
  type: "element",
43
- tagName: "div",
90
+ tagName: settings.lineContainerTagName,
44
91
  children,
45
92
  properties: {
46
- className: classNames,
47
- ...(lineNumber && { "data-line-number": lineNumber }),
93
+ className: clsx([
94
+ "code-line",
95
+ showLineNumbers && "numbered-code-line",
96
+ linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
97
+ ]),
98
+ dataLineNumber: showLineNumbers ? lineNumber : undefined,
48
99
  },
49
100
  };
50
101
  };
@@ -52,12 +103,9 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
52
103
  // match all common types of line breaks
53
104
  const RE = /\r?\n|\r/g;
54
105
 
55
- function starryNightGutter(
56
- tree: Element,
57
- showLineNumbers: boolean,
58
- linesToBeHighlighted: number[],
59
- ) {
106
+ function gutter(tree: Element, showLineNumbers: boolean, linesToBeHighlighted: number[]) {
60
107
  const replacement: Array<ElementContent> = [];
108
+
61
109
  let index = -1;
62
110
  let start = 0;
63
111
  let startTextRemainder = "";
@@ -71,7 +119,7 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
71
119
  let match = RE.exec(child.value);
72
120
 
73
121
  while (match) {
74
- // Nodes in this line.
122
+ // Nodes in this line. (current child is exclusive)
75
123
  const line = tree.children.slice(start, index);
76
124
 
77
125
  /* v8 ignore start */
@@ -86,29 +134,18 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
86
134
 
87
135
  // Append text from this text.
88
136
  if (match.index > textStart) {
89
- line.push({
90
- type: "text",
91
- value: child.value.slice(textStart, match.index),
92
- });
137
+ const value = child.value.slice(textStart, match.index);
138
+ line.push({ type: "text", value });
93
139
  }
94
140
 
95
- // Add a line, and the eol.
141
+ // Add a line
96
142
  lineNumber += 1;
97
- replacement.push(
98
- createLine(
99
- line,
100
- showLineNumbers ? lineNumber : undefined,
101
- clsx([
102
- "code-line",
103
- showLineNumbers && "numbered-code-line",
104
- linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
105
- ]),
106
- ),
107
- {
108
- type: "text",
109
- value: match[0],
110
- },
111
- );
143
+ replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
144
+
145
+ // Add eol if the tag name is "span"
146
+ if (settings.lineContainerTagName === "span") {
147
+ replacement.push({ type: "text", value: match[0] });
148
+ }
112
149
 
113
150
  start = index + 1;
114
151
  textStart = match.index + match[0].length;
@@ -134,17 +171,7 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
134
171
 
135
172
  if (line.length > 0) {
136
173
  lineNumber += 1;
137
- replacement.push(
138
- createLine(
139
- line,
140
- showLineNumbers ? lineNumber : undefined,
141
- clsx([
142
- "code-line",
143
- showLineNumbers && "numbered-code-line",
144
- linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
145
- ]),
146
- ),
147
- );
174
+ replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
148
175
  }
149
176
 
150
177
  /* v8 ignore end */
@@ -188,18 +215,26 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
188
215
  if (language.startsWith("{") || language.startsWith("showlinenumbers")) {
189
216
  meta = meta ? language + meta : language;
190
217
 
191
- const index = code.properties.className.findIndex(testingFunction);
218
+ const idx = code.properties.className.findIndex(testingFunction);
192
219
 
193
- if (index > -1) {
194
- code.properties.className[index] = "language-unknown";
220
+ if (idx > -1) {
221
+ code.properties.className[idx] = "language-unknown";
195
222
  }
196
223
  }
197
224
  }
198
225
  }
199
226
 
227
+ if (settings.showLineNumbers) {
228
+ if (!meta) {
229
+ meta = "showlinenumbers";
230
+ } else if (!meta.includes("showlinenumbers")) {
231
+ meta = meta + " showlinenumbers";
232
+ }
233
+ }
234
+
200
235
  if (!meta) return;
201
236
 
202
- const showLineNumbers = settings.showLineNumbers || meta.includes("showlinenumbers");
237
+ const showLineNumbers = meta.includes("showlinenumbers");
203
238
 
204
239
  // find number range string within curly braces and parse it
205
240
  const RE = /{(?<lines>[\d\s,-]+)}/g;
@@ -208,8 +243,11 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
208
243
 
209
244
  if (!showLineNumbers && linesToBeHighlighted.length === 0) return;
210
245
 
246
+ // flatten deeper nodes into first level <span>s an texts
247
+ code.children = flattenCodeTree(code.children);
248
+
211
249
  // add wrapper for each line mutating the code element
212
- starryNightGutter(code, showLineNumbers, linesToBeHighlighted);
250
+ gutter(code, showLineNumbers, linesToBeHighlighted);
213
251
  });
214
252
  };
215
253
  };