@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +23 -1
- package/corpus/core/src/a2a/handlers.ts +65 -1
- package/corpus/core/src/a2a/server.ts +124 -0
- package/corpus/core/src/a2a/task-store.ts +253 -0
- package/corpus/core/src/a2a/types.ts +26 -0
- package/corpus/core/src/agent/production-agent.ts +5 -2
- package/corpus/core/src/mcp/builtin-tools.ts +25 -1
- package/corpus/core/src/scripts/call-agent.ts +22 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
- package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
- package/corpus/templates/content/app/global.css +53 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +13 -0
- package/corpus/templates/content/app/i18n-data.ts +163 -0
- package/corpus/templates/content/app/root.tsx +2 -0
- package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
- package/corpus/templates/content/package.json +1 -0
- package/corpus/templates/content/shared/document-export.ts +296 -25
- package/corpus/templates/content/shared/inline-math.ts +128 -0
- package/corpus/templates/content/shared/math-rendering.ts +50 -0
- package/corpus/templates/content/shared/nfm.ts +17 -14
- package/corpus/templates/content/vite.config.ts +0 -1
- package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
- package/corpus/templates/mail/AGENTS.md +21 -19
- package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
- package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
- package/corpus/templates/mail/server/handlers/media.ts +84 -61
- package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
- package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
- package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
- package/corpus/templates/mail/server/plugins/auth.ts +9 -1
- package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
- package/dist/a2a/client.d.ts +5 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +18 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +57 -8
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +87 -1
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/task-store.d.ts +27 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +216 -0
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/a2a/types.d.ts +24 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +5 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +22 -3
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/call-agent.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +19 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +71 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
- package/package.json +1 -1
- 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 =
|
|
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: "表",
|