@zephytiju/prism-main-editor 0.1.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/README.md +252 -0
- package/dist/CommentsPane.d.ts +32 -0
- package/dist/CommentsPane.d.ts.map +1 -0
- package/dist/CommentsPane.js +85 -0
- package/dist/MainEditor.d.ts +59 -0
- package/dist/MainEditor.d.ts.map +1 -0
- package/dist/MainEditor.js +102 -0
- package/dist/TitleBar.d.ts +28 -0
- package/dist/TitleBar.d.ts.map +1 -0
- package/dist/TitleBar.js +55 -0
- package/dist/blocks/AssessmentBlockView.d.ts +21 -0
- package/dist/blocks/AssessmentBlockView.d.ts.map +1 -0
- package/dist/blocks/AssessmentBlockView.js +34 -0
- package/dist/blocks/BlockRow.d.ts +33 -0
- package/dist/blocks/BlockRow.d.ts.map +1 -0
- package/dist/blocks/BlockRow.js +56 -0
- package/dist/blocks/BlockStack.d.ts +27 -0
- package/dist/blocks/BlockStack.d.ts.map +1 -0
- package/dist/blocks/BlockStack.js +75 -0
- package/dist/blocks/DragMenu.d.ts +22 -0
- package/dist/blocks/DragMenu.d.ts.map +1 -0
- package/dist/blocks/DragMenu.js +219 -0
- package/dist/blocks/EmbeddedBlockChrome.d.ts +34 -0
- package/dist/blocks/EmbeddedBlockChrome.d.ts.map +1 -0
- package/dist/blocks/EmbeddedBlockChrome.js +81 -0
- package/dist/blocks/EvidenceTableView.d.ts +25 -0
- package/dist/blocks/EvidenceTableView.d.ts.map +1 -0
- package/dist/blocks/EvidenceTableView.js +30 -0
- package/dist/blocks/GeovisionEmbedView.d.ts +28 -0
- package/dist/blocks/GeovisionEmbedView.d.ts.map +1 -0
- package/dist/blocks/GeovisionEmbedView.js +33 -0
- package/dist/blocks/InsertMenu.d.ts +22 -0
- package/dist/blocks/InsertMenu.d.ts.map +1 -0
- package/dist/blocks/InsertMenu.js +59 -0
- package/dist/blocks/InsertRail.d.ts +28 -0
- package/dist/blocks/InsertRail.d.ts.map +1 -0
- package/dist/blocks/InsertRail.js +29 -0
- package/dist/blocks/NarrativeBlockView.d.ts +22 -0
- package/dist/blocks/NarrativeBlockView.d.ts.map +1 -0
- package/dist/blocks/NarrativeBlockView.js +44 -0
- package/dist/blocks/NativeTableView.d.ts +12 -0
- package/dist/blocks/NativeTableView.d.ts.map +1 -0
- package/dist/blocks/NativeTableView.js +36 -0
- package/dist/blocks/SavedViewDropdown.d.ts +17 -0
- package/dist/blocks/SavedViewDropdown.d.ts.map +1 -0
- package/dist/blocks/SavedViewDropdown.js +85 -0
- package/dist/blocks/SelectionToolbar.d.ts +25 -0
- package/dist/blocks/SelectionToolbar.d.ts.map +1 -0
- package/dist/blocks/SelectionToolbar.js +175 -0
- package/dist/blocks/menuStyles.d.ts +32 -0
- package/dist/blocks/menuStyles.d.ts.map +1 -0
- package/dist/blocks/menuStyles.js +32 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/locales/en.json +37 -0
- package/dist/locales/index.d.ts +105 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +27 -0
- package/dist/locales/zh-CN.json +37 -0
- package/dist/tokens.d.ts +42 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +43 -0
- package/dist/types.d.ts +207 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/locales/en.json +37 -0
- package/locales/zh-CN.json +37 -0
- package/package.json +79 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AssessmentBlock as AssessmentBlockModel } from "../types.js";
|
|
2
|
+
import type { MainEditorLocale } from "../locales/index.js";
|
|
3
|
+
export interface AssessmentBlockViewProps {
|
|
4
|
+
readonly block: AssessmentBlockModel;
|
|
5
|
+
readonly locale: MainEditorLocale;
|
|
6
|
+
/**
|
|
7
|
+
* D11: in the editor variant the judgment body is an editable text surface —
|
|
8
|
+
* selecting inside it opens the floating selection toolbar.
|
|
9
|
+
*/
|
|
10
|
+
readonly editable?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Normal document block — analyst-judgment highlight (D10): an accent bar +
|
|
14
|
+
* tint ONLY — no outlining box, no card chrome, no kind pill outside the
|
|
15
|
+
* label. The tint and the bar both derive from the warn token; the label
|
|
16
|
+
* defaults to the locale's "ANALYST JUDGMENT" string. When editable (D11) the
|
|
17
|
+
* body carries the data-main-editor-editable attribute the selection toolbar
|
|
18
|
+
* anchors to.
|
|
19
|
+
*/
|
|
20
|
+
export declare function AssessmentBlockView({ block, locale, editable }: AssessmentBlockViewProps): import("react").JSX.Element;
|
|
21
|
+
//# sourceMappingURL=AssessmentBlockView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssessmentBlockView.d.ts","sourceRoot":"","sources":["../../src/blocks/AssessmentBlockView.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,IAAI,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAgB,EAAE,EAAE,wBAAwB,+BAwDhG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Group, Stack, Text } from "@mantine/core";
|
|
3
|
+
import { stringsForLocale } from "../locales/index.js";
|
|
4
|
+
import { HIGHLIGHT_TINT, MONO, WARN } from "../tokens.js";
|
|
5
|
+
/**
|
|
6
|
+
* Normal document block — analyst-judgment highlight (D10): an accent bar +
|
|
7
|
+
* tint ONLY — no outlining box, no card chrome, no kind pill outside the
|
|
8
|
+
* label. The tint and the bar both derive from the warn token; the label
|
|
9
|
+
* defaults to the locale's "ANALYST JUDGMENT" string. When editable (D11) the
|
|
10
|
+
* body carries the data-main-editor-editable attribute the selection toolbar
|
|
11
|
+
* anchors to.
|
|
12
|
+
*/
|
|
13
|
+
export function AssessmentBlockView({ block, locale, editable = false }) {
|
|
14
|
+
const strings = stringsForLocale(locale);
|
|
15
|
+
return (_jsx(Box, { px: 16, py: 14, style: { background: HIGHLIGHT_TINT, borderRadius: 4 }, "data-testid": `main-editor-block-${block.id}`, children: _jsxs(Group, { align: "stretch", gap: 16, wrap: "nowrap", children: [_jsx("span", { style: {
|
|
16
|
+
display: "inline-block",
|
|
17
|
+
width: 5,
|
|
18
|
+
borderRadius: 2,
|
|
19
|
+
background: WARN,
|
|
20
|
+
alignSelf: "stretch",
|
|
21
|
+
flexShrink: 0,
|
|
22
|
+
}, "data-testid": `main-editor-assessment-bar-${block.id}` }), _jsxs(Stack, { gap: 8, miw: 0, style: { flex: 1 }, children: [_jsx(Text, { ff: MONO, fz: 9, fw: 500, style: { color: WARN, letterSpacing: "0.08em" }, "data-testid": `main-editor-assessment-label-${block.id}`, children: block.label ?? strings.kindAssessment }), _jsx(Text, { fz: 14, fw: 500, c: "text", style: {
|
|
23
|
+
lineHeight: 1.45,
|
|
24
|
+
letterSpacing: "0.01em",
|
|
25
|
+
...(editable ? { cursor: "text", outline: "none" } : {}),
|
|
26
|
+
}, "data-testid": `main-editor-assessment-text-${block.id}`, ...(editable
|
|
27
|
+
? {
|
|
28
|
+
contentEditable: true,
|
|
29
|
+
suppressContentEditableWarning: true,
|
|
30
|
+
spellCheck: false,
|
|
31
|
+
"data-main-editor-editable": block.id,
|
|
32
|
+
}
|
|
33
|
+
: {}), children: block.text })] })] }) }));
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MainEditorLocale } from "../locales/index.js";
|
|
2
|
+
import type { MainEditorBlock, MainEditorBlockKind } from "../types.js";
|
|
3
|
+
import type { KindLabelFn } from "./InsertMenu.js";
|
|
4
|
+
export interface BlockRowProps {
|
|
5
|
+
readonly block: MainEditorBlock;
|
|
6
|
+
readonly locale: MainEditorLocale;
|
|
7
|
+
/** editor — insert + drag controls render; readonly — published view. */
|
|
8
|
+
readonly editor: boolean;
|
|
9
|
+
readonly showInsert: boolean;
|
|
10
|
+
readonly kinds: readonly MainEditorBlockKind[];
|
|
11
|
+
readonly kindLabel: KindLabelFn;
|
|
12
|
+
readonly canMoveUp: boolean;
|
|
13
|
+
readonly canMoveDown: boolean;
|
|
14
|
+
readonly focused: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* D11: narrative + assessment text surfaces are editable (selection toolbar
|
|
17
|
+
* targets them) in the editor variant only.
|
|
18
|
+
*/
|
|
19
|
+
readonly editable: boolean;
|
|
20
|
+
/** Publishes "main-editor.focused-block" for the clicked block id. */
|
|
21
|
+
readonly onFocusBlock: (blockId: string) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One row of the typed block stack: the editor-variant insert rail (gutter)
|
|
25
|
+
* beside the block surface. The gutter renders ONLY on row hover or keyboard
|
|
26
|
+
* focus within the row (D11 — a 120ms opacity fade, kept reachable by TAB
|
|
27
|
+
* because the hidden controls stay focusable and reveal on focus). Clicking
|
|
28
|
+
* the surface publishes "main-editor.focused-block"; the focus wash is an
|
|
29
|
+
* interaction state, not an outlining box — normal blocks stay plain
|
|
30
|
+
* document elements.
|
|
31
|
+
*/
|
|
32
|
+
export declare function BlockRow({ block, locale, editor, showInsert, kinds, kindLabel, canMoveUp, canMoveDown, focused, editable, onFocusBlock, }: BlockRowProps): import("react").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=BlockRow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockRow.d.ts","sourceRoot":"","sources":["../../src/blocks/BlockRow.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAMxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,MAAM,EACN,MAAM,EACN,UAAU,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,OAAO,EACP,QAAQ,EACR,YAAY,GACb,EAAE,aAAa,+BAqDf"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box } from "@mantine/core";
|
|
4
|
+
import { FOCUS_WASH } from "../tokens.js";
|
|
5
|
+
import { AssessmentBlockView } from "./AssessmentBlockView.js";
|
|
6
|
+
import { EvidenceTableView } from "./EvidenceTableView.js";
|
|
7
|
+
import { GeovisionEmbedView } from "./GeovisionEmbedView.js";
|
|
8
|
+
import { InsertRail } from "./InsertRail.js";
|
|
9
|
+
import { NarrativeBlockView } from "./NarrativeBlockView.js";
|
|
10
|
+
import { NativeTableView } from "./NativeTableView.js";
|
|
11
|
+
/**
|
|
12
|
+
* One row of the typed block stack: the editor-variant insert rail (gutter)
|
|
13
|
+
* beside the block surface. The gutter renders ONLY on row hover or keyboard
|
|
14
|
+
* focus within the row (D11 — a 120ms opacity fade, kept reachable by TAB
|
|
15
|
+
* because the hidden controls stay focusable and reveal on focus). Clicking
|
|
16
|
+
* the surface publishes "main-editor.focused-block"; the focus wash is an
|
|
17
|
+
* interaction state, not an outlining box — normal blocks stay plain
|
|
18
|
+
* document elements.
|
|
19
|
+
*/
|
|
20
|
+
export function BlockRow({ block, locale, editor, showInsert, kinds, kindLabel, canMoveUp, canMoveDown, focused, editable, onFocusBlock, }) {
|
|
21
|
+
const [hovered, setHovered] = useState(false);
|
|
22
|
+
const [focusWithin, setFocusWithin] = useState(false);
|
|
23
|
+
return (_jsxs(Box, { style: { display: "flex", alignItems: "flex-start", width: "100%" }, "data-testid": `main-editor-block-row-${block.id}`, "data-block-row-id": block.id, onMouseEnter: () => {
|
|
24
|
+
setHovered(true);
|
|
25
|
+
}, onMouseLeave: () => {
|
|
26
|
+
setHovered(false);
|
|
27
|
+
}, onFocus: () => {
|
|
28
|
+
setFocusWithin(true);
|
|
29
|
+
}, onBlur: (event) => {
|
|
30
|
+
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
31
|
+
setFocusWithin(false);
|
|
32
|
+
}
|
|
33
|
+
}, children: [editor ? (_jsx(InsertRail, { blockId: block.id, kinds: kinds, kindLabel: kindLabel, canMoveUp: canMoveUp, canMoveDown: canMoveDown, locale: locale, showInsert: showInsert, revealed: hovered || focusWithin })) : null, _jsx(Box, { w: editor ? 860 : "100%", onClick: () => {
|
|
34
|
+
onFocusBlock(block.id);
|
|
35
|
+
}, style: {
|
|
36
|
+
background: focused ? FOCUS_WASH : "transparent",
|
|
37
|
+
borderRadius: 6,
|
|
38
|
+
cursor: "pointer",
|
|
39
|
+
transition: "background .15s ease",
|
|
40
|
+
}, "data-testid": `main-editor-block-surface-${block.id}`, children: _jsx(BlockView, { block: block, locale: locale, editable: editable }) })] }));
|
|
41
|
+
}
|
|
42
|
+
/** Dispatches one dossier block to its D10 block-surface view. */
|
|
43
|
+
function BlockView({ block, locale, editable, }) {
|
|
44
|
+
switch (block.kind) {
|
|
45
|
+
case "narrative":
|
|
46
|
+
return _jsx(NarrativeBlockView, { block: block, editable: editable });
|
|
47
|
+
case "assessment":
|
|
48
|
+
return _jsx(AssessmentBlockView, { block: block, locale: locale, editable: editable });
|
|
49
|
+
case "geovision-embed":
|
|
50
|
+
return _jsx(GeovisionEmbedView, { block: block, locale: locale });
|
|
51
|
+
case "evidence-table":
|
|
52
|
+
return _jsx(EvidenceTableView, { block: block, locale: locale });
|
|
53
|
+
case "native-table":
|
|
54
|
+
return _jsx(NativeTableView, { block: block });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { MainEditorLocale } from "../locales/index.js";
|
|
2
|
+
import type { MainEditorBlockKind, MainEditorDossier } from "../types.js";
|
|
3
|
+
export interface BlockStackProps {
|
|
4
|
+
readonly dossier: MainEditorDossier;
|
|
5
|
+
readonly locale: MainEditorLocale;
|
|
6
|
+
/** editor — insert + drag controls (default); readonly — published view. */
|
|
7
|
+
readonly variant: "editor" | "readonly";
|
|
8
|
+
/** Block kinds the insert menu offers (configuration key enabledBlockKinds). */
|
|
9
|
+
readonly enabledBlockKinds: readonly MainEditorBlockKind[];
|
|
10
|
+
/** Configuration key showInsertControls; drag handles follow the editor variant. */
|
|
11
|
+
readonly showInsertControls: boolean;
|
|
12
|
+
readonly focusedBlockId: string | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The typed block stack (D3/D10/D11): same-kind blocks added at app runtime
|
|
16
|
+
* through dossier actions, each preceded by its hover-revealed circular
|
|
17
|
+
* insert control (the + opens the block-kind menu and emits
|
|
18
|
+
* "main-editor.insert-block-requested") and ⠿ drag handle (single click opens
|
|
19
|
+
* the Feishu-style block popover; drag reorders — both emit
|
|
20
|
+
* "main-editor.reorder-block-requested" / "main-editor.remove-block-requested"
|
|
21
|
+
* / "main-editor.duplicate-block-requested" dossier actions the host applies,
|
|
22
|
+
* never composition mutations). Clicking a block publishes
|
|
23
|
+
* "main-editor.focused-block"; the focus wash is an interaction state, not an
|
|
24
|
+
* outlining box — normal blocks stay plain document elements.
|
|
25
|
+
*/
|
|
26
|
+
export declare function BlockStack({ dossier, locale, variant, enabledBlockKinds, showInsertControls, focusedBlockId, }: BlockStackProps): import("react").JSX.Element;
|
|
27
|
+
//# sourceMappingURL=BlockStack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockStack.d.ts","sourceRoot":"","sources":["../../src/blocks/BlockStack.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAElB,MAAM,aAAa,CAAC;AAMrB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,CAAC;IACxC,gFAAgF;IAChF,QAAQ,CAAC,iBAAiB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC3D,oFAAoF;IACpF,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,MAAM,EACN,OAAO,EACP,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,GACf,EAAE,eAAe,+BA+EjB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, Stack, Text } from "@mantine/core";
|
|
4
|
+
import { usePrismStateSetter } from "@zephytiju/prism-react";
|
|
5
|
+
import { formatMessage, stringsForLocale } from "../locales/index.js";
|
|
6
|
+
import { BlockRow } from "./BlockRow.js";
|
|
7
|
+
import { InsertMenu } from "./InsertMenu.js";
|
|
8
|
+
import { BORDER, MONO, MUTED } from "../tokens.js";
|
|
9
|
+
const ALL_KINDS = [
|
|
10
|
+
"narrative",
|
|
11
|
+
"assessment",
|
|
12
|
+
"geovision-embed",
|
|
13
|
+
"evidence-table",
|
|
14
|
+
"native-table",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* The typed block stack (D3/D10/D11): same-kind blocks added at app runtime
|
|
18
|
+
* through dossier actions, each preceded by its hover-revealed circular
|
|
19
|
+
* insert control (the + opens the block-kind menu and emits
|
|
20
|
+
* "main-editor.insert-block-requested") and ⠿ drag handle (single click opens
|
|
21
|
+
* the Feishu-style block popover; drag reorders — both emit
|
|
22
|
+
* "main-editor.reorder-block-requested" / "main-editor.remove-block-requested"
|
|
23
|
+
* / "main-editor.duplicate-block-requested" dossier actions the host applies,
|
|
24
|
+
* never composition mutations). Clicking a block publishes
|
|
25
|
+
* "main-editor.focused-block"; the focus wash is an interaction state, not an
|
|
26
|
+
* outlining box — normal blocks stay plain document elements.
|
|
27
|
+
*/
|
|
28
|
+
export function BlockStack({ dossier, locale, variant, enabledBlockKinds, showInsertControls, focusedBlockId, }) {
|
|
29
|
+
const strings = stringsForLocale(locale);
|
|
30
|
+
const kinds = enabledBlockKinds.length > 0 ? enabledBlockKinds : ALL_KINDS;
|
|
31
|
+
const publishFocusedBlock = usePrismStateSetter("main-editor.focused-block");
|
|
32
|
+
const editor = variant === "editor";
|
|
33
|
+
const showInsert = editor && showInsertControls;
|
|
34
|
+
const kindLabel = (kind) => kind === "narrative"
|
|
35
|
+
? strings.kindNarrative
|
|
36
|
+
: kind === "assessment"
|
|
37
|
+
? strings.kindAssessment
|
|
38
|
+
: kind === "geovision-embed"
|
|
39
|
+
? strings.kindGeovision
|
|
40
|
+
: kind === "evidence-table"
|
|
41
|
+
? strings.kindEvidenceTable
|
|
42
|
+
: strings.kindNativeTable;
|
|
43
|
+
return (_jsxs(Stack, { gap: 16, miw: 0, "data-testid": "main-editor-block-stack", children: [_jsxs(Stack, { gap: 4, miw: 0, children: [_jsx(Text, { ff: MONO, fz: 9, fw: 500, c: "accent", style: { letterSpacing: "0.05em" }, "data-testid": "main-editor-workspace-code", children: `${strings.docPrefix} / ${dossier.docCode}` }), _jsx(Text, { fz: 26, fw: 700, c: "text", style: { lineHeight: 1.25, letterSpacing: "0.01em" }, "data-testid": "main-editor-workspace-title", children: dossier.title }), _jsx(Text, { ff: MONO, fz: 8, style: { color: MUTED, letterSpacing: "0.03em" }, "data-testid": "main-editor-workspace-meta", children: `${dossier.docMeta} • ${formatMessage(strings.blocksCount, { count: dossier.blocks.length })}` })] }), _jsxs(Stack, { gap: 20, miw: 0, align: "flex-start", children: [dossier.blocks.map((block, index) => (_jsx(BlockRow, { block: block, locale: locale, editor: editor, showInsert: showInsert, editable: editor, kinds: kinds, kindLabel: kindLabel, canMoveUp: index > 0, canMoveDown: index < dossier.blocks.length - 1, focused: focusedBlockId === block.id, onFocusBlock: (blockId) => {
|
|
44
|
+
publishFocusedBlock({ blockId });
|
|
45
|
+
} }, block.id))), showInsert ? (_jsx(EndInsertRail, { kinds: kinds, kindLabel: kindLabel, locale: locale, afterBlockId: dossier.blocks.length > 0 ? dossier.blocks[dossier.blocks.length - 1].id : null })) : null] })] }));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The end-of-stack insert affordance (D11): like every block-row gutter it
|
|
49
|
+
* renders ONLY on hover (or focus within), fading in over ~120ms.
|
|
50
|
+
*/
|
|
51
|
+
function EndInsertRail({ kinds, kindLabel, locale, afterBlockId, }) {
|
|
52
|
+
const [revealed, setRevealed] = useState(false);
|
|
53
|
+
return (_jsx(Box, { w: 72, pl: 0, style: { display: "flex", alignItems: "center", flexShrink: 0 }, "data-testid": "main-editor-end-insert", onMouseEnter: () => {
|
|
54
|
+
setRevealed(true);
|
|
55
|
+
}, onMouseLeave: () => {
|
|
56
|
+
setRevealed(false);
|
|
57
|
+
}, onFocus: () => {
|
|
58
|
+
setRevealed(true);
|
|
59
|
+
}, onBlur: (event) => {
|
|
60
|
+
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
61
|
+
setRevealed(false);
|
|
62
|
+
}
|
|
63
|
+
}, children: _jsxs(Box, { style: {
|
|
64
|
+
display: "flex",
|
|
65
|
+
alignItems: "center",
|
|
66
|
+
gap: 6,
|
|
67
|
+
flex: 1,
|
|
68
|
+
opacity: revealed ? 1 : 0,
|
|
69
|
+
transition: "opacity .12s ease",
|
|
70
|
+
}, "data-testid": "main-editor-end-gutter", children: [_jsx(InsertMenu, { buttonTestId: "main-editor-insert-end", kinds: kinds, kindLabel: kindLabel, afterBlockId: afterBlockId, locale: locale, compact: true }), _jsx("span", { style: {
|
|
71
|
+
flex: 1,
|
|
72
|
+
borderTop: `1px dashed ${BORDER}`,
|
|
73
|
+
marginTop: 2,
|
|
74
|
+
}, "aria-hidden": "true" })] }) }));
|
|
75
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MainEditorLocale } from "../locales/index.js";
|
|
2
|
+
export interface DragMenuProps {
|
|
3
|
+
readonly blockId: string;
|
|
4
|
+
readonly canMoveUp: boolean;
|
|
5
|
+
readonly canMoveDown: boolean;
|
|
6
|
+
readonly locale: MainEditorLocale;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The block gutter's ⠿ handle (D3/D11): the drag-to-reorder affordance AND a
|
|
10
|
+
* single-click popover trigger. Click vs drag is distinguished by pointer
|
|
11
|
+
* travel — moving less than DRAG_THRESHOLD_PX before release is a click and
|
|
12
|
+
* opens the Feishu-style block popover; crossing the threshold starts a live
|
|
13
|
+
* drag reorder that emits "main-editor.reorder-block-requested" stepwise each
|
|
14
|
+
* time the pointer crosses onto another block row (the host applies each
|
|
15
|
+
* action and republishes, so the dragged block follows the pointer). The
|
|
16
|
+
* popover mutates the block (move up / move down / duplicate / delete — all
|
|
17
|
+
* IDossierDoc ACTIONS the host applies, never composition mutations) or
|
|
18
|
+
* anchors a new comment thread to the block via
|
|
19
|
+
* "main-editor.comment-requested" (D11). It closes on outside click and ESC.
|
|
20
|
+
*/
|
|
21
|
+
export declare function DragMenu({ blockId, canMoveUp, canMoveDown, locale }: DragMenuProps): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=DragMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DragMenu.d.ts","sourceRoot":"","sources":["../../src/blocks/DragMenu.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAO5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;CACnC;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,aAAa,+BAoSlF"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Box } from "@mantine/core";
|
|
4
|
+
import { emitPrismEvent } from "@zephytiju/prism-react";
|
|
5
|
+
import { stringsForLocale } from "../locales/index.js";
|
|
6
|
+
import { ACCENT, BORDER, CARD, LINE, MUTED, POPOVER_SHADOW } from "../tokens.js";
|
|
7
|
+
/** Pointer-travel beyond this many px counts as a drag, not a click (D11). */
|
|
8
|
+
const DRAG_THRESHOLD_PX = 4;
|
|
9
|
+
/**
|
|
10
|
+
* The block gutter's ⠿ handle (D3/D11): the drag-to-reorder affordance AND a
|
|
11
|
+
* single-click popover trigger. Click vs drag is distinguished by pointer
|
|
12
|
+
* travel — moving less than DRAG_THRESHOLD_PX before release is a click and
|
|
13
|
+
* opens the Feishu-style block popover; crossing the threshold starts a live
|
|
14
|
+
* drag reorder that emits "main-editor.reorder-block-requested" stepwise each
|
|
15
|
+
* time the pointer crosses onto another block row (the host applies each
|
|
16
|
+
* action and republishes, so the dragged block follows the pointer). The
|
|
17
|
+
* popover mutates the block (move up / move down / duplicate / delete — all
|
|
18
|
+
* IDossierDoc ACTIONS the host applies, never composition mutations) or
|
|
19
|
+
* anchors a new comment thread to the block via
|
|
20
|
+
* "main-editor.comment-requested" (D11). It closes on outside click and ESC.
|
|
21
|
+
*/
|
|
22
|
+
export function DragMenu({ blockId, canMoveUp, canMoveDown, locale }) {
|
|
23
|
+
const [open, setOpen] = useState(false);
|
|
24
|
+
const [dragging, setDragging] = useState(false);
|
|
25
|
+
const strings = stringsForLocale(locale);
|
|
26
|
+
const pointerStart = useRef(null);
|
|
27
|
+
const didDrag = useRef(false);
|
|
28
|
+
const draggedRow = useRef(null);
|
|
29
|
+
const lastRowUnder = useRef(null);
|
|
30
|
+
const buttonRef = useRef(null);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!dragging) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Global cursor + selection lock while the block is being dragged.
|
|
36
|
+
const previousCursor = document.body.style.cursor;
|
|
37
|
+
const previousSelect = document.body.style.userSelect;
|
|
38
|
+
document.body.style.cursor = "grabbing";
|
|
39
|
+
document.body.style.userSelect = "none";
|
|
40
|
+
return () => {
|
|
41
|
+
document.body.style.cursor = previousCursor;
|
|
42
|
+
document.body.style.userSelect = previousSelect;
|
|
43
|
+
};
|
|
44
|
+
}, [dragging]);
|
|
45
|
+
const act = (direction) => {
|
|
46
|
+
emitPrismEvent("main-editor.reorder-block-requested", { blockId, direction });
|
|
47
|
+
setOpen(false);
|
|
48
|
+
};
|
|
49
|
+
/** Rows of the block stack in DOM order — DOM order is the block order. */
|
|
50
|
+
const stackRows = () => {
|
|
51
|
+
const row = draggedRow.current;
|
|
52
|
+
if (row === null || row.parentElement === null) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return Array.from(row.parentElement.children).filter((child) => child instanceof HTMLElement && child.hasAttribute("data-block-row-id"));
|
|
56
|
+
};
|
|
57
|
+
/** Live drag reorder: emit one single-step move per row boundary crossed. */
|
|
58
|
+
const dragTo = (clientX, clientY) => {
|
|
59
|
+
const row = draggedRow.current;
|
|
60
|
+
if (row === null) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const under = document
|
|
64
|
+
.elementsFromPoint(clientX, clientY)
|
|
65
|
+
.map((element) => element instanceof HTMLElement ? element.closest("[data-block-row-id]") : null)
|
|
66
|
+
.find((candidate) => candidate !== null);
|
|
67
|
+
if (under === undefined || under === null) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (under === row) {
|
|
71
|
+
lastRowUnder.current = null;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (under === lastRowUnder.current) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
lastRowUnder.current = under;
|
|
78
|
+
const rows = stackRows();
|
|
79
|
+
const from = rows.indexOf(row);
|
|
80
|
+
const to = rows.indexOf(under);
|
|
81
|
+
if (from < 0 || to < 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
emitPrismEvent("main-editor.reorder-block-requested", {
|
|
85
|
+
blockId,
|
|
86
|
+
direction: to < from ? "up" : "down",
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
return (_jsxs(Box, { style: { position: "relative", flexShrink: 0 }, onKeyDown: (event) => {
|
|
90
|
+
// ESC closes the popover from the handle OR any item (D11).
|
|
91
|
+
if (open && event.key === "Escape") {
|
|
92
|
+
setOpen(false);
|
|
93
|
+
buttonRef.current?.focus();
|
|
94
|
+
}
|
|
95
|
+
}, children: [_jsx("button", { ref: buttonRef, type: "button", style: {
|
|
96
|
+
width: 24,
|
|
97
|
+
height: 30,
|
|
98
|
+
fontSize: 16,
|
|
99
|
+
color: dragging ? ACCENT : MUTED,
|
|
100
|
+
background: "transparent",
|
|
101
|
+
border: "none",
|
|
102
|
+
cursor: dragging ? "grabbing" : "grab",
|
|
103
|
+
display: "inline-flex",
|
|
104
|
+
alignItems: "center",
|
|
105
|
+
justifyContent: "center",
|
|
106
|
+
lineHeight: 1,
|
|
107
|
+
touchAction: "none",
|
|
108
|
+
}, title: strings.dragMenuTitle, "aria-haspopup": "menu", "aria-expanded": open, "data-testid": `main-editor-drag-${blockId}`, onPointerDown: (event) => {
|
|
109
|
+
// Browsers always report the primary button as 0; synthetic events
|
|
110
|
+
// without button data (undefined) are treated as primary presses.
|
|
111
|
+
if (event.button !== undefined && event.button !== 0) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
pointerStart.current = { x: event.clientX, y: event.clientY };
|
|
115
|
+
didDrag.current = false;
|
|
116
|
+
draggedRow.current = event.currentTarget.closest("[data-block-row-id]");
|
|
117
|
+
// Pointer capture routes the whole gesture to the handle; guarded
|
|
118
|
+
// because non-browser surfaces (jsdom) lack the API.
|
|
119
|
+
if (typeof event.currentTarget.setPointerCapture === "function") {
|
|
120
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
121
|
+
}
|
|
122
|
+
}, onPointerMove: (event) => {
|
|
123
|
+
const start = pointerStart.current;
|
|
124
|
+
const primaryHeld = event.buttons === undefined || (event.buttons & 1) !== 0;
|
|
125
|
+
if (start === null || !primaryHeld) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const dx = event.clientX - start.x;
|
|
129
|
+
const dy = event.clientY - start.y;
|
|
130
|
+
if (!didDrag.current && dx * dx + dy * dy < DRAG_THRESHOLD_PX * DRAG_THRESHOLD_PX) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
didDrag.current = true;
|
|
134
|
+
setDragging(true);
|
|
135
|
+
dragTo(event.clientX, event.clientY);
|
|
136
|
+
}, onPointerUp: (event) => {
|
|
137
|
+
if (pointerStart.current !== null &&
|
|
138
|
+
typeof event.currentTarget.hasPointerCapture === "function" &&
|
|
139
|
+
event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
140
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
141
|
+
}
|
|
142
|
+
pointerStart.current = null;
|
|
143
|
+
lastRowUnder.current = null;
|
|
144
|
+
draggedRow.current = null;
|
|
145
|
+
if (didDrag.current) {
|
|
146
|
+
setDragging(false);
|
|
147
|
+
}
|
|
148
|
+
}, onPointerCancel: () => {
|
|
149
|
+
pointerStart.current = null;
|
|
150
|
+
lastRowUnder.current = null;
|
|
151
|
+
draggedRow.current = null;
|
|
152
|
+
setDragging(false);
|
|
153
|
+
}, onClick: () => {
|
|
154
|
+
// A completed drag suppresses the trailing click; only a true click
|
|
155
|
+
// (pointer travel under the threshold) toggles the popover.
|
|
156
|
+
if (didDrag.current) {
|
|
157
|
+
didDrag.current = false;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
setOpen((value) => !value);
|
|
161
|
+
}, children: "\u283F" }), open ? (_jsxs(_Fragment, { children: [_jsx("div", { style: { position: "fixed", inset: 0, zIndex: 8 }, "data-testid": `main-editor-drag-${blockId}-overlay`, onClick: () => {
|
|
162
|
+
setOpen(false);
|
|
163
|
+
} }), _jsxs(Box, { component: "ul", w: 240, py: 4, style: {
|
|
164
|
+
position: "absolute",
|
|
165
|
+
top: 32,
|
|
166
|
+
left: 0,
|
|
167
|
+
background: CARD,
|
|
168
|
+
border: `1px solid ${LINE}`,
|
|
169
|
+
borderRadius: 8,
|
|
170
|
+
boxShadow: POPOVER_SHADOW,
|
|
171
|
+
zIndex: 9,
|
|
172
|
+
listStyle: "none",
|
|
173
|
+
margin: 0,
|
|
174
|
+
}, "data-testid": `main-editor-drag-${blockId}-menu`, role: "menu", "aria-label": strings.dragMenuTitle, children: [_jsx("li", { children: _jsxs("button", { type: "button", role: "menuitem", style: { ...popoverItemStyle, color: ACCENT }, "data-testid": `main-editor-drag-${blockId}-comment`, onClick: () => {
|
|
175
|
+
const request = { blockId };
|
|
176
|
+
emitPrismEvent("main-editor.comment-requested", request);
|
|
177
|
+
setOpen(false);
|
|
178
|
+
}, children: [_jsx("span", { "aria-hidden": "true", style: { fontSize: 12 }, children: "\uD83D\uDCAC" }), strings.commentOnBlock] }) }), _jsx("li", { style: popoverDividerStyle, "aria-hidden": "true" }), _jsx("li", { children: _jsxs("button", { type: "button", role: "menuitem", disabled: !canMoveUp, style: {
|
|
179
|
+
...popoverItemStyle,
|
|
180
|
+
opacity: canMoveUp ? 1 : 0.4,
|
|
181
|
+
cursor: canMoveUp ? "pointer" : "default",
|
|
182
|
+
}, "data-testid": `main-editor-drag-${blockId}-up`, onClick: () => {
|
|
183
|
+
act("up");
|
|
184
|
+
}, children: [_jsx("span", { "aria-hidden": "true", children: "\u2912" }), strings.moveUp] }) }), _jsx("li", { children: _jsxs("button", { type: "button", role: "menuitem", disabled: !canMoveDown, style: {
|
|
185
|
+
...popoverItemStyle,
|
|
186
|
+
opacity: canMoveDown ? 1 : 0.4,
|
|
187
|
+
cursor: canMoveDown ? "pointer" : "default",
|
|
188
|
+
}, "data-testid": `main-editor-drag-${blockId}-down`, onClick: () => {
|
|
189
|
+
act("down");
|
|
190
|
+
}, children: [_jsx("span", { "aria-hidden": "true", children: "\u2913" }), strings.moveDown] }) }), _jsx("li", { children: _jsxs("button", { type: "button", role: "menuitem", style: popoverItemStyle, "data-testid": `main-editor-drag-${blockId}-duplicate`, onClick: () => {
|
|
191
|
+
const request = { blockId };
|
|
192
|
+
emitPrismEvent("main-editor.duplicate-block-requested", request);
|
|
193
|
+
setOpen(false);
|
|
194
|
+
}, children: [_jsx("span", { "aria-hidden": "true", children: "\u29C9" }), strings.duplicateBlock] }) }), _jsx("li", { children: _jsxs("button", { type: "button", role: "menuitem", style: { ...popoverItemStyle, color: "var(--mantine-color-threat-filled)" }, "data-testid": `main-editor-drag-${blockId}-remove`, onClick: () => {
|
|
195
|
+
emitPrismEvent("main-editor.remove-block-requested", { blockId });
|
|
196
|
+
setOpen(false);
|
|
197
|
+
}, children: [_jsx("span", { "aria-hidden": "true", children: "\u2715" }), strings.removeBlock] }) })] })] })) : null] }));
|
|
198
|
+
}
|
|
199
|
+
/** One row of the ⠿ block popover (the v10 prototype's .bpitem). */
|
|
200
|
+
const popoverItemStyle = {
|
|
201
|
+
width: "100%",
|
|
202
|
+
height: 28,
|
|
203
|
+
display: "flex",
|
|
204
|
+
alignItems: "center",
|
|
205
|
+
gap: 10,
|
|
206
|
+
padding: "0 12px",
|
|
207
|
+
fontSize: 11,
|
|
208
|
+
color: "var(--mantine-color-text-filled)",
|
|
209
|
+
background: "transparent",
|
|
210
|
+
border: "none",
|
|
211
|
+
cursor: "pointer",
|
|
212
|
+
textAlign: "left",
|
|
213
|
+
};
|
|
214
|
+
/** The popover's divider rule (the v10 prototype's .bpdiv). */
|
|
215
|
+
const popoverDividerStyle = {
|
|
216
|
+
height: 1,
|
|
217
|
+
background: BORDER,
|
|
218
|
+
margin: "4px 12px",
|
|
219
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MainEditorLocale } from "../locales/index.js";
|
|
3
|
+
import type { EvidenceTableBlock, GeovisionEmbedBlock } from "../types.js";
|
|
4
|
+
/** Any embedded (file-preview) block the chrome wraps. */
|
|
5
|
+
export type EmbeddedBlockModel = GeovisionEmbedBlock | EvidenceTableBlock;
|
|
6
|
+
export interface EmbeddedBlockChromeProps {
|
|
7
|
+
readonly block: EmbeddedBlockModel;
|
|
8
|
+
/** Locale label for the kind tag (e.g. "GEOVISION 3D"). */
|
|
9
|
+
readonly kindLabel: string;
|
|
10
|
+
readonly locale: MainEditorLocale;
|
|
11
|
+
/** The live preview of the selected saved view (the render slot body). */
|
|
12
|
+
readonly children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Card chrome shared by the embedded file-preview blocks (D10/D11): an
|
|
16
|
+
* outlined card with a kind tag, the SRC FILE chip naming the previewed
|
|
17
|
+
* source file, the source-file variant dropdown (SavedViewDropdown), and —
|
|
18
|
+
* per the D10 addendum — the ↗ open arrow at the block's top-right BESIDE the
|
|
19
|
+
* dropdown. The embedded body keeps its simple interactive features inline
|
|
20
|
+
* and is NOT click-to-open: the only navigation out of the block is the
|
|
21
|
+
* arrow, which emits "main-editor.open-view-requested" {sourceFileRef,
|
|
22
|
+
* viewId} for the host to open the source view.
|
|
23
|
+
*
|
|
24
|
+
* Render handoff (coordination-channel contract, FINALIZED 2026-09-22): the
|
|
25
|
+
* card body is a render slot stamped with the seven-field handoff
|
|
26
|
+
* `{sourceFileRef, viewId, mode, interactionMode, locale, width, height}`
|
|
27
|
+
* (mode "static" in v1, interactionMode "rich"); the slot enforces the
|
|
28
|
+
* contracted 240px min-height and may be pointer-captured by the hosted
|
|
29
|
+
* component. The Main Editor keeps its internal item-template renders — real
|
|
30
|
+
* hosting of the published GeoVision render package happens in the separate
|
|
31
|
+
* embed-block package, which receives exactly this shape.
|
|
32
|
+
*/
|
|
33
|
+
export declare function EmbeddedBlockChrome({ block, kindLabel, locale, children }: EmbeddedBlockChromeProps): import("react").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=EmbeddedBlockChrome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbeddedBlockChrome.d.ts","sourceRoot":"","sources":["../../src/blocks/EmbeddedBlockChrome.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EAGpB,MAAM,aAAa,CAAC;AAarB,0DAA0D;AAC1D,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAE1E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BA4GnG"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Group } from "@mantine/core";
|
|
3
|
+
import { emitPrismEvent, usePrismStateValue } from "@zephytiju/prism-react";
|
|
4
|
+
import { formatMessage, stringsForLocale } from "../locales/index.js";
|
|
5
|
+
import { ACCENT, BORDER, CARD, CARD_DARK, EMBED_SLOT_MIN_HEIGHT, EMBED_SLOT_WIDTH, MONO, MUTED, } from "../tokens.js";
|
|
6
|
+
import { SavedViewDropdown } from "./SavedViewDropdown.js";
|
|
7
|
+
/**
|
|
8
|
+
* Card chrome shared by the embedded file-preview blocks (D10/D11): an
|
|
9
|
+
* outlined card with a kind tag, the SRC FILE chip naming the previewed
|
|
10
|
+
* source file, the source-file variant dropdown (SavedViewDropdown), and —
|
|
11
|
+
* per the D10 addendum — the ↗ open arrow at the block's top-right BESIDE the
|
|
12
|
+
* dropdown. The embedded body keeps its simple interactive features inline
|
|
13
|
+
* and is NOT click-to-open: the only navigation out of the block is the
|
|
14
|
+
* arrow, which emits "main-editor.open-view-requested" {sourceFileRef,
|
|
15
|
+
* viewId} for the host to open the source view.
|
|
16
|
+
*
|
|
17
|
+
* Render handoff (coordination-channel contract, FINALIZED 2026-09-22): the
|
|
18
|
+
* card body is a render slot stamped with the seven-field handoff
|
|
19
|
+
* `{sourceFileRef, viewId, mode, interactionMode, locale, width, height}`
|
|
20
|
+
* (mode "static" in v1, interactionMode "rich"); the slot enforces the
|
|
21
|
+
* contracted 240px min-height and may be pointer-captured by the hosted
|
|
22
|
+
* component. The Main Editor keeps its internal item-template renders — real
|
|
23
|
+
* hosting of the published GeoVision render package happens in the separate
|
|
24
|
+
* embed-block package, which receives exactly this shape.
|
|
25
|
+
*/
|
|
26
|
+
export function EmbeddedBlockChrome({ block, kindLabel, locale, children }) {
|
|
27
|
+
const strings = stringsForLocale(locale);
|
|
28
|
+
const selectedView = usePrismStateValue("main-editor.selected-view");
|
|
29
|
+
const viewId = selectedView?.blockId === block.id ? selectedView.viewId : block.sourceFile.defaultViewId;
|
|
30
|
+
const handoff = {
|
|
31
|
+
sourceFileRef: block.sourceFile.id,
|
|
32
|
+
viewId,
|
|
33
|
+
mode: "static",
|
|
34
|
+
interactionMode: "rich",
|
|
35
|
+
locale,
|
|
36
|
+
width: EMBED_SLOT_WIDTH,
|
|
37
|
+
height: EMBED_SLOT_MIN_HEIGHT,
|
|
38
|
+
};
|
|
39
|
+
return (_jsxs(Box, { p: 12, style: { background: CARD_DARK, border: `1px solid ${BORDER}`, borderRadius: 6 }, "data-testid": `main-editor-block-${block.id}`, children: [_jsxs(Group, { justify: "space-between", align: "flex-start", gap: 10, wrap: "nowrap", children: [_jsxs(Group, { gap: 10, align: "center", wrap: "nowrap", miw: 0, children: [_jsx(Box, { h: 22, px: 10, style: {
|
|
40
|
+
borderRadius: 11,
|
|
41
|
+
background: CARD,
|
|
42
|
+
border: `1px solid ${ACCENT}`,
|
|
43
|
+
fontFamily: MONO,
|
|
44
|
+
fontSize: 7,
|
|
45
|
+
fontWeight: 500,
|
|
46
|
+
color: ACCENT,
|
|
47
|
+
display: "inline-flex",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
letterSpacing: "0.03em",
|
|
50
|
+
flexShrink: 0,
|
|
51
|
+
}, "data-testid": `main-editor-kind-tag-${block.id}`, children: kindLabel }), _jsx("span", { style: {
|
|
52
|
+
fontFamily: MONO,
|
|
53
|
+
fontSize: 7,
|
|
54
|
+
color: MUTED,
|
|
55
|
+
letterSpacing: "0.05em",
|
|
56
|
+
whiteSpace: "nowrap",
|
|
57
|
+
}, "data-testid": `main-editor-src-file-${block.id}`, children: formatMessage(strings.srcFile, { file: block.sourceFile.name }) })] }), _jsxs(Group, { gap: 6, align: "center", wrap: "nowrap", children: [_jsx(SavedViewDropdown, { blockId: block.id, sourceFile: block.sourceFile, locale: locale }), _jsx("button", { type: "button", style: {
|
|
58
|
+
width: 24,
|
|
59
|
+
height: 22,
|
|
60
|
+
background: CARD,
|
|
61
|
+
border: `1px solid ${BORDER}`,
|
|
62
|
+
borderRadius: 5,
|
|
63
|
+
fontFamily: MONO,
|
|
64
|
+
fontSize: 11,
|
|
65
|
+
fontWeight: 500,
|
|
66
|
+
color: ACCENT,
|
|
67
|
+
display: "inline-flex",
|
|
68
|
+
alignItems: "center",
|
|
69
|
+
justifyContent: "center",
|
|
70
|
+
cursor: "pointer",
|
|
71
|
+
flexShrink: 0,
|
|
72
|
+
lineHeight: 1,
|
|
73
|
+
}, title: strings.openSourceView, "aria-label": strings.openSourceView, "data-testid": `main-editor-open-${block.id}`, onClick: (event) => {
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
const request = {
|
|
76
|
+
sourceFileRef: block.sourceFile.id,
|
|
77
|
+
viewId,
|
|
78
|
+
};
|
|
79
|
+
emitPrismEvent("main-editor.open-view-requested", request);
|
|
80
|
+
}, children: "\u2197" })] })] }), _jsx(Box, { mt: 8, style: { minHeight: EMBED_SLOT_MIN_HEIGHT }, "data-embed-handoff": JSON.stringify(handoff), "data-testid": `main-editor-embed-slot-${block.id}`, children: children })] }));
|
|
81
|
+
}
|