Mathjax: alternância não funciona depois de preencher novamente o innerHTML do div pai

Criado em 28 dez. 2020  ·  5Comentários  ·  Fonte: mathjax/MathJax

Eu tenho um editor de markdown de várias páginas que beneficia o uso do Mathjax. Por ser multi-página, o cruzamento de equações não funcionou como esperado automaticamente até que modifiquei o código para \eqref{}, após ser processado pelo Mathjax, para que em ação ele primeiro defina o valor correto para a página de destino , desvie para a nova página e a referência funcionará conforme o esperado. Isso funciona muito bem, mas recentemente descobri que o ponteiro do mouse sobre o termo a ser alternado é alterado como em uma área clicável, enquanto clicar sobre ele fará com que nada aconteça.

este é um exemplo mínimo para reproduzi-lo:

<!DOCTYPE html>
<html class="htmlMain">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <script type="text/javascript" src="tex-svg-full.js" async></script>
    <!-- <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg-full.js"></script> -->
</head>
<body>
<textarea id="source" style="width:400px; height:100px;"></textarea>
<input type="button" value="Click!" onclick="compile();">
<div id="result" style="display:inline-block;"></div>
<script>
var source=document.getElementById("source"),
    result=document.getElementById("result");

function compile(){
    result.innerHTML=source.value;

    MathJax.texReset();
    MathJax.typesetClear();
    MathJax.typeset();

    result.innerHTML=result.innerHTML;
}
</script>
</body>
</html>

O problema surge por causa da linha: result.innerHTML=result.innerHTML; . Agora é uma declaração trivial deixada por causa da reprodutibilidade do bug, enquanto no código real eu tenho algo não trivial do tipo: result.innerHTML=result.innerHTML.replace(); . Eu assumi que era um bug, já que todo o resto aparentemente funciona até onde eu verifiquei, exceto o comando \toggle.

Obrigada

Expected Behavior v3

Todos 5 comentários

O comando \toggle funciona adicionando ouvintes de eventos ao nó DOM para o elemento de alternância, o que faz com que a expressão atualize qual subexpressão deve ser exibida e redigite a expressão. Sua expressão result.innerHTML = result.innerHTML primeiro serializa a árvore result como uma string HTML e depois a analisa novamente para definir o conteúdo de result . Como os ouvintes de eventos não fazem parte da serialização, eles são perdidos quando você faz isso, então a expressão de alternância parará de funcionar (assim como os menus MathJax, pop-ups e outros recursos que dependem dos ouvintes de eventos). Mesmo se você tivesse os ouvintes de eventos, a redigitação quando uma alternância é alternada significaria que você perderia seus URLs modificados na expressão redigitada (se houver).

