├── .DS_Store ├── design ├── logoGPT.png └── logo.svg ├── useless ├── mathml.html ├── rtf.html ├── radio.html ├── test.html ├── mathjax-config.js ├── index.html ├── radio.css ├── markupToMathml.js ├── google.html ├── latextogoogle.js ├── style.css ├── binreader.html ├── convert.js └── rtf.js ├── dist ├── manifest.json ├── styles.css ├── popup.js ├── popup.css ├── content.js └── popup.html └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeeliang/gptCopy/HEAD/.DS_Store -------------------------------------------------------------------------------- /design/logoGPT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeeliang/gptCopy/HEAD/design/logoGPT.png -------------------------------------------------------------------------------- /useless/mathml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | a 4 | + 5 | b 6 | 7 | + 8 | c 9 | 10 | 11 | -------------------------------------------------------------------------------- /useless/rtf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rtf 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "GPTCopy", 4 | "version": "1.0", 5 | "description": "Copy ChatGPT equations to Word on click", 6 | "permissions": [ 7 | "clipboardWrite", 8 | "tabs", 9 | "activeTab", 10 | "storage" 11 | ], 12 | "action": { 13 | "default_popup": "popup.html" 14 | }, 15 | "content_scripts": [ 16 | { 17 | "matches": ["*://chatgpt.com/*"], 18 | "js": ["content.js"], 19 | "css": ["styles.css"] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /useless/radio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 |
11 |
12 |

Copy to:

13 | 17 | 21 | 25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /useless/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Convert 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | The formula for the slope of a line is given by the equation: 15 | 16 | 17 | BIG NIG 18 | 19 | m=riserun=y2y1x2x1 20 | 21 | m = \frac{\text{rise}}{\text{run}} = \frac{y_2 - y_1}{x_2 - x_1} 22 | 23 |
24 | T -------------------------------------------------------------------------------- /useless/mathjax-config.js: -------------------------------------------------------------------------------- 1 | // Set the equation as a string 2 | const equation = "x = \\frac{a + b}{c}"; 3 | 4 | // Set the formatting metadata 5 | const formatMetadata = { 6 | "font-family": "Cambria Math", 7 | "font-size": "18px", 8 | "font-style": "italic", 9 | "math-variant": "italic", 10 | }; 11 | 12 | // Create a TextFragment with the equation and metadata 13 | const textFragment = new TextFragment(equation, { 14 | formats: { 15 | "text/html": `${equation}`, 16 | "application/xhtml+xml": `${equation}`, 17 | "text/plain": equation, 18 | }, 19 | metadata: { 20 | "org.openxmlformats.office.spreadsheetml.sheet.formula": equation, 21 | "application/vnd.google_docs.formula": equation, 22 | }, 23 | }); 24 | 25 | // Copy the TextFragment to the clipboard 26 | navigator.clipboard.writeText(textFragment).then(() => { 27 | console.log("Equation copied to clipboard with metadata!"); 28 | }); 29 | -------------------------------------------------------------------------------- /useless/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LaTeX to MathML Conversion 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 30 | 31 | -------------------------------------------------------------------------------- /dist/styles.css: -------------------------------------------------------------------------------- 1 | .katex-wrapper { 2 | position: relative; 3 | display: inline-block; 4 | padding: 2px 3px; 5 | border-radius: .5rem; 6 | cursor: pointer; 7 | } 8 | 9 | .katex-wrapper.katex-hover { 10 | background-color: #2f2f2f; 11 | z-index: 100; 12 | } 13 | 14 | .katex-tooltip { 15 | position: fixed; 16 | display: none; 17 | background-color: #09090b; 18 | color: #fff; 19 | padding: 5px 10px; 20 | border-radius: 4px; 21 | font-size: 14px; 22 | z-index: 1000; 23 | font-family: system-ui, -apple-system, sans-serif; 24 | animation: tooltipSlideUpAndFade 0.4s cubic-bezier(0.16, 1, 0.3, 1); 25 | pointer-events: none; 26 | user-select: none; 27 | } 28 | 29 | .tooltip-content { 30 | display: block; 31 | position: relative; 32 | z-index: 2; 33 | pointer-events: none; 34 | } 35 | 36 | .tooltip-arrow { 37 | position: absolute; 38 | top: -4px; /* Changed to top since tooltip is now below */ 39 | left: 50%; 40 | width: 8px; 41 | height: 8px; 42 | background-color: #09090b; 43 | transform-origin: center; 44 | transform: translateX(-50%) rotate(45deg); 45 | pointer-events: none; 46 | } 47 | 48 | @keyframes tooltipSlideUpAndFade { 49 | from { 50 | opacity: 0; 51 | transform: translateY(2px); 52 | } 53 | to { 54 | opacity: 1; 55 | transform: translateY(0); 56 | } 57 | } -------------------------------------------------------------------------------- /dist/popup.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | // Get the buttons 3 | const latexButton = document.getElementById('format1'); 4 | const mathmlButton = document.getElementById('format2'); 5 | 6 | // Add event listeners to buttons 7 | latexButton.addEventListener('click', function() { 8 | chrome.storage.sync.set({ format: 'latex' }, function() { 9 | console.log("Format set to LaTeX"); 10 | updateButtonStyles('latex'); 11 | }); 12 | }); 13 | 14 | mathmlButton.addEventListener('click', function() { 15 | chrome.storage.sync.set({ format: 'mathml' }, function() { 16 | console.log("Format set to MathML"); 17 | updateButtonStyles('mathml'); 18 | }); 19 | }); 20 | 21 | // Function to update button styles 22 | function updateButtonStyles(selectedFormat) { 23 | // Remove selected class from all buttons 24 | latexButton.classList.remove('button-selected'); 25 | mathmlButton.classList.remove('button-selected'); 26 | 27 | // Add selected class to active button 28 | if (selectedFormat === 'latex') { 29 | latexButton.classList.add('button-selected'); 30 | } else if (selectedFormat === 'mathml') { 31 | mathmlButton.classList.add('button-selected'); 32 | } 33 | console.log('changed css'); 34 | } 35 | 36 | // Initialize with stored format or default to 'latex' 37 | chrome.storage.sync.get('format', function(result) { 38 | const storedFormat = result.format || 'latex'; 39 | updateButtonStyles(storedFormat); 40 | }); 41 | }); -------------------------------------------------------------------------------- /useless/radio.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:after, 3 | *:before { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: "Inter", sans-serif; 9 | color: #000039; 10 | font-size: calc(1em + 1.25vw); 11 | background-color: #e6e6f2; 12 | } 13 | 14 | form { 15 | display: flex; 16 | flex-wrap: wrap; 17 | flex-direction: column; 18 | } 19 | 20 | label { 21 | display: flex; 22 | cursor: pointer; 23 | font-weight: 500; 24 | position: relative; 25 | overflow: hidden; 26 | margin-bottom: 0.375em; 27 | } 28 | 29 | /* Accessible outline */ 30 | /* Remove comment to use */ 31 | /* 32 | label:focus-within { 33 | outline: 0.125em solid #00005c; 34 | } 35 | */ 36 | 37 | label input { 38 | position: absolute; 39 | left: -9999px; 40 | } 41 | 42 | /* label input:checked + span { 43 | background-color: #dedee9; 44 | } */ 45 | 46 | label input:checked + span:before { 47 | box-shadow: inset 0 0 0 0.4375em #00005c; 48 | } 49 | 50 | label span { 51 | display: flex; 52 | align-items: center; 53 | padding: 0.375em 0.75em 0.375em 0.375em; 54 | border-radius: 99em; 55 | transition: 0.25s ease; 56 | } 57 | 58 | label span:hover { 59 | background-color: #d4d4eb; 60 | } 61 | 62 | label span:before { 63 | display: flex; 64 | flex-shrink: 0; 65 | content: ""; 66 | background-color: #fff; 67 | width: 1.5em; 68 | height: 1.5em; 69 | border-radius: 50%; 70 | margin-right: 0.375em; 71 | transition: 0.25s ease; 72 | box-shadow: inset 0 0 0 0.125em #00005c; 73 | } 74 | 75 | .container { 76 | position: absolute; 77 | top: 0; 78 | left: 0; 79 | right: 0; 80 | bottom: 0; 81 | width: 100%; 82 | display: flex; 83 | justify-content: center; 84 | align-items: center; 85 | padding: 20px; 86 | } -------------------------------------------------------------------------------- /useless/markupToMathml.js: -------------------------------------------------------------------------------- 1 | function katexToMathML(html) { 2 | // Create a DOM parser 3 | const parser = new DOMParser(); 4 | const doc = parser.parseFromString(html, 'text/html'); 5 | 6 | // Find all KaTeX elements 7 | const katexElements = doc.querySelectorAll('.katex-mathml'); 8 | 9 | // Extract MathML from each element 10 | const mathMLStrings = Array.from(katexElements).map(element => { 11 | const mathElement = element.querySelector('math'); 12 | return mathElement ? mathElement.outerHTML : ''; 13 | }); 14 | 15 | return mathMLStrings; 16 | } 17 | 18 | // Example usage: 19 | const html = `

The equation y=mx+by = mx + b

is the slope-intercept form of a linear equation, which describes a straight line on a Cartesian plane.

`; 20 | 21 | const mathMLResults = katexToMathML(html); 22 | console.log(mathMLResults[0]); -------------------------------------------------------------------------------- /useless/google.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LaTeX to Google Docs Converter 6 | 50 | 51 | 52 |
53 |

LaTeX to Google Docs Equation Converter

54 |
55 | 56 | 57 |
58 | 59 |
60 | 61 |
62 |
63 | 64 |
Copied to clipboard!
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPTCopy 2 | 3 | GPTCopy is a browser extension that allows users to copy equations from ChatGPT to Word with a single click. The extension detects KaTeX-rendered equations on the ChatGPT website and provides a tooltip to copy the equation in MathML or LaTeX format. 4 | 5 | ## Features 6 | 7 | - Detects KaTeX-rendered equations on ChatGPT. 8 | - Provides a tooltip to copy equations in MathML or LaTeX format. 9 | - Automatically hides tooltips on scroll. 10 | - Supports dynamic content with a MutationObserver. 11 | 12 | ## Installation 13 | 14 | 1. Clone the repository to your local machine: 15 | ```sh 16 | git clone https://github.com/yourusername/GPTCopy.git 17 | ``` 18 | 19 | 2. Navigate to the project directory: 20 | ```sh 21 | cd GPTCopy/gptCopy/dist 22 | ``` 23 | 24 | 3. Load the extension in your browser: 25 | - Open Chrome and go to `chrome://extensions/`. 26 | - Enable "Developer mode" in the top right corner. 27 | - Click "Load unpacked" and select the `dist` directory of the project. 28 | 29 | Alternatively, you can install the extension directly from the Chrome Web Store: 30 | [GPTCopy on Chrome Web Store](https://chromewebstore.google.com/detail/gptcopy/ibnfnmnloecjgmephfokgmlppccheoed) 31 | 32 | ## Usage 33 | 34 | 1. Navigate to the ChatGPT website. 35 | 2. Hover over any KaTeX-rendered equation to see the "Copy" tooltip. 36 | 3. Click on the tooltip to copy the equation in the selected format (MathML or LaTeX). 37 | 4. Paste the copied equation into Word or any other application that supports MathML or LaTeX. 38 | 39 | ## Configuration 40 | 41 | You can configure the format (MathML or LaTeX) by setting the `format` key in Chrome's storage. By default, the format is set to MathML. 42 | 43 | ## Development 44 | 45 | ### Project Structure 46 | 47 | - `dist/manifest.json`: The extension manifest file. 48 | - `dist/content.js`: The content script that handles equation detection and copying. 49 | - `dist/styles.css`: The stylesheet for the tooltip. 50 | - `useless/test.html`: A test HTML file for development purposes. 51 | 52 | ### Running the Extension 53 | 54 | 1. Make changes to the code in the `dist` directory. 55 | 2. Reload the extension in Chrome by going to `chrome://extensions/` and clicking the reload button for GPTCopy. 56 | 57 | ## Contributing 58 | 59 | Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes. 60 | 61 | ## License 62 | 63 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 64 | -------------------------------------------------------------------------------- /useless/latextogoogle.js: -------------------------------------------------------------------------------- 1 | function latexToGoogleDocs(latex) { 2 | // Remove any outer math delimiters if present 3 | latex = latex.replace(/^\$|\$$/g, '') 4 | .replace(/^\\\(|\\\)$/g, '') 5 | .replace(/^\\\[|\\\]$/g, ''); 6 | 7 | // Create base HTML structure 8 | let output = ''; 10 | 11 | // Common style for all spans 12 | const baseStyle = 'font-size:12pt;font-family:\'Times New Roman\',serif;color:#000000;' + 13 | 'background-color:transparent;font-weight:400;font-style:normal;' + 14 | 'font-variant:normal;text-decoration:none;vertical-align:baseline;' + 15 | 'white-space:pre;white-space:pre-wrap;'; 16 | 17 | function createSpan(content) { 18 | return `${content}`; 19 | } 20 | 21 | // Handle superscripts (like x^2) 22 | latex = latex.replace(/([a-zA-Z0-9])\^([0-9])/g, (match, base, exp) => { 23 | return createSpan(base) + createSpan(exp); 24 | }); 25 | 26 | // Handle subscripts (like x_n) 27 | latex = latex.replace(/([a-zA-Z0-9])_([0-9])/g, (match, base, sub) => { 28 | return createSpan(base) + createSpan(sub); 29 | }); 30 | 31 | // Handle basic operators 32 | const operators = { 33 | '\\times': '×', 34 | '\\div': '÷', 35 | '\\pm': '±', 36 | '=': '=', 37 | '+': '+', 38 | '-': '-' 39 | }; 40 | 41 | for (let [latexOp, symbol] of Object.entries(operators)) { 42 | latex = latex.replace(new RegExp(latexOp.replace(/\\/g, '\\\\'), 'g'), symbol); 43 | } 44 | 45 | // Split the equation into parts and wrap each in a span 46 | const parts = latex.split(/([+\-=×÷±])/); 47 | output += parts.map(part => part ? createSpan(part) : '').join(''); 48 | 49 | output += ''; 50 | return output; 51 | } 52 | 53 | // Helper function to generate a guid-like string for the internal ID 54 | function generateGuid() { 55 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 56 | const r = Math.random() * 16 | 0; 57 | const v = c === 'x' ? r : (r & 0x3 | 0x8); 58 | return v.toString(16); 59 | }); 60 | } 61 | 62 | function convertEquation() { 63 | const input = document.getElementById('latex').value; 64 | const result = latexToGoogleDocs(input); 65 | document.getElementById('output').textContent = result; 66 | } 67 | 68 | function copyToClipboard() { 69 | const output = document.getElementById('output').textContent; 70 | navigator.clipboard.writeText(output).then(() => { 71 | const message = document.getElementById('copyMessage'); 72 | message.style.display = 'block'; 73 | setTimeout(() => { 74 | message.style.display = 'none'; 75 | }, 2000); 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /useless/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Black Alpha */ 3 | --black-alpha-100: rgba(0, 0, 0, 0.1); 4 | --black-alpha-200: rgba(0, 0, 0, 0.2); 5 | --black-alpha-300: rgba(0, 0, 0, 0.3); 6 | --black-alpha-400: rgba(0, 0, 0, 0.4); 7 | --black-alpha-500: rgba(0, 0, 0, 0.5); 8 | --black-alpha-600: rgba(0, 0, 0, 0.6); 9 | --black-alpha-700: rgba(0, 0, 0, 0.7); 10 | --black-alpha-800: rgba(0, 0, 0, 0.8); 11 | --black-alpha-900: rgba(0, 0, 0, 0.9); 12 | 13 | /* Violet */ 14 | --violet-1: #7c3aed; 15 | --violet-2: #6c2ce8; 16 | --violet-3: #5b26d6; 17 | --violet-4: #4a1bc2; 18 | --violet-5: #3709a9; 19 | --violet-6: #290585; 20 | --violet-7: #1d0066; 21 | --violet-8: #120044; 22 | --violet-9: #080022; 23 | } 24 | 25 | /* reset */ 26 | button { 27 | all: unset; 28 | } 29 | 30 | .TooltipContent { 31 | border-radius: 4px; 32 | padding: 10px 15px; 33 | font-size: 15px; 34 | line-height: 1; 35 | color: var(--violet-11); 36 | background-color: white; 37 | box-shadow: 38 | hsl(206 22% 7% / 35%) 0px 10px 38px -10px, 39 | hsl(206 22% 7% / 20%) 0px 10px 20px -15px; 40 | user-select: none; 41 | animation-duration: 400ms; 42 | animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); 43 | will-change: transform, opacity; 44 | } 45 | .TooltipContent[data-state="delayed-open"][data-side="top"] { 46 | animation-name: slideDownAndFade; 47 | } 48 | .TooltipContent[data-state="delayed-open"][data-side="right"] { 49 | animation-name: slideLeftAndFade; 50 | } 51 | .TooltipContent[data-state="delayed-open"][data-side="bottom"] { 52 | animation-name: slideUpAndFade; 53 | } 54 | .TooltipContent[data-state="delayed-open"][data-side="left"] { 55 | animation-name: slideRightAndFade; 56 | } 57 | 58 | .TooltipArrow { 59 | fill: white; 60 | } 61 | 62 | .IconButton { 63 | font-family: inherit; 64 | border-radius: 100%; 65 | height: 35px; 66 | width: 35px; 67 | display: inline-flex; 68 | align-items: center; 69 | justify-content: center; 70 | color: var(--violet-11); 71 | background-color: white; 72 | box-shadow: 0 2px 10px black; 73 | user-select: none; 74 | } 75 | .IconButton:hover { 76 | background-color: var(--violet-3); 77 | } 78 | .IconButton:focus { 79 | box-shadow: 0 0 0 2px black; 80 | } 81 | 82 | @keyframes slideUpAndFade { 83 | from { 84 | opacity: 0; 85 | transform: translateY(2px); 86 | } 87 | to { 88 | opacity: 1; 89 | transform: translateY(0); 90 | } 91 | } 92 | 93 | @keyframes slideRightAndFade { 94 | from { 95 | opacity: 0; 96 | transform: translateX(-2px); 97 | } 98 | to { 99 | opacity: 1; 100 | transform: translateX(0); 101 | } 102 | } 103 | 104 | @keyframes slideDownAndFade { 105 | from { 106 | opacity: 0; 107 | transform: translateY(-2px); 108 | } 109 | to { 110 | opacity: 1; 111 | transform: translateY(0); 112 | } 113 | } 114 | 115 | @keyframes slideLeftAndFade { 116 | from { 117 | opacity: 0; 118 | transform: translateX(2px); 119 | } 120 | to { 121 | opacity: 1; 122 | transform: translateX(0); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /dist/popup.css: -------------------------------------------------------------------------------- 1 | html { 2 | /* height: 100%; */ 3 | background: #141414; 4 | } 5 | 6 | body { 7 | width: 250px; 8 | margin: 0; 9 | padding: 0; 10 | background: #141414; 11 | } 12 | 13 | .container { 14 | position: relative; /* Changed from absolute */ 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | align-items: center; 19 | margin: 20px 0px; 20 | /* padding: 20px; */ 21 | } 22 | 23 | .extension-preview { 24 | width: 300px; /* Changed from 100px */ 25 | height: auto; 26 | margin: 20px; /* Changed from 50px */ 27 | border: 1px solid #333; 28 | background: #141414; 29 | padding: 20px; 30 | } 31 | 32 | #header { 33 | width: 100%; /* Changed from 50% */ 34 | display: flex; 35 | align-items: center; 36 | justify-content: center; 37 | margin-bottom: 20px; 38 | } 39 | 40 | #header > svg { 41 | width: 41px; /* Changed to match viewBox width */ 42 | height: 41px; /* Changed to match viewBox height */ 43 | flex-shrink: 0; 44 | } 45 | 46 | #logoword { 47 | color: #FFF; 48 | font-family: Katibeh; 49 | font-size: 35px; 50 | font-style: normal; 51 | font-weight: 400; 52 | line-height: normal; 53 | margin: 0 0 0 10px; /* Added margin-left */ 54 | float: none; /* Remove float */ 55 | } 56 | 57 | /* .container { 58 | position: absolute; 59 | top: 0; 60 | left: 0; 61 | right: 0; 62 | bottom: 0; 63 | width: 100%; 64 | display: flex; 65 | flex-wrap: wrap; 66 | justify-content: center; 67 | align-items: center; 68 | padding: 20px; 69 | } */ 70 | 71 | .comments { 72 | color: #CECECE; 73 | text-align: center; 74 | font-family: "Expletus Sans"; 75 | font-size: 12px; 76 | font-style: normal; 77 | font-weight: 400; 78 | line-height: normal; 79 | } 80 | 81 | .buttons { 82 | display: flex; 83 | flex-wrap: wrap; 84 | justify-content: center; 85 | align-items: center; 86 | margin-top: 20px; 87 | } 88 | 89 | 90 | .buttons > button{ 91 | margin: 3px; 92 | width: 192px; 93 | height: 39px; 94 | flex-shrink: 0; 95 | border: none; 96 | color: white; 97 | text-align: center; 98 | font-size: 16px; 99 | color: #FFF; 100 | border-radius: 8px; 101 | text-align: center; 102 | font-family: "Expletus Sans"; 103 | /* i dont think this font exists */ 104 | font-size: 14px; 105 | font-style: normal; 106 | font-weight: 400; 107 | line-height: normal; 108 | background-color: #222; 109 | } 110 | 111 | /* Add hover effect for better UX */ 112 | .buttons > button:hover { 113 | background-color: #333; 114 | } 115 | 116 | .buttons > button.button-selected { 117 | background-color: #444 !important; 118 | } 119 | 120 | .wrapper { 121 | width: 100%; 122 | justify-content: center; 123 | align-items: center; 124 | } 125 | 126 | hr { 127 | width: 1px; 128 | height: 15px; /* Adjust height as needed */ 129 | border: none; 130 | background-color: #CECECE; 131 | margin: 0; 132 | display: inline-block; 133 | vertical-align: middle; 134 | } 135 | #footer { 136 | margin-top: 14px; 137 | } 138 | 139 | .comments a { 140 | margin: 10px; 141 | color: #CECECE; 142 | text-decoration: none; 143 | } 144 | 145 | .comments a:hover { 146 | color: #FFF; 147 | } -------------------------------------------------------------------------------- /useless/binreader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Clipboard Debug Tool 5 | 41 | 42 | 43 |

Clipboard Debug Tool

44 | 45 | 46 | 47 | 48 | 49 |

Available Formats:

50 |
51 | 52 |

Content:

53 |
54 | 55 | 110 | 111 | -------------------------------------------------------------------------------- /dist/content.js: -------------------------------------------------------------------------------- 1 | function createTooltip() { 2 | const tooltip = document.createElement('div'); 3 | tooltip.className = 'katex-tooltip'; 4 | tooltip.innerHTML = ` 5 |
6 | Copy 7 | `; 8 | return tooltip; 9 | } 10 | 11 | function setupEquation(equation) { 12 | const tooltip = createTooltip(); 13 | document.body.appendChild(tooltip); 14 | 15 | // Create a wrapper div if it doesn't exist 16 | let wrapper = equation.parentElement.querySelector('.katex-wrapper'); 17 | if (!wrapper) { 18 | wrapper = document.createElement('div'); 19 | wrapper.className = 'katex-wrapper'; 20 | equation.parentElement.insertBefore(wrapper, equation); 21 | wrapper.appendChild(equation); 22 | } 23 | 24 | function updateTooltipPosition() { 25 | const rect = wrapper.getBoundingClientRect(); 26 | tooltip.style.top = `${rect.bottom + 5}px`; 27 | tooltip.style.left = `${rect.left + (rect.width - tooltip.offsetWidth) / 2}px`; 28 | } 29 | 30 | wrapper.addEventListener('mouseenter', (event) => { 31 | wrapper.classList.add('katex-hover'); 32 | tooltip.style.display = 'block'; 33 | updateTooltipPosition(); 34 | }); 35 | 36 | wrapper.addEventListener('mouseleave', (event) => { 37 | wrapper.classList.remove('katex-hover'); 38 | tooltip.style.display = 'none'; 39 | }); 40 | 41 | // Turn green on mousedown 42 | wrapper.addEventListener('mousedown', () => { 43 | wrapper.style.backgroundColor = 'rgba(0, 0, 0, 0.05)'; 44 | }); 45 | 46 | // Copy MathML on mouseup 47 | wrapper.addEventListener('mouseup', async () => { 48 | try { 49 | const format = await chrome.storage.sync.get('format'); 50 | console.log('Format:', format); 51 | let mathmlContent; 52 | if (format.format === 'latex') { 53 | mathmlContent = equation.closest('.katex').querySelector('annotation').innerHTML; 54 | } else { 55 | mathmlContent = equation.closest('.katex').querySelector('.katex-mathml math').outerHTML;; 56 | } 57 | 58 | wrapper.style.backgroundColor = ''; 59 | if (mathmlContent) { 60 | navigator.clipboard.writeText(mathmlContent).then(() => { 61 | tooltip.querySelector('.tooltip-content').textContent = '✓'; 62 | updateTooltipPosition(); 63 | setTimeout(() => { 64 | tooltip.querySelector('.tooltip-content').textContent = 'Copy'; 65 | updateTooltipPosition(); 66 | tooltip.style.display = 'none'; 67 | }, 800); 68 | }).catch(err => { 69 | console.error('Failed to copy to clipboard:', err); 70 | wrapper.style.backgroundColor = '#FFB6C1'; 71 | setTimeout(() => { 72 | wrapper.style.backgroundColor = ''; 73 | }, 200); 74 | }); 75 | } 76 | } catch (error) { 77 | console.error('Error processing equation:', error); 78 | } 79 | }); 80 | 81 | } 82 | 83 | function initialize() { 84 | console.log('Initializing KaTeX MathML Copier...'); 85 | 86 | // Select all KaTeX elements 87 | const equations = document.querySelectorAll('.katex-html:not([data-katex-processed])'); 88 | console.log(`Found ${equations.length} KaTeX equations`); 89 | 90 | equations.forEach(equation => { 91 | console.log(`Setting up equation`); 92 | setupEquation(equation); 93 | equation.setAttribute('data-katex-processed', 'true'); 94 | }); 95 | 96 | // Add mutation observer for new equations 97 | const observer = new MutationObserver(mutations => { 98 | const newEquations = document.querySelectorAll('.katex-html:not([data-katex-processed])'); 99 | if (newEquations.length > 0) { 100 | console.log(`Found ${newEquations.length} new equations`); 101 | newEquations.forEach(equation => { 102 | setupEquation(equation); 103 | equation.setAttribute('data-katex-processed', 'true'); 104 | }); 105 | } 106 | }); 107 | 108 | observer.observe(document.body, { 109 | childList: true, 110 | subtree: true 111 | }); 112 | } 113 | 114 | // Start when DOM is ready 115 | if (document.readyState === 'loading') { 116 | console.log('Document still loading, adding DOMContentLoaded listener'); 117 | document.addEventListener('DOMContentLoaded', initialize); 118 | } else { 119 | console.log('Document already loaded, initializing immediately'); 120 | initialize(); 121 | } 122 | 123 | // Hide all tooltips on scroll 124 | window.addEventListener('wheel', () => { 125 | const tooltips = document.querySelectorAll('.katex-tooltip'); 126 | tooltips.forEach(tooltip => { 127 | tooltip.style.display = 'none'; 128 | }); 129 | const equations = document.querySelectorAll('.katex-html'); 130 | equations.forEach(equation => { 131 | equation.classList.remove('katex-hover'); 132 | }); 133 | }, { passive: true }); -------------------------------------------------------------------------------- /useless/convert.js: -------------------------------------------------------------------------------- 1 | class KaTeXConverter { 2 | constructor() { 3 | this.katexClassMap = { 4 | 'mord': 'mi', 5 | 'mbin': 'mo', 6 | 'mrel': 'mo', 7 | 'mopen': 'mo', 8 | 'mclose': 'mo', 9 | 'mpunct': 'mo', 10 | 'minner': 'mrow', 11 | 'mfrac': 'mfrac', 12 | 'msqrt': 'msqrt', 13 | 'msup': 'msup' 14 | }; 15 | } 16 | 17 | convert(katexHTML) { 18 | const parser = new DOMParser(); 19 | const doc = parser.parseFromString(katexHTML, 'text/html'); 20 | const katexElement = ``; 40 | 41 | // const katexElement = doc.querySelector('.katex-html'); 42 | 43 | if (!katexElement) { 44 | throw new Error('No KaTeX HTML element found'); 45 | } 46 | 47 | const mathML = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'math'); 48 | mathML.setAttribute('xmlns', 'http://www.w3.org/1998/Math/MathML'); 49 | 50 | const semantics = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'semantics'); 51 | const mrow = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mrow'); 52 | 53 | this.processKaTeXElement(katexElement, mrow); 54 | 55 | semantics.appendChild(mrow); 56 | mathML.appendChild(semantics); 57 | 58 | return mathML; 59 | } 60 | 61 | processKaTeXElement(element, parentMathML) { 62 | for (const child of element.childNodes) { 63 | if (child.nodeType === Node.TEXT_NODE) { 64 | if (child.textContent.trim()) { 65 | const mi = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mi'); 66 | mi.textContent = child.textContent.trim(); 67 | parentMathML.appendChild(mi); 68 | } 69 | continue; 70 | } 71 | 72 | if (child.nodeType !== Node.ELEMENT_NODE) continue; 73 | 74 | const katexClasses = Array.from(child.classList) 75 | .filter(cls => cls in this.katexClassMap); 76 | 77 | if (katexClasses.length === 0) { 78 | this.processKaTeXElement(child, parentMathML); 79 | continue; 80 | } 81 | 82 | const mathMLTag = this.katexClassMap[katexClasses[0]]; 83 | const mathMLElement = document.createElementNS('http://www.w3.org/1998/Math/MathML', mathMLTag); 84 | 85 | // Handle special cases 86 | if (mathMLTag === 'mfrac') { 87 | const num = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mrow'); 88 | const den = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mrow'); 89 | 90 | const numerator = child.querySelector('.vlist > span:last-child .mord'); 91 | const denominator = child.querySelector('.vlist > span:first-child .mord'); 92 | 93 | if (numerator) this.processKaTeXElement(numerator, num); 94 | if (denominator) this.processKaTeXElement(denominator, den); 95 | 96 | mathMLElement.appendChild(num); 97 | mathMLElement.appendChild(den); 98 | } else if (mathMLTag === 'msup') { 99 | const base = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mrow'); 100 | const sup = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mrow'); 101 | 102 | const baseElement = child.querySelector('.vlist > span:last-child'); 103 | const supElement = child.querySelector('.vlist > span:first-child'); 104 | 105 | if (baseElement) this.processKaTeXElement(baseElement, base); 106 | if (supElement) this.processKaTeXElement(supElement, sup); 107 | 108 | mathMLElement.appendChild(base); 109 | mathMLElement.appendChild(sup); 110 | } else { 111 | this.processKaTeXElement(child, mathMLElement); 112 | } 113 | 114 | parentMathML.appendChild(mathMLElement); 115 | } 116 | } 117 | } 118 | 119 | // Create an instance of the converter 120 | const converter = new KaTeXConverter(); 121 | 122 | // Get your KaTeX HTML element 123 | // const katexHTML = document.querySelector('.katex-html').outerHTML; 124 | const katexHTML = ``; 144 | 145 | // Convert to MathML 146 | try { 147 | const mathML = converter.convert(katexHTML); 148 | console.log(mathML.outerHTML); 149 | } catch (error) { 150 | console.error('Conversion failed:', error); 151 | } -------------------------------------------------------------------------------- /dist/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Math Format Selector 6 | 7 | 8 | 9 |
10 | 27 |
28 |
29 |

Select your preferred format:

30 |
31 |
32 |
33 | 34 | 35 | 36 |
37 |
38 |
39 | 44 |
45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /design/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /useless/rtf.js: -------------------------------------------------------------------------------- 1 | async function copyCanvasContentsToClipboard() { 2 | try { 3 | // Create the HTML content with MathML 4 | const htmlContent = `

The quadratic formula is used to find the solutions (or roots) of a quadratic equation of the form:

ax2+bx+c=0ax^2 + bx + c = 0

The formula is:

x=b±b24ac2ax = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}

where:

` 27 | 28 | // Create a Blob with the HTML content 29 | const blob = new Blob([htmlContent], { type: 'text/html' }); 30 | 31 | // Create ClipboardItem with the blob 32 | const clipboardItem = new ClipboardItem({ 33 | 'text/html': blob 34 | }); 35 | 36 | // Write to clipboard 37 | await navigator.clipboard.write([clipboardItem]); 38 | 39 | console.log("Copied to clipboard successfully"); 40 | } catch (error) { 41 | console.error("Failed to copy:", error); 42 | } 43 | } 44 | 45 | // Add event listener to button 46 | document.getElementById('copyButton').addEventListener('click', copyCanvasContentsToClipboard); --------------------------------------------------------------------------------