Copy HTML to Anki

Copy specific parts of HTML text and send them to Anki, converting relative URLs to absolute URLs. Trigger with Ctrl+Shift+Y or via Tampermonkey menu.

当前为 2024-11-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Copy HTML to Anki
  3. // @namespace http://tampermonkey.net/
  4. // @version 4.80
  5. // @description Copy specific parts of HTML text and send them to Anki, converting relative URLs to absolute URLs. Trigger with Ctrl+Shift+Y or via Tampermonkey menu.
  6. // @author nabe
  7. // @match *://*/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_registerMenuCommand
  10. // @connect localhost
  11. // @run-at document-end
  12. // @license MIT
  13. // ==/UserScript==
  14. (function() {
  15. 'use strict';
  16. function copyHtmlToAnki(generateRandomID = false) {
  17. // Function to convert relative URLs to absolute URLs
  18. function makeAbsolute(url) {
  19. return new URL(url, document.baseURI).href;
  20. }
  21.  
  22.  
  23. // Function to generate a random string of numbers
  24. function generateRandomString(length) {
  25. let result = '';
  26. const characters = '0123456789';
  27. for (let i = 0; i < length; i++) {
  28. result += characters.charAt(Math.floor(Math.random() * characters.length));
  29. }
  30. return result;
  31. }
  32. // Clone the document to manipulate it
  33. let docClone = document.documentElement.cloneNode(true);
  34. // Convert all relative URLs to absolute URLs
  35. let elements = docClone.querySelectorAll('[src], [href]');
  36. elements.forEach(function(element) {
  37. if (element.hasAttribute('src')) {
  38. element.setAttribute('src', makeAbsolute(element.getAttribute('src')));
  39. }
  40. if (element.hasAttribute('href')) {
  41. element.setAttribute('href', makeAbsolute(element.getAttribute('href')));
  42. }
  43. });
  44. // Extract the text content of specific parts needed
  45. let frontElement = docClone.querySelector('.container.card');
  46. let frontField = frontElement ? frontElement.innerHTML : '';
  47. let frontFieldText = frontElement ? frontElement.textContent.trim() : '';
  48. console.log("Front Field:", frontField);
  49. // Capture image URLs specifically within elements with class "group img"
  50. let imageUrls = Array.from(frontElement.querySelectorAll('.group.img img'))
  51. .map(img => img.getAttribute('src'))
  52. .filter(src => src && !src.includes('.svg') && src !== 'Front Image');
  53.  
  54. console.log("Image URLs:", imageUrls);
  55.  
  56. //Capture content from .photo-question
  57. let photoQuestionElement = docClone.querySelector('.solution.container .photo-question');
  58. let photoQuestion = photoQuestionElement ? photoQuestionElement.outerHTML : '';
  59. console.log("Photo Question HTML:", photoQuestion);
  60.  
  61. let questionField = docClone.querySelector('form.question h3')?.innerText.trim() || '';
  62. console.log("Question Field:", questionField);
  63. let optionField = Array.from(docClone.querySelectorAll('.options .option'))
  64. .map(option => option.innerText.trim())
  65. .filter(text => text)
  66. .map(text => `<li>${text}</lis>`)
  67. .join('') || '';
  68. console.log("Option Field:", optionField);
  69. let backField = Array.from(docClone.querySelectorAll('.options .option.correct'))
  70. .map(option => option.innerText.trim())
  71. .filter(text => text)
  72. .map(text => `<li>${text}</li>`)
  73. .join('') || '';
  74. console.log("Answer Field:", backField);
  75. //let extraField = docClone.querySelector('.results.container.collected .feedback-container .text')?.innerText.trim() || '';
  76. //console.log("Additional Info Field:", extraField);
  77. let extraElement = docClone.querySelector('.results.container.collected .feedback-container .text');
  78. let extraField = extraElement ? extraElement.innerHTML : '';
  79. let extraFieldText = docClone.querySelector('.results.container.collected .feedback-container .text')?.innerText.trim() || '';
  80. console.log("Additional Info Field:", extraField);
  81.  
  82. let webpageURL = window.location.href;
  83. console.log("Tag:", webpageURL);
  84. // Generate uniqueID
  85. let uniqueID = generateRandomID ? generateRandomString(10) : questionField.concat(backField).concat(extraField).concat(photoQuestion);
  86. // Create the note fields
  87. let noteFields = {
  88. "Front": frontField.concat("<br>").concat(photoQuestion),
  89. "Question": questionField,
  90. "Options": '<div class="psol">'.concat(optionField).concat("</div>"),
  91. "Back": '<div class="psol">'.concat(backField).concat("</div>"),
  92. "Feedback": extraFieldText,
  93. "Extra": extraField,
  94. "Link": "<a href=".concat(webpageURL).concat(">Link To Card</a>"),
  95. "UniqueID": uniqueID,
  96. "Front Image": imageUrls.join('<br>')
  97. };
  98. console.log("Note fields to be sent to Anki:", noteFields);
  99. GM_xmlhttpRequest({
  100. method: "POST",
  101. url: "http://localhost:8765",
  102. data: JSON.stringify({
  103. "action": "addNote",
  104. "version": 6,
  105. "params": {
  106. "note": {
  107. "deckName": "Default",
  108. "modelName": "uofcCard",
  109. "fields": noteFields,
  110. "tags": [webpageURL]
  111. }
  112. }
  113. }),
  114. headers: {
  115. "Content-Type": "application/json"
  116. },
  117. onload: function(response) {
  118. console.log("Response from AnkiConnect:", response);
  119. if (response.status === 200) {
  120. console.log("Note fields sent to Anki successfully!");
  121. } else {
  122. console.error("Failed to send note fields to Anki.");
  123. }
  124. }
  125. });
  126. }
  127. // Add event listener for the keyboard shortcut (Ctrl+Shift+Y)
  128. document.addEventListener('keydown', function(event) {
  129. if (event.ctrlKey && event.shiftKey && event.code === 'KeyY') {
  130. copyHtmlToAnki();
  131. }
  132. });
  133. // Register the menu command to Tampermonkey menu
  134. GM_registerMenuCommand("Copy HTML to Anki", function() { copyHtmlToAnki(false); });
  135. GM_registerMenuCommand("Copy HTML to Anki with Random ID", function() { copyHtmlToAnki(true); });
  136. })();