Seria melhor implementar uma versão de \eqref (e \ref ) que tratasse corretamente os links externos originalmente. Isso pode ser feito substituindo suas definições para chamar sua própria implementação que verifica o rótulo de referência para ver se é um que se refere a um documento externo e, em seguida, configura os dados necessários para fazer o link para esse documento. Por exemplo, você pode fazer a convenção de que \eqref{file.html#1.1} se referiria à equação 1.1 em file.html (ou qualquer URL que você quisesse usar). O \eqref atualizado pode procurar rótulos nesse formulário e configurar os dados para que o código original seja vinculado a ele corretamente. Podemos usar o método tagformat extensão url(id) para verificar se o id já é um URL completo e retorná-lo se for, caso contrário, crie o URL adequado normalmente.

Aqui está uma dessas implementações:

<script>
MathJax = {
  loader: {load: ['[tex]/tagformat']},
  tex: {
    packages: {'[+]': ['tagformat', 'external-eqref']},
    tagformat: {
      //
      //  If the ID is already a URL, use it, otherwise construct the url as usual
      //
      url: (id, base) => (id.indexOf('#') >= 0 ? id : base + '#' + encodeURIComponent(id))
    }
  },
  startup: {
    ready() {
      //
      //  These would be replaced by import commands if you wanted to make
      //  a proper extension.
      //
      const Configuration = MathJax._.input.tex.Configuration.Configuration;
      const CommandMap = MathJax._.input.tex.SymbolMap.CommandMap;
      const Label = MathJax._.input.tex.Tags.Label;
      const BaseMethods = MathJax._.input.tex.base.BaseMethods.default;

      //
      //  Create a command map to override \ref and \eqref
      //
      new CommandMap('external-eqref', {
        ref:   ['HandleRef', false],
        eqref: ['HandleRef', true]
      }, {
        HandleRef(parser, name, eqref) {
          //
          //  Get the label parameter (keeping parse position as it is)
          //
          const i = parser.i;
          const label = parser.GetArgument(name);
          parser.i = i;
          //
          //  If the label is of the form url#tag and the label doesn't already exist
          //    split the url and tag
          //    create a label using the tag and a proper URL for the link
          //
          if (label.indexOf('#') >= 0 && !(parser.tags.allLabels[label] || parser.tags.labels[label])) {
            const [url, tag] = label.split(/#/);
            const id = parser.tags.formatId(tag);
            parser.tags.labels[label] = new Label(tag, parser.tags.formatUrl(id, url));
          }
          //
          //  Call the original function to perform the reference
          //
          BaseMethods.HandleRef(parser, name, eqref);
        }
      });
      //
      //  Create the package for the overridden macros
      //
      Configuration.create('external-eqref', {
        handler: {macro: ['external-eqref']}
      });

      MathJax.startup.defaultReady();
    }
  }
}
</script>

Isso pode ser transformado em uma extensão apropriada do TeX que vive em seu próprio arquivo, se você desejar.

@dvpc , muito obrigado,
então foi minha implementação ruim... obrigado pela explicação completa. Infelizmente, não consegui usar o código que você forneceu, pois minha paginação é diferente, não uso links externos, Divs diferentes representam páginas diferentes no meu caso, todas menos uma estão ocultas, para que eu reconheça apenas as citadas equação reside em qual div para que eu possa reexibi-la primeiro. Eu já estava acostumado com o código

    var eqsLabelsArrayPerPage=[];
    for(pageNum=0; pageNum<totalPageNumber; pageNum++){
        var jax = MathJax.getAllJax("resultPage-"+pageNum);
        var neWLabelsInPage=[];
        for (var i=0, l=jax.length; i<l; i++) {
            //alert(jax[i].math);
            jax[i].math.replace(/\\label\{([^\}]+)\}/g, function(x,y){
                neWLabelsInPage.push(y);
                return false;
            });
        }
        eqsLabelsArrayPerPage.push(neWLabelsInPage);
    }
    var eqOnWPVar;
    result.innerHTML=result.innerHTML.replace(/(href\=\"\#mjx\-eqn\-([^\"]*)\")/g,  function(x,y1,y2){
        eqOnWPVar=eqsLabelsArrayPerPage.findIndex(function(x) {
            return x.indexOf(y2) !== -1;
        });
        return y1+" onclick=\"currentPage="+eqOnWPVar+"; changePage();\"";
    });

cuja segunda parte é agora substituída por

    var eqCitations=[];
    result.innerHTML.replace(/(href\=\"\#mjx\-eqn\-([^\"]*)\")/g,  function(x,y1,y2){
        eqCitations=document.querySelectorAll("mrow[href='#mjx-eqn-"+y2+"']");

        for(var i=0, len=eqCitations.length; i<len; i++){
            eqCitations[i].parentNode.parentNode.parentNode.onclick=function(){
                currentPage=eqsLabelsArrayPerPage.findIndex(function(subArray){
                    return subArray.indexOf(y2) !== -1;
                });
                changePage();
            };
        }
        return false;
    });

Não é eficiente reavaliar o currentPage para cada citação até mesmo para uma única equação, mas com onclick não funcionando de forma síncrona, parece que não tenho maneira melhor.

Obrigado novamente, eu aprecio muito o tempo que você dedica a essas conversas

Bem, aqui está uma abordagem possível que pode fazer o que você está procurando com o mínimo de sobrecarga. Tudo é feito dentro do MathJax, então não precisa ter código extra como o que você deu acima, mas é preciso um pouco de configuração para fazê-lo funcionar.

Existem três componentes principais: uma subclasse da classe de marcação padrão que acompanha a página em que cada equação rotulada está, uma configuração do TeX que substitui as macros \ref e \eqref para marcar as referências que apontam para outras páginas e, finalmente, um pós-filtro de saída que adiciona um ouvinte de evento nesses links que abrem outra página (e fecha a atual) antes de criar o link.

Eu acho que isso deve fazer o que você quer (ou pelo menos dar uma estrutura para isso). Aqui está um arquivo completo que possui várias equações rotuladas com links para elas em diferentes páginas.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>Page switching \eqref links</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script>
let currentPage = 1;
MathJax = {
  startup: {
    ready() {
      //
      //  These would be replaced by import commands if you wanted to make
      //  a proper extension.
      //
      const TagsFactory = MathJax._.input.tex.Tags.TagsFactory;
      const AmsTags = MathJax._.input.tex.ams.AmsConfiguration.AmsTags;

      //
      // Subclass the AmsTags object to track page information
      //
      class PageTags extends AmsTags {

        constructor() {
          super();
          this.page = null;        // the div for the last page where we found an equation
          this.pageNo = "";        // the number of that page
          this.math = null;        // the MathItem for the current equation
        }

        //
        //  Save the MathItem and do the usual startup.
        //
        startEquation(math) {
          this.math = math;
          super.startEquation(math);
        }

        //
        //  Check if there are labels for this equation.
        //  If so, find the page for this MathItem
        //    and save the page with the label information.
        //  The do the usual finishing up.
        //
        finishEquation(math) {
          const labels = Object.keys(this.labels);
          if (labels.length) {
            const page = this.getPage();
            labels.map((label) => {this.labels[label].page = page});
          }
          super.finishEquation(math);
        }

        //
        //  If there is a cached page div and this MathItem is in it, return its page number.
        //  Otherwise, look through the parents of the math until you find its page.
        //  If you found a page, cache it and get its number.
        //  Return the page number.
        //
        getPage() {
          let node = this.math.start.node;
          if (this.page && this.page.contains(node)) return this.pageNo;
          while (node && (!node.id || node.id.substr(0,11) !== 'resultPage-')) {
            node = node.parentNode;
          }
          if (node) {
            this.page = node;
            this.pageNo = node.id.substr(11);
          }
          return this.pageNo;
        }

      }

      //
      //  These would be replaced by import commands if you wanted to make
      //  a proper extension.
      //
      const Configuration = MathJax._.input.tex.Configuration.Configuration;
      const CommandMap = MathJax._.input.tex.SymbolMap.CommandMap;
      const Label = MathJax._.input.tex.Tags.Label;
      const ParseUtil = MathJax._.input.tex.ParseUtil.default;

      //
      //  Create a command map to override \ref and \eqref to handle page changes.
      //
      new CommandMap('page-eqref', {
        ref:   ['HandleRef', false],
        eqref: ['HandleRef', true]
      }, {
        //
        //  Copied from BaseMethods.HandleRef, and modified to add page information.
        //  
        HandleRef(parser, name, eqref) {
          //
          //  Get the label name and look up its data.
          //
          const label = parser.GetArgument(name);
          let ref = parser.tags.allLabels[label] || parser.tags.labels[label];
          //
          //  If none, then reprocess this item, if we are on the first pass,
          //  and use a blank label.
          //
          if (!ref) {
            if (!parser.tags.refUpdate) {
              parser.tags.redo = true;
            }
            ref = new Label();
          }
          //
          //  Get the tag string.
          //
          const tag = (eqref ? parser.tags.formatTag(ref.tag) : ref.tag);
          //
          //  Create an mrow with the parsed tags contents and href link.
          //
          const node = parser.create('node', 'mrow', ParseUtil.internalMath(parser, tag), {
            href: parser.tags.formatUrl(ref.id, parser.options.baseURL), 'class': 'MathJax_ref'
          });
          //
          //  If the page of the linked quation is not the same as our own equation,
          //    Add a page attribute to the mrow.
          //
          if (ref.page !== parser.tags.getPage()) {
            node.attributes.set('data-page', ref.page);
          }
          parser.Push(node);
        }
      });
      //
      //  Create the package for the overridden macros
      //
      Configuration.create('page-eqref', {
        handler: {macro: ['page-eqref']},
        tags: {page: PageTags}
      });

      //
      //  Do the normal setup (create the input and output jax and other objects)
      //
      MathJax.startup.defaultReady();

      //
      //  The listener function for when a link goes to a different page:
      //     Unhide the linked page and hide the current one.
      //     Save the current page as the linked one.
      //
      const listener = function (event) {
        const n = this.getAttribute('data-page');
        document.getElementById('resultPage-' + n).classList.remove('hidden');
        document.getElementById('resultPage-' + currentPage).classList.add('hidden');
        currentPage = n;
      };

      //
      //  Add a post-filter to the output jax to look for links that are to different
      //    pages, add the event listener to the <a> element for the link, and
      //    propagate the page to the anchor where the listener can find it.
      //
      MathJax.startup.output.postFilters.add(({data}) => {
        for (const link of data.querySelectorAll('[data-page]')) {
          link.parentNode.addEventListener('click', listener, true);
          link.parentNode.setAttribute('data-page', link.getAttribute('data-page'));
        }
      });
    }
  },
  tex: {
    packages: {'[+]': ['page-eqref']},
    tags: 'page'
  }
}
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
<style>
div {
  border: 1px solid grey;
  padding: .5em;
}
h1 {
  font-size: 130%;
  margin-top: 0;
}
.hidden {
  display: none;
}
.spacer {
  height: 40em;
  width: 1em;
  background-color: red;
  margin: 1em 0;
  border: none;
}
</style>
</head>
<body>

<div class="spacer"></div>

<div id="resultPage-1">
<h1>Page 1</h1>
A math formula:
$$E = mc^2. \tag{1}\label{eq1}$$
That is the formula.  
We link to \eqref{eq3}.
</div>

<div id="resultPage-2" class="hidden">
<h1>Page 2</h1>
This page has a different formula:
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}. \tag{2}\label{eq2}$$
And a second one:
$$\frac{x+1}{x-1}. \tag{2.1}\label{eq2.1}$$
We link to \eqref{eq1}.
</div>

