Add basic auto-render extension
Summary: Add an auto-render extension to render math on a page. It exposes a global function (maybe we should attach it to `katex`?) to render math in an element. It comes with a README on how to use it. Also, make `make build` build the minified file. Fixes #26 Test Plan: - Visit http://localhost:7936/contrib/auto-render/ - See that all of the math renders correctly - `make test` Reviewers: alpert, kevinb Reviewed By: kevinb Differential Revision: https://phabricator.khanacademy.org/D16620
This commit is contained in:
parent
99a81aca50
commit
cd9bca4a89
|
@ -1,6 +1,12 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Khan Academy
|
Copyright (c) 2015 Khan Academy
|
||||||
|
|
||||||
|
This software also uses portions of the underscore.js project, which is
|
||||||
|
MIT licensed with the following copyright:
|
||||||
|
|
||||||
|
Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative
|
||||||
|
Reporters & Editors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
31
Makefile
31
Makefile
|
@ -1,5 +1,13 @@
|
||||||
.PHONY: build lint setup copy serve clean metrics test zip
|
.PHONY: build lint setup copy serve clean metrics test zip contrib
|
||||||
build: setup lint build/katex.min.js build/katex.min.css zip compress
|
build: setup lint build/katex.min.js build/katex.min.css contrib zip compress
|
||||||
|
|
||||||
|
# Export these variables for use in contrib Makefiles
|
||||||
|
export BUILDDIR = $(realpath build)
|
||||||
|
export BROWSERIFY = $(realpath ./node_modules/.bin/browserify)
|
||||||
|
export UGLIFYJS = $(realpath ./node_modules/.bin/uglifyjs) \
|
||||||
|
--mangle \
|
||||||
|
--beautify \
|
||||||
|
ascii_only=true,beautify=false
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
npm install
|
npm install
|
||||||
|
@ -8,10 +16,10 @@ lint: katex.js $(wildcard src/*.js)
|
||||||
./node_modules/.bin/jshint $^
|
./node_modules/.bin/jshint $^
|
||||||
|
|
||||||
build/katex.js: katex.js $(wildcard src/*.js)
|
build/katex.js: katex.js $(wildcard src/*.js)
|
||||||
./node_modules/.bin/browserify $< --standalone katex > $@
|
$(BROWSERIFY) $< --standalone katex > $@
|
||||||
|
|
||||||
build/katex.min.js: build/katex.js
|
build/katex.min.js: build/katex.js
|
||||||
./node_modules/.bin/uglifyjs --mangle --beautify ascii_only=true,beautify=false < $< > $@
|
$(UGLIFYJS) < $< > $@
|
||||||
|
|
||||||
build/katex.less.css: static/katex.less $(wildcard static/*.less)
|
build/katex.less.css: static/katex.less $(wildcard static/*.less)
|
||||||
./node_modules/.bin/lessc $< $@
|
./node_modules/.bin/lessc $< $@
|
||||||
|
@ -27,15 +35,27 @@ build/fonts:
|
||||||
cp static/fonts/$$font* $@; \
|
cp static/fonts/$$font* $@; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
contrib: build/contrib
|
||||||
|
|
||||||
|
.PHONY: build/contrib
|
||||||
|
build/contrib:
|
||||||
|
mkdir -p build/contrib
|
||||||
|
# Since everything in build/contrib is put in the built files, make sure
|
||||||
|
# there's nothing in there we don't want.
|
||||||
|
rm -rf build/contrib/*
|
||||||
|
$(MAKE) -C contrib/auto-render
|
||||||
|
|
||||||
.PHONY: build/katex
|
.PHONY: build/katex
|
||||||
build/katex: build/katex.min.js build/katex.min.css build/fonts README.md
|
build/katex: build/katex.min.js build/katex.min.css build/fonts README.md build/contrib
|
||||||
mkdir -p build/katex
|
mkdir -p build/katex
|
||||||
|
rm -rf build/katex/*
|
||||||
cp -r $^ build/katex
|
cp -r $^ build/katex
|
||||||
|
|
||||||
build/katex.tar.gz: build/katex
|
build/katex.tar.gz: build/katex
|
||||||
cd build && tar czf katex.tar.gz katex/
|
cd build && tar czf katex.tar.gz katex/
|
||||||
|
|
||||||
build/katex.zip: build/katex
|
build/katex.zip: build/katex
|
||||||
|
rm -f $@
|
||||||
cd build && zip -rq katex.zip katex/
|
cd build && zip -rq katex.zip katex/
|
||||||
|
|
||||||
zip: build/katex.tar.gz build/katex.zip
|
zip: build/katex.tar.gz build/katex.zip
|
||||||
|
@ -53,6 +73,7 @@ serve:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
./node_modules/.bin/jasmine-node test/katex-spec.js
|
./node_modules/.bin/jasmine-node test/katex-spec.js
|
||||||
|
./node_modules/.bin/jasmine-node contrib/auto-render/auto-render-spec.js
|
||||||
|
|
||||||
metrics:
|
metrics:
|
||||||
cd metrics && ./mapping.pl | ./extract_tfms.py | ./extract_ttfs.py | ./replace_line.py
|
cd metrics && ./mapping.pl | ./extract_tfms.py | ./extract_ttfs.py | ./replace_line.py
|
||||||
|
|
|
@ -49,6 +49,10 @@ For example:
|
||||||
katex.render("c = \\pm\\sqrt{a^2 + b^2}", element, { displayMode: true });
|
katex.render("c = \\pm\\sqrt{a^2 + b^2}", element, { displayMode: true });
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Automatic rendering of math on a page
|
||||||
|
|
||||||
|
Math on the page can be automatically rendered using the auto-render extension. See [the Auto-render README](contrib/auto-render/README.md) for more information.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
|
|
9
contrib/auto-render/Makefile
Normal file
9
contrib/auto-render/Makefile
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
build: $(BUILDDIR)/contrib/auto-render.min.js
|
||||||
|
|
||||||
|
$(BUILDDIR)/contrib/auto-render.min.js: $(BUILDDIR)/auto-render.js
|
||||||
|
$(UGLIFYJS) < $< > $@
|
||||||
|
|
||||||
|
$(BUILDDIR)/auto-render.js: auto-render.js
|
||||||
|
$(BROWSERIFY) $< --standalone renderMathInElement > $@
|
63
contrib/auto-render/README.md
Normal file
63
contrib/auto-render/README.md
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# Auto-render extension
|
||||||
|
|
||||||
|
This is an extension to automatically render all of the math inside of text. It
|
||||||
|
searches all of the text nodes in a given element for the given delimiters, and
|
||||||
|
renders the math in place.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
This extension isn't part of KaTeX proper, so the script should be separately
|
||||||
|
included in the page:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="/path/to/auto-render.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, call the exposed `renderMathInElement` function in a script tag
|
||||||
|
before the close body tag:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<body>
|
||||||
|
...
|
||||||
|
<script>
|
||||||
|
renderMathInElement(document.body);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [index.html](index.html) for an example.
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
This extension exposes a single function, `window.renderMathInElement`, with
|
||||||
|
the following API:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function renderMathInElement(elem, options)
|
||||||
|
```
|
||||||
|
|
||||||
|
`elem` is an HTML DOM element. The function will recursively search for text
|
||||||
|
nodes inside this element and render the math in them.
|
||||||
|
|
||||||
|
`options` is an optional object argument with the following keys:
|
||||||
|
|
||||||
|
- `delimiters`: This is a list of delimiters to look for math. Each delimiter
|
||||||
|
has three properties:
|
||||||
|
|
||||||
|
- `left`: A string which starts the math expression (i.e. the left delimiter).
|
||||||
|
- `right`: A string which ends the math expression (i.e. the right delimiter).
|
||||||
|
- `display`: A boolean of whether the math in the expression should be
|
||||||
|
rendered in display mode or not.
|
||||||
|
|
||||||
|
The default value is:
|
||||||
|
```js
|
||||||
|
[
|
||||||
|
{left: "$$", right: "$$", display: true},
|
||||||
|
{left: "\\[", right: "\\]", display: true},
|
||||||
|
{left: "\\(", right: "\\)", display: false}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ignoredTags`: This is a list of DOM node types to ignore when recursing
|
||||||
|
through. The default value is
|
||||||
|
`["script", "noscript", "style", "textarea", "pre", "code"]`.
|
217
contrib/auto-render/auto-render-spec.js
Normal file
217
contrib/auto-render/auto-render-spec.js
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
var splitAtDelimiters = require("./splitAtDelimiters");
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
jasmine.addMatchers({
|
||||||
|
toSplitInto: function() {
|
||||||
|
return {
|
||||||
|
compare: function(actual, left, right, result) {
|
||||||
|
var message = {
|
||||||
|
pass: true,
|
||||||
|
message: "'" + actual + "' split correctly"
|
||||||
|
};
|
||||||
|
|
||||||
|
var startData = [{type: "text", data: actual}];
|
||||||
|
|
||||||
|
var split = splitAtDelimiters(startData, left, right, false);
|
||||||
|
|
||||||
|
if (split.length !== result.length) {
|
||||||
|
message.pass = false;
|
||||||
|
message.message = "Different number of splits: " +
|
||||||
|
split.length + " vs. " + result.length + " (" +
|
||||||
|
JSON.stringify(split) + " vs. " +
|
||||||
|
JSON.stringify(result) + ")";
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < split.length; i++) {
|
||||||
|
var real = split[i];
|
||||||
|
var correct = result[i];
|
||||||
|
|
||||||
|
var good = true;
|
||||||
|
|
||||||
|
if (real.type !== correct.type) {
|
||||||
|
good = false;
|
||||||
|
diff = "type";
|
||||||
|
} else if (real.data !== correct.data) {
|
||||||
|
good = false;
|
||||||
|
diff = "data";
|
||||||
|
} else if (real.display !== correct.display) {
|
||||||
|
good = false;
|
||||||
|
diff = "display";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!good) {
|
||||||
|
message.pass = false;
|
||||||
|
message.message = "Difference at split " +
|
||||||
|
(i + 1) + ": " + JSON.stringify(real) +
|
||||||
|
" vs. " + JSON.stringify(correct) +
|
||||||
|
" (" + diff + " differs)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("A delimiter splitter", function() {
|
||||||
|
it("doesn't split when there are no delimiters", function() {
|
||||||
|
expect("hello").toSplitInto("(", ")", [{type: "text", data: "hello"}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't create a math node when there's only a left delimiter", function() {
|
||||||
|
expect("hello ( world").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "text", data: "( world"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't split when there's only a right delimiter", function() {
|
||||||
|
expect("hello ) world").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello ) world"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("splits when there are both delimiters", function() {
|
||||||
|
expect("hello ( world ) boo").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("splits on multi-character delimiters", function() {
|
||||||
|
expect("hello [[ world ]] boo").toSplitInto(
|
||||||
|
"[[", "]]",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("splits mutliple times", function() {
|
||||||
|
expect("hello ( world ) boo ( more ) stuff").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo "},
|
||||||
|
{type: "math", data: " more ", display: false},
|
||||||
|
{type: "text", data: " stuff"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves the ending when there's only a left delimiter", function() {
|
||||||
|
expect("hello ( world ) boo ( left").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo "},
|
||||||
|
{type: "text", data: "( left"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't split when close delimiters are in {}s", function() {
|
||||||
|
expect("hello ( world { ) } ) boo").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world { ) } ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect("hello ( world { { } ) } ) boo").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world { { } ) } ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't split at escaped delimiters", function() {
|
||||||
|
expect("hello ( world \\) ) boo").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world \\) ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* TODO(emily): make this work maybe?
|
||||||
|
expect("hello \\( ( world ) boo").toSplitInto(
|
||||||
|
"(", ")",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello \\( "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
it("splits when the right and left delimiters are the same", function() {
|
||||||
|
expect("hello $ world $ boo").toSplitInto(
|
||||||
|
"$", "$",
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("remembers which delimiters are display-mode", function() {
|
||||||
|
var startData = [{type: "text", data: "hello ( world ) boo"}];
|
||||||
|
|
||||||
|
expect(splitAtDelimiters(startData, "(", ")", true)).toEqual(
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: true},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works with more than one start datum", function() {
|
||||||
|
var startData = [
|
||||||
|
{type: "text", data: "hello ( world ) boo"},
|
||||||
|
{type: "math", data: "math", display: true},
|
||||||
|
{type: "text", data: "hello ( world ) boo"}
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(splitAtDelimiters(startData, "(", ")", false)).toEqual(
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo"},
|
||||||
|
{type: "math", data: "math", display: true},
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't do splitting inside of math nodes", function() {
|
||||||
|
var startData = [
|
||||||
|
{type: "text", data: "hello ( world ) boo"},
|
||||||
|
{type: "math", data: "hello ( world ) boo", display: true}
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(splitAtDelimiters(startData, "(", ")", false)).toEqual(
|
||||||
|
[
|
||||||
|
{type: "text", data: "hello "},
|
||||||
|
{type: "math", data: " world ", display: false},
|
||||||
|
{type: "text", data: " boo"},
|
||||||
|
{type: "math", data: "hello ( world ) boo", display: true}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
95
contrib/auto-render/auto-render.js
Normal file
95
contrib/auto-render/auto-render.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
var splitAtDelimiters = require("./splitAtDelimiters");
|
||||||
|
|
||||||
|
var splitWithDelimiters = function(text, delimiters) {
|
||||||
|
var data = [{type: "text", data: text}];
|
||||||
|
for (var i = 0; i < delimiters.length; i++) {
|
||||||
|
var delimiter = delimiters[i];
|
||||||
|
data = splitAtDelimiters(
|
||||||
|
data, delimiter.left, delimiter.right,
|
||||||
|
delimiter.display || false);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
var renderMathInText = function(text, delimiters) {
|
||||||
|
var data = splitWithDelimiters(text, delimiters);
|
||||||
|
|
||||||
|
var fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
if (data[i].type === "text") {
|
||||||
|
fragment.appendChild(document.createTextNode(data[i].data));
|
||||||
|
} else {
|
||||||
|
var span = document.createElement("span");
|
||||||
|
var math = data[i].data;
|
||||||
|
katex.render(math, span, {
|
||||||
|
displayMode: data[i].display
|
||||||
|
});
|
||||||
|
fragment.appendChild(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
var renderElem = function(elem, delimiters, ignoredTags) {
|
||||||
|
for (var i = 0; i < elem.childNodes.length; i++) {
|
||||||
|
var childNode = elem.childNodes[i];
|
||||||
|
if (childNode.nodeType === 3) {
|
||||||
|
// Text node
|
||||||
|
var frag = renderMathInText(childNode.textContent, delimiters);
|
||||||
|
i += frag.childNodes.length - 1;
|
||||||
|
elem.replaceChild(frag, childNode);
|
||||||
|
} else if (childNode.nodeType === 1) {
|
||||||
|
// Element node
|
||||||
|
var shouldRender = ignoredTags.indexOf(
|
||||||
|
childNode.nodeName.toLowerCase()) === -1;
|
||||||
|
|
||||||
|
if (shouldRender) {
|
||||||
|
renderElem(childNode, delimiters, ignoredTags);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Something else, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var defaultOptions = {
|
||||||
|
delimiters: [
|
||||||
|
{left: "$$", right: "$$", display: true},
|
||||||
|
{left: "\\[", right: "\\]", display: true},
|
||||||
|
{left: "\\(", right: "\\)", display: false}
|
||||||
|
// LaTeX uses this, but it ruins the display of normal `$` in text:
|
||||||
|
// {left: "$", right: "$", display: false}
|
||||||
|
],
|
||||||
|
|
||||||
|
ignoredTags: [
|
||||||
|
"script", "noscript", "style", "textarea", "pre", "code"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var extend = function(obj) {
|
||||||
|
// Adapted from underscore.js' `_.extend`. See LICENSE.txt for license.
|
||||||
|
var source, prop;
|
||||||
|
for (var i = 1, length = arguments.length; i < length; i++) {
|
||||||
|
source = arguments[i];
|
||||||
|
for (prop in source) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
||||||
|
obj[prop] = source[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
var renderMathInElement = function(elem, options) {
|
||||||
|
if (!elem) {
|
||||||
|
throw new Error("No element provided to render");
|
||||||
|
}
|
||||||
|
|
||||||
|
options = extend({}, defaultOptions, options);
|
||||||
|
|
||||||
|
renderElem(elem, options.delimiters, options.ignoredTags);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = renderMathInElement;
|
47
contrib/auto-render/index.html
Normal file
47
contrib/auto-render/index.html
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Auto-render test</title>
|
||||||
|
<script src="/katex.js" type="text/javascript"></script>
|
||||||
|
<link href="/katex.css" rel="stylesheet" type="text/css">
|
||||||
|
<script src="./auto-render.js" type="text/javascript"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
font-size: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#test > .blue {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="test">
|
||||||
|
This is some text $math \frac12$ other text
|
||||||
|
<span class="blue">
|
||||||
|
Other node \[ displaymath \frac{1}{2} \] blah $$ \int_2^3 $$
|
||||||
|
</span>
|
||||||
|
and some <!-- comment --> more text \(and math\) blah. And $math with a
|
||||||
|
\$ sign$.
|
||||||
|
<pre>
|
||||||
|
Stuff in a $pre tag$
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
renderMathInElement(
|
||||||
|
document.getElementById("test"),
|
||||||
|
{
|
||||||
|
delimiters: [
|
||||||
|
{left: "$$", right: "$$", display: true},
|
||||||
|
{left: "\\[", right: "\\]", display: true},
|
||||||
|
{left: "$", right: "$", display: false},
|
||||||
|
{left: "\\(", right: "\\)", display: false}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
98
contrib/auto-render/splitAtDelimiters.js
Normal file
98
contrib/auto-render/splitAtDelimiters.js
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
var findEndOfMath = function(delimiter, text, startIndex) {
|
||||||
|
// Adapted from
|
||||||
|
// https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
|
||||||
|
var index = startIndex;
|
||||||
|
var braceLevel = 0;
|
||||||
|
|
||||||
|
var delimLength = delimiter.length;
|
||||||
|
|
||||||
|
while (index < text.length) {
|
||||||
|
var character = text[index];
|
||||||
|
|
||||||
|
if (braceLevel <= 0 &&
|
||||||
|
text.slice(index, index + delimLength) === delimiter) {
|
||||||
|
return index;
|
||||||
|
} else if (character === "\\") {
|
||||||
|
index++;
|
||||||
|
} else if (character === "{") {
|
||||||
|
braceLevel++;
|
||||||
|
} else if (character === "}") {
|
||||||
|
braceLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
var splitAtDelimiters = function(startData, leftDelim, rightDelim, display) {
|
||||||
|
var finalData = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < startData.length; i++) {
|
||||||
|
if (startData[i].type === "text") {
|
||||||
|
var text = startData[i].data;
|
||||||
|
|
||||||
|
var lookingForLeft = true;
|
||||||
|
var currIndex = 0;
|
||||||
|
var nextIndex;
|
||||||
|
|
||||||
|
nextIndex = text.indexOf(leftDelim);
|
||||||
|
if (nextIndex !== -1) {
|
||||||
|
currIndex = nextIndex;
|
||||||
|
finalData.push({
|
||||||
|
type: "text",
|
||||||
|
data: text.slice(0, currIndex)
|
||||||
|
});
|
||||||
|
lookingForLeft = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (lookingForLeft) {
|
||||||
|
nextIndex = text.indexOf(leftDelim, currIndex);
|
||||||
|
if (nextIndex === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalData.push({
|
||||||
|
type: "text",
|
||||||
|
data: text.slice(currIndex, nextIndex)
|
||||||
|
});
|
||||||
|
|
||||||
|
currIndex = nextIndex;
|
||||||
|
} else {
|
||||||
|
nextIndex = findEndOfMath(
|
||||||
|
rightDelim,
|
||||||
|
text,
|
||||||
|
currIndex + leftDelim.length);
|
||||||
|
if (nextIndex === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalData.push({
|
||||||
|
type: "math",
|
||||||
|
data: text.slice(
|
||||||
|
currIndex + leftDelim.length,
|
||||||
|
nextIndex),
|
||||||
|
display: display
|
||||||
|
});
|
||||||
|
|
||||||
|
currIndex = nextIndex + rightDelim.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
lookingForLeft = !lookingForLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalData.push({
|
||||||
|
type: "text",
|
||||||
|
data: text.slice(currIndex)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
finalData.push(startData[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalData;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = splitAtDelimiters;
|
|
@ -17,7 +17,7 @@
|
||||||
"browserify": "~2.29.1",
|
"browserify": "~2.29.1",
|
||||||
"clean-css": "~2.2.15",
|
"clean-css": "~2.2.15",
|
||||||
"express": "~3.3.3",
|
"express": "~3.3.3",
|
||||||
"jasmine-node": "git://github.com/mhevery/jasmine-node.git#Jasmine2.0",
|
"jasmine-node": "2.0.0-beta4",
|
||||||
"jshint": "^2.5.6",
|
"jshint": "^2.5.6",
|
||||||
"less": "~1.7.5",
|
"less": "~1.7.5",
|
||||||
"uglify-js": "~2.4.15"
|
"uglify-js": "~2.4.15"
|
||||||
|
|
37
server.js
37
server.js
|
@ -9,11 +9,17 @@ var app = express();
|
||||||
|
|
||||||
app.use(express.logger());
|
app.use(express.logger());
|
||||||
|
|
||||||
app.get("/katex.js", function(req, res, next) {
|
var serveBrowserified = function(file, standaloneName) {
|
||||||
|
return function(req, res, next) {
|
||||||
var b = browserify();
|
var b = browserify();
|
||||||
b.add("./katex");
|
b.add(file);
|
||||||
|
|
||||||
var stream = b.bundle({standalone: "katex"});
|
var options = {};
|
||||||
|
if (standaloneName) {
|
||||||
|
options.standalone = standaloneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = b.bundle(options);
|
||||||
|
|
||||||
var body = "";
|
var body = "";
|
||||||
stream.on("data", function(s) { body += s; });
|
stream.on("data", function(s) { body += s; });
|
||||||
|
@ -22,7 +28,14 @@ app.get("/katex.js", function(req, res, next) {
|
||||||
res.setHeader("Content-Type", "text/javascript");
|
res.setHeader("Content-Type", "text/javascript");
|
||||||
res.send(body);
|
res.send(body);
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
app.get("/katex.js", serveBrowserified("./katex", "katex"));
|
||||||
|
app.get("/test/katex-spec.js", serveBrowserified("./test/katex-spec"));
|
||||||
|
app.get("/contrib/auto-render/auto-render.js",
|
||||||
|
serveBrowserified("./contrib/auto-render/auto-render",
|
||||||
|
"renderMathInElement"));
|
||||||
|
|
||||||
app.get("/katex.css", function(req, res, next) {
|
app.get("/katex.css", function(req, res, next) {
|
||||||
fs.readFile("static/katex.less", {encoding: "utf8"}, function(err, data) {
|
fs.readFile("static/katex.less", {encoding: "utf8"}, function(err, data) {
|
||||||
|
@ -48,24 +61,10 @@ app.get("/katex.css", function(req, res, next) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/test/katex-spec.js", function(req, res, next) {
|
|
||||||
var b = browserify();
|
|
||||||
b.add("./test/katex-spec");
|
|
||||||
|
|
||||||
var stream = b.bundle({});
|
|
||||||
|
|
||||||
var body = "";
|
|
||||||
stream.on("data", function(s) { body += s; });
|
|
||||||
stream.on("error", function(e) { next(e); });
|
|
||||||
stream.on("end", function() {
|
|
||||||
res.setHeader("Content-Type", "text/javascript");
|
|
||||||
res.send(body);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, "static")));
|
app.use(express.static(path.join(__dirname, "static")));
|
||||||
app.use(express.static(path.join(__dirname, "build")));
|
app.use(express.static(path.join(__dirname, "build")));
|
||||||
app.use("/test", express.static(path.join(__dirname, "test")));
|
app.use("/test", express.static(path.join(__dirname, "test")));
|
||||||
|
app.use("/contrib", express.static(path.join(__dirname, "contrib")));
|
||||||
|
|
||||||
app.use(function(err, req, res, next) {
|
app.use(function(err, req, res, next) {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
<title>KaTeX Test</title>
|
<title>KaTeX Test</title>
|
||||||
<script src="katex.js" type="text/javascript"></script>
|
<script src="katex.js" type="text/javascript"></script>
|
||||||
<link href="katex.css" rel="stylesheet" type="text/css">
|
<link href="katex.css" rel="stylesheet" type="text/css">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user