Skip to content

Commit

Permalink
Add line-numbers plugins( #2 )
Browse files Browse the repository at this point in the history
  • Loading branch information
Bunlong authored Dec 13, 2022
2 parents 0eb7154 + 68e6fa2 commit 0f9793e
Show file tree
Hide file tree
Showing 23 changed files with 634 additions and 6 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 0.2.0 (2022-12-14)

### ✨ Features

* Add line-numbers plugins

Credits

* [@Bunlong](https://github.com/Bunlong)

## 0.1.0 (2022-12-09)

### ✨ Features
Expand Down
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A lightweight, robust, and elegant syntax highlighting component for your next R

* Themes
* Languages
* Plugins

## 🔧 Install

Expand Down Expand Up @@ -698,11 +699,43 @@ export default function App() {
</tr>
</table>

## Plugins

### Line Numbers

Line number at the beginning of code lines.

```javascript
import { usePrism } from 'next-prism'

// Import a theme.css
import 'next-prism/themes/twilight.css'

// Import line-numbers source
import 'next-prism/plugins/line-numbers/line-numbers';
// Import line-numbers.css
import 'next-prism/plugins/line-numbers/line-numbers.css';

export default function App() {
const { Code } = usePrism()

return (
<>
<Code language='javascript' lineNumbers={true}>
{`<div className="example">
{Math.random()}
</div>`}
</Code>
</>
)
}
```

## 📜 Changelog

Latest version 0.1.0 (2022-12-09):
Latest version 0.2.0 (2022-12-14):

* Add themes and languages
* Add line-numbers plugins

Details changes for each release are documented in the [CHANGELOG.md](https://github.com/Bunlong/next-prism/blob/master/CHANGELOG.md).

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next-prism",
"version": "0.1.2",
"version": "0.2.0",
"description": "A lightweight, robust, and elegant syntax highlighting component for your next React apps.",
"author": "Bunlong <bunlong.van@gmail.com>",
"license": "MIT",
Expand Down Expand Up @@ -75,7 +75,8 @@
},
"files": [
"dist",
"themes"
"themes/*.css",
"plugins/**/*"
],
"dependencies": {
"prismjs": "^1.29.0"
Expand Down
1 change: 1 addition & 0 deletions plugins/line-numbers/line-numbers.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

252 changes: 252 additions & 0 deletions plugins/line-numbers/line-numbers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
(function () {

if (typeof Prism === 'undefined' || typeof document === 'undefined') {
return;
}

/**
* Plugin name which is used as a class name for <pre> which is activating the plugin
*
* @type {string}
*/
var PLUGIN_NAME = 'line-numbers';

/**
* Regular expression used for determining line breaks
*
* @type {RegExp}
*/
var NEW_LINE_EXP = /\n(?!$)/g;


/**
* Global exports
*/
var config = Prism.plugins.lineNumbers = {
/**
* Get node for provided line number
*
* @param {Element} element pre element
* @param {number} number line number
* @returns {Element|undefined}
*/
getLine: function (element, number) {
if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
return;
}

var lineNumberRows = element.querySelector('.line-numbers-rows');
if (!lineNumberRows) {
return;
}
var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);

if (number < lineNumberStart) {
number = lineNumberStart;
}
if (number > lineNumberEnd) {
number = lineNumberEnd;
}

var lineIndex = number - lineNumberStart;

return lineNumberRows.children[lineIndex];
},

/**
* Resizes the line numbers of the given element.
*
* This function will not add line numbers. It will only resize existing ones.
*
* @param {HTMLElement} element A `<pre>` element with line numbers.
* @returns {void}
*/
resize: function (element) {
resizeElements([element]);
},

/**
* Whether the plugin can assume that the units font sizes and margins are not depended on the size of
* the current viewport.
*
* Setting this to `true` will allow the plugin to do certain optimizations for better performance.
*
* Set this to `false` if you use any of the following CSS units: `vh`, `vw`, `vmin`, `vmax`.
*
* @type {boolean}
*/
assumeViewportIndependence: true
};

/**
* Resizes the given elements.
*
* @param {HTMLElement[]} elements
*/
function resizeElements(elements) {
elements = elements.filter(function (e) {
var codeStyles = getStyles(e);
var whiteSpace = codeStyles['white-space'];
return whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line';
});

if (elements.length == 0) {
return;
}

var infos = elements.map(function (element) {
var codeElement = element.querySelector('code');
var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
if (!codeElement || !lineNumbersWrapper) {
return undefined;
}

/** @type {HTMLElement} */
var lineNumberSizer = element.querySelector('.line-numbers-sizer');
var codeLines = codeElement.textContent.split(NEW_LINE_EXP);

if (!lineNumberSizer) {
lineNumberSizer = document.createElement('span');
lineNumberSizer.className = 'line-numbers-sizer';

codeElement.appendChild(lineNumberSizer);
}

lineNumberSizer.innerHTML = '0';
lineNumberSizer.style.display = 'block';

var oneLinerHeight = lineNumberSizer.getBoundingClientRect().height;
lineNumberSizer.innerHTML = '';

return {
element: element,
lines: codeLines,
lineHeights: [],
oneLinerHeight: oneLinerHeight,
sizer: lineNumberSizer,
};
}).filter(Boolean);

infos.forEach(function (info) {
var lineNumberSizer = info.sizer;
var lines = info.lines;
var lineHeights = info.lineHeights;
var oneLinerHeight = info.oneLinerHeight;

lineHeights[lines.length - 1] = undefined;
lines.forEach(function (line, index) {
if (line && line.length > 1) {
var e = lineNumberSizer.appendChild(document.createElement('span'));
e.style.display = 'block';
e.textContent = line;
} else {
lineHeights[index] = oneLinerHeight;
}
});
});

infos.forEach(function (info) {
var lineNumberSizer = info.sizer;
var lineHeights = info.lineHeights;

var childIndex = 0;
for (var i = 0; i < lineHeights.length; i++) {
if (lineHeights[i] === undefined) {
lineHeights[i] = lineNumberSizer.children[childIndex++].getBoundingClientRect().height;
}
}
});

infos.forEach(function (info) {
var lineNumberSizer = info.sizer;
var wrapper = info.element.querySelector('.line-numbers-rows');

lineNumberSizer.style.display = 'none';
lineNumberSizer.innerHTML = '';

info.lineHeights.forEach(function (height, lineNumber) {
wrapper.children[lineNumber].style.height = height + 'px';
});
});
}

/**
* Returns style declarations for the element
*
* @param {Element} element
*/
function getStyles(element) {
if (!element) {
return null;
}

return window.getComputedStyle ? getComputedStyle(element) : (element.currentStyle || null);
}

var lastWidth = undefined;
window.addEventListener('resize', function () {
if (config.assumeViewportIndependence && lastWidth === window.innerWidth) {
return;
}
lastWidth = window.innerWidth;

resizeElements(Array.prototype.slice.call(document.querySelectorAll('pre.' + PLUGIN_NAME)));
});

Prism.hooks.add('complete', function (env) {
if (!env.code) {
return;
}

var code = /** @type {Element} */ (env.element);
var pre = /** @type {HTMLElement} */ (code.parentNode);

// works only for <code> wrapped inside <pre> (not inline)
if (!pre || !/pre/i.test(pre.nodeName)) {
return;
}

// Abort if line numbers already exists
if (code.querySelector('.line-numbers-rows')) {
return;
}

// only add line numbers if <code> or one of its ancestors has the `line-numbers` class
if (!Prism.util.isActive(code, PLUGIN_NAME)) {
return;
}

// Remove the class 'line-numbers' from the <code>
code.classList.remove(PLUGIN_NAME);
// Add the class 'line-numbers' to the <pre>
pre.classList.add(PLUGIN_NAME);

var match = env.code.match(NEW_LINE_EXP);
var linesNum = match ? match.length + 1 : 1;
var lineNumbersWrapper;

var lines = new Array(linesNum + 1).join('<span></span>');

lineNumbersWrapper = document.createElement('span');
lineNumbersWrapper.setAttribute('aria-hidden', 'true');
lineNumbersWrapper.className = 'line-numbers-rows';
lineNumbersWrapper.innerHTML = lines;

if (pre.hasAttribute('data-start')) {
pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
}

env.element.appendChild(lineNumbersWrapper);

resizeElements([pre]);

Prism.hooks.run('line-numbers', env);
});

Prism.hooks.add('line-numbers', function (env) {
env.plugins = env.plugins || {};
env.plugins.lineNumbers = true;
});

}());
16 changes: 14 additions & 2 deletions src/usePrism.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,29 @@ export interface ICode {
language?: string;
children?: any;
content?: any;
lineNumbers?: boolean;
}

function useCodeComponent() {
const CodeComponent = ({ language, children, content }: ICode) => {
const CodeComponent = ({
language,
children,
content,
lineNumbers,
}: ICode) => {
React.useEffect(() => {
Prism.highlightAll();
}, []);

console.log(lineNumbers);

return (
<pre>
<code className={`language-${language}`}>{children || content}</code>
<code
className={`language-${language} ${lineNumbers && 'line-numbers'} `}
>
{children || content}
</code>
</pre>
);
};
Expand Down
23 changes: 23 additions & 0 deletions supports/create-react-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
Loading

0 comments on commit 0f9793e

Please sign in to comment.