将 Mathjax 升级到 4 版本

@sam 以及所有对在 Discourse 中输入数学内容感兴趣的人。我已经更新了 discourse-math 插件,使其使用 MathJax V3,而不是速度慢得多且非常过时的 V2。正如预期的那样,结果是用户体验更加灵敏,同时与 KaTeX 相比仍保持功能丰富的环境。

如果结果看起来不错,我很乐意提交一个拉取请求(pull request)。


您可以在我的课堂 Discourse 网站上看到它的实际效果:

该网站上的大多数内容都是私密或未列出的。在顶部的 MathJax V3 类别中应该有几个主题来说明这些想法,不过

您可以在这个独立的 discourse-mathjax 插件仓库中检查插件的代码。修改最多的文件是初始化文件

您也可以立即使用该仓库将其安装在独立站点上。只需确保在安装过程中删除旧的仓库。因此,您需要修改标准的插件安装技术使其如下所示:

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - rm -r discourse-math
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/mcmcclur/discourse-math.git

评论

MathJax 的最新版本实际上是 4.0.0。我选择使用 V3.2.2 是有几个原因:

  • 虽然 V4 比 V2 快得多,但它不如 V3 快。
  • V4 中的用户体验略有不同,特别是当用户点击输出时。
  • 4.0.0 的状态让我怀疑可能存在多少错误。

话虽如此,V4 的 API 与 V3 的 API 相同。稍后可以通过简单地放入最新的 MathJax 仓库来升级。

我不得不在 locales/server.en.yml 文件中进行了一个小的更改。当然,对于各种语言还有很多这样的文件。我的理解是,那些其他文件稍后会自动翻译?

我完全不使用聊天,也没有在那种情况下进行测试。

4 个赞

升级 MathJax 到 V3 的拉取请求已提交,所有测试均已通过!

2 个赞

关于:

这太棒了 :hugs:,但我不知道我们是否可以借此机会精简一下我们的仓库。

既然我们已经将 mathjax 移至核心,我们可以依赖 pnpm 来拉取包,并避免像对 FullCalendar 那样打包所有源代码。

特别是,目标是我们的仓库中只保留“链接”,然后我们可以使用构建过程来拉取正确的依赖项。

请给我们几天时间,我想在这里与开发体验 (dev xp) 团队咨询一下。非常感谢您的努力!

4 个赞

是的,我认为这绝对是正确的做法。我一直很奇怪你为什么要打包整个东西!

那么,我猜你会为你的库构建一个 loadMathJax 函数来加载 MathJax 吧?

我要说的是,将所有插件都集成到核心中使得摆弄它们变得更加棘手。将依赖项与构建过程绑定只会让情况更糟,尽管我确信我可以从 CDN 拉取 MathJax 或 FullCalendar。

我主要说的是当我为我自己的论坛修补插件时,我绝对认为你应该在构建过程中拉入 MathJax。

当然!我使用 Discourse 已经很多年了,很高兴你认为这很棒!:rocket:

3 个赞

是的,完全正确。一个好的参考是 morphlex:

1 个赞

我想知道您是否已经和您的开发体验人员讨论过了?如果我能帮上忙,我很乐意提供帮助。不过,我的印象是,如果没有您那边的反馈,我确实无能为力。

我已经在另一个分支中做了一些额外的更改,我很快就会发布相关信息。我明白您手头有很多事情要做,我无意打扰!

我对 discourse-math 插件进行了修改,使其能够解析更多的数学输入。

@sam 我记得 2017 年我第一次向该插件贡献代码时,您非常坚持希望解析非常严格。我首先要说明的是,我放宽和扩展解析的主要动机是使其能更好地与人工智能(AI)配合使用。特别是,当您与 AI 机器人讨论数学问题时,您会发现它经常使用 LaTeX 回复,并且它可能会选择多种方式来分隔该 LaTeX 输入。因此,虽然我理解您对严格解析的动机,但我所做的更改对于该用例来说是相当必要的。

当然,您可能仍然不关心该用例,所以我将更改放在了我的 V3 拉取请求(pull request)的一个单独分支中。如果您决定喜欢这些更改,我很乐意再提交一个拉取请求。

对拉取请求的具体更改如下:

它接受斜杠-括号分隔的行内数学公式,例如 \\(a^2+b^2=c^2\\)。

它接受单行双美元符号分隔的显示数学公式,例如
$$a^2+b^2=c^2.$$

它接受单行斜杠-方括号分隔的显示数学公式,例如
\\[a^2+b^2=c^2.\\]

它接受多行斜杠-方括号分隔的显示数学公式,例如
\\[
a^2+b^2=c^2.
\\]

当然,它仍然接受原始版本的输入:

美元符号分隔的行内数学公式:$a^2+b^2=c^2$。

多行、双美元符号分隔的显示数学公式:
$$
a^2+b^2=c^2.
$$

您可以在此处找到相关的分支。

该代码也作为一个独立插件存在

哦,您还可以在此处看到它的实际效果!

2 个赞

@mcmcclur 感谢您的工作。很高兴能在核心中看到这些功能。

