react-code-view
Version:
Code view for React
94 lines (90 loc) • 3.56 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
var _excluded = ["className"],
_excluded2 = ["children", "className", "copyButtonProps"];
import React, { useEffect, useRef, forwardRef } from 'react';
import classNames from 'classnames';
import copy from 'copy-to-clipboard';
import mergeRefs from './utils/mergeRefs';
import { iconPath as copyIconPath, svgTpl } from './icons/Copy';
import { iconPath as checkIconPath } from './icons/Check';
import { jsx as _jsx } from "react/jsx-runtime";
/**
* Creates and appends a copy button to a code container
* @param container - The container element to append the copy button to
* @param buttonProps - Additional props to apply to the copy button
*/
function createCopyButton(container, buttonProps) {
// If the container is null or the container already has a copy button, return
if (!container || container.querySelector('button[data-type="copy"]')) {
return;
}
var _ref = buttonProps || {},
className = _ref.className,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var button = document.createElement('button');
button.dataset['type'] = 'copy';
button.title = 'Copy code';
button.setAttribute('aria-label', 'Copy code');
button.innerHTML = svgTpl(copyIconPath);
if (className) {
button.className = className;
}
button.onclick = function (e) {
var _container$querySelec;
e.preventDefault();
var code = (_container$querySelec = container.querySelector('code')) === null || _container$querySelec === void 0 ? void 0 : _container$querySelec.textContent;
var icon = button.querySelector('.copy-icon-path');
// Show check icon to indicate successful copy
icon === null || icon === void 0 ? void 0 : icon.setAttribute('d', checkIconPath);
if (code) {
copy(code);
}
// Reset to copy icon after 2 seconds
setTimeout(function () {
icon === null || icon === void 0 ? void 0 : icon.setAttribute('d', copyIconPath);
}, 2000);
};
// Apply additional button properties
if (rest) {
Object.entries(rest).forEach(function (_ref2) {
var key = _ref2[0],
value = _ref2[1];
if (value !== undefined) {
button.setAttribute(key, String(value));
}
});
}
container.appendChild(button);
}
/**
* Renders markdown content with code blocks that have copy buttons
*/
var MarkdownRenderer = /*#__PURE__*/forwardRef(function (props, ref) {
var children = props.children,
className = props.className,
copyButtonProps = props.copyButtonProps,
rest = _objectWithoutPropertiesLoose(props, _excluded2);
var mdRef = useRef(null);
useEffect(function () {
var _mdRef$current;
// Add copy buttons to all code blocks
var codeBlocks = (_mdRef$current = mdRef.current) === null || _mdRef$current === void 0 ? void 0 : _mdRef$current.querySelectorAll('.rcv-code-renderer');
codeBlocks === null || codeBlocks === void 0 ? void 0 : codeBlocks.forEach(function (codeBlock) {
createCopyButton(codeBlock, copyButtonProps);
});
// We only want to run this once when the component mounts
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!children) {
return null;
}
return /*#__PURE__*/_jsx("div", _extends({}, rest, {
ref: mergeRefs(mdRef, ref),
className: classNames(className, 'rcv-markdown'),
dangerouslySetInnerHTML: {
__html: children
}
}));
});
export default MarkdownRenderer;