copy-tex.ts 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. import katexReplaceWithTex from "./katex2tex";
  2. // Return <div class="katex"> element containing node, or null if not found.
  3. function closestKatex(node: Node): Element | null | undefined {
  4. // If node is a Text Node, for example, go up to containing Element,
  5. // where we can apply the `closest` method.
  6. const element: Element | null | undefined =
  7. (node instanceof Element ? node : node.parentElement);
  8. return element && element.closest('.katex');
  9. }
  10. // Global copy handler to modify behavior on/within .katex elements.
  11. document.addEventListener('copy', function(event: ClipboardEvent) {
  12. const selection = window.getSelection();
  13. if (!selection || selection.isCollapsed || !event.clipboardData) {
  14. return; // default action OK if selection is empty or unchangeable
  15. }
  16. const clipboardData = event.clipboardData;
  17. const range = selection.getRangeAt(0);
  18. // When start point is within a formula, expand to entire formula.
  19. const startKatex = closestKatex(range.startContainer);
  20. if (startKatex) {
  21. range.setStartBefore(startKatex);
  22. }
  23. // Similarly, when end point is within a formula, expand to entire formula.
  24. const endKatex = closestKatex(range.endContainer);
  25. if (endKatex) {
  26. range.setEndAfter(endKatex);
  27. }
  28. const fragment = range.cloneContents();
  29. if (!fragment.querySelector('.katex-mathml')) {
  30. return; // default action OK if no .katex-mathml elements
  31. }
  32. const htmlContents = Array.prototype.map.call(fragment.childNodes,
  33. (el) => (el instanceof Text ? el.textContent : el.outerHTML)
  34. ).join('');
  35. // Preserve usual HTML copy/paste behavior.
  36. clipboardData.setData('text/html', htmlContents);
  37. // Rewrite plain-text version.
  38. clipboardData.setData('text/plain',
  39. katexReplaceWithTex(fragment).textContent);
  40. // Prevent normal copy handling.
  41. event.preventDefault();
  42. });