|
57 | 57 | void _isAuthor; |
58 | 58 | void _readOnly; |
59 | 59 | }); |
60 | | -
|
61 | 60 | function handleKeyDown(e: KeyboardEvent) { |
62 | 61 | if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { |
63 | 62 | editFormEl?.requestSubmit(); |
|
67 | 66 | } |
68 | 67 | } |
69 | 68 |
|
| 69 | + function handleCopy(event: ClipboardEvent) { |
| 70 | + if (!contentEl) return; |
| 71 | +
|
| 72 | + const selection = window.getSelection(); |
| 73 | + if (!selection || selection.isCollapsed) return; |
| 74 | + if (!selection.anchorNode || !selection.focusNode) return; |
| 75 | +
|
| 76 | + const anchorInside = contentEl.contains(selection.anchorNode); |
| 77 | + const focusInside = contentEl.contains(selection.focusNode); |
| 78 | + if (!anchorInside && !focusInside) return; |
| 79 | +
|
| 80 | + if (!event.clipboardData) return; |
| 81 | +
|
| 82 | + const range = selection.getRangeAt(0); |
| 83 | + const wrapper = document.createElement("div"); |
| 84 | + wrapper.appendChild(range.cloneContents()); |
| 85 | +
|
| 86 | + wrapper.querySelectorAll("*").forEach((el) => { |
| 87 | + el.removeAttribute("style"); |
| 88 | + el.removeAttribute("class"); |
| 89 | + el.removeAttribute("color"); |
| 90 | + el.removeAttribute("bgcolor"); |
| 91 | + el.removeAttribute("background"); |
| 92 | +
|
| 93 | + for (const attr of Array.from(el.attributes)) { |
| 94 | + if (attr.name === "id" || attr.name.startsWith("data-")) { |
| 95 | + el.removeAttribute(attr.name); |
| 96 | + } |
| 97 | + } |
| 98 | + }); |
| 99 | +
|
| 100 | + const html = wrapper.innerHTML; |
| 101 | + const text = wrapper.textContent ?? ""; |
| 102 | +
|
| 103 | + event.preventDefault(); |
| 104 | + event.clipboardData.setData("text/html", html); |
| 105 | + event.clipboardData.setData("text/plain", text); |
| 106 | + } |
| 107 | +
|
70 | 108 | let editContentEl: HTMLTextAreaElement | undefined = $state(); |
71 | 109 | let editFormEl: HTMLFormElement | undefined = $state(); |
72 | 110 |
|
|
183 | 221 | {/if} |
184 | 222 | {/if} |
185 | 223 |
|
186 | | - <div bind:this={contentEl}> |
| 224 | + <div bind:this={contentEl} oncopy={handleCopy}> |
187 | 225 | {#if isLast && loading && message.content.length === 0} |
188 | 226 | <IconLoading classNames="loading inline ml-2 first:ml-0" /> |
189 | 227 | {/if} |
|
0 commit comments