copy-tex contrib module (#813)

* copy-tex contrib module

* Factor out replacement code

* Fix lint

* Support for more browsers, in particular Firefox

* Use for loop instead of Array.forEach
* Use replaceChild if replaceWith is unavailable
* Browserify to remove let etc.

* Fix HTML handling, in particular for Edge

* Convert DocumentFragment to HTML directly (via children' outerHTML)
* Set HTML content *before* text content; Edge takes the last only

* Handle .katex-html and .katex-mathml separately

* Implement option 2: CSS user-select: all

Also fix auto-render.js build location

* Revise documentation according to @kevinbarabash's comments

* Split copy-tex.js into it + katex2tex.js

This supports re-use of code in a custom copy handler.

* Document custom copy handler

* Add missing file
This commit is contained in:
Erik Demaine
2017-09-05 10:17:46 +09:00
committed by Kevin Barabash
parent ca224eda81
commit e71c7d4b81
9 changed files with 200 additions and 3 deletions

View File

@@ -2,8 +2,8 @@
build: $(BUILDDIR)/contrib/auto-render.min.js
$(BUILDDIR)/contrib/auto-render.min.js: $(BUILDDIR)/auto-render.js
$(BUILDDIR)/contrib/auto-render.min.js: $(BUILDDIR)/contrib/auto-render.js
$(UGLIFYJS) < $< > $@
$(BUILDDIR)/auto-render.js: auto-render.js
$(BUILDDIR)/contrib/auto-render.js: auto-render.js
$(BROWSERIFY) -t [ babelify ] $< --standalone renderMathInElement > $@

15
contrib/copy-tex/Makefile Normal file
View File

@@ -0,0 +1,15 @@
.PHONY: build
build: $(BUILDDIR)/contrib/copy-tex.min.js $(BUILDDIR)/contrib/copy-tex.css $(BUILDDIR)/contrib/copy-tex.min.css
$(BUILDDIR)/contrib/copy-tex.min.js: $(BUILDDIR)/contrib/copy-tex.js
$(UGLIFYJS) < $< > $@
$(BUILDDIR)/contrib/copy-tex.js: copy-tex.js
$(BROWSERIFY) -t [ babelify ] $< --standalone renderMathInElement > $@
$(BUILDDIR)/contrib/copy-tex.css: copy-tex.css
cp $< $@
$(BUILDDIR)/contrib/copy-tex.min.css: $(BUILDDIR)/contrib/copy-tex.css
$(CLEANCSS) -o $@ $<

View File

@@ -0,0 +1,49 @@
# Copy-tex extension
This extension modifes 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
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).
### 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.
```html
<link href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/contrib/copy-tex.css" rel="stylesheet" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/contrib/copy-tex.min.js" integrity="sha384-RkgGHBDdR8eyBOoWeZ/vpGg1cOvSAJRflCUDACusAAIVwkwPrOUYykglPeqWakZu" crossorigin="anonymous"></script>
```
See [index.html](index.html) for an example.
(To run this example from a clone of the repository, run `make serve`
in the root KaTeX directory, and then visit
http://0.0.0.0:7936/contrib/copy-tex/index.html
with your web browser.)
If you want to build your own custom copy handler based on this one,
copy the `copy-tex.js` into your codebase and replace the `require`
statement with `require('katex/contrib/copy-tex/katex2tex.js')`.
### Known Issues
This extension has been tested on Chrome, Firefox, Edge, and Safari.
Microsoft Edge
[does not seem to support](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/clipboardapi/)
text and HTML content in a single clipboard. In this browser, this extension
will just put the text content into the clipboard.
Safari copies correctly, but the selection rectangle renders strangely
(too big) when interacting with display math
(because of the `user-select: all` CSS).

View File

@@ -0,0 +1,12 @@
/* 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;
-moz-user-select: all;
-webkit-user-select: all;
-ms-user-select: all;
}

View File

@@ -0,0 +1,24 @@
const katexReplaceWithTex = require('./katex2tex');
// Global copy handler to modify behavior on .katex elements.
document.addEventListener('copy', function(event) {
const selection = window.getSelection();
if (selection.isCollapsed) {
return; // default action OK if selection is empty
}
const fragment = selection.getRangeAt(0).cloneContents();
if (!fragment.querySelector('.katex-mathml')) {
return; // default action OK if no .katex-mathml elements
}
// 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(''));
// Rewrite plain-text version.
event.clipboardData.setData('text/plain',
katexReplaceWithTex(fragment).textContent);
// Prevent normal copy handling.
event.preventDefault();
});

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<!--To run this example from a clone of the repository, run `make serve`
in the root KaTeX directory and then visit with your web browser:
http://0.0.0.0:7936/contrib/copy-tex/index.html
-->
<html>
<head>
<meta charset="UTF-8">
<title>Copy-tex test</title>
<script src="/katex.js" type="text/javascript"></script>
<link href="/katex.css" rel="stylesheet" type="text/css">
<link href="./copy-tex.css" rel="stylesheet" type="text/css">
<script src="../auto-render/auto-render.js" type="text/javascript"></script>
<script src="./copy-tex.js" type="text/javascript"></script>
<style type="text/css">
body {
margin: 0px;
padding: 0px;
font-size: 36px;
}
#test > .blue {
color: blue;
}
</style>
</head>
<body>
<h1>Copy-tex test</h1>
<h2>Try copy/pasting some of the text below!</h2>
<p>
Here is some \(\KaTeX\) math: $$ x^2+y^2=z^2 $$
The variables are \(x\), \(y\), and \(z\),
which are all in \(\mathbb{R}^+\).
Q.E.D.
</p>
<script>
renderMathInElement(document.body);
</script>
</body>
</html>

View File

@@ -0,0 +1,52 @@
// Set these to how you want inline and display math to be delimited.
export const defaultCopyDelimiters = {
inline: ['$', '$'], // alternative: ['\(', '\)']
display: ['$$', '$$'], // alternative: ['\[', '\]']
};
// 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) {
// 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.parentNode.removeChild(element);
}
}
// Replace .katex-mathml elements with their annotation (TeX source)
// descendant, with inline delimiters.
const katexMathml = fragment.querySelectorAll('.katex-mathml');
for (let i = 0; i < katexMathml.length; i++) {
const element = katexMathml[i];
const texSource = element.querySelector('annotation');
if (texSource) {
if (element.replaceWith) {
element.replaceWith(texSource);
} else {
element.parentNode.replaceChild(texSource, element);
}
texSource.innerHTML = copyDelimiters.inline[0] +
texSource.innerHTML + copyDelimiters.inline[1];
}
}
// Switch display math to display delimiters.
const displays = fragment.querySelectorAll('.katex-display annotation');
for (let i = 0; i < displays.length; i++) {
const element = displays[i];
element.innerHTML = copyDelimiters.display[0] +
element.innerHTML.substr(copyDelimiters.inline[0].length,
element.innerHTML.length - copyDelimiters.inline[0].length
- copyDelimiters.inline[1].length)
+ copyDelimiters.display[1];
}
return fragment;
};
module.exports = katexReplaceWithTex;