Apply Mathjax to arbitrary elements - javascript

I have emacs org-generated HTML that contains dvipng'd equations by default. That's ok, I want to provide a good fallback for those who disabled their JS. The generated pictures and resulting <img> tags have complete LaTeX source in their alt attribute. I have also tweaked the code to apply a specific class to all such images so it becomes a simpler matter in JS end, which isn't my forté.
The MathJax documentation lists a couple of different configurations and a way to customize a configuration by myself; I don't see a selector function or anything to apply Mathjax to suit my needs. I know I can write own DOM-munging JS and then call Mathjax afterwards but surely Mathjax bends to this kind of functionality by itself?
This is an example from the generated HTML.
<img class="dvipng" src="upload/blog_ff4e604.png"
alt="$\frac{1}{2\pi{}i}\int_C\frac{f'(z)}{f(z)}{\rm d}z = N_0(f)$">

MathJax doesn't include this directly, but you could do the following:
<script type="text/x-mathjax-config">
MathJax.Extension.myImg2jax = {
version: "1.0",
PreProcess: function (element) {
var images = element.getElementsByTagName("img");
for (var i = images.length - 1; i >= 0; i--) {
var img = images[i];
if (img.className === "dvipng") {
var script = document.createElement("script"); script.type = "math/tex";
var match = img.alt.match(/^(\$\$?)(.*)\1/);
if (match[1] === "$$") {script.type += ";mode=display"}
MathJax.HTML.setScript(script,match[2]);
img.parentNode.replaceChild(script,img);
}
}
}
};
MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.myImg2jax]);
</script>
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"></script>
which defines a new preprocessor that looks for your images and extracts the math from the ALT tags. This assumes you use $...$ for in-line math and $$...$$ for displayed math.
This code removed the image when it converts it to the MathJax <script> tag. It would be possible to move the image to a preview span where it will show up until MathJax processes the TeX code. That is a bit more sophisticated, but could be done with a little more work. I'll leave that to the interested reader. :-)

Related

R ggiraph dynamically setting tooltip text without Shiny

