Greasy Fork 还支持 简体中文。

Save ChatGPT as PDF

Turn your chats into neatly formatted PDF.

目前為 2024-09-11 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Save ChatGPT as PDF
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.17
  5. // @description Turn your chats into neatly formatted PDF.
  6. // @author PDFCrowd (https://pdfcrowd.com/)
  7. // @match https://chatgpt.com/*
  8. // @icon64 https://github.com/pdfcrowd/save-chatgpt-as-pdf/raw/master/icons/icon64.png
  9. // @run-at document-end
  10. // @grant GM_xmlhttpRequest
  11. // @connect api.pdfcrowd.com
  12. // @license MIT
  13. // ==/UserScript==
  14. /* globals pdfcrowdChatGPT */
  15.  
  16. // do not modify or delete the following line, it serves as a placeholder for
  17. // the common.js contents which is copied here by "make build-userscript-single-file"
  18. //
  19. // shared.js placeholder
  20. 'use strict';
  21.  
  22. const pdfcrowdShared = {};
  23.  
  24. pdfcrowdShared.defaultOptions = {
  25. margins: '',
  26. theme: '',
  27. zoom: 100,
  28. no_questions: false
  29. }
  30.  
  31. pdfcrowdShared.version = 'v1.17';
  32.  
  33. pdfcrowdShared.rateUsLink = '#';
  34. pdfcrowdShared.hasOptions = true;
  35. if (typeof GM_info !== 'undefined') {
  36. pdfcrowdShared.rateUsLink = 'https://greasyfork.org/en/scripts/484463-save-chatgpt-as-pdf/feedback#post-discussion';
  37. pdfcrowdShared.hasOptions = false;
  38. } else if (navigator.userAgent.includes("Chrome")) {
  39. pdfcrowdShared.rateUsLink = 'https://chromewebstore.google.com/detail/save-chatgpt-as-pdf/ccjfggejcoobknjolglgmfhoeneafhhm/reviews';
  40. } else if (navigator.userAgent.includes("Firefox")) {
  41. pdfcrowdShared.rateUsLink = 'https://addons.mozilla.org/en-US/firefox/addon/save-chatgpt-as-pdf/reviews/';
  42. }
  43.  
  44. pdfcrowdShared.helpContent = `
  45. <div class="pdfcrowd-category-title">
  46. Support
  47. </div>
  48.  
  49. <div style="line-height:1.5">
  50. Feel free to contact us with any questions or for assistance. We're always happy to help!
  51. <br>
  52. Email us at <strong>support@pdfcrowd.com</strong> or use our
  53. <a href="https://pdfcrowd.com/contact/?ref=chatgpt&amp;pr=save-chatgpt-as-pdf-pdfcrowd" title="Contact us" target="_blank">
  54. contact form</a>.
  55. <br>
  56. <span class="popup-hidden">
  57. Please <a href="${pdfcrowdShared.rateUsLink}">rate us</a> if you like the extension. It helps a lot!
  58. </span>
  59. </div>
  60.  
  61. <div class="pdfcrowd-category">
  62. <div class="pdfcrowd-category-title">
  63. Tips
  64. </div>
  65. <ul>
  66. <li>
  67. You can download a specific part of the chat by selecting it.
  68. </li>
  69. <li>
  70. If images are missing in the PDF, reload the page and try downloading the PDF again.
  71. </li>
  72. <li>
  73. Customize the PDF file via addon
  74. <a class="options-link">options</a>.
  75. </li>
  76. </ul>
  77. </div>
  78.  
  79. <div class="pdfcrowd-category">
  80. <div class="pdfcrowd-category-title">
  81. Links
  82. </div>
  83. <ul>
  84. <li>
  85. Save ChatGPT as PDF
  86. <a href="https://pdfcrowd.com/save-chatgpt-as-pdf/" target="_blank">homepage</a>
  87. </li>
  88. <li>
  89. Visit <a href="https://pdfcrowd.com/" target="_blank">PDFCrowd</a>
  90. to learn more about our tool and services.
  91. </li>
  92. <li>
  93. Discover how our
  94. <a href="https://pdfcrowd.com/api/html-to-pdf-api/" target="_blank">HTML to PDF API</a>
  95. can enhance your projects.
  96. </li>
  97. </ul>
  98. </div>
  99. `;
  100.  
  101. pdfcrowdShared.getOptions = function(callback) {
  102. if(typeof chrome === 'undefined') {
  103. callback(pdfcrowdShared.defaultOptions);
  104. } else {
  105. try {
  106. chrome.storage.sync.get('options', function(obj) {
  107. let rv = {};
  108. Object.assign(rv, pdfcrowdShared.defaultOptions);
  109. if(obj.options) {
  110. Object.assign(rv, obj.options);
  111. }
  112. callback(rv);
  113. });
  114. } catch(error) {
  115. console.error(error);
  116. callback(pdfcrowdShared.defaultOptions);
  117. }
  118. }
  119. }
  120.  
  121. function init() {
  122. let elem = document.getElementById('version');
  123. if(elem) {
  124. elem.innerHTML = pdfcrowdShared.version;
  125. }
  126.  
  127. elem = document.getElementById('help');
  128. if(elem) {
  129. elem.innerHTML = pdfcrowdShared.helpContent;
  130. }
  131. }
  132.  
  133. document.addEventListener('DOMContentLoaded', init);
  134. // common.js placeholder
  135. const pdfcrowdChatGPT = {};
  136.  
  137. pdfcrowdChatGPT.pdfcrowdAPI = 'https://api.pdfcrowd.com/convert/24.04/';
  138. pdfcrowdChatGPT.username = 'chat-gpt';
  139. pdfcrowdChatGPT.apiKey = '29d211b1f6924c22b7a799b4e8fecb7e';
  140.  
  141. pdfcrowdChatGPT.init = function() {
  142. if(document.querySelectorAll('.pdfcrowd-convert').length > 0) {
  143. // avoid double init
  144. return;
  145. }
  146.  
  147. // remote images live at least 1 minute
  148. const minImageDuration = 60000;
  149.  
  150. const buttonIconFill = (typeof GM_xmlhttpRequest !== 'undefined')
  151. ? '#A72C16' : '#EA4C3A';
  152.  
  153. const blockStyle = document.createElement('style');
  154. blockStyle.textContent = `
  155. .pdfcrowd-block {
  156. position: fixed;
  157. height: 36px;
  158. top: 10px;
  159. right: 180px;
  160. }
  161.  
  162. .pdfcrowd-block-login {
  163. top: 50px;
  164. right: 16px;
  165. }
  166.  
  167. @media (min-width: 768px) {
  168. .pdfcrowd-lg {
  169. display: block;
  170. }
  171.  
  172. .pdfcrowd-sm {
  173. display: none;
  174. }
  175. }
  176.  
  177. @media (max-width: 767px) {
  178. .pdfcrowd-block:not(.pdfcrowd-block-login) {
  179. right: 56px;
  180. }
  181.  
  182. .pdfcrowd-lg {
  183. display: none;
  184. }
  185.  
  186. .pdfcrowd-sm {
  187. display: block;
  188. }
  189. }
  190.  
  191. svg.pdfcrowd-btn-content {
  192. width: 1rem;
  193. height: 1rem;
  194. }
  195.  
  196. #pdfcrowd-convert-main {
  197. padding-right: 0;
  198. }
  199.  
  200. #pdfcrowd-convert-main:disabled {
  201. cursor: wait;
  202. filter: none;
  203. opacity: 1;
  204. }
  205.  
  206. .pdfcrowd-dropdown-arrow::after {
  207. display: inline-block;
  208. width: 0;
  209. height: 0;
  210. vertical-align: .255em;
  211. content: "";
  212. border-top: .3em solid;
  213. border-right: .3em solid transparent;
  214. border-bottom: 0;
  215. border-left: .3em solid transparent;
  216. }
  217.  
  218. .pdfcrowd-fs-small {
  219. font-size: .875rem;
  220. }
  221.  
  222. #pdfcrowd-more {
  223. cursor: pointer;
  224. padding: .5rem;
  225. border-top-right-radius: .5rem;
  226. border-bottom-right-radius: .5rem;
  227. }
  228.  
  229. #pdfcrowd-more:hover {
  230. background-color: rgba(0,0,0,.1);
  231. }
  232.  
  233. #pdfcrowd-extra-btns {
  234. border: 1px solid rgba(0,0,0,.1);
  235. background-color: #fff;
  236. color: #000;
  237. }
  238.  
  239. .pdfcrowd-extra-btn:hover {
  240. background-color: rgba(0,0,0,.1);
  241. }
  242.  
  243. .pdfcrowd-extra-btn {
  244. width: 100%;
  245. text-align: start;
  246. display: block;
  247. }
  248.  
  249. .pdfcrowd-hidden {
  250. display: none;
  251. }
  252.  
  253. #pdfcrowd-spinner {
  254. position: absolute;
  255. width: 100%;
  256. height: 100%;
  257. }
  258.  
  259. .pdfcrowd-spinner {
  260. border: 4px solid #ccc;
  261. border-radius: 50%;
  262. border-top: 4px solid #ffc107;
  263. width: 1.5rem;
  264. height: 1.5rem;
  265. -webkit-animation: spin 1.5s linear infinite;
  266. animation: spin 1.5s linear infinite;
  267. }
  268.  
  269. @-webkit-keyframes spin {
  270. 0% { -webkit-transform: rotate(0deg); }
  271. 100% { -webkit-transform: rotate(360deg); }
  272. }
  273.  
  274. @keyframes spin {
  275. 0% { transform: rotate(0deg); }
  276. 100% { transform: rotate(360deg); }
  277. }
  278.  
  279. .pdfcrowd-invisible {
  280. visibility: hidden;
  281. }
  282.  
  283. .pdfcrowd-overlay {
  284. z-index: 10000;
  285. display: none;
  286. position: fixed;
  287. top: 0;
  288. left: 0;
  289. width: 100%;
  290. height: 100%;
  291. background: rgba(0, 0, 0, 0.5);
  292. justify-content: center;
  293. align-items: center;
  294. color: #000;
  295. }
  296.  
  297. .pdfcrowd-dialog {
  298. background: #fff;
  299. padding: 0;
  300. margin: 0.5em;
  301. border-radius: 5px;
  302. box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
  303. text-align: start;
  304. }
  305.  
  306. .pdfcrowd-dialog a {
  307. color: revert;
  308. }
  309.  
  310. .pdfcrowd-dialog-body {
  311. padding: 0 2em;
  312. line-height: 2;
  313. }
  314.  
  315. .pdfcrowd-dialog-footer {
  316. text-align: center;
  317. margin: .5em;
  318. position: relative;
  319. }
  320.  
  321. .pdfcrowd-dialog-header {
  322. background-color: #eee;
  323. font-size: 1.25em;
  324. padding: .5em;
  325. border-top-left-radius: 10px;
  326. border-top-right-radius: 10px;
  327. }
  328.  
  329. .pdfcrowd-version {
  330. position: absolute;
  331. bottom: 0;
  332. right: 0;
  333. font-size: .65em;
  334. color: #777;
  335. }
  336.  
  337. .pdfcrowd-dialog ul {
  338. list-style: disc;
  339. margin: 0;
  340. padding: 0 0 0 2em;
  341. }
  342.  
  343. .pdfcrowd-close-x {
  344. cursor: pointer;
  345. float: right;
  346. color: #777;
  347. }
  348.  
  349. #pdfcrowd-help {
  350. cursor: pointer;
  351. }
  352.  
  353. .pdfcrowd-py-1 {
  354. padding-bottom: 0.25rem;
  355. padding-top: 0.25rem;
  356. }
  357.  
  358. .pdfcrowd-px-2 {
  359. padding-left: 0.5rem;
  360. padding-right: 0.5rem;
  361. }
  362.  
  363. .pdfcrowd-mr-1 {
  364. margin-right: 0.25rem;
  365. }
  366.  
  367. .pdfcrowd-mr-4 {
  368. margin-right: 1rem;
  369. }
  370.  
  371. .pdfcrowd-justify-center {
  372. justify-content: center;
  373. }
  374.  
  375. .pdfcrowd-items-center {
  376. align-items: center;
  377. }
  378.  
  379. .pdfcrowd-flex {
  380. display: flex;
  381. }
  382.  
  383. .pdfcrowd-text-left {
  384. text-align: left;
  385. }
  386.  
  387. .pdfcrowd-text-right {
  388. text-align: right;
  389. }
  390.  
  391. .pdfcrowd-h-9 {
  392. height: 2.25rem;
  393. }
  394.  
  395. .pdfcrowd-category {
  396. line-height: normal;
  397. margin-top: 1em;
  398. }
  399.  
  400. .pdfcrowd-category-title {
  401. font-size: larger;
  402. font-weight: bold;
  403. }
  404. `;
  405. document.head.appendChild(blockStyle);
  406.  
  407. const pdfcrowdBlockHtml = `
  408. <button
  409. id="pdfcrowd-convert-main"
  410. type="button"
  411. role="button"
  412. tabindex="0"
  413. aria-label="Save as PDF"
  414. data-conv-options='{"page_size": "a4"}'
  415. class="btn btn-secondary btn-small pdfcrowd-h-9 pdfcrowd-convert pdfcrowd-fs-small">
  416. <svg class="pdfcrowd-mr-1 pdfcrowd-btn-content" version="1.1" viewBox="0 0 30 30" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><polyline clip-rule="evenodd" fill="${buttonIconFill}" fill-rule="evenodd" points="30,30 0,30 0,0 30,0 30,30 "/><path d="M15.372,4.377 c0.452,0.213,0.358,0.489,0.219,1.793c-0.142,1.345-0.618,3.802-1.535,6.219c-0.918,2.413-2.28,4.784-3.467,6.539 c-1.186,1.756-2.201,2.897-2.975,3.556c-0.777,0.659-1.314,0.835-1.665,0.893c-0.348,0.058-0.506,0-0.6-0.177 c-0.094-0.176-0.127-0.466-0.046-0.82c0.079-0.35,0.268-0.76,0.804-1.285c0.541-0.527,1.426-1.172,2.661-1.771 c1.235-0.6,2.817-1.156,4.116-1.537c1.299-0.379,2.311-0.585,3.197-0.746c0.888-0.162,1.647-0.277,2.391-0.337 c0.744-0.056,1.474-0.056,2.186,0c0.712,0.06,1.408,0.175,2.011,0.323c0.6,0.146,1.108,0.321,1.551,0.601 c0.442,0.276,0.823,0.657,1.012,1.083c0.192,0.423,0.192,0.893,0.033,1.228c-0.158,0.337-0.476,0.541-0.839,0.66 c-0.364,0.115-0.775,0.144-1.267,0c-0.49-0.148-1.062-0.47-1.662-0.894c-0.601-0.425-1.235-0.952-2.057-1.771 c-0.824-0.819-1.838-1.93-2.692-3.013c-0.854-1.083-1.553-2.136-2.028-3.029c-0.473-0.893-0.727-1.624-0.933-2.355 c-0.206-0.733-0.364-1.464-0.427-2.122S13.326,6.17,13.39,5.701c0.063-0.466,0.16-0.82,0.317-1.055 c0.158-0.23,0.381-0.35,0.539-0.408s0.254-0.058,0.348-0.073c0.094-0.015,0.188-0.044,0.333,0c0.138,0.042,0.321,0.154,0.504,0.268" fill="none" stroke="#FFFFFF" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.4"/></svg>
  417. <div class="pdfcrowd-lg pdfcrowd-btn-content">
  418. Save as PDF
  419. </div>
  420. <div class="pdfcrowd-sm pdfcrowd-btn-content">
  421. PDF
  422. </div>
  423. <div id="pdfcrowd-more" class="pdfcrowd-dropdown-arrow">
  424. </div>
  425. <div id="pdfcrowd-spinner" class="pdfcrowd-hidden">
  426. <div class="pdfcrowd-flex pdfcrowd-justify-center pdfcrowd-items-center pdfcrowd-mr-4" style="height: 100%;">
  427. <div class="pdfcrowd-spinner">
  428. </div>
  429. </div>
  430. </div>
  431. </button>
  432.  
  433. <div id="pdfcrowd-extra-btns" class="pdfcrowd-hidden pdfcrowd-text-left">
  434. <button
  435. id="pdfcrowd-extra-a4p"
  436. type="button"
  437. role="button"
  438. tabindex="0"
  439. aria-label="Save as A4 portrait PDF"
  440. data-conv-options='{"page_size": "a4"}'
  441. class="pdfcrowd-extra-btn pdfcrowd-convert pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1">
  442. A4 Portrait
  443. </button>
  444. <button
  445. id="pdfcrowd-extra-a4l"
  446. type="button"
  447. role="button"
  448. tabindex="0"
  449. aria-label="Save as A4 landscape PDF"
  450. class="pdfcrowd-extra-btn pdfcrowd-convert pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1"
  451. data-conv-options='{"orientation": "landscape", "viewport_width": 1200, "page_size": "a4"}'>
  452. A4 Landscape
  453. </button>
  454. <button
  455. id="pdfcrowd-extra-lp"
  456. type="button"
  457. role="button"
  458. tabindex="0"
  459. aria-label="Save as letter portrait PDF"
  460. data-conv-options='{"page_size": "letter"}'
  461. class="pdfcrowd-extra-btn pdfcrowd-convert pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1">
  462. Letter Portrait
  463. </button>
  464. <button
  465. id="pdfcrowd-extra-ll"
  466. type="button"
  467. role="button"
  468. tabindex="0"
  469. aria-label="Save as letter landscape PDF"
  470. class="pdfcrowd-extra-btn pdfcrowd-convert pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1"
  471. data-conv-options='{"orientation": "landscape", "viewport_width": 1200, "page_size": "letter"}'>
  472. Letter Landscape
  473. </button>
  474. <button
  475. id="pdfcrowd-extra-single-a4p"
  476. type="button"
  477. role="button"
  478. tabindex="0"
  479. aria-label="Save as single page"
  480. data-conv-options='{"page_height": "-1"}'
  481. class="pdfcrowd-extra-btn pdfcrowd-convert pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1">
  482. Single Page
  483. </button>
  484. <hr>
  485. <a id="pdfcrowd-options" href="#"
  486. aria-label="Save ChatGPT as PDF options"
  487. class="pdfcrowd-extra-btn pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1">
  488. Options
  489. </a>
  490. <button
  491. id="pdfcrowd-help"
  492. type="button"
  493. role="button"
  494. aria-label="Save ChatGPT as PDF help"
  495. class="pdfcrowd-extra-btn pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1">
  496. Help
  497. </button>
  498. <a href="${pdfcrowdShared.rateUsLink}" aria-label="Rate the Extension"
  499. class="pdfcrowd-extra-btn pdfcrowd-fs-small pdfcrowd-px-2 pdfcrowd-py-1">
  500. Rate the Extension
  501. </a>
  502. </div>
  503.  
  504. <div class="pdfcrowd-overlay" id="pdfcrowd-error-overlay">
  505. <div class="pdfcrowd-dialog">
  506. <div class="pdfcrowd-dialog-header">
  507. Error occurred
  508. <span class="pdfcrowd-close-x pdfcrowd-close-btn">&times;</span>
  509. </div>
  510. <div class="pdfcrowd-dialog-body" style="text-align: center;">
  511. <p id="pdfcrowd-error-message"></p>
  512. </div>
  513. <div class="pdfcrowd-dialog-footer">
  514. <button class="btn btn-secondary pdfcrowd-close-btn">Close</button>
  515. </div>
  516. </div>
  517. </div>
  518.  
  519. <div class="pdfcrowd-overlay" id="pdfcrowd-help-overlay">
  520. <div class="pdfcrowd-dialog">
  521. <div class="pdfcrowd-dialog-header">
  522. Save ChatGPT as PDF <span style="font-size:smaller">by PDFCrowd</span>
  523. <span class="pdfcrowd-close-x pdfcrowd-close-btn">&times;</span>
  524. </div>
  525. <div class="pdfcrowd-dialog-body">
  526. ${pdfcrowdShared.helpContent}
  527. </div>
  528.  
  529. <div class="pdfcrowd-dialog-footer">
  530. <button class="btn btn-secondary pdfcrowd-close-btn">Close</button>
  531. <div class="pdfcrowd-version">${pdfcrowdShared.version}</div>
  532. </div>
  533. </div>
  534. </div>
  535. `;
  536.  
  537. function findRow(element) {
  538. while(element) {
  539. if(element.classList &&
  540. element.classList.contains('text-token-text-primary')) {
  541. return element;
  542. }
  543. element = element.parentElement;
  544. }
  545. return null;
  546. }
  547.  
  548. function hasParent(element, parent) {
  549. while(element) {
  550. if(element === parent) {
  551. return true;
  552. }
  553. element = element.parentElement;
  554. }
  555. return false;
  556. }
  557.  
  558. function prepareSelection(element) {
  559. const selection = window.getSelection();
  560. if(!selection.isCollapsed) {
  561. const rangeCount = selection.rangeCount;
  562. if(rangeCount > 0) {
  563. const startElement = findRow(
  564. selection.getRangeAt(0).startContainer.parentElement);
  565. if(startElement && hasParent(startElement, element)) {
  566. // selection is in the main block
  567. const endElement = findRow(
  568. selection.getRangeAt(
  569. rangeCount-1).endContainer.parentElement);
  570.  
  571. const newContainer = document.createElement('main');
  572. newContainer.classList.add('h-full', 'w-full');
  573. let currentElement = startElement;
  574. while(currentElement) {
  575. const child_clone = currentElement.cloneNode(true);
  576. newContainer.appendChild(child_clone);
  577. persistCanvases(currentElement, child_clone);
  578. if(currentElement === endElement) {
  579. break;
  580. }
  581. currentElement = currentElement.nextElementSibling;
  582. }
  583. return newContainer;
  584. }
  585. }
  586. }
  587. const element_clone = element.cloneNode(true);
  588. persistCanvases(element, element_clone);
  589. return element_clone;
  590. }
  591.  
  592. function prepareContent(element) {
  593. element = prepareSelection(element);
  594.  
  595. // fix nested buttons error
  596. element.querySelectorAll('button button').forEach(button => {
  597. button.parentNode.removeChild(button);
  598. });
  599.  
  600. // remove all scripts and styles
  601. element.querySelectorAll('script, style').forEach(el => el.remove());
  602.  
  603. // solve expired images
  604. element.querySelectorAll('.grid img').forEach(img => {
  605. img.setAttribute(
  606. 'alt', 'The image has expired. Refresh ChatGPT page and retry saving to PDF.');
  607. });
  608.  
  609. element.classList.add('chat-gpt-custom');
  610.  
  611. return element.outerHTML;
  612. }
  613.  
  614. function showHelp() {
  615. document.getElementById('pdfcrowd-extra-btns').classList.add(
  616. 'pdfcrowd-hidden');
  617.  
  618. document.getElementById('pdfcrowd-help-overlay').style.display = 'flex';
  619. }
  620.  
  621. function addPdfExtension(filename) {
  622. return filename.replace(/\.*$/, '') + '.pdf';
  623. }
  624.  
  625. function isLight(body) {
  626. return window.getComputedStyle(document.body).backgroundColor != 'rgb(33, 33, 33)';
  627. }
  628.  
  629. function isElementVisible(element) {
  630. const style = window.getComputedStyle(element);
  631. return (
  632. style.display !== 'none' &&
  633. style.visibility !== 'hidden' &&
  634. element.offsetWidth > 0 &&
  635. element.offsetHeight > 0
  636. );
  637. }
  638.  
  639. function styleCanvasArea(element, stop_element) {
  640. while(element) {
  641. if(element == stop_element) {
  642. // canvas parent area not found
  643. return;
  644. }
  645.  
  646. const style_height = element.style.height;
  647. if(style_height &&
  648. style_height !== 'auto' &&
  649. style_height !== 'initial') {
  650. element.style.height = '';
  651. return;
  652. }
  653.  
  654. element = element.parentElement;
  655. }
  656. }
  657.  
  658. function persistCanvases(orig_element, new_element) {
  659. const items = [];
  660. const orig_canvases = orig_element.querySelectorAll('canvas');
  661. const new_canvases = new_element.querySelectorAll('canvas');
  662. if(orig_canvases.length !== new_canvases.length) {
  663. return;
  664. }
  665. for(let i = 0; i < orig_canvases.length; i++) {
  666. const orig_canvas = orig_canvases[i];
  667. if(isElementVisible(orig_canvas)) {
  668. const new_canvas = new_canvases[i];
  669. console.log(new_canvas);
  670. const img = new_canvas.ownerDocument.createElement('img');
  671. img.src = orig_canvas.toDataURL();
  672. img.classList.add('pdfcrowd-canvas-img');
  673. new_canvas.parentNode.replaceChild(img, new_canvas);
  674.  
  675. styleCanvasArea(img, new_element);
  676. }
  677. }
  678. }
  679.  
  680. function convert(event) {
  681. let trigger = event.target;
  682. document.getElementById('pdfcrowd-extra-btns').classList.add(
  683. 'pdfcrowd-hidden');
  684.  
  685. const btnConvert = document.getElementById('pdfcrowd-convert-main');
  686. btnConvert.disabled = true;
  687. const spinner = document.getElementById('pdfcrowd-spinner');
  688. spinner.classList.remove('pdfcrowd-hidden');
  689. const btnElems = document.getElementsByClassName('pdfcrowd-btn-content');
  690. for(let i = 0; i < btnElems.length; i++) {
  691. btnElems[i].classList.add('pdfcrowd-invisible');
  692. }
  693.  
  694. function cleanup() {
  695. btnConvert.disabled = false;
  696. spinner.classList.add('pdfcrowd-hidden');
  697. for(let i = 0; i < btnElems.length; i++) {
  698. btnElems[i].classList.remove('pdfcrowd-invisible');
  699. }
  700. }
  701.  
  702. const main = document.getElementsByTagName('main')[0];
  703. const content = prepareContent(main);
  704.  
  705. let body;
  706. let title = '';
  707. const h1 = main.querySelector('h1');
  708. if(h1) {
  709. title = h1.textContent;
  710. body = content;
  711. } else {
  712. const chatTitle = document.querySelector(`nav a[href="${window.location.pathname}"]`);
  713. title = chatTitle
  714. ? chatTitle.textContent
  715. : document.getElementsByTagName('title')[0].textContent;
  716. body = `<h1 class="main-title">${title}</h1>` + content;
  717. }
  718.  
  719. title = title.trim();
  720.  
  721. const data = {
  722. jpeg_quality: 70,
  723. image_dpi: 150,
  724. convert_images_to_jpeg: 'all',
  725. title: title,
  726. rendering_mode: 'viewport',
  727. smart_scaling_mode: 'viewport-fit'
  728. };
  729.  
  730. if(trigger.id) {
  731. localStorage.setItem('pdfcrowd-btn', trigger.id);
  732. } else {
  733. let lastBtn = localStorage.getItem('pdfcrowd-btn');
  734. if(lastBtn) {
  735. lastBtn = document.getElementById(lastBtn);
  736. if(lastBtn) {
  737. trigger = lastBtn;
  738. }
  739. }
  740. }
  741.  
  742. const convOptions = JSON.parse(trigger.dataset.convOptions || '{}');
  743.  
  744. for(let key in convOptions) {
  745. data[key] = convOptions[key];
  746. }
  747.  
  748. if(!('viewport_width' in convOptions)) {
  749. data.viewport_width = 800;
  750. }
  751.  
  752. pdfcrowdShared.getOptions(function(options) {
  753. if(options.margins === 'minimal') {
  754. data.no_margins = true;
  755. } else {
  756. data.margin_bottom = '12px';
  757. }
  758.  
  759. let classes = '';
  760. if(options.theme === 'dark' ||
  761. (options.theme === '' && !isLight(document.body))) {
  762. classes = 'pdfcrowd-dark ';
  763. data.page_background_color = '333333';
  764. }
  765.  
  766. if(options.zoom) {
  767. data.scale_factor = options.zoom;
  768. }
  769.  
  770. if(options.no_questions) {
  771. classes += 'pdfcrowd-no-questions ';
  772. }
  773.  
  774. data.text = `<!DOCTYPE html><html><head><meta charSet="utf-8"/></head><body class="${classes}">${body}</body>`;
  775.  
  776. pdfcrowdChatGPT.doRequest(data, addPdfExtension(title), cleanup);
  777. });
  778. }
  779.  
  780. function addPdfcrowdBlock() {
  781. const container = document.createElement('div');
  782. container.innerHTML = pdfcrowdBlockHtml;
  783. container.classList.add(
  784. 'pdfcrowd-block', 'pdfcrowd-text-right', 'pdfcrowd-hidden');
  785. document.body.appendChild(container);
  786.  
  787. let buttons = document.querySelectorAll('.pdfcrowd-convert');
  788. buttons.forEach(element => {
  789. element.addEventListener('click', convert);
  790. });
  791.  
  792. document.getElementById('pdfcrowd-help').addEventListener(
  793. 'click', event => {
  794. showHelp();
  795. });
  796.  
  797. document.getElementById('pdfcrowd-more').addEventListener('click', event => {
  798. event.stopPropagation();
  799. const moreButtons = document.getElementById(
  800. 'pdfcrowd-extra-btns');
  801. if(moreButtons.classList.contains('pdfcrowd-hidden')) {
  802. moreButtons.classList.remove('pdfcrowd-hidden');
  803. } else {
  804. moreButtons.classList.add('pdfcrowd-hidden');
  805. }
  806. });
  807.  
  808. document.addEventListener('click', event => {
  809. const moreButtons = document.getElementById('pdfcrowd-extra-btns');
  810.  
  811. if (!moreButtons.contains(event.target)) {
  812. moreButtons.classList.add('pdfcrowd-hidden');
  813. }
  814. });
  815.  
  816. buttons = document.querySelectorAll('.pdfcrowd-close-btn');
  817. buttons.forEach(element => {
  818. element.addEventListener('click', () => {
  819. element.closest('.pdfcrowd-overlay').style.display = 'none';
  820. });
  821. });
  822.  
  823. return container;
  824. }
  825.  
  826. function isVisible(el) {
  827. if(el) {
  828. const style = window.getComputedStyle(el);
  829. return style.display !== 'none' &&
  830. style.visibility !== 'hidden' &&
  831. style.opacity !== '0';
  832. }
  833. }
  834.  
  835. const pdfcrowd_block = addPdfcrowdBlock();
  836. function checkForContent() {
  837. if(document.querySelector('main div[role="presentation"]')) {
  838. pdfcrowd_block.classList.remove('pdfcrowd-hidden');
  839. // fix conflict with other extensions which remove the button
  840. if(!pdfcrowd_block.isConnected) {
  841. console.warn('Extension conflict, another extension deleted PDFCrowd HTML, disable other extensions to fix it.\ncreating the Save as PDF button...');
  842. document.body.appendChild(pdfcrowd_block);
  843. }
  844. if(!blockStyle.isConnected) {
  845. console.warn('Extension conflict, another extension deleted PDFCrowd HTML, disable other extensions to fix it.\ncreating the button style...');
  846. document.head.appendChild(blockStyle);
  847. }
  848. if(isVisible(
  849. document.querySelector('[data-testid="login-button"]'))) {
  850. pdfcrowd_block.classList.add('pdfcrowd-block-login');
  851. }
  852. } else {
  853. pdfcrowd_block.classList.add('pdfcrowd-hidden');
  854. }
  855. }
  856.  
  857. const options_el = document.getElementById('pdfcrowd-options');
  858. if(pdfcrowdShared.hasOptions) {
  859. options_el.addEventListener('click', function() {
  860. chrome.runtime.sendMessage({action: "open_options_page"});
  861. });
  862. } else {
  863. options_el.remove();
  864. }
  865.  
  866. setInterval(checkForContent, 1000);
  867. }
  868.  
  869. pdfcrowdChatGPT.showError = function(status, text) {
  870. let html;
  871. if (status == 432) {
  872. html = [
  873. "<strong>Fair Use Notice</strong><br>",
  874. "Current usage is over the limit. Please wait a while before trying again.<br><br>",
  875. ];
  876. } else {
  877. html = [];
  878. if (status) {
  879. if(status == 'network-error') {
  880. html.push('Network error while connecting to the conversion service');
  881. } else {
  882. html.push(`Code: ${status}`);
  883. }
  884. html.push(text);
  885. html.push('Please try again later');
  886. } else {
  887. html.push(text);
  888. }
  889. html.push(`If the problem persists, contact us at
  890. <a href="mailto:support@pdfcrowd.com?subject=ChatGPT%20error">
  891. support@pdfcrowd.com
  892. </a>`);
  893. }
  894. html = html.join('<br>');
  895. document.getElementById('pdfcrowd-error-overlay').style.display = 'flex';
  896. document.getElementById('pdfcrowd-error-message').innerHTML = html;
  897. };
  898.  
  899. pdfcrowdChatGPT.saveBlob = function(url, filename) {
  900. const a = document.createElement('a');
  901. a.href = url;
  902. a.download = filename;
  903. a.click();
  904. setTimeout(() => {
  905. window.URL.revokeObjectURL(url);
  906. }, 100);
  907. };
  908.  
  909. (function() {
  910. pdfcrowdChatGPT.doRequest = function(data, fileName, fnCleanup) {
  911. const formData = new FormData();
  912. for(let key in data) {
  913. formData.append(key, data[key]);
  914. }
  915. GM_xmlhttpRequest({
  916. url: pdfcrowdChatGPT.pdfcrowdAPI,
  917. method: 'POST',
  918. data: formData,
  919. responseType: 'blob',
  920. headers: {
  921. 'Authorization': 'Basic ' + btoa(
  922. pdfcrowdChatGPT.username + ':' + pdfcrowdChatGPT.apiKey),
  923. },
  924. onload: response => {
  925. fnCleanup();
  926. if(response.status == 200) {
  927. const url = window.URL.createObjectURL(response.response);
  928. pdfcrowdChatGPT.saveBlob(url, fileName);
  929. } else {
  930. pdfcrowdChatGPT.showError(
  931. response.status, response.responseText);
  932. }
  933. },
  934. onerror: error => {
  935. console.error('conversion error:', error);
  936. fnCleanup();
  937. pdfcrowdChatGPT.showError(500, error.responseText);
  938. }
  939. });
  940. };
  941.  
  942. pdfcrowdChatGPT.init();
  943. })();