@agent-native/core 0.101.10 → 0.101.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/client.ts +23 -1
  5. package/corpus/core/src/a2a/handlers.ts +65 -1
  6. package/corpus/core/src/a2a/server.ts +124 -0
  7. package/corpus/core/src/a2a/task-store.ts +253 -0
  8. package/corpus/core/src/a2a/types.ts +26 -0
  9. package/corpus/core/src/agent/production-agent.ts +5 -2
  10. package/corpus/core/src/mcp/builtin-tools.ts +25 -1
  11. package/corpus/core/src/scripts/call-agent.ts +22 -3
  12. package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
  13. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  14. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
  15. package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
  16. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
  17. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
  18. package/corpus/templates/content/app/global.css +53 -0
  19. package/corpus/templates/content/app/i18n/zh-TW.ts +13 -0
  20. package/corpus/templates/content/app/i18n-data.ts +163 -0
  21. package/corpus/templates/content/app/root.tsx +2 -0
  22. package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
  23. package/corpus/templates/content/package.json +1 -0
  24. package/corpus/templates/content/shared/document-export.ts +296 -25
  25. package/corpus/templates/content/shared/inline-math.ts +128 -0
  26. package/corpus/templates/content/shared/math-rendering.ts +50 -0
  27. package/corpus/templates/content/shared/nfm.ts +17 -14
  28. package/corpus/templates/content/vite.config.ts +0 -1
  29. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
  30. package/corpus/templates/mail/AGENTS.md +21 -19
  31. package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
  32. package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
  33. package/corpus/templates/mail/server/handlers/media.ts +84 -61
  34. package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
  35. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
  36. package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
  37. package/corpus/templates/mail/server/plugins/auth.ts +9 -1
  38. package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
  39. package/dist/a2a/client.d.ts +5 -1
  40. package/dist/a2a/client.d.ts.map +1 -1
  41. package/dist/a2a/client.js +18 -1
  42. package/dist/a2a/client.js.map +1 -1
  43. package/dist/a2a/handlers.d.ts.map +1 -1
  44. package/dist/a2a/handlers.js +57 -8
  45. package/dist/a2a/handlers.js.map +1 -1
  46. package/dist/a2a/server.d.ts.map +1 -1
  47. package/dist/a2a/server.js +87 -1
  48. package/dist/a2a/server.js.map +1 -1
  49. package/dist/a2a/task-store.d.ts +27 -0
  50. package/dist/a2a/task-store.d.ts.map +1 -1
  51. package/dist/a2a/task-store.js +216 -0
  52. package/dist/a2a/task-store.js.map +1 -1
  53. package/dist/a2a/types.d.ts +24 -0
  54. package/dist/a2a/types.d.ts.map +1 -1
  55. package/dist/a2a/types.js.map +1 -1
  56. package/dist/agent/production-agent.d.ts +1 -0
  57. package/dist/agent/production-agent.d.ts.map +1 -1
  58. package/dist/agent/production-agent.js +5 -2
  59. package/dist/agent/production-agent.js.map +1 -1
  60. package/dist/collab/struct-routes.d.ts +1 -1
  61. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  62. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  63. package/dist/mcp/builtin-tools.js +22 -3
  64. package/dist/mcp/builtin-tools.js.map +1 -1
  65. package/dist/notifications/routes.d.ts +3 -3
  66. package/dist/observability/routes.d.ts +5 -5
  67. package/dist/resources/handlers.d.ts +3 -3
  68. package/dist/scripts/call-agent.d.ts +1 -1
  69. package/dist/scripts/call-agent.d.ts.map +1 -1
  70. package/dist/scripts/call-agent.js +19 -1
  71. package/dist/scripts/call-agent.js.map +1 -1
  72. package/dist/secrets/routes.d.ts +9 -9
  73. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  74. package/dist/server/agent-chat-plugin.js +71 -2
  75. package/dist/server/agent-chat-plugin.js.map +1 -1
  76. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  77. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  78. package/package.json +1 -1
  79. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
@@ -145,6 +145,29 @@ Always run this after any document modification to update the sidebar.
145
145
 
146
146
  Documents use **markdown** for content. The editor renders markdown in real time.
147
147
 
