markdown-it-copy-code
Version:
A markdown-it plugin to add code-copying function for code fence.
126 lines (120 loc) • 4.84 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
const DEFAULT_BUTTON_CLASS = "markdown-copy-code-button";
const DEFAULT_CONTAINER_CLASS = "markdown-copy-code-container";
const DEFAULT_COPY_SVG_CLASS = "markdown-copy-code-copy";
const DEFAULT_DONE_SVG_CLASS = "markdown-copy-code-done";
const DEFAULT_COPY_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M7.998 16h4m-4-5h8M7.5 3.5c-1.556.047-2.483.22-3.125.862c-.879.88-.879 2.295-.879 5.126v6.506c0 2.832 0 4.247.879 5.127C5.253 22 6.668 22 9.496 22h5c2.829 0 4.243 0 5.121-.88c.88-.879.88-2.294.88-5.126V9.488c0-2.83 0-4.246-.88-5.126c-.641-.642-1.569-.815-3.125-.862" />
<path d="M7.496 3.75c0-.966.784-1.75 1.75-1.75h5.5a1.75 1.75 0 1 1 0 3.5h-5.5a1.75 1.75 0 0 1-1.75-1.75" />
</g>
</svg>
`;
const DEFAULT_DONE_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M13.5 20s1 0 2 2c0 0 3.177-5 6-6M7 16h4m-4-5h8M6.5 3.5c-1.556.047-2.483.22-3.125.862c-.879.88-.879 2.295-.879 5.126v6.506c0 2.832 0 4.247.879 5.127C4.253 22 5.668 22 8.496 22h2.5m4.496-18.5c1.556.047 2.484.22 3.125.862c.88.88.88 2.295.88 5.126V13.5"/><path d="M6.496 3.75c0-.966.784-1.75 1.75-1.75h5.5a1.75 1.75 0 1 1 0 3.5h-5.5a1.75 1.75 0 0 1-1.75-1.75" />
</g>
</svg>
`;
const OPTIONS = {};
function useCopyCode(options) {
const btnClass = OPTIONS.buttonClass ?? options?.buttonClass ?? DEFAULT_BUTTON_CLASS;
const client = typeof window !== "undefined";
if (client) {
const timeoutIdMap = /* @__PURE__ */ new WeakMap();
const handleClick = (e) => {
const el = e.target;
if (el.matches(`button.${btnClass}`)) {
const parent = el.parentElement;
const preNode = parent?.querySelector("pre");
const codeNode = preNode?.querySelector("code");
if (!parent || !preNode || !codeNode)
return;
const text = codeNode.textContent || "";
copyToClipboard(text).then(() => {
el.classList.add("copied");
el.focus();
clearTimeout(timeoutIdMap.get(el));
const timeoutId = setTimeout(() => {
el.classList.remove("copied");
el.blur();
timeoutIdMap.delete(el);
}, options?.displayDuration ?? 2e3);
timeoutIdMap.set(el, timeoutId);
});
}
};
window.addEventListener("click", handleClick);
return () => {
window.removeEventListener("click", handleClick);
};
}
}
async function copyToClipboard(text) {
try {
return navigator.clipboard.writeText(text);
} catch {
const element = document.createElement("textarea");
const previouslyFocusedElement = document.activeElement;
element.value = text;
element.setAttribute("readonly", "");
element.style.contain = "strict";
element.style.position = "absolute";
element.style.left = "-9999px";
element.style.fontSize = "12pt";
const selection = document.getSelection();
const originalRange = selection ? selection.rangeCount > 0 && selection.getRangeAt(0) : null;
document.body.appendChild(element);
element.select();
element.selectionStart = 0;
element.selectionEnd = text.length;
document.execCommand("copy");
document.body.removeChild(element);
if (originalRange) {
selection.removeAllRanges();
selection.addRange(originalRange);
}
if (previouslyFocusedElement) {
previouslyFocusedElement.focus();
}
}
}
function renderCodeFence(renderer, options) {
const {
containerClass: _container_ = DEFAULT_CONTAINER_CLASS,
buttonClass: _button_ = DEFAULT_BUTTON_CLASS,
copySVGClass: _copysvg_ = DEFAULT_COPY_SVG_CLASS,
doneSVGClass: _donesvg_ = DEFAULT_DONE_SVG_CLASS,
copySVG: _copy_ = DEFAULT_COPY_SVG,
doneSVG: _done_ = DEFAULT_DONE_SVG
} = options ?? {};
Object.assign(OPTIONS, options);
const rule = (tokens, idx, options2, env, self) => {
const content = tokens[idx].content;
const rendered = renderer(tokens, idx, options2, env, self);
if (content.length === 0 || content === "\n")
return rendered;
return [
`<div class="${_container_}">`,
rendered,
`<button class="${_button_}">`,
`<div class="${_copysvg_}">`,
_copy_,
"</div>",
`<div class="${_donesvg_}">`,
_done_,
"</div>",
"</button>",
"</div>"
].join("\n");
};
return rule;
}
const MarkdownItCopyCode = (md, options) => {
md.renderer.rules.fence = renderCodeFence(md.renderer.rules.fence, options);
};
exports.default = MarkdownItCopyCode;
exports.useCopyCode = useCopyCode;
;