{"version":3,"sources":["../src/components/RichEditor/components/MathNode.tsx"],"sourcesContent":["import { Node, mergeAttributes } from '@tiptap/core';\nimport { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react';\nimport type { NodeViewProps } from '@tiptap/react';\nimport katex from 'katex';\nimport { useMemo } from 'react';\n\ninterface MathNodeViewProps {\n  readonly node: NodeViewProps['node'];\n  readonly editor: NodeViewProps['editor'];\n  readonly getPos: NodeViewProps['getPos'];\n}\n\nfunction MathNodeView({ node, editor, getPos }: MathNodeViewProps) {\n  // `node.attrs.latex` is not guaranteed to be a string: pasted/imported HTML\n  // without a `data-latex` attribute makes parseHTML return `undefined`, which\n  // bypasses the attribute's `default: ''`. Normalising here keeps both the\n  // KaTeX render and the cursor math below working on a real string.\n  const latex = typeof node.attrs.latex === 'string' ? node.attrs.latex : '';\n\n  const renderedHtml = useMemo(() => {\n    try {\n      return katex.renderToString(latex, {\n        throwOnError: false,\n        displayMode: false,\n      });\n    } catch {\n      return `<span class=\"text-error-600\">${latex}</span>`;\n    }\n  }, [latex]);\n\n  // When clicked, convert back to editable text\n  const handleClick = () => {\n    const pos = getPos();\n    // `typeof NaN === 'number'`, so an explicit finite check is required here —\n    // a NaN position would flow into setTextSelection and make ProseMirror throw\n    // \"Position NaN out of range\" (FRONTEND-BACKOFFICE-WEB-P).\n    if (typeof pos !== 'number' || !Number.isFinite(pos)) return;\n\n    editor\n      .chain()\n      .focus()\n      .deleteRange({ from: pos, to: pos + node.nodeSize })\n      .insertContent(`$${latex}$`)\n      .run();\n\n    // Move cursor to the end of the inserted text (before the last $).\n    // Clamp to the document bounds: the position is derived from `pos`, which\n    // predates the edits above, so it can land past the end of the resulting\n    // document — another way ProseMirror would throw \"out of range\".\n    const docSize = editor.state.doc.content.size;\n    const newPos = Math.min(Math.max(pos + latex.length + 1, 0), docSize);\n    editor.commands.setTextSelection(newPos);\n  };\n\n  return (\n    <NodeViewWrapper\n      as=\"span\"\n      className=\"inline-math cursor-pointer hover:bg-primary-50 rounded px-0.5 transition-colors\"\n      onClick={handleClick}\n      title=\"Clique para editar\"\n    >\n      <span\n        dangerouslySetInnerHTML={{ __html: renderedHtml }}\n        className=\"inline\"\n      />\n    </NodeViewWrapper>\n  );\n}\n\nexport const MathNode = Node.create({\n  name: 'mathInline',\n  group: 'inline',\n  inline: true,\n  atom: true,\n  selectable: true,\n\n  addAttributes() {\n    return {\n      latex: {\n        default: '',\n      },\n    };\n  },\n\n  parseHTML() {\n    return [\n      {\n        tag: 'span[data-type=\"math-inline\"]',\n        getAttrs: (dom) => {\n          // Fall back to '' instead of forwarding `undefined`: returning the\n          // attribute explicitly overrides the `default: ''` declared above, so\n          // HTML pasted without `data-latex` would otherwise produce a node\n          // whose `latex` attr is undefined.\n          return { latex: dom.dataset.latex ?? '' };\n        },\n      },\n    ];\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return [\n      'span',\n      mergeAttributes(HTMLAttributes, {\n        'data-type': 'math-inline',\n        'data-latex': node.attrs.latex,\n      }),\n    ];\n  },\n\n  addNodeView() {\n    return ReactNodeViewRenderer(MathNodeView);\n  },\n\n  // Disable default input rules\n  addInputRules() {\n    return [];\n  },\n\n  addKeyboardShortcuts() {\n    return {\n      // When space is pressed after closing $, convert to math node\n      Space: ({ editor }) => {\n        const { state } = editor;\n        const { selection } = state;\n        const { $from } = selection;\n\n        // Get text before cursor\n        const textBefore = $from.parent.textBetween(\n          Math.max(0, $from.parentOffset - 200),\n          $from.parentOffset,\n          '\\n'\n        );\n\n        // Check if there's a complete $...$ pattern right before the cursor\n        const regex = /\\$([^$]+)\\$$/;\n        const match = regex.exec(textBefore);\n        if (match?.[1]?.trim()) {\n          const latex = match[1];\n          const matchStart = $from.pos - match[0].length;\n\n          // Replace $latex$ with math node, then add a space\n          editor\n            .chain()\n            .deleteRange({ from: matchStart, to: $from.pos })\n            .insertContent([\n              { type: 'mathInline', attrs: { latex } },\n              { type: 'text', text: ' ' },\n            ])\n            .run();\n\n          return true;\n        }\n\n        return false;\n      },\n\n      // When backspace is pressed on a math node, convert it back to text for editing\n      Backspace: ({ editor }) => {\n        const { selection } = editor.state;\n        const { $from } = selection;\n\n        // Check if we're right after a math node\n        const nodeBefore = $from.nodeBefore;\n        if (nodeBefore?.type.name === 'mathInline') {\n          const pos = $from.pos - nodeBefore.nodeSize;\n          const latex = nodeBefore.attrs.latex;\n\n          // Convert back to text without the last $, so user can continue editing\n          editor\n            .chain()\n            .deleteRange({ from: pos, to: $from.pos })\n            .insertContent(`$${latex}`)\n            .run();\n\n          return true;\n        }\n\n        return false;\n      },\n    };\n  },\n});\n"],"mappings":";AAAA,SAAS,MAAM,uBAAuB;AACtC,SAAS,uBAAuB,uBAAuB;AAEvD,OAAO,WAAW;AAClB,SAAS,eAAe;AAyDlB;AAjDN,SAAS,aAAa,EAAE,MAAM,QAAQ,OAAO,GAAsB;AAKjE,QAAM,QAAQ,OAAO,KAAK,MAAM,UAAU,WAAW,KAAK,MAAM,QAAQ;AAExE,QAAM,eAAe,QAAQ,MAAM;AACjC,QAAI;AACF,aAAO,MAAM,eAAe,OAAO;AAAA,QACjC,cAAc;AAAA,QACd,aAAa;AAAA,MACf,CAAC;AAAA,IACH,QAAQ;AACN,aAAO,gCAAgC,KAAK;AAAA,IAC9C;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAGV,QAAM,cAAc,MAAM;AACxB,UAAM,MAAM,OAAO;AAInB,QAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG,EAAG;AAEtD,WACG,MAAM,EACN,MAAM,EACN,YAAY,EAAE,MAAM,KAAK,IAAI,MAAM,KAAK,SAAS,CAAC,EAClD,cAAc,IAAI,KAAK,GAAG,EAC1B,IAAI;AAMP,UAAM,UAAU,OAAO,MAAM,IAAI,QAAQ;AACzC,UAAM,SAAS,KAAK,IAAI,KAAK,IAAI,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO;AACpE,WAAO,SAAS,iBAAiB,MAAM;AAAA,EACzC;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAG;AAAA,MACH,WAAU;AAAA,MACV,SAAS;AAAA,MACT,OAAM;AAAA,MAEN;AAAA,QAAC;AAAA;AAAA,UACC,yBAAyB,EAAE,QAAQ,aAAa;AAAA,UAChD,WAAU;AAAA;AAAA,MACZ;AAAA;AAAA,EACF;AAEJ;AAEO,IAAM,WAAW,KAAK,OAAO;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EAEZ,gBAAgB;AACd,WAAO;AAAA,MACL,OAAO;AAAA,QACL,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY;AACV,WAAO;AAAA,MACL;AAAA,QACE,KAAK;AAAA,QACL,UAAU,CAAC,QAAQ;AAKjB,iBAAO,EAAE,OAAO,IAAI,QAAQ,SAAS,GAAG;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,WAAW,EAAE,MAAM,eAAe,GAAG;AACnC,WAAO;AAAA,MACL;AAAA,MACA,gBAAgB,gBAAgB;AAAA,QAC9B,aAAa;AAAA,QACb,cAAc,KAAK,MAAM;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,WAAO,sBAAsB,YAAY;AAAA,EAC3C;AAAA;AAAA,EAGA,gBAAgB;AACd,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA;AAAA,MAEL,OAAO,CAAC,EAAE,OAAO,MAAM;AACrB,cAAM,EAAE,MAAM,IAAI;AAClB,cAAM,EAAE,UAAU,IAAI;AACtB,cAAM,EAAE,MAAM,IAAI;AAGlB,cAAM,aAAa,MAAM,OAAO;AAAA,UAC9B,KAAK,IAAI,GAAG,MAAM,eAAe,GAAG;AAAA,UACpC,MAAM;AAAA,UACN;AAAA,QACF;AAGA,cAAM,QAAQ;AACd,cAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,YAAI,QAAQ,CAAC,GAAG,KAAK,GAAG;AACtB,gBAAM,QAAQ,MAAM,CAAC;AACrB,gBAAM,aAAa,MAAM,MAAM,MAAM,CAAC,EAAE;AAGxC,iBACG,MAAM,EACN,YAAY,EAAE,MAAM,YAAY,IAAI,MAAM,IAAI,CAAC,EAC/C,cAAc;AAAA,YACb,EAAE,MAAM,cAAc,OAAO,EAAE,MAAM,EAAE;AAAA,YACvC,EAAE,MAAM,QAAQ,MAAM,IAAI;AAAA,UAC5B,CAAC,EACA,IAAI;AAEP,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT;AAAA;AAAA,MAGA,WAAW,CAAC,EAAE,OAAO,MAAM;AACzB,cAAM,EAAE,UAAU,IAAI,OAAO;AAC7B,cAAM,EAAE,MAAM,IAAI;AAGlB,cAAM,aAAa,MAAM;AACzB,YAAI,YAAY,KAAK,SAAS,cAAc;AAC1C,gBAAM,MAAM,MAAM,MAAM,WAAW;AACnC,gBAAM,QAAQ,WAAW,MAAM;AAG/B,iBACG,MAAM,EACN,YAAY,EAAE,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,EACxC,cAAc,IAAI,KAAK,EAAE,EACzB,IAAI;AAEP,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}