EventNode.tsx2.2 KBView on GitHub
'use client';

import { Node, mergeAttributes } from '@tiptap/core';
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react';
import type { NodeViewProps } from '@tiptap/react';
import { EventChipContent } from '@/modules/documents/mention/EventChip';

/**
 * Inline atom chip representing an event reference inserted via `{{` mention.
 * Stores `eventId`, `eventType`, and a denormalized `title` snapshot so the
 * chip renders without an extra fetch on first paint. The title is a snapshot
 * — re-renaming the underlying event won't update old chips.
 *
 * The chip itself is `EventChipContent`, shared verbatim with the table grid — it renders
 * AS the `NodeViewWrapper` so the DOM PM sees is unchanged by the extraction.
 */
function EventNodeView({ node }: NodeViewProps) {
  return (
    <EventChipContent
      eventType={node.attrs.eventType as string | null}
      title={node.attrs.title as string | null}
      container={NodeViewWrapper}
      containerProps={{ as: 'span', 'data-event-node': '', contentEditable: false }}
    />
  );
}

export const EventNode = Node.create({
  name: 'eventNode',
  group: 'inline',
  inline: true,
  atom: true,
  selectable: true,
  draggable: false,

  addAttributes() {
    return {
      eventId: {
        default: null as string | null,
        parseHTML: (element) => element.getAttribute('data-event-id'),
        renderHTML: (attributes) =>
          attributes.eventId ? { 'data-event-id': attributes.eventId } : {},
      },
      eventType: {
        default: null as string | null,
        parseHTML: (element) => element.getAttribute('data-event-type'),
        renderHTML: (attributes) =>
          attributes.eventType ? { 'data-event-type': attributes.eventType } : {},
      },
      title: {
        default: null as string | null,
        parseHTML: (element) => element.getAttribute('data-event-title'),
        renderHTML: (attributes) =>
          attributes.title ? { 'data-event-title': attributes.title } : {},
      },
    };
  },

  parseHTML() {
    return [{ tag: 'span[data-event-node]' }];
  },

  renderHTML({ HTMLAttributes }) {
    return ['span', mergeAttributes({ 'data-event-node': '' }, HTMLAttributes)];
  },

  addNodeView() {
    return ReactNodeViewRenderer(EventNodeView);
  },
});