fix(ios): use shared TipTap mathematics extensions

Drop the duplicated iOS math tokenizer. Formula Markdown now round-trips against the web codec, including currency dollar pairs.
This commit is contained in:
tianma-if committed 2026-09-15 12:37:27 +08:00
1 parent 9d4d496823
commit 77d9f9c6aa
3 files changed
+32 -59

No files matched your search

@@ -11,6 +11,7 @@ import {
MERGE_DIVIDER_NODE_TYPE,
MergeDivider as SharedMergeDivider,
} from "@edgeever/shared";
import { createEdgeEverMathematics } from "@edgeever/shared/mathematics";
import { createIosImageGallery, ImageGallery, MergeDivider } from "./document-nodes.ts";
const galleryDoc = {
@@ -30,6 +31,7 @@ const iosMarkdownManager = new MarkdownManager({
StarterKit.configure({ codeBlock: false, link: false }),
Image,
MergeDivider,
...createEdgeEverMathematics(),
createIosImageGallery(() => "en-US"),
Markdown.configure({
markedOptions: { gfm: true },
@@ -81,4 +83,33 @@ describe("iOS document nodes share the web schema", () => {
});
expect(persisted.content[0].content.map((node) => node.attrs.src)).toEqual(["/one.png", "/two.png"]);
});
test("parses inline and block math the same as the web codec", () => {
const markdown = "Euler: $e^{i\\pi}+1=0$.\n\n$$\n\\frac{a}{b}\n$$";
const webDoc = markdownToDoc(markdown);
const iosDoc = iosMarkdownManager.parse(markdown);
expect(webDoc.content[0]?.content?.[1]).toMatchObject({
type: "inlineMath",
attrs: { latex: "e^{i\\pi}+1=0" },
});
expect(webDoc.content[1]).toMatchObject({
type: "blockMath",
attrs: { latex: "\\frac{a}{b}" },
});
expect(iosDoc.content.map((node) => node.type)).toEqual(webDoc.content.map((node) => node.type));
expect(iosDoc.content[0]?.content?.[1]).toMatchObject(webDoc.content[0]?.content?.[1]);
expect(iosDoc.content[1]).toMatchObject(webDoc.content[1]);
expect(docToMarkdown(iosDoc)).toBe(docToMarkdown(webDoc));
});
test("keeps currency dollar pairs as literal text like the web codec", () => {
const markdown = "Price: $100$";
const webDoc = markdownToDoc(markdown);
const iosDoc = iosMarkdownManager.parse(markdown);
expect(webDoc.content[0]?.content?.some((node) => node.type === "inlineMath")).toBe(false);
expect(iosDoc.content[0]?.content?.some((node) => node.type === "inlineMath")).toBe(false);
expect(docToMarkdown(iosDoc)).toBe(docToMarkdown(webDoc));
});
});
+1 -1
View File
@@ -44,7 +44,7 @@ import {
buildNoteImageCardMarkup,
generateCardCss,
} from "@edgeever/shared/note-image-card";
import { createEdgeEverMathematics } from "./mathematics";
import { createEdgeEverMathematics } from "@edgeever/shared/mathematics";
import { createIosImageGallery, MergeDivider } from "./document-nodes";
import { createImageInsertTransaction, groupUploadedImages, NATIVE_IMAGE_GALLERY_CSS } from "@edgeever/shared/native-image-gallery";
-58
View File
@@ -1,58 +0,0 @@
import { BlockMath, InlineMath } from "@tiptap/extension-mathematics";
const isEscaped = (source: string, index: number) => {
let backslashCount = 0;
for (let cursor = index - 1; cursor >= 0 && source[cursor] === "\\"; cursor -= 1) {
backslashCount += 1;
}
return backslashCount % 2 === 1;
};
const findClosingDelimiter = (source: string, delimiter: "$" | "$$", from: number) => {
for (let index = from; index <= source.length - delimiter.length; index += 1) {
if (source.startsWith(delimiter, index) && !isEscaped(source, index)) return index;
}
return -1;
};
const EdgeEverInlineMath = InlineMath.extend({
markdownTokenizer: {
name: "inlineMath",
level: "inline",
start: (source: string) => source.indexOf("$"),
tokenize: (source: string) => {
if (!source.startsWith("$") || source.startsWith("$$")) return undefined;
const closingIndex = findClosingDelimiter(source, "$", 1);
if (closingIndex < 0 || source[closingIndex + 1] === "$") return undefined;
const raw = source.slice(0, closingIndex + 1);
const latex = source.slice(1, closingIndex).trim();
if (!latex || latex.includes("\n")) return undefined;
if (/^\d+(?:[.,]\d+)?$/u.test(latex)) return { type: "text", raw, text: raw };
return { type: "inlineMath", raw, latex };
},
},
});
const EdgeEverBlockMath = BlockMath.extend({
markdownTokenizer: {
name: "blockMath",
level: "block",
start: (source: string) => source.indexOf("$$"),
tokenize: (source: string) => {
if (!source.startsWith("$$") || source.startsWith("$$$")) return undefined;
const closingIndex = findClosingDelimiter(source, "$$", 2);
if (closingIndex < 0) return undefined;
const latex = source.slice(2, closingIndex).trim();
if (!latex) return undefined;
return { type: "blockMath", raw: source.slice(0, closingIndex + 2), latex };
},
},
});
const katexOptions = { throwOnError: false, strict: "warn" as const, trust: false };
/** Keep in sync with packages/shared/src/mathematics.ts. */
export const createEdgeEverMathematics = () => [
EdgeEverBlockMath.configure({ katexOptions }),
EdgeEverInlineMath.configure({ katexOptions }),
];