├── .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 |
--------------------------------------------------------------------------------
/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 |
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 |
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+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 = `
21 |
22 |
23 | x
24 | =
25 |
26 |
27 |
28 |
29 |
30 |
31 | −b ± √(b² − 4ac)
32 | 2a
33 |
34 |
35 |
36 |
37 |
38 |
39 | `;
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 = `
125 |
126 |
127 | x
128 | =
129 |
130 |
131 |
132 |
133 |
134 |
135 | −b ± √(b² − 4ac)
136 | 2a
137 |
138 |
139 |
140 |
141 |
142 |
143 | `;
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 |
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=0
The formula is:
x=2a−b±b2−4ac​​ where:
a
, b
, and c
are coefficients from the quadratic equation,±
means there are generally two solutions,- is called the discriminant, which determines the nature of the roots:
- If
b2−4ac>0
, there are two distinct real roots. - If
b2−4ac=0
, there is one real root (a repeated root). - If
b2−4ac<0
, the roots are **complex (
`
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);
--------------------------------------------------------------------------------
Select your preferred format:
30 |