/*
This file is part of LONCAPA-Daxe.
LONCAPA-Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
LONCAPA-Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of loncapa_daxe;
/**
* Displays tm and dtm elements (LaTeX inline and display math) with MathJax.
* Note: conversion from m to tm/dtm has been delayed. This is now used
* to display m.
* Jaxe display type: 'texmathjax'.
*/
class TeXMathJax extends DaxeNode {
TeXMathJax.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
}
TeXMathJax.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
}
@override
h.Element html() {
h.SpanElement span = new h.SpanElement();
span.id = "$id";
span.classes.add('dn');
if (!valid)
span.classes.add('invalid');
span.classes.add('tex');
span.onClick.listen((h.MouseEvent event) => editDialog());
return(span);
}
@override
void newNodeCreationUI(ActionFunction okfct) {
editDialog(() => okfct());
}
@override
Position firstCursorPositionInside() {
return(null);
}
@override
Position lastCursorPositionInside() {
return(null);
}
@override
void afterInsert() {
updateEquationDisplay();
}
void updateEquationDisplay() {
String equationText = '?';
if (firstChild != null && firstChild.nodeValue.trim() != '' &&
firstChild.nodeValue.trim() != '\$\$')
equationText = firstChild.nodeValue;
h.SpanElement span = h.document.getElementById(id);
if (span == null)
return;
equationText = convertForMathJax(equationText, getAttribute('eval') == 'on');
span.text = equationText;
js.JsArray params = new js.JsObject.jsify( ['Typeset', js.context['MathJax']['Hub'], id] );
js.context['MathJax']['Hub'].callMethod('Queue', [params]);
_fixCursorPosition();
}
/**
* Move the cursor if it is inside the element.
*/
void _fixCursorPosition() {
Position start = page.getSelectionStart();
if (start != null) {
DaxeNode dn = start.dn;
if (dn is DNText)
dn = dn.parent;
if (dn == this) {
page.moveCursorTo(new Position(parent, parent.offsetOf(this) + 1));
page.updateAfterPathChange();
}
}
}
void editDialog([ActionFunction okfct]) {
TeXMathJaxeDialog dlg = new TeXMathJaxeDialog(this, okfct);
dlg.show();
}
/**
* Returns a new TeX expression ready to be displayed by MathJax
* with a nice display of variables and functions.
*/
String convertForMathJax(String text, bool eval) {
bool hasDelimiters = false;
text = text.trim();
if (text.length > 3 && text.startsWith('\$\$') && text.endsWith('\$\$')) {
text = "\\[${text.substring(2,text.length-2)}\\]";
hasDelimiters = true;
} else if (text.length > 1 && text.startsWith('\$') && text.endsWith('\$')) {
text = "\\(${text.substring(1,text.length-1)}\\)";
hasDelimiters = true;
}
if (hasDelimiters && eval) {
// turn Perl variables to tt style
text = text.replaceAllMapped(new RegExp(r'\$[a-zA-Z_]*[0-9]*(\[[^\]]*\])?'), (match) {
String name = match.group(0);
name = name.replaceAll('_', '\\_');
return "\\mathtt{$name}";
});
// same for Perl functions
text = text.replaceAllMapped(new RegExp(r'&[a-zA-Z_]*[0-9]*\([^)]*\)'), (match) {
String fct = match.group(0);
fct = fct.replaceAll('&', '\\&');
fct = fct.replaceAll('_', '\\_');
fct = fct.replaceAll("'", "\\textsf{'}");
return "\\mathtt{$fct}";
});
}
return(text);
}
@override
void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
Timer.run(() => updateEquationDisplay());
}
}
class TeXMathJaxeDialog {
TeXMathJax dn;
ActionFunction _okfct;
TeXMathJaxeDialog(this.dn, [this._okfct]) {
}
void show() {
h.DivElement div1 = new h.DivElement();
div1.id = 'dlg1';
div1.classes.add('dlg1');
h.DivElement div2 = new h.DivElement();
div2.classes.add('dlg2');
h.DivElement div3 = new h.DivElement();
div3.classes.add('dlg3');
h.FormElement form = new h.FormElement();
h.TextAreaElement ta = new h.TextAreaElement();
ta.id = 'eqtext';
if (dn.firstChild != null) {
ta.value = dn.firstChild.nodeValue.trim();
adaptTAToText(ta);
} else {
ta.value = '\$\$';
ta.rows = 5;
ta.cols = 50;
}
ta.attributes['spellcheck'] = 'false';
ta.onInput.listen((h.Event event) => input());
form.append(ta);
var p = h.document.createElement('p');
var cb = new h.CheckboxInputElement();
cb.id = 'eqeval';
cb.checked = dn.getAttribute('eval') == 'on';
cb.onChange.listen((h.Event event) => input());
p.append(cb);
var label = new h.LabelElement();
label.htmlFor = cb.id;
label.appendText(LCDStrings.get('evaluate_variables'));
p.append(label);
form.append(p);
h.DivElement preview = new h.DivElement();
preview.id = 'preview';
form.append(preview);
h.DivElement div_buttons = new h.DivElement();
div_buttons.classes.add('buttons');
h.ButtonElement bCancel = new h.ButtonElement();
bCancel.attributes['type'] = 'button';
bCancel.appendText(Strings.get("button.Cancel"));
bCancel.onClick.listen((h.MouseEvent event) {
div1.remove();
dn._fixCursorPosition();
});
div_buttons.append(bCancel);
h.ButtonElement bOk = new h.ButtonElement();
bOk.attributes['type'] = 'submit';
bOk.appendText(Strings.get("button.OK"));
bOk.onClick.listen((h.MouseEvent event) => ok(event));
div_buttons.append(bOk);
form.append(div_buttons);
div3.append(form);
div2.append(div3);
div1.append(div2);
h.document.body.append(div1);
ta.focus();
if (ta.value == '\$\$')
ta.setSelectionRange(1, 1);
input();
}
void ok(h.MouseEvent event) {
h.TextAreaElement ta = h.querySelector('textarea#eqtext');
String equationText = ta.value;
h.CheckboxInputElement cb = h.document.getElementById('eqeval');
if (equationText != '') {
UndoableEdit compound = new UndoableEdit.compound(Strings.get('undo.insert_text'));
if (dn.firstChild != null)
compound.addSubEdit(new UndoableEdit.removeNode(dn.firstChild));
compound.addSubEdit(new UndoableEdit.insertNode(new Position(dn, 0),
new DNText(equationText)));
bool initial_eval = dn.getAttribute('eval') == 'on';
if (cb.checked != initial_eval) {
DaxeAttr attr = new DaxeAttr('eval', cb.checked ? 'on' : null);
compound.addSubEdit(new UndoableEdit.changeAttribute(dn, attr));
}
doc.doNewEdit(compound);
} else {
if (dn.firstChild != null)
doc.removeNode(dn.firstChild);
}
h.querySelector('div#dlg1').remove();
if (event != null)
event.preventDefault();
if (_okfct != null)
_okfct();
}
void input() {
h.TextAreaElement ta = h.querySelector('textarea#eqtext');
String text = ta.value;
adaptTAToText(ta);
/* newlines allowed -> must use button to insert equations
if (text.length > 0 && text.contains('\n')) {
ta.value = text.replaceAll('\n', '');
ok(null);
return;
}
*/
h.DivElement previewDiv = h.document.getElementById('preview');
/* this does not work bec. the initial text has not been processed before
js.JsObject queue = js.context['MathJax']['Hub']['queue'];
//math = MathJax.Hub.getAllJax("MathOutput")[0]
js.JsArray all = js.context['MathJax']['Hub'].callMethod('getAllJax', ['preview']);
js.JsObject math = all[0];
// MathJax.Hub.queue.Push(['Text', math, "\\displaystyle{$text}"]);
js.JsObject params = new js.JsObject.jsify(['Text', math, "\\displaystyle{$text}"]);
queue.callMethod('Push', [params]);
*/
h.CheckboxInputElement cb = h.document.getElementById('eqeval');
text = dn.convertForMathJax(text, cb.checked);
previewDiv.text = text;
js.JsArray params = new js.JsObject.jsify( ['Typeset', js.context['MathJax']['Hub'], 'preview'] );
js.context['MathJax']['Hub'].callMethod('Queue', [params]);
}
/*
* Adapt textarea dimensions to the equation text, with some limits.
*/
void adaptTAToText(h.TextAreaElement ta) {
List<String> lines = ta.value.split('\n');
int rows = lines.length;
if (rows < 5)
rows = 5;
if (rows > 10)
rows = 10;
ta.rows = rows;
int cols = 50;
for (String line in lines) {
if (line.length > cols)
cols = line.length;
}
if (cols > 90)
cols = 90;
ta.cols = cols;
}
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>