mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-04 18:58:39 +00:00
fix(copy-tex): Use JS (instead of CSS) to select full equation, solving display glitches (#3586)
* copy-tex: Use JS to select full equation instead of CSS * remove CSS * Update webpack.common.js * more build tweaks * Update contrib/copy-tex/copy-tex.js Co-authored-by: Erik Demaine <edemaine@mit.edu> * Document new behavior BREAKING CHANGE: copy-tex extension no longer has (or requires) a CSS file. * Code cleanup, lint fixes, port to Flow * Rewrite to extend both start and end of range * Remove contrib/**/*.css linting Co-authored-by: Erik Demaine <edemaine@mit.edu>
This commit is contained in:
committed by
GitHub
parent
023cc0342c
commit
8c2d852c4a
@@ -2,29 +2,27 @@
|
||||
|
||||
This extension modifies the copy/paste behavior in any browser supporting the
|
||||
[Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent)
|
||||
so that, when selecting and copying whole KaTeX-rendered elements, the text
|
||||
so that, when selecting and copying KaTeX-rendered elements, the text
|
||||
content of the resulting clipboard renders KaTeX elements as their LaTeX source
|
||||
surrounded by specified delimiters. (The HTML content of the resulting
|
||||
clipboard remains the selected HTML content, as it normally would.)
|
||||
The default delimiters are `$...$` for inline math and `$$...$$` for display
|
||||
math, but you can easy switch them to e.g. `\(...\)` and `\[...\]` by
|
||||
modifying `copyDelimiters` in [the source code](copy-tex.js).
|
||||
Note that a selection containing part of a KaTeX formula gets extended to
|
||||
include the entire KaTeX formula.
|
||||
|
||||
### Usage
|
||||
## Usage
|
||||
|
||||
This extension isn't part of KaTeX proper, so the script should be separately
|
||||
included in the page. It also provides *optional* custom CSS that
|
||||
defines KaTeX equations as
|
||||
[`user-select: all`](https://developer.mozilla.org/en-US/docs/Web/CSS/user-select)
|
||||
so that they get selected all-or-nothing (and thus trigger the good behavior
|
||||
provided by this extension). Without this CSS, partially selected equations
|
||||
will just get the usual HTML copy/paste behavior.
|
||||
included in the page.
|
||||
|
||||
```html
|
||||
<link href="https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/contrib/copy-tex.css" rel="stylesheet" type="text/css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/contrib/copy-tex.min.js" integrity="sha384-Ep9Es0VCjVn9dFeaN2uQxgGcGmG+pfZ4eBaHxUpxXDORrrVACZVOpywyzvFRGbmv" crossorigin="anonymous"></script>
|
||||
```
|
||||
|
||||
(Note that, as of KaTeX 0.16.0, there is no longer a corresponding CSS file.)
|
||||
|
||||
See [index.html](index.html) for an example.
|
||||
(To run this example from a clone of the repository, run `yarn start`
|
||||
in the root KaTeX directory, and then visit
|
||||
@@ -39,11 +37,3 @@ ECMAScript module is also available:
|
||||
```html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/contrib/copy-tex.mjs" integrity="sha384-+gSYJ3yzY30+a6FGYJXOx9swmWs5oPKEi1AeCsAxsLexABlUXgHXkOkEZCj0Lz8U" crossorigin="anonymous"></script>
|
||||
```
|
||||
|
||||
### Known Issues
|
||||
|
||||
This extension has been tested on Chrome, Firefox, Edge, and Safari.
|
||||
|
||||
Safari copies correctly, but the selection rectangle renders strangely
|
||||
(too big) when interacting with display math
|
||||
(because of the `user-select: all` CSS).
|
||||
|
@@ -1,10 +0,0 @@
|
||||
/* Force selection of entire .katex/.katex-display blocks, so that we can
|
||||
* copy/paste the entire source code. If you omit this CSS, partial
|
||||
* selections of a formula will work, but will copy the ugly HTML
|
||||
* representation instead of the LaTeX source code. (Full selections will
|
||||
* still produce the LaTeX source code.)
|
||||
*/
|
||||
.katex,
|
||||
.katex-display {
|
||||
user-select: all;
|
||||
}
|
@@ -1,23 +1,50 @@
|
||||
// @flow
|
||||
|
||||
import katexReplaceWithTex from './katex2tex';
|
||||
|
||||
// Global copy handler to modify behavior on .katex elements.
|
||||
document.addEventListener('copy', function(event) {
|
||||
// Return <div class="katex"> element containing node, or null if not found.
|
||||
function closestKatex(node: Node): ?Element {
|
||||
// If node is a Text Node, for example, go up to containing Element,
|
||||
// where we can apply the `closest` method.
|
||||
const element: ?Element =
|
||||
(node instanceof Element ? node : node.parentElement);
|
||||
return element && element.closest('.katex');
|
||||
}
|
||||
|
||||
// Global copy handler to modify behavior on/within .katex elements.
|
||||
document.addEventListener('copy', function(event: ClipboardEvent) {
|
||||
const selection = window.getSelection();
|
||||
if (selection.isCollapsed) {
|
||||
return; // default action OK if selection is empty
|
||||
if (selection.isCollapsed || !event.clipboardData) {
|
||||
return; // default action OK if selection is empty or unchangeable
|
||||
}
|
||||
const fragment = selection.getRangeAt(0).cloneContents();
|
||||
const clipboardData = event.clipboardData;
|
||||
const range = selection.getRangeAt(0);
|
||||
|
||||
// When start point is within a formula, expand to entire formula.
|
||||
const startKatex = closestKatex(range.startContainer);
|
||||
if (startKatex) {
|
||||
range.setStartBefore(startKatex);
|
||||
}
|
||||
|
||||
// Similarly, when end point is within a formula, expand to entire formula.
|
||||
const endKatex = closestKatex(range.endContainer);
|
||||
if (endKatex) {
|
||||
range.setEndAfter(endKatex);
|
||||
}
|
||||
|
||||
const fragment = range.cloneContents();
|
||||
if (!fragment.querySelector('.katex-mathml')) {
|
||||
return; // default action OK if no .katex-mathml elements
|
||||
return; // default action OK if no .katex-mathml elements
|
||||
}
|
||||
|
||||
const htmlContents = Array.prototype.map.call(fragment.childNodes,
|
||||
(el) => (el instanceof Text ? el.textContent : el.outerHTML)
|
||||
).join('');
|
||||
|
||||
// Preserve usual HTML copy/paste behavior.
|
||||
const html = [];
|
||||
for (let i = 0; i < fragment.childNodes.length; i++) {
|
||||
html.push(fragment.childNodes[i].outerHTML);
|
||||
}
|
||||
event.clipboardData.setData('text/html', html.join(''));
|
||||
clipboardData.setData('text/html', htmlContents);
|
||||
// Rewrite plain-text version.
|
||||
event.clipboardData.setData('text/plain',
|
||||
clipboardData.setData('text/plain',
|
||||
katexReplaceWithTex(fragment).textContent);
|
||||
// Prevent normal copy handling.
|
||||
event.preventDefault();
|
||||
|
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* This is the webpack entry point for KaTeX. As ECMAScript doesn't support
|
||||
* CSS modules natively, a separate entry point is used.
|
||||
*/
|
||||
import './copy-tex.css';
|
||||
import './copy-tex.js';
|
@@ -1,5 +1,12 @@
|
||||
// @flow
|
||||
|
||||
export interface CopyDelimiters {
|
||||
inline: [string, string],
|
||||
display: [string, string],
|
||||
}
|
||||
|
||||
// Set these to how you want inline and display math to be delimited.
|
||||
export const defaultCopyDelimiters = {
|
||||
export const defaultCopyDelimiters: CopyDelimiters = {
|
||||
inline: ['$', '$'], // alternative: ['\(', '\)']
|
||||
display: ['$$', '$$'], // alternative: ['\[', '\]']
|
||||
};
|
||||
@@ -7,16 +14,18 @@ export const defaultCopyDelimiters = {
|
||||
// Replace .katex elements with their TeX source (<annotation> element).
|
||||
// Modifies fragment in-place. Useful for writing your own 'copy' handler,
|
||||
// as in copy-tex.js.
|
||||
export const katexReplaceWithTex = function(fragment,
|
||||
copyDelimiters = defaultCopyDelimiters) {
|
||||
export function katexReplaceWithTex(
|
||||
fragment: DocumentFragment,
|
||||
copyDelimiters: CopyDelimiters = defaultCopyDelimiters
|
||||
): DocumentFragment {
|
||||
// Remove .katex-html blocks that are preceded by .katex-mathml blocks
|
||||
// (which will get replaced below).
|
||||
const katexHtml = fragment.querySelectorAll('.katex-mathml + .katex-html');
|
||||
for (let i = 0; i < katexHtml.length; i++) {
|
||||
const element = katexHtml[i];
|
||||
if (element.remove) {
|
||||
element.remove(null);
|
||||
} else {
|
||||
element.remove();
|
||||
} else if (element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +38,7 @@ export const katexReplaceWithTex = function(fragment,
|
||||
if (texSource) {
|
||||
if (element.replaceWith) {
|
||||
element.replaceWith(texSource);
|
||||
} else {
|
||||
} else if (element.parentNode) {
|
||||
element.parentNode.replaceChild(texSource, element);
|
||||
}
|
||||
texSource.innerHTML = copyDelimiters.inline[0] +
|
||||
@@ -47,6 +56,6 @@ export const katexReplaceWithTex = function(fragment,
|
||||
+ copyDelimiters.display[1];
|
||||
}
|
||||
return fragment;
|
||||
};
|
||||
}
|
||||
|
||||
export default katexReplaceWithTex;
|
||||
|
@@ -127,7 +127,7 @@
|
||||
"test": "yarn test:lint && yarn test:flow && yarn test:jest",
|
||||
"test:lint": "yarn test:lint:js && yarn test:lint:css",
|
||||
"test:lint:js": "eslint .",
|
||||
"test:lint:css": "stylelint src/katex.less static/main.css contrib/**/*.css website/static/**/*.css",
|
||||
"test:lint:css": "stylelint src/katex.less static/main.css website/static/**/*.css",
|
||||
"test:flow": "flow",
|
||||
"test:jest": "jest",
|
||||
"test:jest:watch": "jest --watch",
|
||||
|
@@ -43,7 +43,7 @@ const targets /*: Array<Target> */ = [
|
||||
},
|
||||
{
|
||||
name: 'contrib/copy-tex',
|
||||
entry: './contrib/copy-tex/copy-tex.webpack.js',
|
||||
entry: './contrib/copy-tex/copy-tex.js',
|
||||
},
|
||||
{
|
||||
name: 'contrib/mathtex-script-type',
|
||||
|
@@ -17,7 +17,6 @@ fs.copySync('../dist/katex.min.css', 'static/static/katex.min.css');
|
||||
fs.copySync('../dist/fonts', 'static/static/fonts');
|
||||
|
||||
// copy local built KaTeX extensions
|
||||
fs.copySync('../dist/contrib/copy-tex.min.css', 'static/static/copy-tex.min.css');
|
||||
fs.copySync('../dist/contrib/copy-tex.min.js', 'static/static/copy-tex.min.js');
|
||||
fs.copySync('../dist/contrib/mhchem.min.js', 'static/static/mhchem.min.js');
|
||||
|
||||
|
@@ -24,7 +24,6 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:300,400,700,700i">
|
||||
<link rel="stylesheet" href="static/katex.min.css" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="static/index.css">
|
||||
<link rel="stylesheet" href="static/copy-tex.min.css" crossorigin="anonymous">
|
||||
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/clipboard@2.0.1/dist/clipboard.min.js" integrity="sha256-hIvIxeqhGZF+VVeM55k0mJvWpQ6gTkWk3Emc+NmowYA=" crossorigin="anonymous"></script>
|
||||
<script defer src="static/katex.min.js" crossorigin="anonymous"></script>
|
||||
|
Reference in New Issue
Block a user