148
+ ### Math
149
+
150
+ Content renders LaTeX with KaTeX while preserving the source in NFM. Use the
151
+ canonical Content delimiters when an agent creates or edits math:
152
+
153
+ ```md
154
+ Inline math: The relationship is $`E = mc^2`$ in this example.
155
+
156
+ Block math:
157
+
158
+ $$
159
+ \int_0^1 x^2 dx = \frac{1}{3}
160
+ $$
161
+ ```
162
+
163
+ - Inline math uses ``$`...`$``. Do not substitute ordinary `$...$`; dollar
164
+ amounts in prose are intentionally not interpreted as equations.
165
+ - Block equations use `$$` on their own lines before and after the LaTeX.
166
+ - Keep the raw LaTeX intact when editing around an equation. Invalid or
167
+ unsupported expressions remain visible as source so they can be repaired.
168
+ - Markdown exports preserve these delimiters. HTML and PDF-ready exports render
169
+ the equation.
170
+
148
171
  ## Parent-Child Hierarchy
149
172
 
150
173
  Documents form a tree via `parent_id`:
@@ -0,0 +1,43 @@
1
+ import { renderMathToHtml } from "@shared/math-rendering";
2
+ import { useMemo } from "react";
3
+
4
+ interface MathRendererProps {
5
+ latex: string;
6
+ displayMode: boolean;
7
+ }
8
+
9
+ export function MathRenderer({ latex, displayMode }: MathRendererProps) {
10
+ const rendered = useMemo(
11
+ () => renderMathToHtml(latex, displayMode),
12
+ [displayMode, latex],
13
+ );
14
+
15
+ if (!rendered.ok) {
16
+ return (
17
+ <code
18
+ className={
19
+ displayMode
20
+ ? "content-math-error content-math-error--block"
21
+ : "content-math-error content-math-error--inline"
22
+ }
23
+ title={rendered.error}
24
+ >
25
+ {latex || "Empty equation"}
26
+ </code>
27
+ );
28
+ }
29
+
30
+ return displayMode ? (
31
+ <span
32
+ className="content-math content-math--block"
33
+ contentEditable={false}
34
+ dangerouslySetInnerHTML={{ __html: rendered.html }}
35
+ />
36
+ ) : (
37
+ <span
38
+ className="content-math content-math--inline"
39
+ contentEditable={false}
40
+ dangerouslySetInnerHTML={{ __html: rendered.html }}
41
+ />
42
+ );
43
+ }
@@ -1,8 +1,10 @@
1
1
  import { useSendToAgentChat, useT } from "@agent-native/core/client";
2
2
  import type { CreateInlineDatabaseResponse } from "@shared/api";
3
+ import { renderMathToHtml } from "@shared/math-rendering";
3
4
  import { collapseExactRepeatedNfm, docToNfm } from "@shared/nfm";
4
5
  import { serializeRegistryBlockToMdx } from "@shared/nfm-registry";
