@vortexlabs/kirari-rehype 1.0.1 → 1.0.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @vortexlabs/kirari-rehype
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 52ad674: Stop line wrappers from adding an empty last line after a code block's trailing newline. Markdown code blocks always end with a newline, so every block rendered with `lines` or fence line metadata such as `{2,4-6}` showed an extra empty line, and an extra number when line numbers come from CSS. The newline still follows the last wrapper, so the source text is unchanged, and blank lines before it keep their wrappers.
8
+
3
9
  ## 1.0.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -132,8 +132,8 @@ const options = {
132
132
  } satisfies Options
133
133
  ```
134
134
 
135
- Line endings remain outside the wrappers. Empty and trailing lines are
136
- preserved. Callback offsets are UTF-16 code units into the code block's source;
135
+ Line endings remain outside the wrappers. Empty lines are preserved; the
136
+ fence's trailing newline does not add an extra line. Callback offsets are UTF-16 code units into the code block's source;
137
137
  `start` is inclusive and `end` is exclusive and excludes the line ending.
138
138
  Token class callbacks receive each original token once, even when a token spans
139
139
  multiple lines.
package/dist/index.js CHANGED
@@ -106,10 +106,30 @@ const tokenNodes = (source, tokens, options, ranges) => {
106
106
  break;
107
107
  nodes.push({ type: 'text', value: match[0] });
108
108
  start = end + match[0].length;
109
+ if (start === source.length)
110
+ break;
109
111
  number++;
110
112
  } while (true);
111
113
  return nodes;
112
114
  };
115
+ const optsOut = (classNames) => {
116
+ return classNames.some(name => name === 'no-highlight' || name === 'nohighlight');
117
+ };
118
+ /** Replaces the text of a `<code>` with highlighted nodes; returns whether it did. */
119
+ const highlightCode = (code, registry, options) => {
120
+ if (code.type !== 'element' || code.tagName !== 'code' || code.children.some(node => node.type !== 'text'))
121
+ return false;
122
+ const codeClasses = classes(code.properties.className);
123
+ if (optsOut(codeClasses))
124
+ return false;
125
+ const language = codeClasses.filter(name => name.startsWith('language-')).map(name => registry.resolve(name.slice(9))).find(Boolean);
126
+ if (!language)
127
+ return false;
128
+ const source = code.children.map(node => node.type === 'text' ? node.value : '').join('');
129
+ const meta = code.data && 'meta' in code.data ? code.data.meta : undefined;
130
+ code.children = tokenNodes(source, tokenize(source, language), options, lineRanges(meta));
131
+ return true;
132
+ };
113
133
  const rehypeKirari = (options = {}) => {
114
134
  const registry = createRegistry(options.languages ?? []);
115
135
  return tree => {
@@ -119,22 +139,11 @@ const rehypeKirari = (options = {}) => {
119
139
  for (const child of parent.children) {
120
140
  if (child.type === 'element' && child.tagName === 'pre') {
121
141
  const preClasses = classes(child.properties.className);
122
- if (!preClasses.some(name => name === 'no-highlight' || name === 'nohighlight')) {
123
- for (const code of child.children) {
124
- if (code.type !== 'element' || code.tagName !== 'code' || code.children.some(node => node.type !== 'text'))
125
- continue;
126
- const codeClasses = classes(code.properties.className);
127
- if (codeClasses.some(name => name === 'no-highlight' || name === 'nohighlight'))
128
- continue;
129
- const language = codeClasses.filter(name => name.startsWith('language-')).map(name => registry.resolve(name.slice(9))).find(Boolean);
130
- if (!language)
131
- continue;
132
- const source = code.children.map(node => node.type === 'text' ? node.value : '').join('');
133
- const meta = code.data && 'meta' in code.data ? code.data.meta : undefined;
134
- const children = tokenNodes(source, tokenize(source, language), options, lineRanges(meta));
135
- code.children = children;
142
+ if (optsOut(preClasses))
143
+ continue;
144
+ for (const code of child.children) {
145
+ if (highlightCode(code, registry, options))
136
146
  child.properties.className = [...new Set([...preClasses, 'kirari'])];
137
- }
138
147
  }
139
148
  continue;
140
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vortexlabs/kirari-rehype",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Kirari syntax highlighting for Rehype and MDX.",
5
5
  "keywords": [
6
6
  "rehype",
@@ -47,7 +47,7 @@
47
47
  "@mdx-js/mdx": "^3.1.1",
48
48
  "@types/react": "^19.3.0",
49
49
  "@types/react-dom": "^19.3.0",
50
- "@vortexlabs/kirari": "1.0.1",
50
+ "@vortexlabs/kirari": "1.0.5",
51
51
  "react": "^19.3.1",
52
52
  "react-dom": "^19.3.1",
53
53
  "rehype-sanitize": "^6.0.0",