{"version":3,"file":"core-CbSTyrV4.mjs","names":[],"sources":["../src/core/serializer/default-base-template.tsx","../src/core/serializer/email-mark.ts","../src/core/serializer/compose-react-email.tsx","../src/core/is-document-visually-empty.ts"],"sourcesContent":["import type * as React from 'react';\nimport { Body, Head, Html, Preview } from 'react-email';\n\ntype BaseTemplateProps = {\n  children: React.ReactNode;\n  previewText?: string;\n};\n\nexport function DefaultBaseTemplate({\n  children,\n  previewText,\n}: BaseTemplateProps) {\n  return (\n    <Html>\n      <Head>\n        <meta content=\"width=device-width\" name=\"viewport\" />\n        <meta content=\"IE=edge\" httpEquiv=\"X-UA-Compatible\" />\n        <meta name=\"x-apple-disable-message-reformatting\" />\n        <meta\n          content=\"telephone=no,address=no,email=no,date=no,url=no\"\n          name=\"format-detection\"\n        />\n      </Head>\n      {previewText && previewText !== '' && <Preview>{previewText}</Preview>}\n\n      <Body>{children}</Body>\n    </Html>\n  );\n}\n","import {\n  type Editor,\n  type JSONContent,\n  Mark,\n  type MarkConfig,\n  type MarkType,\n} from '@tiptap/core';\n\nexport type SerializedMark = NonNullable<JSONContent['marks']>[number];\n\nexport type MarkRendererComponent = (props: {\n  mark: SerializedMark;\n  node: JSONContent;\n  style: React.CSSProperties;\n  children?: React.ReactNode;\n\n  extension: EmailMark<any, any>;\n}) => React.ReactNode;\n\nexport interface EmailMarkConfig<Options, Storage>\n  extends MarkConfig<Options, Storage> {\n  renderToReactEmail: MarkRendererComponent;\n}\n\ntype ConfigParameter<Options, Storage> = Partial<\n  Omit<EmailMarkConfig<Options, Storage>, 'renderToReactEmail'>\n> &\n  Pick<EmailMarkConfig<Options, Storage>, 'renderToReactEmail'> &\n  ThisType<{\n    name: string;\n    options: Options;\n    storage: Storage;\n    editor: Editor;\n    type: MarkType;\n    parent: (...args: any[]) => any;\n  }>;\n\nexport class EmailMark<\n  Options = Record<string, never>,\n  Storage = Record<string, never>,\n> extends Mark<Options, Storage> {\n  declare config: EmailMarkConfig<Options, Storage>;\n\n  // biome-ignore lint/complexity/noUselessConstructor: This is only meant to change the types for config, hence why we keep it\n  constructor(config: ConfigParameter<Options, Storage>) {\n    super(config);\n  }\n\n  /**\n   * Create a new Mark instance\n   * @param config - Mark configuration object or a function that returns a configuration object\n   */\n  static create<O = Record<string, never>, S = Record<string, never>>(\n    config: ConfigParameter<O, S> | (() => ConfigParameter<O, S>),\n  ) {\n    const resolvedConfig = typeof config === 'function' ? config() : config;\n    return new EmailMark<O, S>(resolvedConfig);\n  }\n\n  static from<O, S>(\n    mark: Mark<O, S>,\n    renderToReactEmail: MarkRendererComponent,\n  ): EmailMark<O, S> {\n    const customMark = EmailMark.create({} as ConfigParameter<O, S>);\n    // This only makes a shallow copy, so if there's nested objects here mutating things will be dangerous\n    Object.assign(customMark, { ...mark });\n    customMark.config = { ...mark.config, renderToReactEmail };\n    return customMark;\n  }\n\n  // Subclass return types for configure/extend; safe at runtime. TipTap's Mark typings cause TS2416 when returning EmailMark.\n  // @ts-expect-error - EmailMark is a valid Mark subclass; base typings don't support subclass return types\n  configure(options?: Partial<Options>) {\n    return super.configure(options) as EmailMark<Options, Storage>;\n  }\n\n  // @ts-expect-error - same as configure: extend returns EmailMark for chaining; base typings are incompatible\n  extend<\n    ExtendedOptions = Options,\n    ExtendedStorage = Storage,\n    ExtendedConfig extends MarkConfig<\n      ExtendedOptions,\n      ExtendedStorage\n    > = EmailMarkConfig<ExtendedOptions, ExtendedStorage>,\n  >(\n    extendedConfig?:\n      | (() => Partial<ExtendedConfig>)\n      | (Partial<ExtendedConfig> &\n          ThisType<{\n            name: string;\n            options: ExtendedOptions;\n            storage: ExtendedStorage;\n            editor: Editor;\n            type: MarkType;\n          }>),\n  ): EmailMark<ExtendedOptions, ExtendedStorage> {\n    const resolvedConfig =\n      typeof extendedConfig === 'function' ? extendedConfig() : extendedConfig;\n    return super.extend(resolvedConfig) as EmailMark<\n      ExtendedOptions,\n      ExtendedStorage\n    >;\n  }\n}\n","import type { Editor, JSONContent } from '@tiptap/core';\nimport type { MarkType, Schema } from '@tiptap/pm/model';\nimport { pretty, render, toPlainText } from 'react-email';\nimport { inlineCssToJs } from '../../utils/styles';\nimport { DefaultBaseTemplate } from './default-base-template';\nimport { EmailMark } from './email-mark';\nimport { EmailNode } from './email-node';\nimport type { SerializerPlugin } from './serializer-plugin';\n\nconst NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set([\n  'bulletList',\n  'orderedList',\n]);\n\n/**\n * ProseMirror assigns each mark type a numeric `rank` at schema compile time; the public\n * `MarkType` typings omit it, but it exists at runtime (see prosemirror-model `MarkType.compile`).\n */\ntype MarkTypeWithRank = MarkType & { rank: number };\n\nfunction getMarkRank(schema: Schema, markName: string): number {\n  const markType = schema.marks[markName] as MarkTypeWithRank | undefined;\n  return markType?.rank ?? Number.MAX_SAFE_INTEGER;\n}\n\n/** Sort marks by schema rank (Tiptap extension priority → ProseMirror order). */\nfunction sortMarksBySchema(\n  marks: NonNullable<JSONContent['marks']>,\n  schema: Schema,\n): NonNullable<JSONContent['marks']> {\n  return [...marks].sort(\n    (a, b) => getMarkRank(schema, b.type) - getMarkRank(schema, a.type),\n  );\n}\n\ninterface ComposeReactEmailResult {\n  html: string;\n  text: string;\n}\n\nexport const composeReactEmail = async ({\n  editor,\n  preview,\n}: {\n  editor: Editor;\n  preview?: string;\n}): Promise<ComposeReactEmailResult> => {\n  const data = editor.getJSON();\n  const extensions = editor.extensionManager.extensions;\n\n  const serializerPlugin = extensions\n    .map(\n      (ext) =>\n        (ext as { options?: { serializerPlugin?: SerializerPlugin } }).options\n          ?.serializerPlugin,\n    )\n    .filter((p) => Boolean(p))\n    .at(-1);\n\n  const typeToExtensionMap = Object.fromEntries(\n    extensions.map((extension) => [extension.name, extension]),\n  );\n\n  function parseContent(content: JSONContent[] | undefined, depth = 0) {\n    if (!content) {\n      return;\n    }\n\n    return content.map((node: JSONContent, index: number) => {\n      const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};\n\n      const inlineStyles = inlineCssToJs(node.attrs?.style);\n\n      if (!node.type) {\n        return null;\n      }\n\n      const emailNode = typeToExtensionMap[node.type];\n      if (!emailNode || !(emailNode instanceof EmailNode)) {\n        return null;\n      }\n\n      const NodeComponent = emailNode.config.renderToReactEmail;\n      const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type)\n        ? depth + 1\n        : depth;\n\n      let renderedNode: React.ReactNode = node.text ? (\n        node.text\n      ) : (\n        <NodeComponent\n          key={index}\n          node={\n            node.type === 'table' && inlineStyles.width && !node.attrs?.width\n              ? {\n                  ...node,\n                  attrs: { ...node.attrs, width: inlineStyles.width },\n                }\n              : node\n          }\n          style={style}\n          extension={emailNode}\n        >\n          {parseContent(node.content, childDepth)}\n        </NodeComponent>\n      );\n      if (node.marks) {\n        for (const mark of sortMarksBySchema(node.marks, editor.schema)) {\n          const emailMark = typeToExtensionMap[mark.type];\n          if (emailMark instanceof EmailMark) {\n            const MarkComponent = emailMark.config.renderToReactEmail;\n            const markStyle =\n              serializerPlugin?.getNodeStyles(\n                {\n                  type: mark.type,\n                  attrs: mark.attrs ?? {},\n                },\n                depth,\n                editor,\n              ) ?? {};\n            renderedNode = (\n              <MarkComponent\n                mark={mark}\n                node={node}\n                style={markStyle}\n                extension={emailMark}\n              >\n                {renderedNode}\n              </MarkComponent>\n            );\n          }\n        }\n      }\n\n      return renderedNode;\n    });\n  }\n\n  const BaseTemplate = serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate;\n\n  const parsedContent = parseContent(data.content);\n  const unformattedHtml = await render(\n    <BaseTemplate previewText={preview} editor={editor}>\n      {parsedContent}\n    </BaseTemplate>,\n  );\n\n  const [prettyHtml, text] = await Promise.all([\n    pretty(unformattedHtml),\n    toPlainText(unformattedHtml),\n  ]);\n\n  return { html: prettyHtml, text };\n};\n","import type { Node } from '@tiptap/pm/model';\n\nexport function isDocumentVisuallyEmpty(doc: Node): boolean {\n  let nonGlobalNodeCount = 0;\n  let firstNonGlobalNode: Node | null = null;\n\n  for (let index = 0; index < doc.childCount; index += 1) {\n    const node = doc.child(index);\n\n    if (node.type.name === 'globalContent') {\n      continue;\n    }\n\n    nonGlobalNodeCount += 1;\n\n    if (firstNonGlobalNode === null) {\n      firstNonGlobalNode = node;\n    }\n  }\n\n  if (nonGlobalNodeCount === 0) {\n    return true;\n  }\n\n  if (nonGlobalNodeCount !== 1) {\n    return false;\n  }\n\n  if (firstNonGlobalNode!.type.name === 'container') {\n    return hasOnlyEmptyParagraph(firstNonGlobalNode!);\n  }\n\n  return isEmptyParagraph(firstNonGlobalNode!);\n}\n\nfunction hasOnlyEmptyParagraph(node: Node): boolean {\n  if (node.childCount === 0) {\n    return true;\n  }\n\n  if (node.childCount !== 1) {\n    return false;\n  }\n\n  return isEmptyParagraph(node.child(0));\n}\n\nfunction isEmptyParagraph(node: Node): boolean {\n  return node.type.name === 'paragraph' && node.content.size === 0;\n}\n"],"mappings":";;;;;AAQA,SAAgB,oBAAoB,EAClC,UACA,eACoB;AACpB,QACE,qBAAC,MAAD,EAAA,UAAA;EACE,qBAAC,MAAD,EAAA,UAAA;GACE,oBAAC,QAAD;IAAM,SAAQ;IAAqB,MAAK;IAAa,CAAA;GACrD,oBAAC,QAAD;IAAM,SAAQ;IAAU,WAAU;IAAoB,CAAA;GACtD,oBAAC,QAAD,EAAM,MAAK,wCAAyC,CAAA;GACpD,oBAAC,QAAD;IACE,SAAQ;IACR,MAAK;IACL,CAAA;GACG,EAAA,CAAA;EACN,eAAe,gBAAgB,MAAM,oBAAC,SAAD,EAAA,UAAU,aAAsB,CAAA;EAEtE,oBAAC,MAAD,EAAO,UAAgB,CAAA;EAClB,EAAA,CAAA;;;;ACWX,IAAa,YAAb,MAAa,kBAGH,KAAuB;CAI/B,YAAY,QAA2C;AACrD,QAAM,OAAO;;;;;;CAOf,OAAO,OACL,QACA;AAEA,SAAO,IAAI,UADY,OAAO,WAAW,aAAa,QAAQ,GAAG,OACvB;;CAG5C,OAAO,KACL,MACA,oBACiB;EACjB,MAAM,aAAa,UAAU,OAAO,EAAE,CAA0B;AAEhE,SAAO,OAAO,YAAY,EAAE,GAAG,MAAM,CAAC;AACtC,aAAW,SAAS;GAAE,GAAG,KAAK;GAAQ;GAAoB;AAC1D,SAAO;;CAKT,UAAU,SAA4B;AACpC,SAAO,MAAM,UAAU,QAAQ;;CAIjC,OAQE,gBAU6C;EAC7C,MAAM,iBACJ,OAAO,mBAAmB,aAAa,gBAAgB,GAAG;AAC5D,SAAO,MAAM,OAAO,eAAe;;;;;ACzFvC,MAAM,qCAAqC,IAAI,IAAI,CACjD,cACA,cACD,CAAC;AAQF,SAAS,YAAY,QAAgB,UAA0B;AAE7D,QADiB,OAAO,MAAM,WACb,QAAQ,OAAO;;;AAIlC,SAAS,kBACP,OACA,QACmC;AACnC,QAAO,CAAC,GAAG,MAAM,CAAC,MACf,GAAG,MAAM,YAAY,QAAQ,EAAE,KAAK,GAAG,YAAY,QAAQ,EAAE,KAAK,CACpE;;AAQH,MAAa,oBAAoB,OAAO,EACtC,QACA,cAIsC;CACtC,MAAM,OAAO,OAAO,SAAS;CAC7B,MAAM,aAAa,OAAO,iBAAiB;CAE3C,MAAM,mBAAmB,WACtB,KACE,QACE,IAA8D,SAC3D,iBACP,CACA,QAAQ,MAAM,QAAQ,EAAE,CAAC,CACzB,GAAG,GAAG;CAET,MAAM,qBAAqB,OAAO,YAChC,WAAW,KAAK,cAAc,CAAC,UAAU,MAAM,UAAU,CAAC,CAC3D;CAED,SAAS,aAAa,SAAoC,QAAQ,GAAG;AACnE,MAAI,CAAC,QACH;AAGF,SAAO,QAAQ,KAAK,MAAmB,UAAkB;GACvD,MAAM,QAAQ,kBAAkB,cAAc,MAAM,OAAO,OAAO,IAAI,EAAE;GAExE,MAAM,eAAe,cAAc,KAAK,OAAO,MAAM;AAErD,OAAI,CAAC,KAAK,KACR,QAAO;GAGT,MAAM,YAAY,mBAAmB,KAAK;AAC1C,OAAI,CAAC,aAAa,EAAE,qBAAqB,WACvC,QAAO;GAGT,MAAM,gBAAgB,UAAU,OAAO;GACvC,MAAM,aAAa,mCAAmC,IAAI,KAAK,KAAK,GAChE,QAAQ,IACR;GAEJ,IAAI,eAAgC,KAAK,OACvC,KAAK,OAEL,oBAAC,eAAD;IAEE,MACE,KAAK,SAAS,WAAW,aAAa,SAAS,CAAC,KAAK,OAAO,QACxD;KACE,GAAG;KACH,OAAO;MAAE,GAAG,KAAK;MAAO,OAAO,aAAa;MAAO;KACpD,GACD;IAEC;IACP,WAAW;cAEV,aAAa,KAAK,SAAS,WAAW;IACzB,EAbT,MAaS;AAElB,OAAI,KAAK,MACP,MAAK,MAAM,QAAQ,kBAAkB,KAAK,OAAO,OAAO,OAAO,EAAE;IAC/D,MAAM,YAAY,mBAAmB,KAAK;AAC1C,QAAI,qBAAqB,WAAW;KAClC,MAAM,gBAAgB,UAAU,OAAO;AAUvC,oBACE,oBAAC,eAAD;MACQ;MACA;MACN,OAZF,kBAAkB,cAChB;OACE,MAAM,KAAK;OACX,OAAO,KAAK,SAAS,EAAE;OACxB,EACD,OACA,OACD,IAAI,EAAE;MAML,WAAW;gBAEV;MACa,CAAA;;;AAMxB,UAAO;IACP;;CAMJ,MAAM,kBAAkB,MAAM,OAC5B,oBAJmB,kBAAkB,gBAAgB,qBAIrD;EAAc,aAAa;EAAiB;YAFxB,aAAa,KAAK,QAAQ;EAI/B,CAAA,CAChB;CAED,MAAM,CAAC,YAAY,QAAQ,MAAM,QAAQ,IAAI,CAC3C,OAAO,gBAAgB,EACvB,YAAY,gBAAgB,CAC7B,CAAC;AAEF,QAAO;EAAE,MAAM;EAAY;EAAM;;;;ACtJnC,SAAgB,wBAAwB,KAAoB;CAC1D,IAAI,qBAAqB;CACzB,IAAI,qBAAkC;AAEtC,MAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,YAAY,SAAS,GAAG;EACtD,MAAM,OAAO,IAAI,MAAM,MAAM;AAE7B,MAAI,KAAK,KAAK,SAAS,gBACrB;AAGF,wBAAsB;AAEtB,MAAI,uBAAuB,KACzB,sBAAqB;;AAIzB,KAAI,uBAAuB,EACzB,QAAO;AAGT,KAAI,uBAAuB,EACzB,QAAO;AAGT,KAAI,mBAAoB,KAAK,SAAS,YACpC,QAAO,sBAAsB,mBAAoB;AAGnD,QAAO,iBAAiB,mBAAoB;;AAG9C,SAAS,sBAAsB,MAAqB;AAClD,KAAI,KAAK,eAAe,EACtB,QAAO;AAGT,KAAI,KAAK,eAAe,EACtB,QAAO;AAGT,QAAO,iBAAiB,KAAK,MAAM,EAAE,CAAC;;AAGxC,SAAS,iBAAiB,MAAqB;AAC7C,QAAO,KAAK,KAAK,SAAS,eAAe,KAAK,QAAQ,SAAS"}