1 个赞

非常感谢马克。

我目前最大的阻碍是,我真的很想采用新的依赖项分发模式,参见:

您能看一下这个问题吗?

关于宽松的语法,我觉得这应该是一个站点设置,鉴于目前所有的大型语言模型(LLM)的普及,甚至可以默认开启?

3 个赞

@mcmcclur 我今天摆弄了一下这个:

远未准备好……但东西在 4.1 上可以启动了,这很好。

2 个赞

是的,这绝对是进步!
我要解决的第一个关键问题是找不到字体,我怀疑您也知道。事实上,我摆弄了 discourse-math-mathjax.js 中的这一行:

fontURL: getURLWithCDN("/assets/mathjax/woff-v2"),

作为测试,我将 URL 设置为仅指向我自己的 Web 服务器上的一个临时目录,初步结果看起来非常好。所以,问题在于如何将这些字体正确地安装到 Discourse 中。
在我机器上的一个简单的 pnpm 项目中,以下命令会安装字体:

pnpm install @mathjax/mathjax-newcm-font@4

当我在 discourse/frontend/discourse 中运行该命令时,字体会出现在

/discourse/frontend/discourse/npm_modules/@mathjax/mathjax-newcm-font/chtml/woff2/

然而,那些字体在构建后似乎没有出现在 /assets/mathjax/woff-v2 中。我已经尝试了目录的多种变体,但没有成功。我猜这是一种路由魔术,我对此并不擅长。我很有把握,一旦那个路径问题解决,我就可以在清理工作上取得良好进展。

1 个赞

@sam认为我在这个问题上取得了相当大的进展,但有一个重要的注意事项。我不确定应该从哪里加载所需的组件。用代码表示如下:

window.MathJax = {
    loader: {
      // 这不起作用:
      // paths: { mathjax: getURLWithCDN("/assets/mathjax") },
      // 但这个效果很好:
      paths: { mathjax: "https://cdn.jsdelivr.net/npm/mathjax@4.1.0" },
      load: ["core", "input/tex", "input/mml", "output/chtml", "output/svg"],
    },
    // 更多配置 ...
  };

当我说注释掉的版本不起作用时,我的意思是出现了明确的消息:
MathJax(core): 无法加载 “/assets/mathjax/core.js”

请注意,在这两种情况下,loadMathJax 函数都是从本地副本中获取 MathJax 启动代码的。也就是说,我在以下位置有以下内容:
/discourse/frontend/discourse/app/static/mathjax-bundle.js

export * from "mathjax/startup.js";

然后,在以下位置定义的 loadMathJax
/discourse/frontend/discourse/app/lib/load-mathjax.js
调用

const bundle = await import("discourse/static/mathjax-bundle");

这暗示了几个可能性:

  1. 也许 /assets/mathjax 不是正确的位置,或者
  2. 也许需要以某种方式注册这些资产,以便它们出现在分发目录中?

基于 CDN 版本,我似乎可以取得重大进展,但我假设这对你来说是一个主要的障碍。

如果你愿意,我可以与你分享我的代码,但这是否提供了足够的信息来进行诊断?

1 个赞

当然,代码在这里会非常有帮助,也许可以 fork discourse 然后将你的更改推送到一个分支,这样我就可以从你的分支拉取更改到 PR 中。

很高兴看到你取得了进展,正在努力诊断这个问题。

你也能拉取最新的代码吗,我做了一轮清理。

1 个赞

好的,这是代码:

不过请注意,我不是直接从你的最新提交开始的。我是直接从 Discourse 主分支开始并在其上进行更改的。因此,我从你的工作中学习了不少,但整体结构有所不同。

我认为你可以将主要区别总结如下:你在协调与加载和排版相关的时间安排方面(很自然地)使用了从 Ember 继承的 Discourse 功能,而我使用了 MathJax 的功能。因此,我的 load-mathjaxmathjax 捆绑包(一个用于 svg,一个用于 chtml)比你的要简单得多。加载完全是通过 discourse-math-mathjax 中的 window.MathJax 对象协调的。

我仍然存在我之前描述的同一个问题,即 这个被注释掉的加载器 不起作用;我必须使用 这个 CDN 版本 才能工作。我真的不知道为什么。

我认为你的代码也存在同样的问题。这就是 AsciiMath 似乎不起作用的原因。

1 个赞

你能检查一下我最新的提交吗?我想我为 Ember 添加了一个漏斗(funnel),所以 Ember 构建现在会将所有文件放在正确的位置。

2 个赞

好的,我有一个非常好的消息和一个令人沮丧的消息。

首先,你说得完全正确,添加 funnel 将这些文件放在了正确的位置。我在我的分支中添加了 funnel,现在它在没有 CDN 依赖的情况下运行得非常好。:tada:

不幸的是,我目前无法运行你的代码。每当我导航到包含数学内容的页面时,数学内容都不会被排版,并且我在控制台中看到了以下错误消息:
Uncaught (in promise) Error: State EXPLORER already exists

