UNPKG

markdown-it-copy-code

Version:

A markdown-it plugin to add code-copying function for code fence.

126 lines (120 loc) 4.84 kB
'use strict'; 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;