'use client'; /** * React NodeView for the NotionEditor `mapBlock` node. A wrapper around * `LazyMapContainer` (mirrors the chat `MapBlock` renderer) seeded from the * node's serializable `MapBlockPayload` attrs: viewport from `center`/`zoom`, * markers via `MapMarker`. The heavy MapLibre bundle only loads when the node * actually mounts (the lazy container handles that). * * INTERACTIVE when the editor is editable: markers render through the Map * tool's `DraggableMarkers` inside `` and every edit (drag a pin, * add / remove a pin, switch basemap) is written back into the node attrs via * `useNodeAttrs().patch(...)`, so the change survives the ```map markdown * round-trip. READ-ONLY editors keep static, non-draggable pins. * * Payloads are validated through `MapBlockPayloadSchema` first — a corrupted * ```map JSON renders a safe `` card instead of throwing. */ import { useCallback } from 'react'; import { NodeViewWrapper, type NodeViewProps } from '@tiptap/react'; import { LazyMapContainer, MapMarker } from '../../dev/Map/lazy'; import type { MarkerData, MapStyleKey } from '../../dev/Map/lazy'; import type { MapContainerProps } from '../../dev/Map/components'; import { BlockError, MapBlockPayloadSchema, safeParseBlock } from '../../../common/blocks'; import type { MapMarkerPayload } from '../../../common/blocks'; import { useNodeAttrs } from '../../../common/tiptap'; import { MapEditLayer } from './MapEditLayer'; export function MapView(props: NodeViewProps) { const { node, editor } = props; const editable = editor.isEditable; // Single write-back seam: discrete edits (drag-end, add/remove pin, basemap) // commit immediately — they don't fire per-frame, so no debounce needed. const { patch } = useNodeAttrs>(props); const onMarkersChange = useCallback( (markers: MapMarkerPayload[]) => patch({ markers }), [patch], ); const onBasemapChange = useCallback( (key: MapStyleKey) => patch({ basemap: key }), [patch], ); // Validate the node's attrs through the shared schema BEFORE touching the // map — a hand-authored / corrupted ```map JSON renders a safe `` // card instead of throwing inside MapLibre and taking down the editor. const parsed = safeParseBlock(MapBlockPayloadSchema, node.attrs); if (!parsed.ok) { return ( ); } const payload = parsed.data; const markers = payload.markers ?? []; const initialViewport = { longitude: payload.center.lng, latitude: payload.center.lat, zoom: payload.zoom ?? 11, }; // Build optional map props as ONE typed literal (same TS2769-avoidance // reasoning as the chat MapBlock renderer) before spreading. The basemap // switcher write-back is only wired when the editor is editable. const mapOptions: Partial = { ...(payload.basemap ? { mapStyle: payload.basemap } : {}), ...(payload.terrain ? { terrain: true } : {}), ...(editable ? { onBasemapChange } : {}), }; return (
{editable ? ( // Interactive: draggable pins + add/remove affordances, all writing // back through `onMarkersChange` → `patch({ markers })`. ) : ( // Read-only: static, non-draggable pins (original behaviour). markers.map((m) => { const marker: MarkerData = { id: m.id, longitude: m.lng, latitude: m.lat, data: m.label ? { label: m.label } : undefined, }; return ( ); }) )}
); }