我确信我之前让你的代码可以工作,所以我想这可能是我做错了什么。但话说清楚,我确实是使用在 macOS 上为开发安装 Discourse 中描述的技术,从一个全新的目录开始的。

git clone https://github.com/discourse/discourse.git ./discourse
cd ./discourse
bundle install
pnpm install
bundle exec rake db:create
bundle exec rake db:migrate
RAILS_ENV=test bundle exec rake db:create db:migrate

# 在一个终端中
bundle exec rails server

# 在另一个终端中
bin/ember-cli

然后我用以下命令获取了你的代码:

git checkout 71ad0305f812311f2a4570edf7c33f97de46c457
git switch -c mathjax-sam

即使从这个全新的设置开始,我也会收到错误。


到目前为止,我对我的代码版本很满意,但仍然好奇你的代码到底发生了什么。不过,我需要因为假期休息一下。我很高兴几天后再来看看这个问题。

最后一点:据我所知,

await import("tex-mml-chtml.js") // 接着是
await import("input/asciimath.js")

不应该起作用,这实际上就是我认为你的代码正在做的事情。

我在路径上说得不精确,但我的观点是,我不知道连续的动态 import 调用是否能产生正确的 MathJax 结构。我认为加载 MathJax 组件非常复杂,这就是为什么他们有如此详细的加载过程,带有 MathJax 对象和所有东西。

非常感谢你的帮助和耐心,@sam

2 个赞

我在这里取得了一些进展:

我已将大型 JavaScript 有效载荷移至一个专用的 gem 中

这将使保持最新状态变得更加容易,而且 mathjax 不再提交到仓库中。

3 个赞

嗨,Sam——我今天花了很多时间研究这个。它看起来很棒!但我确实认为还有很多工作要做。其中一些我肯定可以帮忙。有些可能超出了我的能力范围,尤其是我大学快要开学了。

无论如何,以下是我的一些想法。

缩放 (Zoom)

MathJax V4 不再支持悬停缩放。不过,很容易将其设置为按住 Alt 键单击时缩放。我已经在以下位置进行了设置:

请注意,有一个已知的 MathJax 错误需要通过一些 CSS 来解决,如此 GitHub Issue 中所述。我也已将该修复包含在此代码中。

加载选项 (Loading options)

目前,AsciiMath 无法打开,而可访问性 (Accessibility) 无法关闭。我认为这是因为 load-mathjax.js 中子模块是按顺序加载的。

正如我在上一条消息中所说,更常见的是预先定义一个 window.MathJax 对象,其中指定了你想要的组件。MathJax 对象在主脚本加载时会被重新定义。这就是我能够在我的 V3 版本中实现此功能的方式。如果你愿意,我想我可以在下周初将该方法整合到你的代码库中,试试看?

一旦我们确定了选项,也值得考虑 V4 中是否有应该包含的新选项。

富文本编辑器 (The rich editor)

这太棒了——我很高兴看到它!

我想知道是否可以在模态框内获得一个闪亮的 AI 上下文菜单?我这样问是因为学生(和教授 :confused:)有时在输入 LaTeX 时会遇到困难。一个小的 AI 校对工具可以让这个过程顺畅得多。我已经将它集成到我的课堂 Discourse 中,并期待着在即将到来的学期中使用它。


好的,我确信还有很多,但我今天就到此为止了。

非常感谢!!!:rocket: :fire: :tada:

4 个赞

我理解 discourse-math 插件依赖于单独的 MathJax/KaTeX 资源 gem,而不是直接捆绑这些库,这使插件保持轻量级,并允许数学库独立更新。

我想通过进行一些实际测试来帮助在首次生产发布之前验证这一点。我的初步想法是启动一个单独的、一次性的实例并在那里启用该插件,以测试大量数学内容、通过标准管道进行的资源加载、内容安全策略 (CSP) 行为和性能。

在这样做之前,我想询问一下现阶段推荐的环境是什么——是在类似生产的设置中进行早期测试是否合适,还是您更希望在首次生产发布之前使用开发环境进行测试。

我非常乐意以最有用的方式进行测试,并向上游报告我遇到的任何问题或边缘情况。由于大学事务,我无法承诺固定的测试时间表,但我乐意在时间允许的情况下尽力进行测试,并且我可能在 6 月 6 日之后有更多的时间。

我的选项现在运行良好;你可以在这里看到代码:

这里有几点评论:

  • 所有配置和加载都由 math-renderer.js 中定义的 MathJaxInitConfig 对象处理。
  • 我从 load-mathjax.js 中移除了相当多的无效代码。
  • ui/safe 扩展总是被加载。
  • 我添加了一个“Discourse 数学启用菜单”选项,默认值为 true。当设置为 false 时,它会完全移除菜单,使 MathJax 速度更快。
  • 接下来的两个菜单项是
    • 点击时放大 Discourse 数学公式
    • 启用 Discourse 数学辅助功能
      如果菜单被禁用,这些将不起作用,但在启用时它们是相互独立的。

整个菜单如下所示:

我还没有添加任何测试,但如果你想要一个拉取请求(pull request),我可以尝试添加。