@elabs-ai/components-editor 4.1.0 → 4.2.0
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/dist/{chunk-C62O7IOQ.js → chunk-FO5S3YZM.js} +241 -158
- package/dist/chunk-FO5S3YZM.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +57 -39
- package/dist/index.js.map +1 -1
- package/dist/markdown/index.d.ts +2 -2
- package/dist/markdown/index.js +264 -187
- package/dist/markdown/index.js.map +1 -1
- package/dist/{markdown-editor-CBp_4eDv.d.ts → markdown-editor-Dn-0L_MM.d.ts} +8 -1
- package/dist/monaco.d.ts +2 -0
- package/dist/monaco.js +9 -0
- package/dist/monaco.js.map +1 -0
- package/package.json +9 -5
- package/src/ai-objects/decision-card.tsx +15 -9
- package/src/ai-objects/knowledge-card.tsx +18 -9
- package/src/barrel-monaco-lazy.test.ts +50 -0
- package/src/calc-block/calc-block.tsx +10 -4
- package/src/code-editor/code-editor.test.tsx +141 -14
- package/src/code-editor/code-editor.tsx +166 -35
- package/src/code-workspace/code-workspace.test.tsx +31 -12
- package/src/copy-button/copy-button.tsx +6 -3
- package/src/diff-editor/diff-editor.test.tsx +9 -3
- package/src/diff-editor/diff-editor.tsx +47 -23
- package/src/editor-toolbar/editor-toolbar.tsx +8 -2
- package/src/index.ts +4 -3
- package/src/markdown-academic/citations.tsx +14 -7
- package/src/markdown-academic/footnotes.tsx +1 -1
- package/src/markdown-academic/math.tsx +7 -4
- package/src/markdown-editor/completions/completions-menu.tsx +5 -2
- package/src/markdown-editor/directive-views.tsx +33 -19
- package/src/markdown-editor/slash/slash-menu.tsx +5 -2
- package/src/markdown-editor/table-view.tsx +28 -15
- package/src/markdown-iteration/iteration-builder-dialog.tsx +39 -18
- package/src/markdown-iteration/template-dialog.tsx +25 -7
- package/src/markdown-outline/document-outline.tsx +6 -2
- package/src/markdown-preview/markdown-preview-academic.test.tsx +3 -3
- package/src/markdown-toolbar/markdown-toolbar.tsx +42 -24
- package/src/markdown-workspace/markdown-workspace.test.tsx +22 -3
- package/src/markdown-workspace/markdown-workspace.tsx +6 -3
- package/src/mermaid-diagram/mermaid-diagram-fixes.test.tsx +130 -0
- package/src/mermaid-diagram/mermaid-diagram.test.tsx +2 -1
- package/src/mermaid-diagram/mermaid-diagram.tsx +89 -40
- package/src/mermaid-diagram/mermaid-viewer.tsx +21 -20
- package/src/monaco.ts +21 -0
- package/dist/chunk-C62O7IOQ.js.map +0 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* pre-pass over the source) so an inline `[1]` and bibliography entry 1 always agree.
|
|
17
17
|
*/
|
|
18
18
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
19
|
-
import { Separator } from "@elabs-ai/components-ui";
|
|
19
|
+
import { Separator, useLocale } from "@elabs-ai/components-ui";
|
|
20
20
|
import {
|
|
21
21
|
createContext,
|
|
22
22
|
forwardRef,
|
|
@@ -249,6 +249,7 @@ function hoverTitle(data: CitationData): string {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
function CiteLink({ entry, label }: { entry: ResolvedCitation; label: string }) {
|
|
252
|
+
const { t } = useLocale();
|
|
252
253
|
// For numeric style the visible label is a bare "[1]" — give AT a real name
|
|
253
254
|
// (the `title` tooltip is for mouse users and is not reliably announced).
|
|
254
255
|
const name = entry.data ? hoverTitle(entry.data) : entry.key;
|
|
@@ -256,13 +257,13 @@ function CiteLink({ entry, label }: { entry: ResolvedCitation; label: string })
|
|
|
256
257
|
<a
|
|
257
258
|
href={`#ref-${cssId(entry.key)}`}
|
|
258
259
|
title={entry.data ? hoverTitle(entry.data) : undefined}
|
|
259
|
-
aria-label={
|
|
260
|
+
aria-label={t("editor.citations.citationLabel", { name })}
|
|
260
261
|
// #317/#399 — the on-surface `-text` rung, NOT the `--primary` FILL: an
|
|
261
262
|
// inline cite is body text inside a paragraph and owes WCAG 1.4.3 AA
|
|
262
263
|
// (4.5:1), which `--primary` missed at 4.29-4.31:1 in light. The
|
|
263
264
|
// resting `underline` is the separate 1.4.1 non-colour cue (#317's
|
|
264
265
|
// link-in-text-block half) — keep both.
|
|
265
|
-
className="text-
|
|
266
|
+
className="text-link underline hover:underline focus-visible:rounded-sm focus-ring"
|
|
266
267
|
>
|
|
267
268
|
{label}
|
|
268
269
|
</a>
|
|
@@ -292,6 +293,7 @@ function readCite(rest: TagProps): CitePayload | null {
|
|
|
292
293
|
|
|
293
294
|
/** Renderer for the inline `<brand-cite>` element produced by the transform. */
|
|
294
295
|
export function InlineCite({ node: _n, children: _c, ...rest }: TagProps) {
|
|
296
|
+
const { t } = useLocale();
|
|
295
297
|
const ctx = useContext(CitationContext);
|
|
296
298
|
const payload = readCite(rest);
|
|
297
299
|
if (!payload) return null;
|
|
@@ -302,7 +304,7 @@ export function InlineCite({ node: _n, children: _c, ...rest }: TagProps) {
|
|
|
302
304
|
if (!anyResolved) {
|
|
303
305
|
// Graceful: nothing resolved → keep the literal, marked for sighted + AT.
|
|
304
306
|
return (
|
|
305
|
-
<span className="text-muted-foreground" title="
|
|
307
|
+
<span className="text-muted-foreground" title={t("editor.citations.unresolvedCitation")}>
|
|
306
308
|
{payload.original}
|
|
307
309
|
</span>
|
|
308
310
|
);
|
|
@@ -324,7 +326,10 @@ export function InlineCite({ node: _n, children: _c, ...rest }: TagProps) {
|
|
|
324
326
|
{entry?.data ? (
|
|
325
327
|
<CiteLink entry={entry} label={label} />
|
|
326
328
|
) : (
|
|
327
|
-
<span
|
|
329
|
+
<span
|
|
330
|
+
className="text-muted-foreground"
|
|
331
|
+
title={t("editor.citations.unresolvedKey", { key: it.key })}
|
|
332
|
+
>
|
|
328
333
|
{label}
|
|
329
334
|
</span>
|
|
330
335
|
)}
|
|
@@ -387,9 +392,11 @@ export interface BibliographyProps extends Omit<HTMLAttributes<HTMLElement>, "ch
|
|
|
387
392
|
* One focal separation gesture — a top `Separator` — over a quiet label + list.
|
|
388
393
|
*/
|
|
389
394
|
export const Bibliography = forwardRef<HTMLElement, BibliographyProps>(function Bibliography(
|
|
390
|
-
{ entries, style, title
|
|
395
|
+
{ entries, style, title: titleProp, className, ...props },
|
|
391
396
|
ref,
|
|
392
397
|
) {
|
|
398
|
+
const { t } = useLocale();
|
|
399
|
+
const title = titleProp ?? t("editor.citations.references");
|
|
393
400
|
const ctx = useContext(CitationContext);
|
|
394
401
|
const labelId = useId();
|
|
395
402
|
const list = entries ?? ctx?.order ?? [];
|
|
@@ -427,7 +434,7 @@ export const Bibliography = forwardRef<HTMLElement, BibliographyProps>(function
|
|
|
427
434
|
rel="noopener noreferrer"
|
|
428
435
|
// #317/#399 — bibliography DOI/URL is body text: `-text` rung
|
|
429
436
|
// + resting underline (the non-colour cue).
|
|
430
|
-
className="break-words text-
|
|
437
|
+
className="break-words text-link underline underline-offset-2 hover:underline focus-visible:rounded-sm focus-ring"
|
|
431
438
|
>
|
|
432
439
|
{data.url ?? `doi:${data.doi}`}
|
|
433
440
|
</a>
|
|
@@ -165,7 +165,7 @@ export function FootnoteRef({ node: _n, children: _c, ...rest }: TagProps) {
|
|
|
165
165
|
data-footnote-ref=""
|
|
166
166
|
aria-label={`Footnote ${n}`}
|
|
167
167
|
// #399 — a footnote marker is superscript body text: `-text` rung.
|
|
168
|
-
className="px-0.5 font-medium text-
|
|
168
|
+
className="px-0.5 font-medium text-link underline tabular-nums hover:underline focus-visible:rounded-sm focus-ring"
|
|
169
169
|
>
|
|
170
170
|
{n}
|
|
171
171
|
</a>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* a contained error, never crashes the page). a11y: `output: "htmlAndMathml"`
|
|
15
15
|
* emits MathML (read by assistive tech) alongside the visual HTML.
|
|
16
16
|
*/
|
|
17
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
17
18
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
18
19
|
import katex from "katex";
|
|
19
20
|
import { useMemo, type HTMLAttributes } from "react";
|
|
@@ -96,13 +97,14 @@ export interface MathProps extends Omit<HTMLAttributes<HTMLElement>, "children">
|
|
|
96
97
|
|
|
97
98
|
/** Inline math (`$…$`) → KaTeX, in the text flow. */
|
|
98
99
|
export function MathInline({ tex, className, ...props }: MathProps) {
|
|
100
|
+
const { t } = useLocale();
|
|
99
101
|
const { html, error } = useMemo(() => renderKatex(tex, false), [tex]);
|
|
100
102
|
if (error) {
|
|
101
103
|
return (
|
|
102
104
|
<code
|
|
103
105
|
className={cn("text-destructive-text", className)}
|
|
104
|
-
aria-label={
|
|
105
|
-
title="
|
|
106
|
+
aria-label={t("editor.math.renderErrorLabel", { tex })}
|
|
107
|
+
title={t("editor.math.renderError")}
|
|
106
108
|
{...props}
|
|
107
109
|
>
|
|
108
110
|
{tex}
|
|
@@ -125,6 +127,7 @@ export function MathInline({ tex, className, ...props }: MathProps) {
|
|
|
125
127
|
|
|
126
128
|
/** Block math (`$$…$$`) → centered display KaTeX. */
|
|
127
129
|
export function MathBlock({ tex, className, ...props }: MathProps) {
|
|
130
|
+
const { t } = useLocale();
|
|
128
131
|
const { html, error } = useMemo(() => renderKatex(tex, true), [tex]);
|
|
129
132
|
if (error) {
|
|
130
133
|
return (
|
|
@@ -133,8 +136,8 @@ export function MathBlock({ tex, className, ...props }: MathProps) {
|
|
|
133
136
|
"overflow-x-auto rounded-md bg-surface-muted p-3 text-destructive-text",
|
|
134
137
|
className,
|
|
135
138
|
)}
|
|
136
|
-
aria-label={
|
|
137
|
-
title="
|
|
139
|
+
aria-label={t("editor.math.renderErrorLabel", { tex })}
|
|
140
|
+
title={t("editor.math.renderError")}
|
|
138
141
|
{...props}
|
|
139
142
|
>
|
|
140
143
|
<code>{tex}</code>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Same visual grammar (bg-popover, accent selection) for consistency with the
|
|
9
9
|
* slash popup and Monaco's own themed suggest widget.
|
|
10
10
|
*/
|
|
11
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
11
12
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
12
13
|
import { forwardRef, type HTMLAttributes } from "react";
|
|
13
14
|
|
|
@@ -38,17 +39,19 @@ export const CompletionMenu = forwardRef<HTMLDivElement, CompletionMenuProps>(
|
|
|
38
39
|
activeIndex,
|
|
39
40
|
onSelect,
|
|
40
41
|
idPrefix = "brand-completions",
|
|
41
|
-
emptyLabel
|
|
42
|
+
emptyLabel: emptyLabelProp,
|
|
42
43
|
className,
|
|
43
44
|
...props
|
|
44
45
|
},
|
|
45
46
|
ref,
|
|
46
47
|
) {
|
|
48
|
+
const { t } = useLocale();
|
|
49
|
+
const emptyLabel = emptyLabelProp ?? t("editor.completions.noSuggestions");
|
|
47
50
|
return (
|
|
48
51
|
<div
|
|
49
52
|
ref={ref}
|
|
50
53
|
role="listbox"
|
|
51
|
-
aria-label="
|
|
54
|
+
aria-label={t("editor.completions.suggestions")}
|
|
52
55
|
className={cn(
|
|
53
56
|
"max-h-[min(280px,50vh)] w-64 overflow-y-auto overflow-x-hidden rounded-md bg-popover p-1 text-popover-foreground shadow-ring-md",
|
|
54
57
|
className,
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
DropdownMenuSubContent,
|
|
47
47
|
DropdownMenuSubTrigger,
|
|
48
48
|
DropdownMenuTrigger,
|
|
49
|
+
useLocale,
|
|
49
50
|
} from "@elabs-ai/components-ui";
|
|
50
51
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
51
52
|
import { editorViewCtx, parserCtx, serializerCtx } from "@milkdown/kit/core";
|
|
@@ -177,6 +178,7 @@ function InlineEdit({ value, onCommit, ariaLabel, placeholder, className }: Inli
|
|
|
177
178
|
|
|
178
179
|
/** `:::name` block directives → live @brand component with an editable body. */
|
|
179
180
|
function ContainerDirectiveView() {
|
|
181
|
+
const { t } = useLocale();
|
|
180
182
|
const { contentRef } = useNodeViewContext();
|
|
181
183
|
const { name, attributes, update } = useDirectiveAttrs();
|
|
182
184
|
|
|
@@ -189,8 +191,8 @@ function ContainerDirectiveView() {
|
|
|
189
191
|
<CardHeader className="pb-3">
|
|
190
192
|
<CardTitle>
|
|
191
193
|
<InlineEdit
|
|
192
|
-
ariaLabel="
|
|
193
|
-
placeholder="
|
|
194
|
+
ariaLabel={t("editor.directiveViews.cardTitle")}
|
|
195
|
+
placeholder={t("editor.directiveViews.cardTitle")}
|
|
194
196
|
value={attributes.title ?? ""}
|
|
195
197
|
onCommit={(v) => update("title", v)}
|
|
196
198
|
/>
|
|
@@ -213,7 +215,7 @@ function ContainerDirectiveView() {
|
|
|
213
215
|
flow, so its label must not join the document heading outline (see #21). */}
|
|
214
216
|
<div className="mb-1 font-medium leading-none tracking-tight">
|
|
215
217
|
<InlineEdit
|
|
216
|
-
ariaLabel="
|
|
218
|
+
ariaLabel={t("editor.directiveViews.calloutTitle")}
|
|
217
219
|
placeholder={capitalize(attributes.type ?? "note")}
|
|
218
220
|
value={attributes.title ?? ""}
|
|
219
221
|
onCommit={(v) => update("title", v)}
|
|
@@ -250,7 +252,9 @@ function ContainerDirectiveView() {
|
|
|
250
252
|
className="brand-directive brand-directive--unknown"
|
|
251
253
|
data-brand-directive={name}
|
|
252
254
|
>
|
|
253
|
-
<div className="mb-1 font-medium leading-none tracking-tight">
|
|
255
|
+
<div className="mb-1 font-medium leading-none tracking-tight">
|
|
256
|
+
{t("editor.directiveViews.unknownBlock", { name })}
|
|
257
|
+
</div>
|
|
254
258
|
<AlertDescription>{body}</AlertDescription>
|
|
255
259
|
</Alert>
|
|
256
260
|
);
|
|
@@ -492,6 +496,7 @@ function IterationMenuItems({
|
|
|
492
496
|
* inline (today's behaviour, unchanged).
|
|
493
497
|
*/
|
|
494
498
|
function IterationDirectiveView() {
|
|
499
|
+
const { t } = useLocale();
|
|
495
500
|
const { contentRef, node, getPos, setAttrs } = useNodeViewContext();
|
|
496
501
|
const { name, attributes } = useDirectiveAttrs();
|
|
497
502
|
const [, getInstance] = useInstance();
|
|
@@ -577,7 +582,7 @@ function IterationDirectiveView() {
|
|
|
577
582
|
attributes: attributes as Record<string, string>,
|
|
578
583
|
}).cells.length > 0;
|
|
579
584
|
|
|
580
|
-
const disabledHint = "
|
|
585
|
+
const disabledHint = t("editor.directiveViews.needsEmbeddedValues");
|
|
581
586
|
|
|
582
587
|
const convertToStatic = () => {
|
|
583
588
|
const template = readBodyMarkdown(getInstance as GetEditor, node);
|
|
@@ -589,14 +594,14 @@ function IterationDirectiveView() {
|
|
|
589
594
|
{
|
|
590
595
|
type: "item",
|
|
591
596
|
id: "edit",
|
|
592
|
-
label: "
|
|
597
|
+
label: t("editor.directiveViews.editIteration"),
|
|
593
598
|
icon: <Pencil className="size-4" aria-hidden="true" />,
|
|
594
599
|
onSelect: requestEdit,
|
|
595
600
|
},
|
|
596
601
|
{
|
|
597
602
|
type: "layout",
|
|
598
603
|
id: "layout",
|
|
599
|
-
label: "
|
|
604
|
+
label: t("editor.directiveViews.changeLayout"),
|
|
600
605
|
icon: <LayoutGrid className="size-4" aria-hidden="true" />,
|
|
601
606
|
value: (attributes.layout as IterationLayout) || ITERATION_LAYOUTS[kind][0]!,
|
|
602
607
|
options: ITERATION_LAYOUTS[kind],
|
|
@@ -607,7 +612,9 @@ function IterationDirectiveView() {
|
|
|
607
612
|
{
|
|
608
613
|
type: "item",
|
|
609
614
|
id: "transpose",
|
|
610
|
-
label: hasEmbeddedData
|
|
615
|
+
label: hasEmbeddedData
|
|
616
|
+
? t("editor.directiveViews.transpose")
|
|
617
|
+
: `${t("editor.directiveViews.transpose")} ${disabledHint}`,
|
|
611
618
|
icon: <ArrowLeftRight className="size-4" aria-hidden="true" />,
|
|
612
619
|
onSelect: transpose,
|
|
613
620
|
disabled: !hasEmbeddedData,
|
|
@@ -617,7 +624,9 @@ function IterationDirectiveView() {
|
|
|
617
624
|
{
|
|
618
625
|
type: "item",
|
|
619
626
|
id: "convert-to-static",
|
|
620
|
-
label: hasEmbeddedData
|
|
627
|
+
label: hasEmbeddedData
|
|
628
|
+
? t("editor.directiveViews.convertToStatic")
|
|
629
|
+
: `${t("editor.directiveViews.convertToStatic")} ${disabledHint}`,
|
|
621
630
|
icon: <FileText className="size-4" aria-hidden="true" />,
|
|
622
631
|
onSelect: convertToStatic,
|
|
623
632
|
disabled: !hasEmbeddedData,
|
|
@@ -627,11 +636,15 @@ function IterationDirectiveView() {
|
|
|
627
636
|
const header = (
|
|
628
637
|
<div className="mb-1.5 flex items-center gap-1.5 text-meta font-medium text-info-text">
|
|
629
638
|
<Icon className="size-3.5 shrink-0" aria-hidden="true" />
|
|
630
|
-
<span>{isPivot ? "
|
|
639
|
+
<span>{isPivot ? t("editor.directiveViews.pivot") : t("editor.directiveViews.iterate")}</span>
|
|
631
640
|
{!isPivot && attributes.as ? (
|
|
632
|
-
<span className="font-normal text-muted-foreground"
|
|
641
|
+
<span className="font-normal text-muted-foreground">
|
|
642
|
+
{t("editor.directiveViews.perItem", { as: attributes.as })}
|
|
643
|
+
</span>
|
|
633
644
|
) : null}
|
|
634
|
-
<span className="font-normal text-muted-foreground"
|
|
645
|
+
<span className="font-normal text-muted-foreground">
|
|
646
|
+
{t("editor.directiveViews.templateSuffix")}
|
|
647
|
+
</span>
|
|
635
648
|
{onEdit ? (
|
|
636
649
|
<DropdownMenu>
|
|
637
650
|
<DropdownMenuTrigger asChild>
|
|
@@ -639,8 +652,8 @@ function IterationDirectiveView() {
|
|
|
639
652
|
type="button"
|
|
640
653
|
// `data-directive-chrome` routes the click to the browser, not ProseMirror.
|
|
641
654
|
data-directive-chrome=""
|
|
642
|
-
aria-label="
|
|
643
|
-
title="
|
|
655
|
+
aria-label={t("editor.directiveViews.iterationActions")}
|
|
656
|
+
title={t("editor.directiveViews.iterationActionsTitle")}
|
|
644
657
|
className="ms-auto inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground focus-ring"
|
|
645
658
|
>
|
|
646
659
|
<MoreHorizontal className="size-4" aria-hidden="true" />
|
|
@@ -700,6 +713,7 @@ function IterationDirectiveView() {
|
|
|
700
713
|
|
|
701
714
|
/** `::name` leaf directives (e.g. `::metric`) → live, atomic @brand component. */
|
|
702
715
|
function LeafDirectiveView() {
|
|
716
|
+
const { t } = useLocale();
|
|
703
717
|
const { name, attributes, update } = useDirectiveAttrs();
|
|
704
718
|
|
|
705
719
|
if (name !== "metric") {
|
|
@@ -708,7 +722,7 @@ function LeafDirectiveView() {
|
|
|
708
722
|
className="brand-directive brand-directive--leaf brand-directive--unknown rounded-md border border-destructive/40 bg-surface-muted p-3 text-sm text-muted-foreground"
|
|
709
723
|
data-brand-leaf={name}
|
|
710
724
|
>
|
|
711
|
-
|
|
725
|
+
{t("editor.directiveViews.unknownInlineBlock")} <code>::{name}</code>
|
|
712
726
|
</div>
|
|
713
727
|
);
|
|
714
728
|
}
|
|
@@ -720,16 +734,16 @@ function LeafDirectiveView() {
|
|
|
720
734
|
data-brand-leaf="metric"
|
|
721
735
|
label={
|
|
722
736
|
<InlineEdit
|
|
723
|
-
ariaLabel="
|
|
724
|
-
placeholder="
|
|
737
|
+
ariaLabel={t("editor.directiveViews.metricLabel")}
|
|
738
|
+
placeholder={t("editor.directiveViews.metricLabelPlaceholder")}
|
|
725
739
|
value={attributes.label ?? ""}
|
|
726
740
|
onCommit={(v) => update("label", v)}
|
|
727
741
|
/>
|
|
728
742
|
}
|
|
729
743
|
value={
|
|
730
744
|
<InlineEdit
|
|
731
|
-
ariaLabel="
|
|
732
|
-
placeholder="
|
|
745
|
+
ariaLabel={t("editor.directiveViews.metricValue")}
|
|
746
|
+
placeholder={t("editor.directiveViews.metricValuePlaceholder")}
|
|
733
747
|
value={attributes.value ?? ""}
|
|
734
748
|
onCommit={(v) => update("value", v)}
|
|
735
749
|
className="min-w-[1ch]"
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* Semantic tokens only; matches the `Command` popover look (bg-popover, accent
|
|
15
15
|
* selection, muted group headings).
|
|
16
16
|
*/
|
|
17
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
17
18
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
18
19
|
import { forwardRef, type HTMLAttributes } from "react";
|
|
19
20
|
|
|
@@ -43,12 +44,14 @@ export const SlashMenu = forwardRef<HTMLDivElement, SlashMenuProps>(function Sla
|
|
|
43
44
|
activeId,
|
|
44
45
|
onSelect,
|
|
45
46
|
idPrefix = "brand-slash",
|
|
46
|
-
emptyLabel
|
|
47
|
+
emptyLabel: emptyLabelProp,
|
|
47
48
|
className,
|
|
48
49
|
...props
|
|
49
50
|
},
|
|
50
51
|
ref,
|
|
51
52
|
) {
|
|
53
|
+
const { t } = useLocale();
|
|
54
|
+
const emptyLabel = emptyLabelProp ?? t("editor.slashMenu.noMatchingBlocks");
|
|
52
55
|
const groups = groupSlashCommands(commands);
|
|
53
56
|
|
|
54
57
|
return (
|
|
@@ -57,7 +60,7 @@ export const SlashMenu = forwardRef<HTMLDivElement, SlashMenuProps>(function Sla
|
|
|
57
60
|
// The editor surface carries `role="textbox"`; this list is its popup. The
|
|
58
61
|
// plugin sets `aria-controls`/`aria-activedescendant` on the textbox.
|
|
59
62
|
role="listbox"
|
|
60
|
-
aria-label="
|
|
63
|
+
aria-label={t("editor.slashMenu.insertBlock")}
|
|
61
64
|
className={cn(
|
|
62
65
|
"max-h-[min(320px,60vh)] w-72 overflow-y-auto overflow-x-hidden rounded-md bg-popover p-1 text-popover-foreground shadow-ring-md",
|
|
63
66
|
className,
|
|
@@ -42,6 +42,7 @@ import { Plugin, PluginKey } from "@milkdown/kit/prose/state";
|
|
|
42
42
|
import type { EditorView } from "@milkdown/kit/prose/view";
|
|
43
43
|
import { $prose } from "@milkdown/kit/utils";
|
|
44
44
|
import { isInTable } from "@milkdown/kit/prose/tables";
|
|
45
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
45
46
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
46
47
|
import { usePluginViewContext } from "@prosemirror-adapter/react";
|
|
47
48
|
import type { usePluginViewFactory } from "@prosemirror-adapter/react";
|
|
@@ -137,39 +138,49 @@ function ToolbarDivider() {
|
|
|
137
138
|
*/
|
|
138
139
|
function TableControlsView() {
|
|
139
140
|
const { view } = usePluginViewContext();
|
|
141
|
+
const { t } = useLocale();
|
|
140
142
|
const inTable = isInTable(view.state);
|
|
141
143
|
const run = (key: Parameters<typeof dispatchCommand>[1]) => () => dispatchCommand(view, key);
|
|
142
144
|
|
|
143
145
|
if (!inTable) return null;
|
|
144
146
|
|
|
147
|
+
const addRowAbove = t("editor.tableView.addRowAbove");
|
|
148
|
+
const addRowBelow = t("editor.tableView.addRowBelow");
|
|
149
|
+
const deleteRow = t("editor.tableView.deleteRow");
|
|
150
|
+
const addColLeft = t("editor.tableView.addColumnLeft");
|
|
151
|
+
const addColRight = t("editor.tableView.addColumnRight");
|
|
152
|
+
const deleteCol = t("editor.tableView.deleteColumn");
|
|
153
|
+
|
|
145
154
|
return (
|
|
146
155
|
<div
|
|
147
156
|
role="toolbar"
|
|
148
|
-
aria-label="
|
|
157
|
+
aria-label={t("editor.tableView.tableControls")}
|
|
149
158
|
className={cn(
|
|
150
159
|
"flex flex-wrap items-center gap-1 px-2 py-1.5",
|
|
151
160
|
"border-t border-border-strong bg-surface-muted",
|
|
152
161
|
)}
|
|
153
162
|
>
|
|
154
163
|
{/* Row group */}
|
|
155
|
-
<span className="mr-1 select-none text-caption text-muted-foreground">
|
|
164
|
+
<span className="mr-1 select-none text-caption text-muted-foreground">
|
|
165
|
+
{t("editor.tableView.row")}
|
|
166
|
+
</span>
|
|
156
167
|
<ToolbarButton
|
|
157
|
-
ariaLabel=
|
|
158
|
-
title=
|
|
168
|
+
ariaLabel={addRowAbove}
|
|
169
|
+
title={addRowAbove}
|
|
159
170
|
onClick={run(addRowBeforeCommand.key)}
|
|
160
171
|
>
|
|
161
172
|
↑+
|
|
162
173
|
</ToolbarButton>
|
|
163
174
|
<ToolbarButton
|
|
164
|
-
ariaLabel=
|
|
165
|
-
title=
|
|
175
|
+
ariaLabel={addRowBelow}
|
|
176
|
+
title={addRowBelow}
|
|
166
177
|
onClick={run(addRowAfterCommand.key)}
|
|
167
178
|
>
|
|
168
179
|
↓+
|
|
169
180
|
</ToolbarButton>
|
|
170
181
|
<ToolbarButton
|
|
171
|
-
ariaLabel=
|
|
172
|
-
title=
|
|
182
|
+
ariaLabel={deleteRow}
|
|
183
|
+
title={deleteRow}
|
|
173
184
|
variant="destructive"
|
|
174
185
|
onClick={run(deleteSelectedCellsCommand.key)}
|
|
175
186
|
>
|
|
@@ -179,24 +190,26 @@ function TableControlsView() {
|
|
|
179
190
|
<ToolbarDivider />
|
|
180
191
|
|
|
181
192
|
{/* Column group */}
|
|
182
|
-
<span className="mr-1 select-none text-caption text-muted-foreground">
|
|
193
|
+
<span className="mr-1 select-none text-caption text-muted-foreground">
|
|
194
|
+
{t("editor.tableView.col")}
|
|
195
|
+
</span>
|
|
183
196
|
<ToolbarButton
|
|
184
|
-
ariaLabel=
|
|
185
|
-
title=
|
|
197
|
+
ariaLabel={addColLeft}
|
|
198
|
+
title={addColLeft}
|
|
186
199
|
onClick={run(addColBeforeCommand.key)}
|
|
187
200
|
>
|
|
188
201
|
←+
|
|
189
202
|
</ToolbarButton>
|
|
190
203
|
<ToolbarButton
|
|
191
|
-
ariaLabel=
|
|
192
|
-
title=
|
|
204
|
+
ariaLabel={addColRight}
|
|
205
|
+
title={addColRight}
|
|
193
206
|
onClick={run(addColAfterCommand.key)}
|
|
194
207
|
>
|
|
195
208
|
→+
|
|
196
209
|
</ToolbarButton>
|
|
197
210
|
<ToolbarButton
|
|
198
|
-
ariaLabel=
|
|
199
|
-
title=
|
|
211
|
+
ariaLabel={deleteCol}
|
|
212
|
+
title={deleteCol}
|
|
200
213
|
variant="destructive"
|
|
201
214
|
onClick={run(deleteSelectedCellsCommand.key)}
|
|
202
215
|
>
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
TagInput,
|
|
29
29
|
ToggleGroup,
|
|
30
30
|
ToggleGroupItem,
|
|
31
|
+
useLocale,
|
|
31
32
|
} from "@elabs-ai/components-ui";
|
|
32
33
|
import { useEffect, useId, useMemo, useState, type ReactNode } from "react";
|
|
33
34
|
|
|
@@ -79,6 +80,7 @@ export function IterationBuilderDialog({
|
|
|
79
80
|
evaluate,
|
|
80
81
|
interpolate,
|
|
81
82
|
}: IterationBuilderDialogProps) {
|
|
83
|
+
const { t } = useLocale();
|
|
82
84
|
const kind = value?.kind ?? kindProp;
|
|
83
85
|
const isPivot = kind === "pivot";
|
|
84
86
|
const ids = useId();
|
|
@@ -130,19 +132,26 @@ export function IterationBuilderDialog({
|
|
|
130
132
|
onOpenChange(false);
|
|
131
133
|
};
|
|
132
134
|
|
|
133
|
-
const noun = isPivot
|
|
135
|
+
const noun = isPivot
|
|
136
|
+
? t("editor.iterationBuilder.pivotNoun")
|
|
137
|
+
: t("editor.iterationBuilder.iterationNoun");
|
|
134
138
|
|
|
135
139
|
return (
|
|
136
140
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
137
141
|
<DialogContent className="flex max-h-[88vh] w-[min(56rem,94vw)] max-w-none flex-col">
|
|
138
142
|
<DialogHeader>
|
|
139
143
|
<DialogTitle>
|
|
140
|
-
{
|
|
144
|
+
{t(
|
|
145
|
+
value ? "editor.iterationBuilder.editTitle" : "editor.iterationBuilder.insertTitle",
|
|
146
|
+
{
|
|
147
|
+
noun,
|
|
148
|
+
},
|
|
149
|
+
)}
|
|
141
150
|
</DialogTitle>
|
|
142
151
|
<DialogDescription>
|
|
143
152
|
{isPivot
|
|
144
|
-
? "
|
|
145
|
-
: "
|
|
153
|
+
? t("editor.iterationBuilder.pivotDescription")
|
|
154
|
+
: t("editor.iterationBuilder.iterationDescription")}
|
|
146
155
|
</DialogDescription>
|
|
147
156
|
</DialogHeader>
|
|
148
157
|
|
|
@@ -151,47 +160,53 @@ export function IterationBuilderDialog({
|
|
|
151
160
|
<div className="flex min-w-0 flex-col gap-4">
|
|
152
161
|
{!isPivot ? (
|
|
153
162
|
<div className="flex flex-col gap-1.5">
|
|
154
|
-
<Label htmlFor={`${ids}-as`}>
|
|
163
|
+
<Label htmlFor={`${ids}-as`}>{t("editor.iterationBuilder.bindName")}</Label>
|
|
155
164
|
<Input
|
|
156
165
|
id={`${ids}-as`}
|
|
157
166
|
value={asName}
|
|
158
167
|
spellCheck={false}
|
|
159
168
|
autoComplete="off"
|
|
160
|
-
placeholder="
|
|
169
|
+
placeholder={t("editor.iterationBuilder.bindNamePlaceholder")}
|
|
161
170
|
onChange={(e) => setAsName(e.target.value)}
|
|
162
171
|
/>
|
|
163
172
|
<p className="text-meta text-muted-foreground">
|
|
164
|
-
|
|
173
|
+
{t("editor.iterationBuilder.bindNameHintPrefix")}
|
|
174
|
+
<code>{`{{${asName.trim() || "item"}.name}}`}</code>
|
|
175
|
+
{t("editor.iterationBuilder.bindNameHintSuffix")}
|
|
165
176
|
</p>
|
|
166
177
|
</div>
|
|
167
178
|
) : null}
|
|
168
179
|
|
|
169
180
|
<div className="flex flex-col gap-1.5">
|
|
170
|
-
<Label htmlFor={`${ids}-values`}>
|
|
181
|
+
<Label htmlFor={`${ids}-values`}>
|
|
182
|
+
{isPivot
|
|
183
|
+
? t("editor.iterationBuilder.rowValues")
|
|
184
|
+
: t("editor.iterationBuilder.values")}
|
|
185
|
+
</Label>
|
|
171
186
|
<TagInput
|
|
172
187
|
id={`${ids}-values`}
|
|
173
188
|
value={values}
|
|
174
189
|
onValueChange={setValues}
|
|
175
190
|
delimiter={[",", "\n"]}
|
|
176
|
-
placeholder="
|
|
191
|
+
placeholder={t("editor.iterationBuilder.valuePlaceholder")}
|
|
177
192
|
/>
|
|
178
193
|
</div>
|
|
179
194
|
|
|
180
195
|
{isPivot ? (
|
|
181
196
|
<div className="flex flex-col gap-1.5">
|
|
182
|
-
<Label htmlFor={`${ids}-cols`}>
|
|
197
|
+
<Label htmlFor={`${ids}-cols`}>{t("editor.iterationBuilder.columnValues")}</Label>
|
|
183
198
|
<TagInput
|
|
184
199
|
id={`${ids}-cols`}
|
|
185
200
|
value={cols}
|
|
186
201
|
onValueChange={setCols}
|
|
187
202
|
delimiter={[",", "\n"]}
|
|
188
|
-
placeholder="
|
|
203
|
+
placeholder={t("editor.iterationBuilder.valuePlaceholder")}
|
|
189
204
|
/>
|
|
190
205
|
</div>
|
|
191
206
|
) : null}
|
|
192
207
|
|
|
193
208
|
<div className="flex flex-col gap-1.5">
|
|
194
|
-
<Label id={`${ids}-layout`}>
|
|
209
|
+
<Label id={`${ids}-layout`}>{t("editor.iterationBuilder.layout")}</Label>
|
|
195
210
|
<ToggleGroup
|
|
196
211
|
type="single"
|
|
197
212
|
variant="segmented"
|
|
@@ -216,23 +231,27 @@ export function IterationBuilderDialog({
|
|
|
216
231
|
{/* Right column — the per-cell TEMPLATE + the live populated preview. */}
|
|
217
232
|
<div className="flex min-h-0 min-w-0 flex-col gap-4">
|
|
218
233
|
<div className="flex min-h-0 flex-col gap-1.5">
|
|
219
|
-
<Label>
|
|
234
|
+
<Label>
|
|
235
|
+
{isPivot
|
|
236
|
+
? t("editor.iterationBuilder.perCellTemplate")
|
|
237
|
+
: t("editor.iterationBuilder.perRowTemplate")}
|
|
238
|
+
</Label>
|
|
220
239
|
<div className="h-44 min-h-0 overflow-hidden rounded-md border border-border">
|
|
221
240
|
<MarkdownWorkspace
|
|
222
241
|
value={template}
|
|
223
242
|
onChange={setTemplate}
|
|
224
243
|
defaultMode="source"
|
|
225
244
|
className="h-full"
|
|
226
|
-
aria-label="
|
|
245
|
+
aria-label={t("editor.iterationBuilder.perCellTemplate")}
|
|
227
246
|
/>
|
|
228
247
|
</div>
|
|
229
248
|
</div>
|
|
230
249
|
|
|
231
250
|
<div className="flex min-h-0 flex-col gap-1.5">
|
|
232
|
-
<Label>
|
|
251
|
+
<Label>{t("editor.iterationBuilder.livePreview")}</Label>
|
|
233
252
|
<div
|
|
234
253
|
role="region"
|
|
235
|
-
aria-label="
|
|
254
|
+
aria-label={t("editor.iterationBuilder.livePreview")}
|
|
236
255
|
className="min-h-0 flex-1 overflow-auto rounded-md border border-border bg-card p-3"
|
|
237
256
|
>
|
|
238
257
|
<MarkdownPreview
|
|
@@ -249,9 +268,11 @@ export function IterationBuilderDialog({
|
|
|
249
268
|
|
|
250
269
|
<DialogFooter>
|
|
251
270
|
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
|
252
|
-
|
|
271
|
+
{t("editor.iterationBuilder.cancel")}
|
|
272
|
+
</Button>
|
|
273
|
+
<Button onClick={save}>
|
|
274
|
+
{value ? t("editor.iterationBuilder.save") : t("editor.iterationBuilder.insert")}
|
|
253
275
|
</Button>
|
|
254
|
-
<Button onClick={save}>{value ? "Save" : "Insert"}</Button>
|
|
255
276
|
</DialogFooter>
|
|
256
277
|
</DialogContent>
|
|
257
278
|
</Dialog>
|