<div id="resultPage-3" class="hidden">
<h1>Page 3</h1>
This one has yet another formula:
$$f(a) = \frac{1}{2\pi i} \oint\frac{f(z)}{z-a}dz. \tag{3}\label{eq3}$$
We link to \eqref{eq2}, and \eqref{eq3}.
</div>

<div class="spacer"></div>

</body>
</html>

As barras vermelhas acima e abaixo da matemática são apenas para tornar a página grande o suficiente para rolar para que você possa ver que a equação correta está sendo direcionada. Observe, no entanto, que navegadores diferentes lidam com a rolagem para as posições SVG de forma ligeiramente diferente. Estou procurando melhorar a segmentação para que fique mais consistente, mas isso ainda não está pronto. CommonHTML faz melhor com isso.

De qualquer forma, veja se isso te deixa mais parecido com o que você quer.

Eu não posso apreciar o suficiente o tempo e a energia que você colocou em responder às perguntas. Acabei de ver sua resposta, parece tão incrível. Levaria algumas vezes, no entanto, antes que eu pudesse aplicar o código que você forneceu ao meu aplicativo. Muito obrigado.

Sem problemas. Foi um projeto interessante, e vale a pena descobrir como poderia ser feito, para referência futura por outros.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

Esporia picture Esporia  ·  4Comentários

photino picture photino  ·  5Comentários

parhizkari picture parhizkari  ·  6Comentários

dpvc picture dpvc  ·  3Comentários

kiwi0fruit picture kiwi0fruit  ·  3Comentários