What: Dynamically set the contents of ggiraph tooltips in rmarkdown knitted to html on page load.
Why: Using embedded png's tooltips can be made to be graphics which is valuable for certain biological structures where text is insufficient. Here is a minimal example of what I'm currently doing:
encodedImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAUCAIAAAAcIrrpAAAACXBIWXMAAASdAAAEnQF8NGuhAAADGElEQVR4nI2Uy08TURTG+VuMJmCCoIKIxq2ujEQT9y40yKsiILrSGITIzoVxZUwDFQlKQiC0ttMpMwXsi9JSSkvftJ0WWkuhBVtoAb+ZKeNQy+PkZjJz5t7fPfec892yg1K2u7u7tbW1vr4ejUYDgYDL5fJ4PHhhGCYej+NXyVWwsqLvnZ0dULAsFAphvdfrBcvhcNjt9oWFhfn5eZPJpNfr8b62tra/v38sLpPJxGIxTML+q6urIAaDQZ/P53a7nU4ncFar1Ww2G41GnU43MzND0/T09DR2LYHb3t4OcxaJRAQiPq2u8KI36vDHXKGEJ5xkX/yMPxCYm5sjSVKlUikUCmx5BIdcrHCGcIBAXCBig3w+X95E1HVR4nG9i7rWSd3opls+mX+ZrJOTkxMTE8hsAYc1vkPz+/2AIsBcLsf/vtKuOfdEWXJcaFTdeTPLMJHx8fGxsTFknMVtbGwgOx7OkHhEhw2E4E/AYZQ3E8Na9iijo6M2m43FIZfLnKGC4KZSKXFqBVxFs/rRB/PDfuP9PgPOe75RxfsbevWYRlEUksjigHBwhvIBKg5NjKvvpgVnNJm9+nyK9996pYUHJxsZGWFxSPySyI7DIf3h35lg/A+en4mVKgnJ++/26DANJyMIgsVtbm4ucmbjLJFIlMRVtpIoa23HFJ6VbaRQjfc/3JimVCoRCovb29vDm5Uzi8WCU0MYZyxF/Qs6nclDPENDQ+l0utB30AOig4DQ9OhPcNF0J+NqO6l77/RMIgO1DA4OouePqCKbzUKGEJDBYIAkISOUBVUWcOXNaqSp5rAC1c80PTITOm5gYAASKhbZASd+hA0W9KjVajUaDTJyqZXg19d1aHC7qHVL1ZKC52KT/Mt3dTKZFOe6+EaBHiA1EKFHuVxe2fKTX3y5TS6TyaRSaXv/14qnCt55+/Vs0fJiHG+4eZA+XDDVEjW/sqZdjcZEzyOchl5dodxtJGGNn477v7LiNrYHU8gd77/5khZfeqfgqiQlcLAHfQZBtlIyeFbc448WSBWjZ3hZ7HeG07wf4+23f7/+An2Gx2N7QYsiAAAAAElFTkSuQmCC"
g = ggplot()+
geom_point_interactive(aes(x=1,y=1,tooltip=sprintf('<img src=\"%s\" />',encodedImage)))
girafe(code=print(g))
The downside to this is that for every plot using the tooltip graphic the encoded image is repeated, resulting in a file size that is too large to store many of.
How: To mitigate the increase in file size with the increase in use of tooltips I'm looking to assign the embedded image text into a json object, then dynamically update all tooltips to be the embedded image using javascript.
Things I've Done: I'm able to get the embedded image stored in a json by simply including script tags and outputing json text inside of those tags. For testing heres the hard-coded example with a simple replace text:
<script type="application/json" id="lookuptable">
{"ID1":"ReplaceText"}
</script>
What I can't do is replace the text of the tooltips. Essentially I was planning on setting the tooltip to and ID of some sort and using that to match the embedded image to the corresponding point. The tooltip text is stored in a json by ggiraph in something like:
<script type="application/json" data-for="htmlwidget-b8ceca7828d4dd46f692"> {x:{"html":.....}}</script>
In this json all html components (<,",>,etc) are "escaped," I believe this "htmlwidget" data is passed to what is essentially a mini-html page embedded in the html page, the "htmlwidget".
I've tried wrapping my temporary tooltips in <div> tags, but since they are tied up in a JSON they aren't seen in the DOM.
I've tried just naively replacing all instances if ID in any script tags:
<script type="application/javascript">
console.log(document.getElementById('lookuptable').innerHTML)
var lookuptable = JSON.parse(document.getElementById('lookuptable').innerHTML);
var scriptTags = document.getElementsByTagName("script");
for (s=0; s < scriptTags.length; s++){
var item = scriptTags[s];
for(var k in lookuptable){
console.log(item.innerHTML);
item.innerHTML.replace(k,lookuptable[k])
}
}
</script>
However it seems that by the time that this script runs the json no longer has the tooltip text in it (though it's in the html source).
This is where I currently am stuck. I'll update or answer this question if I make any more progress.
Also I'm well aware this would be trivial with Shiny, unfortunately that is not an option as the html page needs to be entirely standalone as the real rmarkdown I'm building is meant to be a standalone report.
Finally here is a complete, reproducible example. The final knitted project should result in the tooltip being "ReplaceText" (fix R code blocks by removing \):
---
title: "Demo"
author: "Zachary Klamer"
date: "12/9/2021"
output: html_document
---
\```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(ggplot2)
library(ggiraph)
library(rjson)
\```
\```{r,echo=FALSE}
lookup = list()
lookup$FullID1 = "ReplaceText"
jsonData = toJSON(lookup)
\```
<script type="application/json" id="lookuptable">
`r jsonData`
</script>
\```{r,echo=FALSE}
g = ggplot()+
geom_point_interactive(aes(x=1,y=1,tooltip='FullID1'))
girafe(code=print(g))
\```
<script type="application/javascript">
console.log(document.getElementById('lookuptable').innerHTML)
var lookuptable = JSON.parse(document.getElementById('lookuptable').innerHTML);
var scriptTags = document.getElementsByTagName("script");
for (s=0; s < scriptTags.length; s++){
var item = scriptTags[s];
for(var k in lookuptable){
console.log(item.innerHTML);
item.innerHTML.replace(k,lookuptable[k])
}
}
</script>
There may be several different ways to achieve this goal, in particular I suspect that using the htmlwidgets "onRender" function may be able to achieve this more cleanly but I never got that to work.
What I've found is that any editing of the innerHTML of the htmlwidget or htmlwidget data breaks the mouseover text completely, because it breaks the event listener which powers the mouseover text.
Instead I've found I can edit the resulting svg of the htmlwidget by wrapping my ending script in a $(window).load(function(){ ... }) call. If I find all svg elements (in this case circles!) and edit the title property of those svg objects I can preserve the event listeners and change the title contents (to be an image!).
See complete example below which has 1000 1kb images as tooltips but gives no increase in file size:
---
title: "Demo"
author: "Zachary Klamer"
date: "12/9/2021"
output: html_document
---
\```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(ggplot2)
library(ggiraph)
library(rjson)
library(htmlwidgets)
\```
\```{r,echo=FALSE}
lookup = list()
lookup$FullID1 = '<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAUCAIAAAAcIrrpAAAACXBIWXMAAASdAAAEnQF8NGuhAAADGElEQVR4nI2Uy08TURTG+VuMJmCCoIKIxq2ujEQT9y40yKsiILrSGITIzoVxZUwDFQlKQiC0ttMpMwXsi9JSSkvftJ0WWkuhBVtoAb+ZKeNQy+PkZjJz5t7fPfec892yg1K2u7u7tbW1vr4ejUYDgYDL5fJ4PHhhGCYej+NXyVWwsqLvnZ0dULAsFAphvdfrBcvhcNjt9oWFhfn5eZPJpNfr8b62tra/v38sLpPJxGIxTML+q6urIAaDQZ/P53a7nU4ncFar1Ww2G41GnU43MzND0/T09DR2LYHb3t4OcxaJRAQiPq2u8KI36vDHXKGEJ5xkX/yMPxCYm5sjSVKlUikUCmx5BIdcrHCGcIBAXCBig3w+X95E1HVR4nG9i7rWSd3opls+mX+ZrJOTkxMTE8hsAYc1vkPz+/2AIsBcLsf/vtKuOfdEWXJcaFTdeTPLMJHx8fGxsTFknMVtbGwgOx7OkHhEhw2E4E/AYZQ3E8Na9iijo6M2m43FIZfLnKGC4KZSKXFqBVxFs/rRB/PDfuP9PgPOe75RxfsbevWYRlEUksjigHBwhvIBKg5NjKvvpgVnNJm9+nyK9996pYUHJxsZGWFxSPySyI7DIf3h35lg/A+en4mVKgnJ++/26DANJyMIgsVtbm4ucmbjLJFIlMRVtpIoa23HFJ6VbaRQjfc/3JimVCoRCovb29vDm5Uzi8WCU0MYZyxF/Qs6nclDPENDQ+l0utB30AOig4DQ9OhPcNF0J+NqO6l77/RMIgO1DA4OouePqCKbzUKGEJDBYIAkISOUBVUWcOXNaqSp5rAC1c80PTITOm5gYAASKhbZASd+hA0W9KjVajUaDTJyqZXg19d1aHC7qHVL1ZKC52KT/Mt3dTKZFOe6+EaBHiA1EKFHuVxe2fKTX3y5TS6TyaRSaXv/14qnCt55+/Vs0fJiHG+4eZA+XDDVEjW/sqZdjcZEzyOchl5dodxtJGGNn477v7LiNrYHU8gd77/5khZfeqfgqiQlcLAHfQZBtlIyeFbc448WSBWjZ3hZ7HeG07wf4+23f7/+An2Gx2N7QYsiAAAAAElFTkSuQmCC\" />'
jsonData = toJSON(lookup)
\```
<script type="application/json" id="lookuptable">
`r jsonData`
</script>
\```{r,echo=FALSE}
g = ggplot()+
geom_point_interactive(aes(x=1:1000,y=1:1000,tooltip='FullID1'))
girafe(code=print(g))
\```
<script type="application/javascript">
$(window).load(function(){
var lookuptable = JSON.parse(document.getElementById('lookuptable').innerHTML);
var keys = Object.keys(lookuptable);
var circleTags = document.getElementsByTagName("circle");
for (s=0; s < circleTags.length; s++){
var item = circleTags[s];
var itemTitle = item.attributes.title.nodeValue;
console.log(itemTitle);
if (keys.includes(itemTitle)){
item.attributes.title.nodeValue=lookuptable[itemTitle];
}
console.log(item.attributes.title.nodeValue);
}
});
</script>

Relative images broken, need javascript fix

new here. I have no access to most of the source files on my website, so I am trying to fix some broken images on page load with javascript.
When I use the inspect element for one of these broken images it shows like this:
<img src="-82.jpg" width="60px">
when they should be
<img src="http://example.com/files/images/-82.jpg" width="60px">
This is for bunch of different images, -82.jpg, -2482.jpg, -3582.jpg
Here's what I have tried so far. This seems to work but for some reason it breaks other javascript on the page.
html
<script type="text/javascript" src="http://example.com/files/js/fiximages.js"></script>
<body onload="fixImages();">
my fiximages.js file
function fixImages() {
var toReplace = '<img src="-';
var replaceWith ='<img src="http://www.example.com/files/images/-';
document.body.innerHTML = document.body.innerHTML.replace(toReplace, replaceWith);
}
I'm a bit of a noob so I also need to know how to link the html to the javascript to get it to load when the page loads. Thanks guys.
This should solve your problem:
function fixImages() {
// Create a list of all img which src starts with "-".
var imgs = document.querySelectorAll('img[src^="-"]');
// Loop through this list.
for (var i = 0; i < imgs.length; i++) {
// For each img, replace its src with the correct path + the
// src that's already there.
imgs[i].setAttribute('src', 'http://www.example.com/files/images/' + imgs[i].getAttribute('src'));
}
}
Demo
Welcome to SO!
The problem in your approach is that changing the body's pure HTML is never a good idea for a dynamic page. Also, javascript's replace, when used without a regex, is going to replace only the first ocurrence of the string.
Now, when you need to change an element's attribute, Javascript has a manipulation called DOM. There are plenty material and tutorials on the web... You should look into it!
http://www.w3.org/TR/DOM-Level-2-Core/introduction.html
http://www.w3schools.com/jsref/dom_obj_document.asp
With DOM, you can select an element as a variable, and manipulate its properties and attributes, so, in your case it would be:
function fixImages() {
var imgs = document.getElementsByTagName("img");
for(var i=0; i<imgs.length; i++) {
if(imgs[i].src.indexOf("-") == 0)
imgs[i].src = "http://www.example.com/files/images/" + imgs[i].src;
}
}

How can I Strip all regular html tags except <a></a>, <img>(attributes inside) and <br> with javascript?

When a user create a message there is a multibox and this multibox is connected to a design panel which lets users change fonts, color, size etc.. When the message is submited the message will be displayed with html tags if the user have changed color, size etc on the font.
Note: I need the design panel, I know its possible to remove it but this is not the case :)
It's a Sharepoint standard, The only solution I have is to use javascript to strip these tags when it displayed. The user should only be able to insert links, images and add linebreaks.
Which means that all html tags should be stripped except <a></a>, <img> and <br> tags.
Its also important that the attributes inside the the <img> tag that wont be removed. It could be isplayed like this:
<img src="/image/Penguins.jpg" alt="Penguins.jpg" style="margin:5px;width:331px;">
How can I accomplish this with javascript?
I used to use this following codebehind C# code which worked perfectly but it would strip all html tags except <br> tag only.
public string Strip(string text)
{
return Regex.Replace(text, #"<(?!br[\x20/>])[^<>]+>", string.Empty);
}
Any kind of help is appreciated alot
Does this do what you want? http://jsfiddle.net/smerny/r7vhd/
$("body").find("*").not("a,img,br").each(function() {
$(this).replaceWith(this.innerHTML);
});
Basically select everything except a, img, br and replace them with their content.
Smerny's answer is working well except that the HTML structure is like:
var s = '<div><div>Link<span> Span</span><li></li></div></div>';
var $s = $(s);
$s.find("*").not("a,img,br").each(function() {
$(this).replaceWith(this.innerHTML);
});
console.log($s.html());
The live code is here: http://jsfiddle.net/btvuut55/1/
This happens when there are more than two wrapper outside (two divs in the example above).
Because jQuery reaches the most outside div first, and its innerHTML, which contains span has been retained.
This answer $('#container').find('*:not(br,a,img)').contents().unwrap() fails to deal with tags with empty content.
A working solution is simple: loop from the most inner element towards outside:
var $elements = $s.find("*").not("a,img,br");
for (var i = $elements.length - 1; i >= 0; i--) {
var e = $elements[i];
$(e).replaceWith(e.innerHTML);
}
The working copy is: http://jsfiddle.net/btvuut55/3/
with jQuery you can find all the elements you don't want - then use unwrap to strip the tags
$('#container').find('*:not(br,a,img)').contents().unwrap()
FIDDLE
I think it would be better to extract to good tags. It is easy to match a few tags than to remove the rest of the element and all html possibilities. Try something like this, I tested it and it works fine:
// the following regex matches the good tags with attrinutes an inner content
var ptt = new RegExp("<(?:img|a|br){1}.*/?>(?:(?:.|\n)*</(?:img|a|br){1}>)?", "g");
var input = "<this string would contain the html input to clean>";
var result = "";
var match = ptt.exec(input);
while (match) {
result += match;
match = ptt.exec(input);
}
// result will contain the clean HTML with only the good tags
console.log(result);

Merging Multiple Stylesheets

i have been tasked with changing a bit of css for a site that i did not build and is quite old. Long story short - building the site from scratch is not an option. There are two CSS stylesheets associated with the site. I have tried combining them with no luck. The first stylesheet is declared in the head section of the document as normal. The second seems to be pulled from the following code:
<script type="text/javascript">
<!--
function P7_StyleLoader(tS) { //v1.3 by PVII
var tH = '',
tDoc = '',
tA = '<LIN' + 'K REL="stylesheet" HREF=',
tB = ' TYPE="text/css">';
if (document.getElementsByTagName) {
var bb = document.getElementsByTagName("LINK");
if (bb) {
for (var k = 0; k < bb.length; k++) {
if (bb[k].rel.toLowerCase() == "stylesheet") {
var h = bb[k].href,
x = h.lastIndexOf("/");
if (x > 0) {
tH = h.substring(0, x + 1);
}
bb[k].disabled = true;
tDoc = tA + '"' + tH + tS + '"' + tB;
document.write(tDoc);
break;
}
}
}
}
}
P7_StyleLoader('STYLE-SHEET-2.css');
//-->
</script>
if i merge them and drop one of these stylesheets from the html, the site pretty much breaks in terms of layout. Could someone tell me if the above code is doing anything else besides just pulling "STYLE-SHEET-2.css"? My thinking is that i'd like to use either the above code and put the other styles into it or vice-versa? Am i missing something here?
thanks!
the code REPLACES the current stylesheets by disabling other stylesheets
It seems to be code designed to remove Netscape 4 specific stylesheets since NS4 does not understand the disabled stylesheet :)
Here is a 2002 discussion about this obviously even older code
http://www.webxpertz.net/forums/showthread.php?t=20718
The javascript fragment is disabling all stylesheet link statements, and importing only style-sheet-2.css.
Yeuk. That is some nasty javascript.
What it's doing is roughly this: It scans through any <link> elements in the document already and disables them. It loads the stylesheet specified in the function call instead.
In addition, it works out what the directory path was in the original CSS include, and appends that to the front of the stylesheet it's loading, so that it is loaded from the same path as the original would have been.
The code is (slightly) obfuscated as well, presumably to prevent automated scripts from detecting what they're doing.
The question is why would anyone actually go to all that trouble?
I can't think of too many reasons why they may have done this. The only thing that makes sense would be to fool spam bots into loading the wrong styles or something similar to that. Make the page look different to a bot to how a human would see it. Possibly trying to fool search engine bots too, but that would be considered a very bad move - if Google catches on to that you'd get black-listed.
Well, actually, what it does is inserting a new -attribute for a stylesheet. You should view the DOM-document with firebug to view the exact generated link with style-sheet-2.css.
My guess is that it loads the following line:
<link rel="stylesheet" href="STYLE-SHEET-2.css" type="text/css" />
It's also possible (re-read your code..) that it's stripping all other CSS and replace the src with this one. Very weird..

Changing content CSS on the run in TinyMCE or CKEditor

I have a form in which I want to edit a HTML template. It has 2 textareas, one for the HTML and another one for the CSS.
I'd like to use either TinyMCE or CKEditor for the HTML textarea.
Is there any way to change the content CSS in either of them to match the CSS in the CSS textarea on the run, so when I change the CSS it is automatically loaded into the editor?
Thanks.
I have no experience with CKEditor, but i know that it is possible with TinyMce. What you need to do is to write an own plugin which will provide the necessary functionality.
OnNodeChange in the 2nd textarea (the one with your css) you need to update the head of the first editors iframe. This code snippet to be executed on a special action (for example onNodeChange) should point you into the right direction:
var DEBUG = false;
var css_code = tinymce.editors[1].getContent(); // get content of 2nd editorinstance on page (your css)
iframe_id = tinymce.editors[0].id+'_ifr';
with(document.getElementById(iframe_id).contentWindow){
var h=document.getElementsByTagName("head");
if (!h.length) {
if (DEBUG) console.log('length of h is null');
return;
}
var newStyleSheet=document.createElement("style");
newStyleSheet.type="text/css";
h[0].appendChild(newStyleSheet);
try{
if (typeof newStyleSheet.styleSheet !== "undefined") {
newStyleSheet.styleSheet.cssText = css_code;
}
else {
newStyleSheet.appendChild(document.createTextNode(css_code));
newStyleSheet.innerHTML=css_code;
}
}
Be aware that this code will add a new style sheet everytime it is called - yielding in increasing the editor iframes head. So i think best practice is to clean up the last inserted style before appliing the new one. Removing the last Node of the head shozld be sufficient.

Categories