mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-06 19:58:40 +00:00
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:
committed by
Kevin Barabash
parent
ca224eda81
commit
e71c7d4b81
4
Makefile
4
Makefile
@@ -26,6 +26,7 @@ export UGLIFYJS = $(realpath ./node_modules/.bin/uglifyjs) \
|
|||||||
--mangle \
|
--mangle \
|
||||||
--beautify \
|
--beautify \
|
||||||
ascii_only=true,beautify=false
|
ascii_only=true,beautify=false
|
||||||
|
export CLEANCSS = $(realpath ./node_modules/.bin/cleancss)
|
||||||
|
|
||||||
# The prepublish script in package.json will override the following variable,
|
# The prepublish script in package.json will override the following variable,
|
||||||
# setting it to the empty string and thereby avoiding an infinite recursion
|
# setting it to the empty string and thereby avoiding an infinite recursion
|
||||||
@@ -48,7 +49,7 @@ build/katex.css: static/katex.less $(wildcard static/*.less) $(NIS)
|
|||||||
./node_modules/.bin/lessc $< $@
|
./node_modules/.bin/lessc $< $@
|
||||||
|
|
||||||
build/katex.min.css: build/katex.css
|
build/katex.min.css: build/katex.css
|
||||||
./node_modules/.bin/cleancss -o $@ $<
|
$(CLEANCSS) -o $@ $<
|
||||||
|
|
||||||
.PHONY: build/fonts
|
.PHONY: build/fonts
|
||||||
build/fonts:
|
build/fonts:
|
||||||
@@ -73,6 +74,7 @@ build/contrib:
|
|||||||
@# there's nothing in there we don't want.
|
@# there's nothing in there we don't want.
|
||||||
rm -rf build/contrib/*
|
rm -rf build/contrib/*
|
||||||
$(MAKE) -C contrib/auto-render
|
$(MAKE) -C contrib/auto-render
|
||||||
|
$(MAKE) -C contrib/copy-tex
|
||||||
$(MAKE) -C contrib/mathtex-script-type
|
$(MAKE) -C contrib/mathtex-script-type
|
||||||
|
|
||||||
.PHONY: build/katex
|
.PHONY: build/katex
|
||||||
|
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
build: $(BUILDDIR)/contrib/auto-render.min.js
|
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) < $< > $@
|
$(UGLIFYJS) < $< > $@
|
||||||
|
|
||||||
$(BUILDDIR)/auto-render.js: auto-render.js
|
$(BUILDDIR)/contrib/auto-render.js: auto-render.js
|
||||||
$(BROWSERIFY) -t [ babelify ] $< --standalone renderMathInElement > $@
|
$(BROWSERIFY) -t [ babelify ] $< --standalone renderMathInElement > $@
|
||||||
|
15
contrib/copy-tex/Makefile
Normal file
15
contrib/copy-tex/Makefile
Normal 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 $@ $<
|
49
contrib/copy-tex/README.md
Normal file
49
contrib/copy-tex/README.md
Normal 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).
|
12
contrib/copy-tex/copy-tex.css
Normal file
12
contrib/copy-tex/copy-tex.css
Normal 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;
|
||||||
|
}
|
24
contrib/copy-tex/copy-tex.js
Normal file
24
contrib/copy-tex/copy-tex.js
Normal 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();
|
||||||
|
});
|
40
contrib/copy-tex/index.html
Normal file
40
contrib/copy-tex/index.html
Normal 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>
|
52
contrib/copy-tex/katex2tex.js
Normal file
52
contrib/copy-tex/katex2tex.js
Normal 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;
|
@@ -59,6 +59,9 @@ browserified(
|
|||||||
"/contrib/auto-render/auto-render.js",
|
"/contrib/auto-render/auto-render.js",
|
||||||
"contrib/auto-render/auto-render",
|
"contrib/auto-render/auto-render",
|
||||||
"renderMathInElement");
|
"renderMathInElement");
|
||||||
|
browserified(
|
||||||
|
"/contrib/copy-tex/copy-tex.js",
|
||||||
|
"contrib/copy-tex/copy-tex");
|
||||||
|
|
||||||
app.use("/katex.css", function(req, res, next) {
|
app.use("/katex.css", function(req, res, next) {
|
||||||
const lessfile = path.join(__dirname, "static", "katex.less");
|
const lessfile = path.join(__dirname, "static", "katex.less");
|
||||||
|
Reference in New Issue
Block a user