From 98a91f9a479aef1f0b1d735718aecc5add540617 Mon Sep 17 00:00:00 2001 From: Christoph Hauert Date: Sat, 6 Sep 2025 15:42:25 -0700 Subject: [PATCH 1/2] add copy button for lstlistings --- lib/LaTeXML/resources/CSS/ltx-listings.css | 57 ++++++++++++++++++- .../resources/javascript/lstlisting.js | 39 +++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 lib/LaTeXML/resources/javascript/lstlisting.js diff --git a/lib/LaTeXML/resources/CSS/ltx-listings.css b/lib/LaTeXML/resources/CSS/ltx-listings.css index 3dcf678ce9..11a00068c9 100644 --- a/lib/LaTeXML/resources/CSS/ltx-listings.css +++ b/lib/LaTeXML/resources/CSS/ltx-listings.css @@ -1,5 +1,56 @@ - .ltx_listing_data { - float:right; } + float: right; +} .ltx_listing_data a { - text-decoration:none; } \ No newline at end of file + text-decoration: none; +} +.ltx_listing_copy2clip { + position: absolute; +} +.ltx_listing_button { + appearance: none; + background-color: #FAFBFC; + border: 1px solid rgba(27, 31, 35, 0.15); + border-radius: 6px; + box-shadow: rgba(27, 31, 35, 0.04) 0 1px 0, rgba(255, 255, 255, 0.25) 0 1px 0 inset; + box-sizing: border-box; + color: #24292E; + cursor: pointer; + height: 36px; + padding: 6px; + position: relative; + overflow: hidden; + width: 36px +} +.ltx_listing_button::before, +.ltx_listing_button::after { + content: ""; + position: absolute; + inset: 0; + background-position: center; + background-repeat: no-repeat; + background-size: 60%; + transition: opacity 0.6s ease-in-out; +} +.ltx_listing_button::before { + opacity: 1; + /* copy icon */ + background-image: url("data:image/svg+xml;utf8,"); +} +.ltx_listing_button::after { + opacity: 0; +} +.ltx_listing_button.success::after { + opacity: 1; + /* success icon */ + background-image: url("data:image/svg+xml;utf8,"); +} +.ltx_listing_button.error::after { + opacity: 1; + /* error icon */ + background-image: url("data:image/svg+xml;utf8,"); +} +.ltx_listing_button.success::before, +.ltx_listing_button.error::before { + opacity: 0; +} \ No newline at end of file diff --git a/lib/LaTeXML/resources/javascript/lstlisting.js b/lib/LaTeXML/resources/javascript/lstlisting.js new file mode 100644 index 0000000000..a0dd3ca520 --- /dev/null +++ b/lib/LaTeXML/resources/javascript/lstlisting.js @@ -0,0 +1,39 @@ +// replace download links in lstlisting environments by copy to clipboard button +// requires images/copy.svg and images/ok.svg +// (c) Christoph Hauert 2025 + +function copy2clip(button) { + try { + // temporarily hide line numbers in code listing + const style = document.createElement('style'); + document.head.appendChild(style); + const styleSheet = style.sheet; + var hideLine = styleSheet.insertRule(".ltx_tag_listingline { display: none; }"); + var hideCopy = styleSheet.insertRule(".ltx_listing_data { display: none; }"); + const textToCopy = button.parentNode.parentNode.parentNode.innerText; + navigator.clipboard.writeText(textToCopy); + styleSheet.deleteRule(hideCopy); + styleSheet.deleteRule(hideLine); + button.classList.remove("error"); + button.classList.add("success"); + } catch (e) { + button.classList.remove("success"); + button.classList.add("error"); + } + setTimeout(() => { + button.classList.remove("success", "error"); + }, 1200); +} + +window.addEventListener("load", () => { + const elements = document.getElementsByClassName("ltx_listing_data"); + const copyButton = '
' + + '
'; + Array.from(elements).forEach(el => { + if (el.firstChild) { + el.removeChild(el.firstChild); + } + el.insertAdjacentHTML('afterbegin', copyButton); + }); +}); From b0d0c3219a1fa273c0dd2817e3f40b04198e416f Mon Sep 17 00:00:00 2001 From: Christoph Hauert Date: Sun, 17 May 2026 00:36:14 +0200 Subject: [PATCH 2/2] unencoded svg in url rejected by epubcheck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix 'Illegal character in scheme data: “<” is not allowed' --- lib/LaTeXML/resources/CSS/ltx-listings.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/LaTeXML/resources/CSS/ltx-listings.css b/lib/LaTeXML/resources/CSS/ltx-listings.css index 11a00068c9..c0f47215af 100644 --- a/lib/LaTeXML/resources/CSS/ltx-listings.css +++ b/lib/LaTeXML/resources/CSS/ltx-listings.css @@ -35,7 +35,7 @@ .ltx_listing_button::before { opacity: 1; /* copy icon */ - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 359 444' fill='none'%3E%3Cdefs%3E%3CclipPath id='clip1'%3E%3Cpath d='M0 0h359v444H0zM37 97v310h225V97z'/%3E%3C/clipPath%3E%3CclipPath id='clip2'%3E%3Cpath d='M0 0h359v444H0zM97 37v310h225V37z'/%3E%3C/clipPath%3E%3C/defs%3E%3Crect x='37' y='97' width='225' height='310' fill='white'/%3E%3Crect x='37' y='97' width='225' height='310' fill='none' stroke='gray' stroke-linecap='round' stroke-linejoin='round' stroke-width='72' clip-path='url(%23clip1)'/%3E%3Crect x='97' y='37' width='225' height='310' fill='white'/%3E%3Crect x='97' y='37' width='225' height='310' fill='none' stroke='gray' stroke-linecap='round' stroke-linejoin='round' stroke-width='72' clip-path='url(%23clip2)'/%3E%3C/svg%3E"); } .ltx_listing_button::after { opacity: 0; @@ -43,12 +43,12 @@ .ltx_listing_button.success::after { opacity: 1; /* success icon */ - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 359 444'%3E%3Ccircle cx='179.5' cy='222' r='178.3' fill='%2300a337'/%3E%3Cpath d='M235.3 109.5 L123.6 308.2 L155.7 305.4 L110.7 245.6 L96.7 227.7 L65.8 267.7 L80.5 283.0 L125.3 340.5 L143.7 364.9 L158.5 338.1 L270.2 138.8 L281.8 117.8 L247.0 87.9 Z' fill='white'/%3E%3C/svg%3E"); } .ltx_listing_button.error::after { opacity: 1; /* error icon */ - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 359 444'%3E%3Ccircle cx='179.5' cy='222' r='178.3' fill='%23c33641'/%3E%3Cline x1='110.3' y1='138.3' x2='245.6' y2='305.4' stroke='white' stroke-linecap='square' stroke-width='56.1'/%3E%3Cline x1='247.0' y1='136.0' x2='111.6' y2='303.7' stroke='white' stroke-linecap='square' stroke-width='56.1'/%3E%3C/svg%3E"); } .ltx_listing_button.success::before, .ltx_listing_button.error::before {