Skip to content

Commit 2a4430c

Browse files
committed
Make copy button reusable
1 parent 9053d43 commit 2a4430c

File tree

2 files changed

+51
-47
lines changed

2 files changed

+51
-47
lines changed

llamafile/server/www/clipboard.js

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,58 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
function copyTextToClipboard(text) {
15+
const clipboardIcon = `<svg xmlns="http://www.w3.org/2000/svg"
16+
viewBox="0 0 24 24" fill="none"
17+
stroke="currentColor"
18+
stroke-width="2"
19+
stroke-linecap="round"
20+
stroke-linejoin="round">
21+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
22+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
23+
</svg>`;
24+
const checkmarkIcon = `<svg xmlns="http://www.w3.org/2000/svg"
25+
viewBox="0 0 24 24" fill="none"
26+
stroke="green"
27+
stroke-width="2"
28+
stroke-linecap="round"
29+
stroke-linejoin="round">
30+
<polyline points="20 6 9 17 4 12"></polyline>
31+
</svg>`;
32+
33+
function createCopyButton(textProviderFunction, htmlProviderFunction) {
34+
if (!textProviderFunction) {
35+
throw new Error("textProviderFunction is null");
36+
}
37+
const copyButton = document.createElement('button');
38+
copyButton.className = 'copy-button';
39+
copyButton.innerHTML = clipboardIcon;
40+
copyButton.addEventListener('click', async function () {
41+
try {
42+
await copyTextToClipboard(textProviderFunction(), htmlProviderFunction ? htmlProviderFunction() : null);
43+
copyButton.innerHTML = checkmarkIcon
44+
setTimeout(() => copyButton.innerHTML = clipboardIcon, 2000);
45+
} catch (err) {
46+
console.error('Failed to copy text:', err);
47+
}
48+
});
49+
return copyButton;
50+
}
51+
52+
async function copyTextToClipboard(text, html) {
1653
// see https://stackoverflow.com/a/30810322/1653720
1754
if (navigator.clipboard) {
18-
navigator.clipboard.writeText(text).then(function() {
19-
}, function(err) {
20-
console.error('could not copy text: ', err);
21-
});
55+
if (html && window.ClipboardItem && navigator.clipboard.write) {
56+
const data = [
57+
new ClipboardItem({
58+
"text/html": new Blob([html], { type: "text/html" }),
59+
"text/plain": new Blob([text], { type: "text/plain" }),
60+
})
61+
];
62+
return navigator.clipboard.write(data);
63+
} else {
64+
// Fallback to text-only copy if ClipboardItem is not supported (or html is not needed)
65+
return navigator.clipboard.writeText(text);
66+
}
2267
} else {
2368
var textArea = document.createElement('textarea');
2469
textArea.value = text;

llamafile/server/www/render_markdown.js

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -915,48 +915,7 @@ class RenderMarkdown extends Highlighter {
915915
this.tick2 = 0;
916916
}
917917

918-
static COPY_BUTTON = null;
919-
920-
static makeCopyButton() {
921-
if (!RenderMarkdown.COPY_BUTTON) {
922-
RenderMarkdown.COPY_BUTTON = document.createElement('button');
923-
RenderMarkdown.COPY_BUTTON.className = 'copy-button';
924-
RenderMarkdown.COPY_BUTTON.innerHTML =
925-
`<svg xmlns="http://www.w3.org/2000/svg"
926-
viewBox="0 0 24 24" fill="none"
927-
stroke="currentColor"
928-
stroke-width="2"
929-
stroke-linecap="round"
930-
stroke-linejoin="round">
931-
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
932-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
933-
</svg>`;
934-
}
935-
return RenderMarkdown.COPY_BUTTON.cloneNode(true);
936-
}
937-
938918
setupCodeBlock(pre) {
939-
const copyButton = RenderMarkdown.makeCopyButton();
940-
copyButton.addEventListener('click', function() {
941-
try {
942-
copyTextToClipboard(pre.innerText);
943-
const originalInnerHTML = copyButton.innerHTML;
944-
copyButton.innerHTML =
945-
`<svg xmlns="http://www.w3.org/2000/svg"
946-
viewBox="0 0 24 24" fill="none"
947-
stroke="green"
948-
stroke-width="2"
949-
stroke-linecap="round"
950-
stroke-linejoin="round">
951-
<polyline points="20 6 9 17 4 12"></polyline>
952-
</svg>`;
953-
setTimeout(() => {
954-
copyButton.innerHTML = originalInnerHTML;
955-
}, 2000);
956-
} catch (err) {
957-
console.error('Failed to copy text:', err);
958-
}
959-
});
960-
pre.appendChild(copyButton);
919+
pre.appendChild(createCopyButton(() => pre.innerText));
961920
}
962921
}

0 commit comments

Comments
 (0)