|
1 |
| -import { chatGPTAvatarSVG, fileCode, iconCopy } from './icons' |
| 1 | +import html2canvas from 'html2canvas' |
| 2 | +import { chatGPTAvatarSVG, fileCode, iconCamera, iconCopy } from './icons' |
2 | 3 | import './style.scss'
|
3 |
| -import { copyToClipboard, downloadFile, getBase64FromImg, onloadSafe } from './utils' |
| 4 | +import { copyToClipboard, downloadFile, downloadUrl, getBase64FromImg, onloadSafe, sleep } from './utils' |
4 | 5 | import templateHtml from './template.html?raw'
|
5 | 6 |
|
6 | 7 | type ConversationLine = |
|
@@ -56,9 +57,15 @@ function main() {
|
56 | 57 | })
|
57 | 58 | container.appendChild(copyButton)
|
58 | 59 |
|
| 60 | + const imageButton = <HTMLAnchorElement>firstItem.cloneNode(true) |
| 61 | + imageButton.removeAttribute('href') |
| 62 | + imageButton.innerHTML = `${iconCamera}Screenshot` |
| 63 | + imageButton.addEventListener('click', () => exportToPng()) |
| 64 | + container.appendChild(imageButton) |
| 65 | + |
59 | 66 | const htmlButton = <HTMLAnchorElement>firstItem.cloneNode(true)
|
60 | 67 | htmlButton.removeAttribute('href')
|
61 |
| - htmlButton.innerHTML = `${fileCode}Export to .html` |
| 68 | + htmlButton.innerHTML = `${fileCode}Export WebPage` |
62 | 69 | htmlButton.addEventListener('click', () => exportToHtml())
|
63 | 70 | container.appendChild(htmlButton)
|
64 | 71 | })
|
@@ -120,6 +127,56 @@ ${linesHtml}
|
120 | 127 | downloadFile(fileName, 'text/html', html)
|
121 | 128 | }
|
122 | 129 |
|
| 130 | +async function exportToPng() { |
| 131 | + const thread = document.querySelector('[class^="ThreadLayout__NodeWrapper"]') as HTMLElement |
| 132 | + if (!thread) return |
| 133 | + |
| 134 | + // hide irrelevant elements |
| 135 | + thread.style.height = 'auto' |
| 136 | + |
| 137 | + const alertWrapper = document.querySelector('[class^="_app__AlertWrapper"]') |
| 138 | + if (alertWrapper) alertWrapper.remove() |
| 139 | + |
| 140 | + const positionForm = document.querySelector('[class^="Thread__PositionForm"]') as HTMLElement |
| 141 | + if (positionForm) positionForm.style.display = 'none' |
| 142 | + |
| 143 | + const bottomSpacer = document.querySelector('[class^="ThreadLayout__BottomSpacer"]') as HTMLElement |
| 144 | + if (bottomSpacer) bottomSpacer.style.display = 'none' |
| 145 | + |
| 146 | + const threadWrapper = document.querySelector('[class^="Thread__Wrapper"]') |
| 147 | + if (threadWrapper && threadWrapper.children.length > 1) { |
| 148 | + const leftSidebar = threadWrapper.children[1] as HTMLElement |
| 149 | + const mainContent = threadWrapper.children[0] as HTMLElement |
| 150 | + leftSidebar.style.display = 'none' |
| 151 | + mainContent.style.paddingLeft = '0' |
| 152 | + } |
| 153 | + |
| 154 | + await sleep(100) |
| 155 | + |
| 156 | + const canvas = await html2canvas(thread, { |
| 157 | + scrollX: -window.scrollX, |
| 158 | + scrollY: -window.scrollY, |
| 159 | + windowWidth: thread.scrollWidth, |
| 160 | + windowHeight: thread.scrollHeight, |
| 161 | + }) |
| 162 | + |
| 163 | + // restore the layout |
| 164 | + if (threadWrapper && threadWrapper.children.length > 1) { |
| 165 | + const leftSidebar = threadWrapper.children[1] as HTMLElement |
| 166 | + const mainContent = threadWrapper.children[0] as HTMLElement |
| 167 | + leftSidebar.style.display = '' |
| 168 | + mainContent.style.paddingLeft = '' |
| 169 | + } |
| 170 | + if (positionForm) positionForm.style.display = '' |
| 171 | + if (bottomSpacer) bottomSpacer.style.display = '' |
| 172 | + thread.style.height = '' |
| 173 | + |
| 174 | + const dataUrl = canvas.toDataURL('image/png', 1) |
| 175 | + .replace(/^data:image\/[^;]/, 'data:application/octet-stream') |
| 176 | + const fileName = `ChatGPT-${new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '')}.png` |
| 177 | + downloadUrl(fileName, dataUrl) |
| 178 | +} |
| 179 | + |
123 | 180 | function getConversation(): ConversationItem[] {
|
124 | 181 | const thread = document.querySelector('[class^="ThreadLayout__NodeWrapper"]')
|
125 | 182 | if (!thread) return []
|
|
0 commit comments