Embedding mathematical equations in an Elm Spa

huangapple go评论46阅读模式
英文:

Embedding mathematical equations in an Elm Spa

问题

以下是您提供的内容的翻译:

我想在一个用Elm编写的单页应用中放置数学方程。
我希望这些方程在应用中呈现,而不是作为预渲染图像嵌入。

我尝试使用Elm的Katex(https://package.elm-lang.org/packages/yotamDvir/elm-katex/latest/)来实现这一点,但我的方法存在三个主要问题:
- 初始加载页面时,方程未呈现
- 当单击链接时,呈现不始终有效
- 单击链接时,已呈现的数学元素在页面更改时保留,从而破坏页面内容。

看起来是这样的:
[![上述3个问题的示例][1]][1]

这是我目前正在使用的代码:

`index.html`
```html
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>SPA with Math formulas</title>
  <style>
    body {
      padding: 0;
      margin: 0;
      background-color: #000000;
      color: #ffffff;
    }
  </style>
  <script src="main.js"></script>

  <style>
    /* LaTeX display environment will affect the LaTeX characters but not the layout on the page */
    span.katex-display {
      display: inherit;
      /* You may comment this out if you want the default behavior */
    }
  </style>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css"
    integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
  <script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js"
    integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4"
    crossorigin="anonymous"></script>
  <script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js"
    integrity="sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa" crossorigin="anonymous"
    onload="renderMathInElement(document.body);"></script>
</head>

<body>

  <div id="elm-main"> </div>
  <script>
    // Initialize your Elm program
    var app = Elm.Main.init({
      flags: location.href,
      node: document.getElementById('elm-main')
    });

    // Inform app of browser navigation (the BACK and FORWARD buttons)
    window.addEventListener('popstate', function () {
      app.ports.onUrlChange.send(location.href);
    });

    // Change the URL upon request, inform app of the change.
    app.ports.pushUrl.subscribe(function (url) {
      history.pushState({}, '', url);
      app.ports.onUrlChange.send(location.href);
    });

    // Render math texts in app
    app.ports.renderMath.subscribe(function () {
      renderMathInElement(document.body, {
        delimiters: [
          {
            left: "$begin-inline$",
            right: "$end-inline$",
            display: false
          },
          {
            left: "$begin-display$",
            right: "$end-display$",
            display: true
          }]
      });
    });
  </script>
  <noscript>
    This site needs javascript enabled in order to work.
  </noscript>
</body>

</html>

src/Main.elm

port module Main exposing (..)

import Browser exposing (Document, application)
import Element exposing (Attribute, Element)
import Element.Font
import Html
import Html.Attributes
import Html.Events
import Json.Decode as D
import Katex
import Url
import Url.Parser

main =
    Browser.document
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
    }

type Route
    = Home
    | Other

init : String -> ( Route, Cmd Msg )
init url =
    ( locationHrefToRoute url, Cmd.none )

type Msg
    = PushUrl Route
    | UrlChanged String

update : Msg -> Route -> ( Route, Cmd Msg )
update msg route =
    case msg of
        PushUrl newRoute ->
            ( route, pushUrl (stringFromRoute newRoute) )

        UrlChanged url ->
            ( locationHrefToRoute url, renderMath () )

stringFromRoute : Route -> String
stringFromRoute route =
    case route of
        Home ->
            "/"

        Other ->
            "/other"

locationHrefToRoute : String -> Route
locationHrefToRoute locationHref =
    case Url.fromString locationHref of
        Nothing ->
            Home

        Just url ->
            Maybe.withDefault Home (Url.Parser.parse routeParser url)

routeParser : Url.Parser.Parser (Route -> a) a
routeParser =
    Url.Parser.oneOf
        [ Url.Parser.map Home Url.Parser.top
        , Url.Parser.map Other (Url.Parser.s "other")
        ]

view : Route -> Document Msg
view route =
    { title = "SPA and Katex"
    , body =
        [ Element.layout
            [ Element.Font.color (Element.rgb 1 1 1)
            ]
            (Element.el
                [ Element.centerX, Element.centerY ]
                (viewPage route)
            )
        ]
    }

viewPage : Route -> Element Msg
viewPage route =
    case route of
        Home ->
            Element.column [ Element.spacing 20 ]
                [ link Other "Link to Other"
                , Element.text "some text"
                , "\\mathrm{home} =  6.2 \\times 10^{-34}"
                    |> Katex.inline
                    |> Katex.print
                    |> Element.text
                ]

        Other ->
            Element.column [ Element.spacing 20 ]
                [ link Home "Link to Home"
                , "\\mathrm{other} = 1.3 \\times 10^{-6}"
                    |> Katex.inline
                    |> Katex.print
                    |> Element.text
                , Element.text "other text"
                ]

linkBehaviour : Route -> Attribute Msg
linkBehaviour route =
    Element.htmlAttribute
        (Html.Events.preventDefaultOn "click"
            (D.succeed
                ( PushUrl route, True )
            )
        )

link : Route -> String -> Element Msg
link route labelText =
    Element.link
        [ linkBehaviour route
        , Element.Font.color (Element.rgb255 

<details>
<summary>英文:</summary>

I want to put mathematical equations in a single page app written in elm.
I would like the equations to be rendered in the app and not being embedded as prerendered images.


I tried to realize this using Katex for elm (https://package.elm-lang.org/packages/yotamDvir/elm-katex/latest/) but my approach has 3 major problems:
- when the page is loaded initially the equations are not rendered
- the rendering does not always work when a link is clicked
- rendered math elements are preserved in page changes when a link is clicked, thus destroying the content of the page.

This is hoow it looks:
[![Example of the 3 problems described above][1]][1]

Here is the code that I am using right now:

`index.html`

<!DOCTYPE HTML>
<html>

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>SPA with Math formulas</title>
<style>
body {
padding: 0;
margin: 0;
background-color: #000000;
color: #ffffff;
}
</style>
<script src="main.js"></script>

<style>
/* LaTeX display environment will effect the LaTeX characters but not the layout on the page /
span.katex-display {
display: inherit;
/
You may comment this out if you want the default behavior */
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css"
integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js"
integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4"
crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js"
integrity="sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa" crossorigin="anonymous"
onload="renderMathInElement(document.body);"></script>
</head>

<body>

<div id="elm-main"> </div>
<script>
// Initialize your Elm program
var app = Elm.Main.init({
flags: location.href,
node: document.getElementById('elm-main')
});

// Inform app of browser navigation (the BACK and FORWARD buttons)
window.addEventListener(&#39;popstate&#39;, function () {
app.ports.onUrlChange.send(location.href);
});
// Change the URL upon request, inform app of the change.
app.ports.pushUrl.subscribe(function (url) {
history.pushState({}, &#39;&#39;, url);
app.ports.onUrlChange.send(location.href);
});
// Render math texts in app
app.ports.renderMath.subscribe(function () {
renderMathInElement(document.body, {
delimiters: [
{
left: &quot;$begin-inline$&quot;,
right: &quot;$end-inline$&quot;,
display: false
},
{
left: &quot;$begin-display$&quot;,
right: &quot;$end-display$&quot;,
display: true
}]
});
});

</script>
<noscript>
This site needs javascript enabled in order to work.
</noscript>
</body>

</html>


`src/Main.elm`

port module Main exposing (..)

import Browser exposing (Document, application)
import Element exposing (Attribute, Element)
import Element.Font
import Html
import Html.Attributes
import Html.Events
import Json.Decode as D
import Katex
import Url
import Url.Parser

main =
Browser.document
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}

type Route
= Home
| Other

init : String -> ( Route, Cmd Msg )
init url =
( locationHrefToRoute url, Cmd.none )

type Msg
= PushUrl Route
| UrlChanged String

update : Msg -> Route -> ( Route, Cmd Msg )
update msg route =
case msg of
PushUrl newRoute ->
( route, pushUrl (stringFromRoute newRoute) )

    UrlChanged url -&gt;
( locationHrefToRoute url, renderMath () )

stringFromRoute : Route -> String
stringFromRoute route =
case route of
Home ->
"/"

    Other -&gt;
&quot;/other&quot;

locationHrefToRoute : String -> Route
locationHrefToRoute locationHref =
case Url.fromString locationHref of
Nothing ->
Home

    Just url -&gt;
Maybe.withDefault Home (Url.Parser.parse routeParser url)

routeParser : Url.Parser.Parser (Route -> a) a
routeParser =
Url.Parser.oneOf
[ Url.Parser.map Home Url.Parser.top
, Url.Parser.map Other (Url.Parser.s "other")
]

view : Route -> Document Msg
view route =
{ title = "SPA and Katex"
, body =
[ Element.layout
[ Element.Font.color (Element.rgb 1 1 1)
]
(Element.el
[ Element.centerX, Element.centerY ]
(viewPage route)
)
]
}

viewPage : Route -> Element Msg
viewPage route =
case route of
Home ->
Element.column [ Element.spacing 20 ]
[ link Other "Link to Other"
, Element.text "some text"
, "\mathrm{home} = 6.2 \times 10^{-34}"
|> Katex.inline
|> Katex.print
|> Element.text
]

    Other -&gt;
Element.column [ Element.spacing 20 ]
[ link Home &quot;Link to Home&quot;
, &quot;\\mathrm{other} = 1.3 \\times 10^{-6}&quot;
|&gt; Katex.inline
|&gt; Katex.print
|&gt; Element.text
, Element.text &quot;other text&quot;
]

linkBehaviour : Route -> Attribute Msg
linkBehaviour route =
Element.htmlAttribute
(Html.Events.preventDefaultOn "click"
(D.succeed
( PushUrl route, True )
)
)

link : Route -> String -> Element Msg
link route labelText =
Element.link
[ linkBehaviour route
, Element.Font.color (Element.rgb255 119 35 177)
, Element.Font.underline
]
{ url = stringFromRoute route
, label = Element.text labelText
}

subscriptions : Route -> Sub Msg
subscriptions route =
Sub.batch
[ onUrlChange UrlChanged
]

port onUrlChange : (String -> msg) -> Sub msg

port pushUrl : String -> Cmd msg

port renderMath : () -> Cmd msg


I start my app with `elm-live src/Main.elm -u --open -- --output=main.js --debug`:
[1]: https://i.stack.imgur.com/wmL55.gif
</details>
# 答案1
**得分**: 2
elm-katex文档指出不需要使用ports。
&gt; 不需要使用ports,但是KaTeX库必须在事件循环中加载。有关详细信息,请参见底部的加载KaTeX一节。
文档中的[加载KaTeX][1]部分建议监听DOMContentLoaded事件,然后调用KaTeX的自动渲染扩展。话虽如此,KaTeX的自动渲染扩展通过编辑DOM来工作,这意味着它与Elm不太兼容。
使用KaTeX渲染Tex数学公式是使用[自定义元素][2](Web组件)的一个很好的用例。如果你熟悉自定义元素,实现起来相对简单,但很可能已经有现成的解决方案可用。
查看[Elm指南的自定义元素部分][3]以获取很好的介绍。
我没有仔细研究过,但这是一个选择:[navsgh/katex-expression][4]
在初始化Elm代码之前加载*katex-expression*,然后编写类似于以下的内容:
```elm
import Html exposing (Html, node)
import Html.Attributes exposing (attribute)
import Json.Encode as Encode
viewLatex : String -> Html msg
viewLatex expr =
node "katex-expression"
[ attribute "expression" expr
, attribute "katex-options" (Encode.encode 0 options)
]
[]
options : Encode.Value
options =
Encode.object
[ ( "displayMode", Encode.bool True )
]

这里有一个快速的Ellie演示: https://ellie-app.com/m9HQrnXmydLa1

英文:

The elm-katex docs point out ports being unnecessary.

> No ports are necessary, but the KaTeX library must be loaded in the
> event loop. See §Loading KaTeX at the bottom for details.

The Loading KaTeX section of the docs suggests listening for DOMContentLoaded, then calling KaTeX's auto-render extension. That said, KaTeX's auto-render extension works by editing the DOM, meaning it won't play nice with Elm.

Rendering Tex math using KaTeX is a good use-case for a Custom Element (Web Components). It would be relatively straightforward to implement if you're familiar with custom elements but there's likely something already out there that will work.

See the Custom Elements section of the Elm Guide for a good intro.

I've not taken much of a look at it, but here's an option: navsgh/katex-expression

Load katex-expression before you initialize your Elm code, then write something like this:

import Html exposing (Html, node)
import Html.Attributes exposing (attribute)
import Json.Encode as Encode


viewLatex : String -&gt; Html msg
viewLatex expr =
  node &quot;katex-expression&quot;
    [ attribute &quot;expression&quot; expr
    , attribute &quot;katex-options&quot; (Encode.encode 0 options)
    ]
    []


options : Encode.Value
options =
    Encode.object
        [ ( &quot;displayMode&quot;, Encode.bool True )
        ]

Here's a quick Ellie demo: https://ellie-app.com/m9HQrnXmydLa1

huangapple
  • 本文由 发表于 2023年2月18日 18:52:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75492820.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定