5
6
  import {
7
+ IconCheck,
6
8
  IconTypography,
7
9
  IconH1,
8
10
  IconH2,
@@ -23,6 +25,8 @@ import {
23
25
  IconFileText,
24
26
  IconDatabase,
25
27
  IconVideo,
28
+ IconMathFunction,
29
+ IconSquareRoot2,
26
30
  } from "@tabler/icons-react";
27
31
  import { Editor } from "@tiptap/react";
28
32
  import { useState, useEffect, useCallback, useRef, useMemo } from "react";
@@ -30,6 +34,7 @@ import { useNavigate } from "react-router";
30
34
  import { toast } from "sonner";
31
35
 
32
36
  import { contentBlockRegistry } from "@/blocks/contentBlockRegistry";
37
+ import { Button } from "@/components/ui/button";
33
38
  import {
34
39
  Popover,
35
40
  PopoverContent,
@@ -42,6 +47,7 @@ import { localContentComponents } from "@/local-components";
42
47
 
43
48
  import { focusMostRecentEmptyToggleSummary } from "./extensions/NotionExtensions";
44
49
  import { buildLocalComponentSlashItems } from "./localComponentSlashItems";
50
+ import { MathRenderer } from "./MathRenderer";
45
51
  import { buildRegistrySlashItems } from "./registrySlashItems";
46
52
 
47
53
  interface SlashCommandMenuProps {
@@ -64,6 +70,13 @@ interface EditorMenuPosition {
64
70
  left: number;
65
71
  }
66
72
 
73
+ interface EquationDraft {
74
+ displayMode: boolean;
75
+ insertionRange: { from: number; to: number };
76
+ slashRange: { from: number; to: number };
77
+ position: EditorMenuPosition;
78
+ }
79
+
67
80
  interface CommandItem {
68
81
  title: string;
69
82
  description: string;
@@ -178,6 +191,47 @@ export function insertInlineDatabaseBlock(
178
191
  : chain.insertContent(content).run();
179
192
  }
180
193
 
194
+ export function equationNodeContent(latex: string, displayMode: boolean) {
195
+ return displayMode
196
+ ? {
197
+ type: "notionBlockAtom",
198
+ attrs: { tagName: "equation", attrsJson: "{}", label: latex },
199
+ }
200
+ : {
201
+ type: "notionInlineAtom",
202
+ attrs: { tagName: "math", attrsJson: "{}", label: latex },
203
+ };
204
+ }
205
+
206
+ export function insertEquation(
207
+ editor: Editor,
208
+ latex: string,
209
+ displayMode: boolean,
210
+ range: { from: number; to: number },
211
+ ) {
212
+ const content = equationNodeContent(latex, displayMode);
213
+ return editor
214
+ .chain()
215
+ .focus()
216
+ .insertContentAt(
217
+ range,
218
+ displayMode ? [content, { type: "paragraph" }] : content,
219
+ )
220
+ .run();
221
+ }
222
+
223
+ export function getEquationInsertionRange(
224
+ editor: Editor,
225
+ slashRange: { from: number; to: number },
226
+ displayMode: boolean,
227
+ ) {
228
+ if (!displayMode) return slashRange;
229
+ const resolved = editor.state.doc.resolve(slashRange.from);
230
+ return resolved.parent.isTextblock
231
+ ? { from: resolved.before(), to: resolved.after() }
232
+ : slashRange;
233
+ }
234
+
181
235
  const commands: CommandTemplate[] = [
182
236
  {
183
237
  titleKey: "editor.slash.text",
@@ -460,6 +514,16 @@ export function SlashCommandMenu({
460
514
  );
461
515
  const generateTextareaRef = useRef<HTMLTextAreaElement>(null);
462
516
 
517
+ const [equationDraft, setEquationDraft] = useState<EquationDraft | null>(
518
+ null,
519
+ );
520
+ const [equationLatex, setEquationLatex] = useState("");
521
+ const equationInputRef = useRef<HTMLTextAreaElement>(null);
522
+ const equationResult = useMemo(
523
+ () => renderMathToHtml(equationLatex, equationDraft?.displayMode ?? false),
524
+ [equationDraft?.displayMode, equationLatex],
525
+ );
526
+
463
527
  const submitGeneratePrompt = useCallback(
464
528
  (prompt: string) => {
465
529
  const trimmed = prompt.trim();
@@ -684,6 +748,80 @@ export function SlashCommandMenu({
684
748
  },
685
749
  };
686
750
 
751
+ const openEquationComposer = useCallback(
752
+ (displayMode: boolean, slashRange: { from: number; to: number } | null) => {
753
+ const menuPosition = position ?? getSelectionMenuPosition();
754
+ if (!slashRange || !menuPosition) return false;
755
+ setEquationLatex("");
756
+ setEquationDraft({
757
+ displayMode,
758
+ slashRange,
759
+ insertionRange: getEquationInsertionRange(
760
+ editor,
761
+ slashRange,
762
+ displayMode,
763
+ ),
764
+ position: menuPosition,
765
+ });
766
+ setTimeout(() => equationInputRef.current?.focus(), 0);
767
+ return true;
768
+ },
769
+ [editor, getSelectionMenuPosition, position],
770
+ );
771
+
772
+ const cancelEquation = useCallback(() => {
773
+ const draft = equationDraft;
774
+ setEquationDraft(null);
775
+ setEquationLatex("");
776
+ if (draft) {
777
+ editor.chain().focus().deleteRange(draft.slashRange).run();
778
+ }
779
+ }, [editor, equationDraft]);
780
+
781
+ const submitEquation = useCallback(() => {
782
+ if (!equationDraft || !equationResult.ok) return;
783
+ const latex = equationLatex.trim();
784
+ const { displayMode, insertionRange } = equationDraft;
785
+ setEquationDraft(null);
786
+ setEquationLatex("");
787
+ const inserted = insertEquation(editor, latex, displayMode, insertionRange);
788
+ if (!inserted) {
789
+ toast.error(t("editor.slash.equationInsertFailed"));
790
+ return;
791
+ }
792
+ void onDraftCommitted?.();
793
+ }, [
794
+ editor,
795
+ equationDraft,
796
+ equationLatex,
797
+ equationResult.ok,
798
+ onDraftCommitted,
799
+ t,
800
+ ]);
801
+
802
+ const equationCommands: CommandItem[] = isTurnInto
803
+ ? []
804
+ : [
805
+ {
806
+ title: t("editor.slash.blockEquation"),
807
+ description: t("editor.slash.blockEquationDescription"),
808
+ searchText: "latex katex math formula",
809
+ icon: IconMathFunction,
810
+ preserveSlashRange: true,
811
+ action: (_editor, { slashRange }) =>
812
+ openEquationComposer(true, slashRange),
813
+ },
814
+ {
815
+ title: t("editor.slash.inlineEquation"),
816
+ description: t("editor.slash.inlineEquationDescription"),
817
+ searchText: "latex katex math formula",
818
+ icon: IconSquareRoot2,
819
+ preserveSlashRange: true,
820
+ action: (_editor, { slashRange }) =>
821
+ openEquationComposer(false, slashRange),
822
+ },
823
+ ];
824
+
687
825
  // Registry-derived block items (the shared dev-doc / OpenAPI / structured
688
826
  // library). Filtered to Notion-compatible specs when the document is linked to
689
827
  // a Notion page. "Turn into" only converts the current text block, so these
@@ -713,9 +851,10 @@ export function SlashCommandMenu({
713
851
  title: t(cmd.titleKey),
714
852
  description: t(cmd.descriptionKey),
715
853
  });
716
- const blockCommands = (isTurnInto ? turnIntoCommands : commands).map(
717
- localizeCommand,
718
- );
854
+ const blockCommands = [
855
+ ...(isTurnInto ? turnIntoCommands : commands).map(localizeCommand),
856
+ ...equationCommands,
857
+ ];
719
858
  const pageCommands = isTurnInto ? [] : [pageCommand, databaseCommand];
720
859
  const mediaCommands = isTurnInto
721
860
  ? []
@@ -1042,7 +1181,7 @@ export function SlashCommandMenu({
1042
1181
  align="start"
1043
1182
  side="bottom"
1044
1183
  className="w-[calc(100vw-2rem)] max-w-80 rounded-xl p-0"
1045
- onOpenAutoFocus={(e) => {
1184
+ onOpenAutoFocus={(e: Event) => {
1046
1185
  e.preventDefault();
1047
1186
  generateTextareaRef.current?.focus();
1048
1187
  }}
@@ -1086,6 +1225,123 @@ export function SlashCommandMenu({
1086
1225
  </PopoverContent>
1087
1226
  </Popover>
1088
1227
  )}
1228
+
1229
+ {equationDraft && (
1230
+ <Popover
1231
+ open
1232
+ onOpenChange={(open: boolean) => {
1233
+ if (!open) cancelEquation();
1234
+ }}
1235
+ >
1236
+ <PopoverTrigger asChild>
1237
+ <span
1238
+ className="pointer-events-none absolute size-0"
1239
+ style={{
1240
+ top: equationDraft.position.top,
1241
+ left: Math.min(equationDraft.position.left, 16),
1242
+ }}
1243
+ />
1244
+ </PopoverTrigger>
1245
+ <PopoverContent
1246
+ align="start"
1247
+ side="bottom"
1248
+ className="w-[calc(100vw-2rem)] max-w-md rounded-xl p-0"
1249
+ onOpenAutoFocus={(event: Event) => {
1250
+ event.preventDefault();
1251
+ equationInputRef.current?.focus();
1252
+ }}
1253
+ >
1254
+ <div className="p-4">
1255
+ <div className="flex items-center gap-2 text-sm font-semibold">
1256
+ {equationDraft.displayMode ? (
1257
+ <IconMathFunction
1258
+ className="text-muted-foreground"
1259
+ size={16}
1260
+ />
1261
+ ) : (
1262
+ <IconSquareRoot2
1263
+ className="text-muted-foreground"
1264
+ size={16}
1265
+ />
1266
+ )}
1267
+ {equationDraft.displayMode
1268
+ ? t("editor.slash.blockEquation")
1269
+ : t("editor.slash.inlineEquation")}
1270
+ </div>
1271
+ <textarea
1272
+ ref={equationInputRef}
1273
+ value={equationLatex}
1274
+ onChange={(event) => setEquationLatex(event.target.value)}
1275
+ onKeyDown={(event) => {
1276
+ if (
1277
+ event.key === "Enter" &&
1278
+ (event.metaKey || event.ctrlKey) &&
1279
+ equationResult.ok
1280
+ ) {
1281
+ event.preventDefault();
1282
+ submitEquation();
1283
+ }
1284
+ if (event.key === "Escape") {
1285
+ event.preventDefault();
1286
+ cancelEquation();
1287
+ }
1288
+ }}
1289
+ rows={equationDraft.displayMode ? 3 : 2}
1290
+ placeholder={t("editor.slash.equationPlaceholder")}
1291
+ aria-label={t("editor.slash.equationInputLabel")}
1292
+ aria-invalid={equationLatex.length > 0 && !equationResult.ok}
1293
+ aria-describedby="equation-preview-status"
1294
+ className="mt-3 w-full resize-y rounded-lg border border-input bg-background px-3 py-2 font-mono text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
1295
+ />
1296
+ <div className="mt-3 min-h-20 rounded-lg border border-border bg-muted/30 p-3">
1297
+ <div className="mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1298
+ {t("editor.slash.equationPreview")}
1299
+ </div>
1300
+ <div className="flex min-h-9 items-center justify-center overflow-x-auto text-foreground">
1301
+ {equationResult.ok ? (
1302
+ <MathRenderer
1303
+ latex={equationLatex}
1304
+ displayMode={equationDraft.displayMode}
1305
+ />
1306
+ ) : (
1307
+ <span className="text-sm text-muted-foreground">
1308
+ {equationLatex
1309
+ ? t("editor.slash.equationNeedsRepair")
1310
+ : t("editor.slash.equationPreviewEmpty")}
1311
+ </span>
1312
+ )}
1313
+ </div>
1314
+ </div>
1315
+ <p
1316
+ id="equation-preview-status"
1317
+ className={cn(
1318
+ "mt-2 min-h-5 text-xs",
1319
+ equationLatex && !equationResult.ok
1320
+ ? "text-destructive"
1321
+ : "text-muted-foreground",
1322
+ )}
1323
+ >
1324
+ {equationLatex && !equationResult.ok
1325
+ ? equationResult.error
1326
+ : t("editor.slash.equationSubmitHint")}
1327
+ </p>
1328
+ </div>
1329
+ <div className="flex items-center justify-end gap-2 border-t border-border px-4 py-3">
1330
+ <Button variant="ghost" size="sm" onClick={cancelEquation}>
1331
+ {t("editor.slash.cancelEquation")}
1332
+ </Button>
1333
+ <Button
1334
+ size="sm"
1335
+ onClick={submitEquation}
1336
+ disabled={!equationResult.ok}
1337
+ >
1338
+ <IconCheck />
1339
+ {t("editor.slash.insertEquation")}
1340
+ </Button>
1341
+ </div>
1342
+ </PopoverContent>
1343
+ </Popover>
1344
+ )}
1089
1345
  </>
1090
1346
  );
1091
1347
  }
@@ -1,3 +1,4 @@
1
+ import { findTrailingPlainInlineMath } from "@shared/inline-math";
1
2
  import {
2
3
  escapeHtml,
3
4
  indentMarkdown,
@@ -10,6 +11,7 @@ import {
10
11
  IconExternalLink,
11
12
  IconFileText,
12
13
  } from "@tabler/icons-react";
14
+ import { InputRule } from "@tiptap/core";
13
15
  import type { Fragment, Node as ProseMirrorNode } from "@tiptap/pm/model";
14
16
  import {
15
17
  Mark,
@@ -21,6 +23,8 @@ import {
21
23
  type NodeViewProps,
22
24
  } from "@tiptap/react";
23
25
 
26
+ import { MathRenderer } from "../MathRenderer";
27
+
24
28
  const BLOCK_ATOM_TAGS = [
25
29
  "page",
26
30
  "database",
@@ -452,6 +456,17 @@ function BlockAtomView({ node, extension }: NodeViewProps) {
452
456
  const canOpenLocalPage = Boolean(pageLink && options.onOpenPageLink);
453
457
  const externalUrl = safeExternalPageUrl(attrs.url || attrs.href || null);
454
458
 
459
+ if (tagName === "equation") {
460
+ return (
461
+ <NodeViewWrapper
462
+ className="content-equation"
463
+ data-latex={label || attrs.latex || ""}
464
+ >
465
+ <MathRenderer latex={label || attrs.latex || ""} displayMode={true} />
466
+ </NodeViewWrapper>
467
+ );
468
+ }
469
+
455
470
  if (tagName === "page") {
456
471
  const openPage = () => {
457
472
  if (pageLink && options.onOpenPageLink) {
@@ -511,6 +526,36 @@ function BlockAtomView({ node, extension }: NodeViewProps) {
511
526
  );
512
527
  }
513
528
 
529
+ function InlineAtomView({ node }: NodeViewProps) {
530
+ const tagName = (node.attrs.tagName || "mention") as string;
531
+ const label = (node.attrs.label || "") as string;
532
+ const attrs = parseAttrsJson(node.attrs.attrsJson as string);
533
+
534
+ if (tagName === "math") {
535
+ const latex = label || attrs.latex || "";
536
+ return (
537
+ <NodeViewWrapper
538
+ as="span"
539
+ className="content-inline-equation"
540
+ contentEditable={false}
541
+ data-latex={latex}
542
+ >
543
+ <MathRenderer latex={latex} displayMode={false} />
544
+ </NodeViewWrapper>
545
+ );
546
+ }
547
+
548
+ return (
549
+ <NodeViewWrapper
550
+ as="span"
551
+ className="notion-inline-atom"
552
+ contentEditable={false}
553
+ >
554
+ {label || humanizeTag(tagName)}
555
+ </NodeViewWrapper>
556
+ );
557
+ }
558
+
514
559
  export const NotionSpanMark = Mark.create({
515
560
  name: "notionSpan",
516
561
 
@@ -975,6 +1020,35 @@ export const NotionInlineAtom = Node.create({
975
1020
  atom: true,
976
1021
  selectable: false,
977
1022
 
1023
+ addInputRules() {
1024
+ return [
1025
+ new InputRule({
1026
+ find: (text) => {
1027
+ const match = findTrailingPlainInlineMath(text);
1028
+ if (!match) return null;
1029
+ return {
1030
+ index: match.from,
1031
+ text: text.slice(match.from, match.to),
1032
+ data: { latex: match.latex },
1033
+ };
1034
+ },
1035
+ handler: ({ state, range, match }) => {
1036
+ const latex = match.data?.latex;
1037
+ if (typeof latex !== "string") return null;
1038
+
1039
+ const mathNode = state.schema.nodes.notionInlineAtom?.create({
1040
+ tagName: "math",
1041
+ attrsJson: "{}",
1042
+ label: latex,
1043
+ });
1044
+ if (!mathNode) return null;
1045
+
1046
+ state.tr.replaceWith(range.from, range.to, mathNode).scrollIntoView();
1047
+ },
1048
+ }),
1049
+ ];
1050
+ },
1051
+
978
1052
  addAttributes() {
979
1053
  return {
980
1054
  tagName: { default: "mention-user" },
@@ -1023,6 +1097,10 @@ export const NotionInlineAtom = Node.create({
1023
1097
  ];
1024
1098
  },
1025
1099
 
1100
+ addNodeView() {
1101
+ return ReactNodeViewRenderer(InlineAtomView);
1102
+ },
1103
+
1026
1104
  addStorage() {
1027
1105
  return {
1028
1106
  markdown: {
@@ -1218,6 +1218,59 @@ div[data-notion-column] > :last-child {
1218
1218
  white-space: nowrap;
1219
1219
  }
1220
1220
 
1221
+ .content-inline-equation {
1222
+ display: inline-block;
1223
+ max-width: 100%;
1224
+ margin: 0 0.08em;
1225
+ vertical-align: -0.08em;
1226
+ }
1227
+
1228
+ .content-equation {
1229
+ display: block;
1230
+ max-width: 100%;
1231
+ margin: 1em 0;
1232
+ overflow-x: auto;
1233
+ padding: 0.25rem 0;
1234
+ }
1235
+
1236
+ .content-math {
1237
+ color: inherit;
1238
+ }
1239
+
1240
+ .content-math--inline {
1241
+ display: inline-block;
1242
+ }
1243
+
1244
+ .content-math--block {
1245
+ display: block;
1246
+ min-width: max-content;
1247
+ text-align: center;
1248
+ }
1249
+
1250
+ .content-math-error {
1251
+ border: 1px solid hsl(var(--destructive) / 0.3);
1252
+ border-radius: 0.375rem;
1253
+ background: hsl(var(--destructive) / 0.06);
1254
+ color: hsl(var(--foreground));
1255
+ font-family:
1256
+ "JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Consolas,
1257
+ monospace;
1258
+ white-space: pre-wrap;
1259
+ }
1260
+
1261
+ .content-math-error--inline {
1262
+ padding: 0.05rem 0.25rem;
1263
+ font-size: 0.9em;
1264
+ }
1265
+
1266
+ .content-math-error--block {
1267
+ display: block;
1268
+ width: 100%;
1269
+ padding: 0.75rem 0.9rem;
1270
+ overflow-wrap: anywhere;
1271
+ text-align: left;
1272
+ }
1273
+
1221
1274
  .notion-editor span[data-notion-span="true"][underline="true"] {
1222
1275
  text-decoration: underline;
1223
1276
  text-underline-offset: 3px;
@@ -393,6 +393,8 @@ const messages = {
393
393
  audio: "音訊",
394
394
  audioDescription: "上傳或嵌入音訊",
395
395
  basicBlocks: "基本塊",
396
+ blockEquation: "區塊公式",
397
+ blockEquationDescription: "在獨立一行顯示 LaTeX 公式",
396
398
  blocks: "積木",
397
399
  bulletedList: "專案符號清單",
398
400
  bulletedListDescription: "無序清單",
@@ -403,6 +405,7 @@ const messages = {
403
405
  collapsibleBlockDescription: "可折疊塊",
404
406
  database: "資料庫",
405
407
  databaseDescription: "此頁面中的內嵌資料庫",
408
+ cancelEquation: "取消",
406
409
  divider: "分頻器",
407
410
  dividerDescription: "水平尺",
408
411
  generate: "生成",
@@ -413,6 +416,9 @@ const messages = {
413
416
  heading4Description: "副標題",
414
417
  image: "圖片",
415
418
  imageDescription: "上傳或嵌入圖片",
419
+ inlineEquation: "行內公式",
420
+ inlineEquationDescription: "在文字中加入 LaTeX 公式",
421
+ insertEquation: "插入",
416
422
  localComponents: "本機元件",
417
423
  media: "媒體",
418
424
  numberedList: "編號清單",
@@ -420,6 +426,13 @@ const messages = {
420
426
  page: "頁面",
421
427
  pageDescription: "建立子頁面",
422
428
  pages: "頁面數",
429
+ equationInputLabel: "LaTeX 公式",
430
+ equationInsertFailed: "無法插入公式。",
431
+ equationNeedsRepair: "請修正 LaTeX 以查看預覽。",
432
+ equationPlaceholder: String.raw`輸入 LaTeX,例如 \frac{a}{b}`,
433
+ equationPreview: "預覽",
434
+ equationPreviewEmpty: "公式將顯示於此。",
435
+ equationSubmitHint: "輸入時會即時更新預覽。按 Cmd/Ctrl+Enter 插入。",
423
436
  quote: "報價",
424
437
  quoteDescription: "塊報價",
425
438
  table: "表",