{"version":3,"sources":["../src/index.ts","../src/hyperMultimediaKit.ts","../src/extensions/mediaResizeControls.ts","../src/utils/media-resize-controls.ts","../src/toolbar/contextRegistry.ts","../../../packages/floating-tooltip/src/tooltip.ts","../src/kitStorage.ts","../../../packages/floating-popover/src/controller.ts","../../../packages/floating-popover/src/createPopover.ts","../src/utils/icons.ts","../src/utils/media-placement.ts","../src/toolbar/resolveIcon.ts","../src/toolbar/menu.ts","../src/nodes/x/embedOptions.ts","../src/extensions/decorationHelpers.ts","../src/utils/utils.ts","../src/utils/media-node-attrs.ts","../src/toolbar/handlers.ts","../src/utils/media-target.ts","../src/utils/fitImageDimensions.ts","../src/utils/embedKit.ts","../src/nodes/loom/embedOptions.ts","../src/nodes/loom/helper.ts","../src/nodes/soundcloud/helper.ts","../src/utils/layoutStyle.ts","../src/loading/syncLayout.ts","../src/nodes/spotify/embedOptions.ts","../src/nodes/spotify/helper.ts","../src/nodes/vimeo/embedOptions.ts","../src/nodes/vimeo/helper.ts","../src/utils/logger.ts","../src/utils/sanitizeHtml.ts","../src/nodes/x/helper.ts","../src/nodes/youtube/embedOptions.ts","../src/nodes/youtube/helper.ts","../src/utils/mediaUrl.ts","../src/toolbar/replaceUrl.ts","../src/toolbar/actions.ts","../src/toolbar/compose.ts","../src/toolbar/registry.ts","../src/toolbar/renderToolbar.ts","../src/toolbar/createMediaToolbar.ts","../src/toolbar/mount.ts","../src/extensions/mediaResizeGripper.ts","../src/extensions/decoration/index.ts","../src/nodes/soundcloud/embedOptions.ts","../src/extensions/decoration/utils.ts","../src/extensions/decoration/gripperDrag.ts","../src/extensions/decoration/media-resize-gripper.ts","../src/nodes/audio/audio.ts","../src/caption.ts","../src/loading/defaultShell.ts","../src/loading/attach.ts","../src/markdown/typedMediaMarkdown.ts","../src/utils/htmlMediaNode.ts","../src/utils/ignoreNodeViewMutation.ts","../src/nodes/audio/helper.ts","../src/nodes/image/image.ts","../src/nodes/image/helper.ts","../src/nodes/image/nodeView.ts","../src/nodes/image/plugin.ts","../src/nodes/loom/loom.ts","../src/utils/iframeEmbedNode.ts","../src/nodes/soundcloud/soundcloud.ts","../src/nodes/spotify/spotify.ts","../src/nodes/video/video.ts","../src/nodes/video/helper.ts","../src/nodes/vimeo/vimeo.ts","../src/nodes/x/x.ts","../src/nodes/youtube/youtube.ts","../src/utils/detectMediaType.ts","../src/utils/isMediaUrl.ts"],"sourcesContent":["// Re-exported so the `media` command augmentation in surface.ts lands in the\n// bundled .d.ts (it is the single declaration point for every node's commands).\nexport type { MediaPublicCommands } from './commands/surface'\nexport * from './hyperMultimediaKit'\nexport {\n  createDefaultMediaLoadingShell,\n  type MediaLoadingBindLoadOptions,\n  type MediaLoadingController,\n  type MediaLoadingKind,\n  type MediaLoadingShellContext,\n  type MediaLoadingShellFactory,\n  type MediaLoadingShellOption,\n  type MediaLoadingShellWrapOptions,\n  wrapMediaWithLoadingShell\n} from './loading'\nexport type { SetAudioOptions } from './nodes/audio/audio'\nexport { isImageUrl } from './nodes/image/helper'\nexport type { SetImageOptions, UpdateImageDimensionsParams } from './nodes/image/types'\nexport { isValidLoomUrl } from './nodes/loom/helper'\nexport type { SetLoomOptions } from './nodes/loom/loom'\nexport { isValidSoundCloudUrl } from './nodes/soundcloud/helper'\nexport type { SetSoundCloudOptions } from './nodes/soundcloud/soundcloud'\nexport {\n  isValidSpotifyUrl,\n  parseSpotifyEntity,\n  SPOTIFY_ENTITY_TYPES,\n  type SpotifyEntityType,\n  type SpotifyTheme\n} from './nodes/spotify/helper'\nexport type { SetSpotifyOptions } from './nodes/spotify/spotify'\nexport type { SetVideoOptions } from './nodes/video/video'\nexport { isValidVimeoUrl } from './nodes/vimeo/helper'\nexport type { SetVimeoOptions } from './nodes/vimeo/vimeo'\nexport {\n  buildXOEmbedParams,\n  resolveXEmbedSizeId,\n  X_EMBED_DEFAULT_MAXWIDTH,\n  X_EMBED_SIZE_OPTIONS,\n  X_EMBED_THEME_OPTIONS,\n  type XEmbedSizeId,\n  type XEmbedTheme\n} from './nodes/x/embedOptions'\nexport { isValidXUrl } from './nodes/x/helper'\nexport type { AddXOptions } from './nodes/x/x'\nexport { isValidYoutubeUrl, parseYoutubeVideoId } from './nodes/youtube/helper'\nexport type { SetYoutubeVideoOptions } from './nodes/youtube/youtube'\nexport {\n  attachTooltip,\n  canViewOriginal,\n  closeMediaToolbar,\n  closeToolbarPopover,\n  composeMediaActions,\n  copyMediaNode,\n  createMediaToolbar,\n  createReplaceUrlPopover,\n  downloadMedia,\n  hideTooltip,\n  isDownloadable,\n  layoutMediaActions,\n  type MediaAction,\n  type MediaActionAnchor,\n  type MediaActionContext,\n  type MediaActionList,\n  type MediaActionPlacement,\n  type MediaActionsBuilder,\n  type MediaActionsResolver,\n  type MediaToolbarFactory,\n  type MediaToolbarIconContext,\n  type MediaToolbarIconKey,\n  type MediaToolbarIconsResolver,\n  type MediaToolbarLayout,\n  type MediaToolbarOptions,\n  openMediaPopover,\n  openMediaToolbar,\n  openReplaceUrlPopover,\n  openToolbarPopover,\n  removeMediaNode,\n  type ReplaceUrlPopoverFactory,\n  type ReplaceUrlPopoverOptions,\n  resolveMediaActions,\n  viewOriginalMedia\n} from './toolbar'\nexport { detectMediaType, type MediaNodeType } from './utils/detectMediaType'\nexport {\n  fitDimensionsToBounds,\n  fitLayoutToEditorColumn,\n  getEditorContentWidth\n} from './utils/fitImageDimensions'\nexport { isMediaUrl } from './utils/isMediaUrl'\nexport { applyNodeAttributes } from './utils/media-node-attrs'\nexport {\n  getCurrentMediaPlacement,\n  getMediaPlacementAttrs,\n  MEDIA_MARGIN_OPTIONS,\n  MEDIA_PLACEMENT_OPTIONS,\n  type MediaPlacementId\n} from './utils/media-placement'\nexport { resolveMediaNodePos } from './utils/media-resize-controls'\nexport { isAudioUrl, isVideoUrl } from './utils/mediaUrl'\n","import { Extension } from '@tiptap/core'\n\nimport { MediaResizeControls } from './extensions/mediaResizeControls'\nimport { MediaResizeGripper } from './extensions/mediaResizeGripper'\nimport { HYPER_MULTIMEDIA_KIT_EXTENSION_NAME } from './kitStorage'\nimport type { MediaLoadingShellOption } from './loading'\nimport { Audio, AudioOptions } from './nodes/audio/audio'\nimport { Image } from './nodes/image/image'\nimport type { ImageOptions } from './nodes/image/types'\nimport { Loom, LoomOptions } from './nodes/loom/loom'\nimport { SoundCloud, SoundCloudOptions } from './nodes/soundcloud/soundcloud'\nimport { Spotify, SpotifyOptions } from './nodes/spotify/spotify'\nimport { Video, VideoOptions } from './nodes/video/video'\nimport { Vimeo, VimeoOptions } from './nodes/vimeo/vimeo'\nimport { X, XOptions } from './nodes/x/x'\nimport { Youtube, YoutubeOptions } from './nodes/youtube/youtube'\nimport type { ReplaceUrlPopoverFactory } from './toolbar/replaceUrl'\nimport type { MediaToolbarIconsResolver } from './toolbar/resolveIcon'\nimport type { MediaActionContext, MediaActionsResolver, MediaToolbarFactory } from './toolbar/types'\n\nexport interface HyperMultimediaKitOptions {\n  Image: Partial<ImageOptions & { resizeGripper?: boolean }> | true | false\n  Audio: Partial<AudioOptions & { resizeGripper?: boolean }> | true | false\n  Video: Partial<VideoOptions & { resizeGripper?: boolean }> | true | false\n  Youtube: Partial<YoutubeOptions & { resizeGripper?: boolean }> | true | false\n  Vimeo: Partial<VimeoOptions & { resizeGripper?: boolean }> | true | false\n  SoundCloud: Partial<SoundCloudOptions & { resizeGripper?: boolean }> | true | false\n  Spotify: Partial<SpotifyOptions & { resizeGripper?: boolean }> | true | false\n  Loom: Partial<LoomOptions & { resizeGripper?: boolean }> | true | false\n  X: Partial<XOptions> | true | false\n  /**\n   * Host-agnostic toolbar slot. Return an element for the desktop floating\n   * toolbar or `null` to opt out so the host renders its own surface (mobile\n   * sheet). Omit to use the built-in `createMediaToolbar`.\n   */\n  mediaToolbar?: MediaToolbarFactory\n  /**\n   * Loading overlay while remote media / embeds resolve. `true` = built-in\n   * shimmer shell; `false` = off; pass a factory to replace the overlay UI only.\n   */\n  loadingShell?: MediaLoadingShellOption\n  /** Customize/extend/reorder the built-in toolbar actions per node. */\n  mediaActions?: MediaActionsResolver\n  /**\n   * Swap toolbar/menu SVG markup by icon key (`caption`, `more`, `align:center`,\n   * custom action ids) without replacing toolbar factories or submenus.\n   */\n  mediaToolbarIcons?: MediaToolbarIconsResolver\n  /**\n   * Replace-URL popover slot. Return the popover content for the node-anchored\n   * dialog, or `null` to opt out so the host renders its own surface (mobile\n   * sheet). Omit to use the built-in URL editor.\n   */\n  replaceUrlPopover?: ReplaceUrlPopoverFactory\n  /** Return true for host-uploaded assets so \"View original\" is hidden (image/video/audio). */\n  isUploadedMedia?: (ctx: MediaActionContext) => boolean\n}\n\ntype MediaExtension =\n  | typeof Image\n  | typeof Video\n  | typeof Audio\n  | typeof Youtube\n  | typeof Vimeo\n  | typeof SoundCloud\n  | typeof Spotify\n  | typeof Loom\n  | typeof X\n\nexport const HyperMultimediaKit = Extension.create<HyperMultimediaKitOptions>({\n  name: HYPER_MULTIMEDIA_KIT_EXTENSION_NAME,\n\n  // Expose the host-agnostic toolbar factory on per-editor storage so\n  // `openMediaToolbar` (called from every node-view) can resolve it without\n  // threading the option through each node. A host wires a mobile-sheet\n  // factory here; absent, the built-in desktop toolbar is used.\n  addStorage() {\n    return {\n      mediaToolbar: this.options.mediaToolbar,\n      loadingShell: this.options.loadingShell ?? true,\n      mediaActions: this.options.mediaActions,\n      mediaToolbarIcons: this.options.mediaToolbarIcons,\n      replaceUrlPopover: this.options.replaceUrlPopover,\n      isUploadedMedia: this.options.isUploadedMedia\n    }\n  },\n\n  addExtensions() {\n    const extensions = []\n    const resizableMedia: string[] = []\n    const mediaNodeNames: string[] = []\n\n    const addMediaExtension = (media: MediaExtension, mediaOptions: any) => {\n      if (mediaOptions !== false) {\n        mediaNodeNames.push(media.name)\n        extensions.push(media.configure(mediaOptions))\n\n        // X embeds use oEmbed maxwidth presets in the toolbar, not drag handles.\n        if (mediaOptions?.resizeGripper !== false && media.name !== 'x') {\n          resizableMedia.push(media.name)\n        }\n      }\n    }\n\n    addMediaExtension(Image, this.options.Image)\n    addMediaExtension(Video, this.options.Video)\n    addMediaExtension(Audio, this.options.Audio)\n    addMediaExtension(Youtube, this.options.Youtube)\n    addMediaExtension(Vimeo, this.options.Vimeo)\n    addMediaExtension(SoundCloud, this.options.SoundCloud)\n    addMediaExtension(Spotify, this.options.Spotify)\n    addMediaExtension(Loom, this.options.Loom)\n    addMediaExtension(X, this.options.X)\n\n    if (mediaNodeNames.length > 0) {\n      if (resizableMedia.length > 0) {\n        extensions.push(\n          MediaResizeGripper.configure({\n            acceptedNodes: resizableMedia\n          })\n        )\n      }\n      extensions.push(\n        MediaResizeControls.configure({\n          trackedNodes: mediaNodeNames\n        })\n      )\n    }\n\n    return extensions\n  }\n})\n","import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport {\n  handleMediaClick,\n  handleMediaHover,\n  handleMediaUnhover,\n  hideMediaResizeControls,\n  remapActiveNodePos,\n  syncControlsToDoc\n} from '../utils/media-resize-controls'\nimport { hasFinePointer } from '../utils/media-target'\nimport { shouldHideResizeGripper, transactionAffectsTrackedNodes } from './decorationHelpers'\n\nexport interface MediaResizeControlsOptions {\n  /** Media node types whose add/remove should tear down hover controls (includes X). */\n  trackedNodes: string[]\n}\n\nexport const MediaResizeControls = Extension.create<MediaResizeControlsOptions>({\n  name: 'MediaResizeControls',\n\n  addOptions() {\n    return {\n      trackedNodes: ['image']\n    }\n  },\n\n  addProseMirrorPlugins() {\n    const editor = this.editor\n    const { trackedNodes } = this.options\n\n    return [\n      new Plugin({\n        key: new PluginKey('MediaResizeControls'),\n        view: () => ({\n          // PM calls this on every state update; a selection-only change moves no\n          // media, so skip the reposition + editor-wide orphan scan on keystrokes.\n          update: (view, prevState) => {\n            if (view.state.doc !== prevState.doc) syncControlsToDoc(editor)\n          },\n          destroy: () => hideMediaResizeControls(editor)\n        }),\n        appendTransaction(transactions) {\n          const shouldHide = transactions.some(\n            (tr) => shouldHideResizeGripper(tr) || transactionAffectsTrackedNodes(tr, trackedNodes)\n          )\n          if (shouldHide) {\n            hideMediaResizeControls(editor)\n          } else {\n            remapActiveNodePos(editor, transactions)\n          }\n          return null\n        },\n        props: {\n          handleDOMEvents: {\n            mouseover: (view, event) =>\n              view.editable ? handleMediaHover(editor, event, hasFinePointer()) : false,\n            mouseout: (view, event) =>\n              view.editable ? handleMediaUnhover(editor, event, hasFinePointer()) : false,\n            // Touch taps synthesize `click`; finePointer gates hover vs click-to-lock downstream.\n            click: (view, event) => (view.editable ? handleMediaClick(editor, event) : false)\n          }\n        }\n      })\n    ]\n  }\n})\n","import type { Editor } from '@tiptap/core'\nimport { TextSelection, type Transaction } from '@tiptap/pm/state'\n\nimport { updateToolbarContextNodePos } from '../toolbar/contextRegistry'\nimport { closeMediaToolbar, openMediaToolbar } from '../toolbar/mount'\nimport {\n  findGripperForMedia,\n  findMediaTarget,\n  getMediaNodeType,\n  hasFinePointer,\n  isInsideMediaControlsUI,\n  isInteractiveEmbed,\n  resolveMediaFromGripper\n} from './media-target'\n\n/** Grace period to traverse from the inline bar to the detached `…`/submenu popovers. */\nconst HOVER_HIDE_DELAY_MS = 250\n\ninterface MediaControlsState {\n  activeTarget: HTMLElement | null\n  activeNodePos: number | null\n  selectionLocked: boolean\n  mediaResizing: boolean\n  listenersAttached: boolean\n  hoverHideTimer: ReturnType<typeof setTimeout> | null\n  pointerX: number\n  pointerY: number\n  pointerMoveAttached: boolean\n  onPointerOutside: (event: PointerEvent) => void\n  onDocumentKeyDown: (event: KeyboardEvent) => void\n  onWindowResize: () => void\n  onPointerMove: (event: PointerEvent) => void\n}\n\nconst controlsByEditor = new WeakMap<Editor, MediaControlsState>()\n\nfunction createControlsState(editor: Editor): MediaControlsState {\n  return {\n    activeTarget: null,\n    activeNodePos: null,\n    selectionLocked: false,\n    mediaResizing: false,\n    listenersAttached: false,\n    hoverHideTimer: null,\n    pointerX: 0,\n    pointerY: 0,\n    pointerMoveAttached: false,\n    onPointerOutside: (event) => handlePointerOutside(editor, event),\n    onDocumentKeyDown: (event) => {\n      handleMediaDeleteKey(editor, event)\n      handleToolbarEscape(editor, event)\n    },\n    onWindowResize: () => hideMediaResizeControls(editor),\n    onPointerMove: (event) => {\n      const state = getControlsState(editor)\n      state.pointerX = event.clientX\n      state.pointerY = event.clientY\n    }\n  }\n}\n\nfunction getControlsState(editor: Editor): MediaControlsState {\n  let state = controlsByEditor.get(editor)\n  if (!state) {\n    state = createControlsState(editor)\n    controlsByEditor.set(editor, state)\n  }\n  return state\n}\n\nfunction clearActiveState(state: MediaControlsState): void {\n  state.activeTarget = null\n  state.activeNodePos = null\n  state.selectionLocked = false\n  state.mediaResizing = false\n}\n\nfunction canUseHoverControls(state: MediaControlsState, finePointer: boolean): boolean {\n  return finePointer && !state.selectionLocked\n}\n\nfunction isDeleteKey(event: KeyboardEvent): boolean {\n  return event.key === 'Delete' || event.key === 'Backspace'\n}\n\nfunction isFormControlInMediaUI(element: Element | null): boolean {\n  if (!(element instanceof HTMLElement) || !isInsideMediaControlsUI(element)) {\n    return false\n  }\n  const tag = element.tagName\n  return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'\n}\n\n/** The caption is an editable shell in node content; its Backspace/Delete must edit text, not nuke the node. */\nfunction isEditingMediaCaption(element: Element | null): boolean {\n  return element instanceof HTMLElement && element.closest('.hm-caption') != null\n}\n\n/** Set by the drag layer so hover-out/outside-pointer don't tear down controls mid-drag. */\nexport function setMediaResizing(editor: Editor, active: boolean): void {\n  getControlsState(editor).mediaResizing = active\n}\n\n/** The pixel-sized loading-shell host, not the wrapper-with-caption, is the visible media box. */\nfunction resolveMediaBox(target: HTMLElement): HTMLElement {\n  const host = target.querySelector('.hm-media-host')\n  return host instanceof HTMLElement ? host : target\n}\n\n/**\n * Overlay the visible media box. Both rects come from `getBoundingClientRect`, then convert\n * into the gripper's own `offsetParent` frame (its border via clientLeft/Top). Drag math\n * reads and writes `offset*`, so the conversion keeps it in one coordinate space.\n */\nfunction positionResizeGripper(gripper: HTMLElement, target: HTMLElement): void {\n  const parent = gripper.offsetParent as HTMLElement | null\n  const parentRect = parent?.getBoundingClientRect()\n  const box = resolveMediaBox(target).getBoundingClientRect()\n\n  const originLeft = (parentRect?.left ?? 0) + (parent?.clientLeft ?? 0)\n  const originTop = (parentRect?.top ?? 0) + (parent?.clientTop ?? 0)\n\n  gripper.style.width = `${box.width}px`\n  gripper.style.height = `${box.height}px`\n  gripper.style.left = `${box.left - originLeft}px`\n  gripper.style.top = `${box.top - originTop}px`\n}\n\nfunction activateResizeGripper(gripper: HTMLElement, target: HTMLElement): void {\n  // `--active` flips display:none→block first: a hidden gripper has a null offsetParent\n  // and zero-size rects, so positionResizeGripper must run while it is laid out.\n  gripper.classList.add('hypermultimedia__resize-gripper--active')\n  positionResizeGripper(gripper, target)\n}\n\nfunction deactivateResizeGripper(gripper: HTMLElement): void {\n  gripper.classList.remove(\n    'hypermultimedia__resize-gripper--active',\n    'hypermultimedia__resize-gripper--dragging'\n  )\n  gripper.style.width = ''\n  gripper.style.height = ''\n  gripper.style.left = ''\n  gripper.style.top = ''\n}\n\n/** Active grippers whose media sibling is gone (delete, cut, collab) must not linger in the DOM. */\nfunction purgeOrphanedResizeControls(root: ParentNode = document): void {\n  root.querySelectorAll('.hypermultimedia__resize-gripper--active').forEach((node) => {\n    if (!(node instanceof HTMLElement)) return\n    const media = resolveMediaFromGripper(node, root)\n    if (!media || !findMediaTarget(media, root)) {\n      deactivateResizeGripper(node)\n    }\n  })\n}\n\nfunction cancelHoverHide(state: MediaControlsState): void {\n  if (state.hoverHideTimer != null) {\n    window.clearTimeout(state.hoverHideTimer)\n    state.hoverHideTimer = null\n  }\n}\n\nfunction isPointerOverActiveControls(editor: Editor): boolean {\n  const state = getControlsState(editor)\n  if (!state.activeTarget) return false\n\n  // Keyboard use parks the pointer anywhere: typing in the Replace dialog or\n  // arrowing through menus must not die to the pointer-based 250ms hide.\n  const focused = document.activeElement\n  if (focused instanceof HTMLElement && isInsideMediaControlsUI(focused)) return true\n\n  const hit = document.elementFromPoint(state.pointerX, state.pointerY)\n  if (!(hit instanceof HTMLElement)) return false\n  if (isInsideMediaControlsUI(hit)) return true\n  return findMediaTarget(hit, editor.view.dom) === state.activeTarget\n}\n\nfunction scheduleHoverHide(editor: Editor, event?: MouseEvent): void {\n  const state = getControlsState(editor)\n  if (event) {\n    state.pointerX = event.clientX\n    state.pointerY = event.clientY\n  }\n\n  cancelHoverHide(state)\n  state.hoverHideTimer = window.setTimeout(() => {\n    state.hoverHideTimer = null\n    if (!isPointerOverActiveControls(editor)) hideMediaResizeControls(editor)\n  }, HOVER_HIDE_DELAY_MS)\n}\n\nfunction setPointerTracking(editor: Editor, state: MediaControlsState, attach: boolean): void {\n  if (state.pointerMoveAttached === attach) return\n  if (attach) {\n    document.addEventListener('pointermove', state.onPointerMove, { passive: true })\n  } else {\n    document.removeEventListener('pointermove', state.onPointerMove)\n  }\n  state.pointerMoveAttached = attach\n}\n\nexport function hideMediaResizeControls(editor: Editor): void {\n  const state = getControlsState(editor)\n  const gripper = state.activeTarget\n    ? findGripperForMedia(state.activeTarget, editor.view.dom)\n    : null\n\n  cancelHoverHide(state)\n  setPointerTracking(editor, state, false)\n  if (gripper) deactivateResizeGripper(gripper)\n  // Idle controls have no active target, and `closeMediaToolbar` then falls back\n  // to `document` — which would tear down another editor's mounted bar.\n  closeMediaToolbar(state.activeTarget ?? editor.view.dom)\n  clearActiveState(state)\n  setOutsideListeners(state, false)\n  purgeOrphanedResizeControls(editor.view.dom)\n}\n\n/** Keeps Delete-key and toolbar actions aimed at the node when edits above shift its position. */\nexport function remapActiveNodePos(editor: Editor, transactions: readonly Transaction[]): void {\n  const state = getControlsState(editor)\n  if (state.activeNodePos === null || state.mediaResizing) return\n\n  let pos = state.activeNodePos\n  for (const tr of transactions) {\n    if (!tr.docChanged) continue\n    const mapped = tr.mapping.mapResult(pos)\n    if (mapped.deleted) {\n      hideMediaResizeControls(editor)\n      return\n    }\n    pos = mapped.pos\n  }\n  if (pos === state.activeNodePos) return\n\n  state.activeNodePos = pos\n  if (state.activeTarget) updateToolbarContextNodePos(state.activeTarget, pos)\n}\n\n/** Runs post-DOM-update: edits above reflow the node, but the gripper's inline box is a snapshot. */\nfunction repositionActiveGripper(editor: Editor, state: MediaControlsState): void {\n  if (!state.activeTarget) return\n  const gripper = findGripperForMedia(state.activeTarget, editor.view.dom)\n  if (gripper?.classList.contains('hypermultimedia__resize-gripper--active')) {\n    positionResizeGripper(gripper, state.activeTarget)\n  }\n}\n\n/** Tear down when the node behind the active controls has left the document (delete, cut, remote edit). */\nexport function syncControlsToDoc(editor: Editor): void {\n  const state = getControlsState(editor)\n  if (state.mediaResizing) return\n  // Idle controls ⇒ nothing to tear down; skip the document-wide orphan scan on every transaction.\n  if (!state.activeTarget && !state.listenersAttached) return\n  if (state.activeTarget && !state.activeTarget.isConnected) {\n    hideMediaResizeControls(editor)\n    return\n  }\n  repositionActiveGripper(editor, state)\n  purgeOrphanedResizeControls(editor.view.dom)\n}\n\n// Scroll is not a teardown trigger: gripper rides content; toolbar tracks via autoUpdate.\nfunction setOutsideListeners(state: MediaControlsState, attach: boolean): void {\n  if (state.listenersAttached === attach) return\n\n  if (attach) {\n    document.addEventListener('pointerdown', state.onPointerOutside, true)\n    document.addEventListener('keydown', state.onDocumentKeyDown, true)\n    window.addEventListener('resize', state.onWindowResize)\n  } else {\n    document.removeEventListener('pointerdown', state.onPointerOutside, true)\n    document.removeEventListener('keydown', state.onDocumentKeyDown, true)\n    window.removeEventListener('resize', state.onWindowResize)\n  }\n\n  state.listenersAttached = attach\n}\n\nfunction handlePointerOutside(editor: Editor, event: PointerEvent): void {\n  const state = getControlsState(editor)\n  if (state.mediaResizing) return\n  const clickTarget = event.target as HTMLElement | null\n  if (isInsideMediaControlsUI(clickTarget)) return\n  const media = findMediaTarget(clickTarget, editor.view.dom)\n  if (media === state.activeTarget) return\n  hideMediaResizeControls(editor)\n}\n\n/** DOM → doc position with a node-type guard; also re-resolves stale drag-end positions. */\nexport function resolveMediaNodePos(\n  view: Editor['view'],\n  target: HTMLElement,\n  nodeType: string\n): number | null {\n  try {\n    const pos = view.posAtDOM(target, 0)\n    const node = view.state.doc.nodeAt(pos)\n    if (node?.type.name === nodeType) return pos\n\n    const $pos = view.state.doc.resolve(pos)\n    if ($pos.nodeAfter?.type.name === nodeType) return $pos.pos\n    if ($pos.nodeBefore?.type.name === nodeType) {\n      return $pos.pos - $pos.nodeBefore.nodeSize\n    }\n  } catch {\n    return null\n  }\n  return null\n}\n\n/** Desktop hover or touch click — gripper + toolbar, or toolbar-only for X embeds. */\nfunction showMediaResizeControls(editor: Editor, target: HTMLElement): boolean {\n  const state = getControlsState(editor)\n  const nodeType = getMediaNodeType(target)\n  if (!nodeType) return false\n\n  const nodePos = resolveMediaNodePos(editor.view, target, nodeType)\n  if (nodePos === null) return false\n\n  const gripper = findGripperForMedia(target, editor.view.dom)\n  const toolbarOnly = nodeType === 'x'\n  if (!gripper && !toolbarOnly) return false\n\n  if (state.activeTarget && state.activeTarget !== target) {\n    hideMediaResizeControls(editor)\n  }\n\n  cancelHoverHide(state)\n\n  if (gripper) activateResizeGripper(gripper, target)\n  openMediaToolbar({\n    target,\n    editor,\n    nodeType,\n    nodePos\n  })\n\n  state.activeTarget = target\n  state.activeNodePos = nodePos\n  setPointerTracking(editor, state, true)\n  setOutsideListeners(state, true)\n  return true\n}\n\nfunction lockMediaSelection(editor: Editor): void {\n  getControlsState(editor).selectionLocked = true\n}\n\nfunction deleteActiveMediaNode(editor: Editor): boolean {\n  const state = getControlsState(editor)\n  const { activeTarget, activeNodePos, mediaResizing } = state\n  if (!activeTarget || activeNodePos === null || mediaResizing || !editor.isEditable) {\n    return false\n  }\n\n  const node = editor.state.doc.nodeAt(activeNodePos)\n  const nodeType = getMediaNodeType(activeTarget)\n  if (!node || !nodeType || node.type.name !== nodeType) return false\n\n  editor.view.dispatch(editor.state.tr.delete(activeNodePos, activeNodePos + node.nodeSize))\n  return true\n}\n\nfunction handleMediaDeleteKey(editor: Editor, event: KeyboardEvent): boolean {\n  if (!isDeleteKey(event)) return false\n  if (event.metaKey || event.ctrlKey || event.altKey || event.isComposing) return false\n  if (!getControlsState(editor).activeTarget) return false\n  if (isFormControlInMediaUI(document.activeElement)) return false\n  if (isEditingMediaCaption(document.activeElement)) return false\n  // A focused caret in text must keep editing text. Hover controls only own the\n  // key when the node is selected or focus is outside the editor.\n  if (editor.view.hasFocus() && editor.state.selection instanceof TextSelection) return false\n  if (!deleteActiveMediaNode(editor)) return false\n\n  event.preventDefault()\n  return true\n}\n\n/** Escape with focus in the toolbar dismisses it and refocuses the editor. Skips when focus sits in a floating popover — its own bubble-phase Escape closes just the popover. */\nfunction handleToolbarEscape(editor: Editor, event: KeyboardEvent): boolean {\n  if (event.key !== 'Escape' || !getControlsState(editor).activeTarget) return false\n  const focused = document.activeElement\n  if (!(focused instanceof HTMLElement) || focused.closest('.floating-popover')) return false\n  if (!focused.closest('[data-hm-toolbar]')) return false\n\n  hideMediaResizeControls(editor)\n  editor.commands.focus()\n  event.preventDefault()\n  return true\n}\n\nexport function handleMediaHover(editor: Editor, event: MouseEvent, finePointer: boolean): boolean {\n  const state = getControlsState(editor)\n  if (!canUseHoverControls(state, finePointer)) return false\n\n  cancelHoverHide(state)\n  state.pointerX = event.clientX\n  state.pointerY = event.clientY\n\n  const fromTarget = event.target as HTMLElement\n  if (isInsideMediaControlsUI(fromTarget)) return false\n\n  const target = findMediaTarget(fromTarget, editor.view.dom)\n  if (!target || target === state.activeTarget) return false\n\n  showMediaResizeControls(editor, target)\n  return false\n}\n\nexport function handleMediaUnhover(\n  editor: Editor,\n  event: MouseEvent,\n  finePointer: boolean\n): boolean {\n  const state = getControlsState(editor)\n  if (!canUseHoverControls(state, finePointer) || !state.activeTarget || state.mediaResizing) {\n    return false\n  }\n\n  const related = event.relatedTarget as HTMLElement | null\n  if (isInsideMediaControlsUI(related)) return false\n  if (findMediaTarget(related, editor.view.dom) === state.activeTarget) return false\n\n  scheduleHoverHide(editor, event)\n  return false\n}\n\nexport function handleMediaClick(editor: Editor, event: MouseEvent | TouchEvent): boolean {\n  if (isInsideMediaControlsUI(event.target as HTMLElement)) return false\n\n  const target = findMediaTarget(event.target as HTMLElement, editor.view.dom)\n  if (!target) return false\n\n  // Notion-style: embed iframes stay interactive; only images use click-to-lock.\n  if (isInteractiveEmbed(getMediaNodeType(target))) return false\n\n  lockMediaSelection(editor)\n  showMediaResizeControls(editor, target)\n  event.preventDefault()\n  return true\n}\n\n/** Re-mount hover controls after resize rebuilds the gripper decoration. */\nexport function restoreControlsAfterResize(\n  editor: Editor,\n  gripper: HTMLElement,\n  nodePos?: number\n): void {\n  setMediaResizing(editor, false)\n\n  let target: HTMLElement | null = null\n  if (nodePos !== undefined) {\n    target = editor.view.nodeDOM(nodePos) as HTMLElement | null\n  } else {\n    target = resolveMediaFromGripper(gripper, editor.view.dom)\n  }\n\n  hideMediaResizeControls(editor)\n\n  if (hasFinePointer() && target?.matches(':hover')) {\n    showMediaResizeControls(editor, target)\n  }\n\n  editor.commands.focus()\n}\n","import type { MediaActionContext } from './types'\n\n// Internal bridge: lets the controls layer refresh a mounted bar's nodePos\n// without importing toolbar internals beyond this module (no utils↔toolbar cycle).\nconst contextByToolbar = new WeakMap<HTMLElement, MediaActionContext>()\n\n/** Only the default factory registers; host-provided toolbars opt out by never appearing here. */\nexport function registerToolbarContext(bar: HTMLElement, ctx: MediaActionContext): void {\n  contextByToolbar.set(bar, ctx)\n}\n\n/** Action handlers close over ctx and read it at click time, so mutating in place is enough. */\nexport function updateToolbarContextNodePos(wrapper: HTMLElement, nodePos: number): void {\n  const bar = wrapper.querySelector<HTMLElement>(':scope > [data-hm-toolbar]')\n  const ctx = bar ? contextByToolbar.get(bar) : undefined\n  if (ctx) ctx.nodePos = nodePos\n}\n","import { computePosition, flip, offset, shift } from '@floating-ui/dom'\n\n/*\n * Built on raw `@floating-ui/dom`, deliberately not `createPopover`: the engine\n * self-adopts into the one-popover-at-a-time controller, so showing a tooltip\n * would dismiss an open menu or dialog. One shared bubble serves every button.\n */\n\nconst SHOW_DELAY_MS = 400\nconst VISIBLE_CLASS = 'visible'\n\nlet bubble: HTMLDivElement | null = null\nlet showTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction ensureBubble(): HTMLDivElement {\n  if (!bubble) {\n    bubble = document.createElement('div')\n    bubble.className = 'floating-tooltip'\n    bubble.setAttribute('role', 'tooltip')\n    document.body.append(bubble)\n  }\n  return bubble\n}\n\nfunction cancelShow(): void {\n  if (showTimer !== null) {\n    clearTimeout(showTimer)\n    showTimer = null\n  }\n}\n\n// No autoUpdate: tooltips are ephemeral, every hide/re-show re-positions.\nasync function show(target: HTMLElement, label: string): Promise<void> {\n  if (!target.isConnected) return\n  const el = ensureBubble()\n  el.textContent = label\n  const { x, y } = await computePosition(target, el, {\n    strategy: 'fixed',\n    placement: 'top',\n    middleware: [offset(6), flip(), shift({ padding: 8 })]\n  })\n  el.style.left = `${x}px`\n  el.style.top = `${y}px`\n  el.classList.add(VISIBLE_CLASS)\n}\n\n/** Hide the shared tooltip bubble and cancel any pending show. */\nexport function hideTooltip(): void {\n  cancelShow()\n  bubble?.classList.remove(VISIBLE_CLASS)\n}\n\n/** Show `label` above `target` after a short hover/focus delay. Returns a detach function for surfaces that re-render in place. */\nexport function attachTooltip(target: HTMLElement, label: string): () => void {\n  const schedule = (): void => {\n    cancelShow()\n    showTimer = setTimeout(() => {\n      showTimer = null\n      void show(target, label)\n    }, SHOW_DELAY_MS)\n  }\n  // Keyboard focus only: click-focus would re-show over the popover the click opened.\n  const scheduleOnFocus = (): void => {\n    if (target.matches(':focus-visible')) schedule()\n  }\n  target.addEventListener('mouseenter', schedule)\n  target.addEventListener('focus', scheduleOnFocus)\n  target.addEventListener('mouseleave', hideTooltip)\n  target.addEventListener('blur', hideTooltip)\n  // Activation opens popovers; the tooltip must not linger over them.\n  // `click` covers keyboard Enter/Space, which fire no pointerdown.\n  target.addEventListener('pointerdown', hideTooltip)\n  target.addEventListener('click', hideTooltip)\n  return () => {\n    target.removeEventListener('mouseenter', schedule)\n    target.removeEventListener('focus', scheduleOnFocus)\n    target.removeEventListener('mouseleave', hideTooltip)\n    target.removeEventListener('blur', hideTooltip)\n    target.removeEventListener('pointerdown', hideTooltip)\n    target.removeEventListener('click', hideTooltip)\n    hideTooltip()\n  }\n}\n","import type { Editor } from '@tiptap/core'\n\nimport type { MediaLoadingShellOption } from './loading/types'\nimport type { ReplaceUrlPopoverFactory } from './toolbar/replaceUrl'\nimport type { MediaToolbarIconsResolver } from './toolbar/resolveIcon'\nimport type { MediaActionContext, MediaActionsResolver, MediaToolbarFactory } from './toolbar/types'\n\nexport const HYPER_MULTIMEDIA_KIT_EXTENSION_NAME = 'HyperMultimediaKit'\n\n/** Single source of truth for the kit's storage shape — read via `getKitStorage`. */\nexport interface HyperMultimediaKitStorage {\n  mediaToolbar?: MediaToolbarFactory\n  /** Matches the real stored value (`boolean | factory`), not a bare boolean. */\n  loadingShell?: MediaLoadingShellOption\n  mediaActions?: MediaActionsResolver\n  /** Swap toolbar/menu SVG markup by icon key without replacing toolbar factories. */\n  mediaToolbarIcons?: MediaToolbarIconsResolver\n  replaceUrlPopover?: ReplaceUrlPopoverFactory\n  isUploadedMedia?: (ctx: MediaActionContext) => boolean\n}\n\n/** Typed accessor that replaces the ad-hoc `editor.storage as Record<...>` casts. */\nexport function getKitStorage(editor: Editor): HyperMultimediaKitStorage {\n  return (\n    (editor.storage as Record<string, HyperMultimediaKitStorage | undefined>)[\n      HYPER_MULTIMEDIA_KIT_EXTENSION_NAME\n    ] ?? {}\n  )\n}\n","// Extension-agnostic single-popover lifecycle owner. `adopt` takes\n// ownership of a built popover (destroying the previous owner), `close`\n// hides + idles, `reposition` re-anchors, `subscribe` observes\n// idle ↔ mounted transitions.\n\nexport type PopoverKind = 'preview' | 'edit' | 'create' | (string & {})\n\nexport type VirtualCoordinates = {\n  getBoundingClientRect: () => { x: number; y: number; width: number; height: number }\n  contextElement?: HTMLElement\n}\n\nexport interface ManagedPopover {\n  hide: () => void\n  destroy: () => void\n  updateReference: (referenceElement?: HTMLElement, coordinates?: VirtualCoordinates) => void\n}\n\nexport type AdoptMetadata = {\n  /** The popover's root element — what subscribers attach focus rings, scroll-freezes, or DOM observers to. */\n  element: HTMLElement\n  /** The anchor the popover is glued to — `null` for virtual-coords popovers (e.g. selection-anchored create). */\n  referenceElement: HTMLElement | null\n}\n\nexport type ControllerState =\n  { kind: 'idle' } | ({ kind: 'mounted'; popoverKind: PopoverKind } & AdoptMetadata)\n\nexport interface PopoverController {\n  adopt(popover: ManagedPopover, kind: PopoverKind, metadata: AdoptMetadata): () => void\n  close(): void\n  reposition(referenceElement?: HTMLElement, coordinates?: VirtualCoordinates): void\n  getState(): ControllerState\n  subscribe(listener: (state: ControllerState) => void): () => void\n}\n\nexport function createPopoverController(): PopoverController {\n  let current: { popover: ManagedPopover; kind: PopoverKind; metadata: AdoptMetadata } | null = null\n  const listeners = new Set<(state: ControllerState) => void>()\n\n  const snapshot = (): ControllerState =>\n    current === null\n      ? { kind: 'idle' }\n      : {\n          kind: 'mounted',\n          popoverKind: current.kind,\n          element: current.metadata.element,\n          referenceElement: current.metadata.referenceElement\n        }\n\n  const notify = (): void => {\n    const state = snapshot()\n    listeners.forEach((listener) => listener(state))\n  }\n\n  return {\n    adopt(popover, kind, metadata) {\n      const previous = current\n      current = { popover, kind, metadata }\n      if (previous && previous.popover !== popover) previous.popover.destroy()\n      notify()\n      return () => {\n        if (current?.popover === popover) {\n          current = null\n          notify()\n        }\n      }\n    },\n    close() {\n      if (current === null) return\n      const { popover } = current\n      current = null\n      popover.hide()\n      notify()\n    },\n    reposition(referenceElement, coordinates) {\n      current?.popover.updateReference(referenceElement, coordinates)\n    },\n    getState: snapshot,\n    subscribe(listener) {\n      listeners.add(listener)\n      return () => {\n        listeners.delete(listener)\n      }\n    }\n  }\n}\n\nlet defaultController: PopoverController = createPopoverController()\n\nexport function getDefaultController(): PopoverController {\n  return defaultController\n}\n\nexport function setDefaultController(controller: PopoverController): void {\n  defaultController = controller\n}\n\nexport function resetDefaultController(): void {\n  defaultController = createPopoverController()\n}\n","import {\n  arrow,\n  autoUpdate,\n  computePosition,\n  flip,\n  hide as hideMiddleware,\n  offset,\n  type Placement,\n  type ReferenceElement,\n  shift,\n  type VirtualElement\n} from '@floating-ui/dom'\n\nimport { getDefaultController, type VirtualCoordinates } from './controller'\n\ntype PopoverAnchor =\n  | { referenceElement: HTMLElement; coordinates?: never }\n  | { referenceElement?: never; coordinates: VirtualCoordinates }\n\ntype PopoverShellOptions = {\n  content: HTMLElement\n  placement?: Placement\n  offset?: number\n  showArrow?: boolean\n  className?: string\n  zIndex?: number\n  /**\n   * ARIA has no popover role — the shell carries its content's role\n   * (`toolbar`, `dialog`, …) and stays role-less when omitted.\n   */\n  role?: string\n  /** Same ARIA axis as `role` — shell-level so BYO content inherits the accessible name. */\n  ariaLabel?: string\n  /**\n   * Nodes that must not light-dismiss (toggle triggers). When set, only these\n   * are ignored — not `referenceElement` — so a larger position surface (e.g.\n   * a toolbar bar) can still dismiss on sibling clicks. When omitted, the\n   * HTMLElement `referenceElement` is ignored instead.\n   */\n  ignoreOutsideClickOn?: HTMLElement | HTMLElement[]\n  /**\n   * When false, `shift` only moves on the main axis so end-aligned menus\n   * (e.g. toolbar overflow) stay pinned to the reference’s end edge.\n   * @default true\n   */\n  crossAxisShift?: boolean\n  onShow?: () => void\n  onHide?: () => void\n}\n\nexport type PopoverOptions = PopoverAnchor & PopoverShellOptions\n\n/**\n * One-shot once opened: hiding releases the controller's ownership and nothing\n * re-adopts, so `show()` will not reopen it. Build a new popover instead — a\n * re-shown one would sit outside the single-popover invariant, invisible to\n * `close()` and never evicted by the next `adopt()`.\n */\nexport interface Popover {\n  element: HTMLElement\n  /** No-op once the popover has been hidden or destroyed. */\n  show: () => void\n  /** Terminal once shown. Before the first `show()` it no-ops and keeps ownership. */\n  hide: () => void\n  destroy: () => void\n  isVisible: () => boolean\n  setContent: (content: HTMLElement) => void\n  updateReference: (referenceElement?: HTMLElement, coordinates?: VirtualCoordinates) => void\n}\n\nexport const DEFAULT_OFFSET = 8\nconst DEFAULT_Z_INDEX = 9999\nconst SHIFT_PADDING = 8\nconst ARROW_STATIC_OFFSET = '-5px'\nconst OUTSIDE_CLICK_DEFER_MS = 50\n// Covers the skins' 80ms exit; transitionend never fires under display:none or\n// prefers-reduced-motion `transition: none`, so removal cannot rely on it alone.\nconst EXIT_FALLBACK_MS = 150\n\nconst ORIGIN_BY_SIDE: Record<string, string> = {\n  top: 'bottom',\n  bottom: 'top',\n  left: 'right',\n  right: 'left'\n}\n\nfunction createVirtualReference(coords: VirtualCoordinates): VirtualElement {\n  return {\n    getBoundingClientRect: () => {\n      const { x, y, width, height } = coords.getBoundingClientRect()\n      return { width, height, top: y, right: x + width, bottom: y + height, left: x, x, y }\n    },\n    contextElement: coords.contextElement || document.body\n  }\n}\n\n// Wraps focusables in a Tab cycle. Returns removers so each `setContent`\n// can drop the prior batch before re-binding — without it the per-focusable\n// `keydown` listeners leaked across content swaps and on destroy.\nfunction setupKeyboardNav(container: HTMLElement): Array<() => void> {\n  const focusable = container.querySelectorAll<HTMLElement>(\n    'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n  )\n  if (!focusable.length) return []\n  const removers: Array<() => void> = []\n  focusable.forEach((el, i) => {\n    const onKeydown = (e: KeyboardEvent) => {\n      if (e.key !== 'Tab') return\n      e.preventDefault()\n      const next = e.shiftKey\n        ? (i - 1 + focusable.length) % focusable.length\n        : (i + 1) % focusable.length\n      focusable[next].focus()\n    }\n    el.addEventListener('keydown', onKeydown)\n    removers.push(() => el.removeEventListener('keydown', onKeydown))\n  })\n  return removers\n}\n\nexport function createPopover(options: PopoverOptions): Popover {\n  const controller = getDefaultController()\n\n  const {\n    referenceElement,\n    coordinates,\n    content,\n    placement = 'bottom-start',\n    offset: offsetVal = DEFAULT_OFFSET,\n    showArrow = false,\n    className = '',\n    zIndex = DEFAULT_Z_INDEX,\n    role,\n    ariaLabel,\n    ignoreOutsideClickOn,\n    crossAxisShift = true,\n    onShow,\n    onHide\n  } = options\n\n  // Defence-in-depth for `any`-typed and JS-only callers; the discriminated\n  // union in `PopoverOptions` already makes this case a compile error.\n  if (!referenceElement && !coordinates) {\n    throw new Error('Either referenceElement or coordinates must be provided')\n  }\n\n  let reference: ReferenceElement = referenceElement || createVirtualReference(coordinates!)\n  // Prefer an explicit ignore list (position ref ≠ toggle trigger). Otherwise\n  // the HTMLElement reference is the toggle target and must not light-dismiss.\n  const outsideIgnore: HTMLElement[] = ignoreOutsideClickOn\n    ? Array.isArray(ignoreOutsideClickOn)\n      ? [...ignoreOutsideClickOn]\n      : [ignoreOutsideClickOn]\n    : referenceElement\n      ? [referenceElement]\n      : []\n  const cleanups: (() => void)[] = []\n  let visible = false\n  let terminated = false\n  let autoUpdateCleanup: (() => void) | null = null\n\n  const isOutsideDismissTarget = (target: Node | null): boolean => {\n    if (!target) return true\n    if (root.contains(target)) return false\n    return !outsideIgnore.some((el) => el === target || el.contains(target))\n  }\n\n  const root = document.createElement('div')\n  root.className = `floating-popover ${className}`.trim()\n  if (role) root.setAttribute('role', role)\n  if (ariaLabel) root.setAttribute('aria-label', ariaLabel)\n  root.setAttribute('tabindex', '-1')\n  root.style.zIndex = String(zIndex)\n  root.style.position = 'fixed'\n\n  let arrowEl: HTMLElement | null = null\n  if (showArrow) {\n    arrowEl = document.createElement('div')\n    arrowEl.className = 'floating-popover-arrow'\n    root.appendChild(arrowEl)\n  }\n\n  const contentContainer = document.createElement('div')\n  contentContainer.className = 'floating-popover-content'\n  root.appendChild(contentContainer)\n\n  const navListeners: Array<() => void> = []\n  const clearNavListeners = (): void => {\n    while (navListeners.length) navListeners.pop()!()\n  }\n\n  const setContent = (el: HTMLElement): void => {\n    clearNavListeners()\n    contentContainer.innerHTML = ''\n    contentContainer.appendChild(el)\n    navListeners.push(...setupKeyboardNav(root))\n  }\n\n  setContent(content)\n\n  const updatePosition = async (): Promise<void> => {\n    if (!visible) return\n    const middleware = [\n      offset(offsetVal),\n      flip(),\n      shift({ padding: SHIFT_PADDING, crossAxis: crossAxisShift })\n    ]\n    if (arrowEl) middleware.push(arrow({ element: arrowEl }))\n    middleware.push(hideMiddleware({ strategy: 'referenceHidden' }))\n\n    const {\n      x,\n      y,\n      placement: actual,\n      middlewareData\n    } = await computePosition(reference, root, {\n      placement,\n      strategy: 'fixed',\n      middleware\n    })\n    if (!visible) return\n    Object.assign(root.style, { left: `${x}px`, top: `${y}px` })\n    root.style.visibility = middlewareData.hide?.referenceHidden ? 'hidden' : 'visible'\n\n    const side = actual.split('-')[0]\n    // Scale blooms from the anchored side instead of center.\n    root.style.transformOrigin = ORIGIN_BY_SIDE[side] ?? 'center'\n\n    if (arrowEl && middlewareData.arrow) {\n      const { x: ax, y: ay } = middlewareData.arrow\n      // Arrow pins to the side opposite the placement — the same mapping.\n      const staticSide = ORIGIN_BY_SIDE[side]!\n      arrowEl.className = `floating-popover-arrow floating-popover-arrow-${side}`\n      Object.assign(arrowEl.style, {\n        left: ax != null ? `${ax}px` : '',\n        top: ay != null ? `${ay}px` : '',\n        right: '',\n        bottom: '',\n        [staticSide]: ARROW_STATIC_OFFSET\n      })\n    }\n  }\n\n  let unregister: (() => void) | null = null\n  let removalTimer: ReturnType<typeof setTimeout> | null = null\n  let onExitEnd: ((e: TransitionEvent) => void) | null = null\n\n  const cancelPendingRemoval = (): void => {\n    if (removalTimer) {\n      clearTimeout(removalTimer)\n      removalTimer = null\n    }\n    if (onExitEnd) {\n      root.removeEventListener('transitionend', onExitEnd)\n      onExitEnd = null\n    }\n  }\n\n  const finishHide = (): void => {\n    cancelPendingRemoval()\n    root.remove()\n  }\n\n  const hide = (): void => {\n    if (!visible) return\n    visible = false\n    terminated = true\n    autoUpdateCleanup?.()\n    autoUpdateCleanup = null\n    clearNavListeners()\n    cleanups.forEach((fn) => fn())\n    cleanups.length = 0\n    unregister?.()\n    unregister = null\n    // Removing .visible plays the skin's exit transition; the DOM node leaves on\n    // transitionend, with the fallback timer as the guarantee.\n    root.classList.remove('visible')\n    onExitEnd = (e) => {\n      if (e.target === root) finishHide()\n    }\n    root.addEventListener('transitionend', onExitEnd)\n    removalTimer = setTimeout(finishHide, EXIT_FALLBACK_MS)\n    onHide?.()\n  }\n\n  const show = (): void => {\n    // A removal is only ever armed after `terminated` is set, so this can never\n    // find one pending — the re-show/removal race is gone rather than guarded.\n    if (visible || terminated) return\n    document.body.appendChild(root)\n    visible = true\n    autoUpdateCleanup = autoUpdate(reference, root, updatePosition)\n    requestAnimationFrame(() => root.classList.add('visible'))\n    setTimeout(() => {\n      if (!visible) return\n      const onMouseDown = (e: MouseEvent) => {\n        if (isOutsideDismissTarget(e.target as Node)) hide()\n      }\n      const onTouchStart = (e: TouchEvent) => {\n        if (!e.touches.length) return\n        const target = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY)\n        if (isOutsideDismissTarget(target)) hide()\n      }\n      document.addEventListener('mousedown', onMouseDown, { passive: true })\n      document.addEventListener('touchstart', onTouchStart, { passive: true })\n      cleanups.push(\n        () => document.removeEventListener('mousedown', onMouseDown),\n        () => document.removeEventListener('touchstart', onTouchStart)\n      )\n    }, OUTSIDE_CLICK_DEFER_MS)\n\n    const onKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        e.preventDefault()\n        hide()\n      }\n    }\n    root.addEventListener('keydown', onKeyDown)\n    cleanups.push(() => root.removeEventListener('keydown', onKeyDown))\n    onShow?.()\n  }\n\n  const destroy = (): void => {\n    hide()\n    // `hide()` no-ops when the popover was never shown; destroying is terminal either way.\n    terminated = true\n    // Teardown is immediate — destroy callers replace or unmount the surface.\n    finishHide()\n  }\n\n  const popover: Popover = {\n    element: root,\n    show,\n    hide,\n    destroy,\n    isVisible: () => visible,\n    setContent,\n    updateReference: (ref?: HTMLElement, coords?: VirtualCoordinates) => {\n      if (ref) reference = ref\n      else if (coords) reference = createVirtualReference(coords)\n      if (visible) {\n        autoUpdateCleanup?.()\n        autoUpdateCleanup = autoUpdate(reference, root, updatePosition)\n      } else {\n        updatePosition()\n      }\n    }\n  }\n\n  unregister = controller.adopt(popover, 'unknown', {\n    element: root,\n    referenceElement: referenceElement ?? null\n  })\n  return popover\n}\n","interface IconProps {\n  size?: number\n  fill?: string\n  className?: string\n}\n\nconst VIEW_BOX = '0 -960 960 960'\n\n/** Google Material Symbols (outlined) path → inline SVG for imperative toolbar DOM. */\nfunction materialIcon(\n  path: string,\n  { size = 20, fill = 'currentColor', className = '' }: IconProps = {}\n): string {\n  const klass = className ? ` class=\"${className}\"` : ''\n  return `<svg xmlns=\"http://www.w3.org/2000/svg\"${klass} height=\"${size}\" width=\"${size}\" viewBox=\"${VIEW_BOX}\" fill=\"${fill}\" aria-hidden=\"true\"><path d=\"${path}\"/></svg>`\n}\n\n/** Official Material Symbols outlined paths — matches `@docs.plus/extension-hyperlink` icon style. */\nconst PATHS = {\n  alignLeft:\n    'M120-120v-80h720v80H120Zm0-160v-80h480v80H120Zm0-160v-80h720v80H120Zm0-160v-80h480v80H120Zm0-160v-80h720v80H120Z',\n  alignCenter:\n    'M120-120v-80h720v80H120Zm160-160v-80h400v80H280ZM120-440v-80h720v80H120Zm160-160v-80h400v80H280ZM120-760v-80h720v80H120Z',\n  alignRight:\n    'M120-120v-80h720v80H120Zm240-160v-80h480v80H360ZM120-440v-80h720v80H120Zm240-160v-80h480v80H360ZM120-760v-80h720v80H120Z',\n  imageLeft:\n    'M120-280v-400h400v400H120Zm80-80h240v-240H200v240Zm-80-400v-80h720v80H120Zm480 160v-80h240v80H600Zm0 160v-80h240v80H600Zm0 160v-80h240v80H600ZM120-120v-80h720v80H120Z',\n  imageRight:\n    'M440-280v-400h400v400H440Zm80-80h240v-240H520v240ZM120-120v-80h720v80H120Zm0-160v-80h240v80H120Zm0-160v-80h240v80H120Zm0-160v-80h240v80H120Zm0-160v-80h720v80H120Z',\n  caption:\n    'M240-320h320v-80H240v80Zm400 0h80v-80h-80v80ZM240-480h80v-80h-80v80Zm160 0h320v-80H400v80ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Z',\n  externalLink: 'M256-240 200-296l384-384H320v-80h400v400h-80v-264L256-240Z',\n  download:\n    'M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z',\n  replaceUrl:\n    'M280-120 80-320l200-200 57 56-104 104h607v80H233l104 104-57 56Zm400-320-57-56 104-104H120v-80h607L623-784l57-56 200 200-200 200Z',\n  copy: 'M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z',\n  trash:\n    'M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z',\n  more: 'M240-400q-33 0-56.5-23.5T160-480q0-33 23.5-56.5T240-560q33 0 56.5 23.5T320-480q0 33-23.5 56.5T240-400Zm240 0q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm240 0q-33 0-56.5-23.5T640-480q0-33 23.5-56.5T720-560q33 0 56.5 23.5T800-480q0 33-23.5 56.5T720-400Z'\n} as const\n\ntype IconRenderer = (props?: IconProps) => string\n\nfunction icon(path: string): IconRenderer {\n  return (props) => materialIcon(path, props)\n}\n\nexport const AlignLeft = icon(PATHS.alignLeft)\nexport const AlignCenter = icon(PATHS.alignCenter)\nexport const AlignRight = icon(PATHS.alignRight)\nexport const ImageLeft = icon(PATHS.imageLeft)\nexport const ImageRight = icon(PATHS.imageRight)\nexport const Caption = icon(PATHS.caption)\nexport const ExternalLink = icon(PATHS.externalLink)\nexport const Download = icon(PATHS.download)\nexport const Replace = icon(PATHS.replaceUrl)\nexport const Copy = icon(PATHS.copy)\nexport const Trash = icon(PATHS.trash)\nexport const More = icon(PATHS.more)\n","export const MEDIA_MARGIN_OPTIONS = [\n  { value: '0in', label: '0\"' },\n  { value: '0.06in', label: '1/16\"' },\n  { value: '0.13in', label: '1/8\"' },\n  { value: '0.25in', label: '1/4\"' },\n  { value: '0.38in', label: '3/8\"' },\n  { value: '0.5in', label: '1/2\"' },\n  { value: '0.75in', label: '3/4\"' },\n  { value: '1in', label: '1\"' }\n] as const\n\nexport type MediaPlacementId = 'inline' | 'center' | 'right' | 'float-left' | 'float-right'\n\nexport const MEDIA_PLACEMENT_OPTIONS: ReadonlyArray<{ id: MediaPlacementId; label: string }> = [\n  { id: 'inline', label: 'Left' },\n  { id: 'center', label: 'Center' },\n  { id: 'right', label: 'Right' },\n  { id: 'float-left', label: 'Wrap left' },\n  { id: 'float-right', label: 'Wrap right' }\n]\n\nexport function getMediaPlacementAttrs(\n  id: MediaPlacementId,\n  margin = '0.5in'\n): Record<string, string> {\n  switch (id) {\n    case 'inline':\n      return { display: 'block', float: 'none', clear: 'none', margin: '0' }\n    case 'center':\n      return { display: 'block', float: 'none', clear: 'none', margin: 'auto' }\n    case 'right':\n      return { display: 'block', float: 'none', clear: 'none', margin: '0 0 0 auto' }\n    case 'float-left':\n      return { display: 'block', float: 'left', clear: 'none', margin }\n    case 'float-right':\n      return { display: 'block', float: 'right', clear: 'none', margin }\n  }\n}\n\nexport function getCurrentMediaPlacement(attrs: Record<string, unknown>): MediaPlacementId {\n  if (attrs.float === 'left') return 'float-left'\n  if (attrs.float === 'right') return 'float-right'\n  if (attrs.display === 'block' && attrs.margin === 'auto') return 'center'\n  if (attrs.display === 'block' && attrs.margin === '0 0 0 auto') return 'right'\n  return 'inline'\n}\n\nconst isZeroLength = (value: string): boolean =>\n  value === '0' || value === '0px' || value === '0in' || value === '0rem' || value === '0em'\n\n/** Left+right margin expression for `calc(100% - …)` — null when there is no horizontal inset. */\nfunction horizontalMarginExpression(margin: string | null | undefined): string | null {\n  if (margin == null) return null\n  const trimmed = margin.trim()\n  if (!trimmed || isZeroLength(trimmed) || /\\bauto\\b/i.test(trimmed)) return null\n\n  const parts = trimmed.split(/\\s+/)\n  switch (parts.length) {\n    case 1:\n      return isZeroLength(parts[0]) ? null : `2 * ${parts[0]}`\n    case 2:\n    case 3:\n      return isZeroLength(parts[1]) ? null : `2 * ${parts[1]}`\n    case 4: {\n      const right = parts[1]\n      const left = parts[3]\n      if (isZeroLength(left) && isZeroLength(right)) return null\n      if (isZeroLength(left)) return right\n      if (isZeroLength(right)) return left\n      return `${left} + ${right}`\n    }\n    default:\n      return null\n  }\n}\n\n/**\n * Floated media keeps a pixel `width` while wrap margins sit outside the box —\n * `max-width: 100%` alone still overflows. Cap the border box so width + margins\n * fit the column.\n */\nexport function maxWidthForFloatedMedia(margin: string | null | undefined): string {\n  const sum = horizontalMarginExpression(margin)\n  return sum ? `calc(100% - (${sum}))` : '100%'\n}\n\nexport function isFloatedMedia(float: string | null | undefined): boolean {\n  return float === 'left' || float === 'right'\n}\n","import { getKitStorage } from '../kitStorage'\nimport * as Icons from '../utils/icons'\nimport { getCurrentMediaPlacement, type MediaPlacementId } from '../utils/media-placement'\nimport type { MediaActionContext } from './types'\n\ntype BuiltinIconKey =\n  'caption' | 'view-original' | 'download' | 'replace' | 'copy' | 'delete' | 'more'\n\ntype PlacementIconKey = `align:${MediaPlacementId}`\n\n/** A built-in slot, an `align:<placement>` slot, or a custom action id from `mediaActions`. */\nexport type MediaToolbarIconKey = BuiltinIconKey | PlacementIconKey | (string & {})\n\n/** Everything a host needs to pick an icon — the key encodes the dynamic part. */\nexport interface MediaToolbarIconContext {\n  key: MediaToolbarIconKey\n  nodeType: string\n}\n\n/** Host hook: return SVG markup for a slot, or nullish to keep the built-in icon. */\nexport type MediaToolbarIconsResolver = (ctx: MediaToolbarIconContext) => string | null | undefined\n\nexport type MediaToolbarIconScope = Pick<MediaActionContext, 'editor' | 'nodeType'> & {\n  /** Required for the inline align button (`align:<placement>`). */\n  attrs?: Record<string, unknown>\n}\n\nconst ICON_SIZE = 18\n\nconst ALIGN_MATERIAL: Record<MediaPlacementId, keyof typeof Icons> = {\n  inline: 'AlignLeft',\n  center: 'AlignCenter',\n  right: 'AlignRight',\n  'float-left': 'ImageLeft',\n  'float-right': 'ImageRight'\n}\n\nconst BUILTIN_MATERIAL: Record<BuiltinIconKey, () => string> = {\n  caption: () => Icons.Caption({ size: ICON_SIZE }),\n  'view-original': () => Icons.ExternalLink({ size: ICON_SIZE }),\n  download: () => Icons.Download({ size: ICON_SIZE }),\n  replace: () => Icons.Replace({ size: ICON_SIZE }),\n  copy: () => Icons.Copy({ size: ICON_SIZE }),\n  delete: () => Icons.Trash({ size: ICON_SIZE }),\n  more: () => Icons.More({ size: ICON_SIZE })\n}\n\nfunction materialPlacementIcon(key: PlacementIconKey): string | null {\n  const id = key.slice('align:'.length) as MediaPlacementId\n  const icon = ALIGN_MATERIAL[id]\n  return icon ? Icons[icon]({ size: ICON_SIZE }) : null\n}\n\nfunction normalizeIconKey(\n  key: MediaToolbarIconKey,\n  attrs?: Record<string, unknown>\n): MediaToolbarIconKey {\n  if (key === 'align' && attrs) {\n    return `align:${getCurrentMediaPlacement(attrs)}`\n  }\n  return key\n}\n\nfunction defaultMaterialIcon(key: MediaToolbarIconKey): string | null {\n  if (key.startsWith('align:')) return materialPlacementIcon(key as PlacementIconKey)\n  if (key in BUILTIN_MATERIAL) return BUILTIN_MATERIAL[key as BuiltinIconKey]()\n  return null\n}\n\n/** Host override → built-in Material icon → null (text button or host-only custom id). */\nexport function resolveMediaToolbarIcon(\n  scope: MediaToolbarIconScope,\n  key: MediaToolbarIconKey,\n  icons?: MediaToolbarIconsResolver | null\n): string | null {\n  const resolvedKey = normalizeIconKey(key, scope.attrs)\n  const iconCtx = { key: resolvedKey, nodeType: scope.nodeType }\n  const override = (icons ?? getKitStorage(scope.editor).mediaToolbarIcons)?.(iconCtx)\n  if (override) return override\n  return defaultMaterialIcon(resolvedKey)\n}\n","import { createPopover, DEFAULT_OFFSET, getDefaultController } from '@docs.plus/floating-popover'\nimport { attachTooltip } from '@docs.plus/floating-tooltip'\n\nimport { type MediaToolbarIconsResolver, resolveMediaToolbarIcon } from './resolveIcon'\nimport type { MediaAction, MediaActionContext } from './types'\n\n/** Tuck menus under the bar — DEFAULT_OFFSET (8) leaves a visible gutter. */\nconst TOOLBAR_MENU_OFFSET = 2\n\nconst POPOVER_BY_VARIANT = {\n  menu: {\n    placement: 'bottom-end' as const,\n    offset: TOOLBAR_MENU_OFFSET,\n    crossAxisShift: false\n  },\n  dialog: {\n    placement: 'bottom' as const,\n    offset: DEFAULT_OFFSET\n  }\n} as const\n\nconst toolbarTooltipDetaches = new WeakMap<HTMLElement, (() => void)[]>()\n\nexport function bindToolbarTooltips(bar: HTMLElement, detaches: (() => void)[]): void {\n  toolbarTooltipDetaches.set(bar, detaches)\n}\n\nexport function releaseToolbarTooltips(bar: HTMLElement): void {\n  toolbarTooltipDetaches.get(bar)?.forEach((detach) => detach())\n  toolbarTooltipDetaches.delete(bar)\n}\n\nexport function actionButton(\n  action: MediaAction,\n  ctx: MediaActionContext,\n  variant: 'inline' | 'row',\n  icons?: MediaToolbarIconsResolver | null,\n  tooltipDetaches?: (() => void)[]\n): HTMLButtonElement {\n  const btn = document.createElement('button')\n  btn.type = 'button'\n  btn.dataset.actionId = action.id\n  const active = action.isActive?.(ctx) ?? false\n  // Only toggle-semantics actions (those declaring isActive) announce a pressed state.\n  if (action.isActive) btn.setAttribute('aria-pressed', active ? 'true' : 'false')\n  const iconMarkup = action.icon?.(ctx) ?? resolveMediaToolbarIcon(ctx, action.id, icons) ?? null\n  const label = action.label(ctx)\n\n  // A label can carry a document-controlled attribute (the margin action reports\n  // a non-preset value verbatim), so it is written as text, never interpolated.\n  const labelSpan = (): HTMLSpanElement => {\n    const span = document.createElement('span')\n    span.textContent = label\n    return span\n  }\n\n  if (variant === 'inline') {\n    btn.className = 'media-toolbar__button' + (active ? ' media-toolbar__button--active' : '')\n    if (iconMarkup) btn.innerHTML = iconMarkup\n    else {\n      btn.append(labelSpan())\n      btn.classList.add('media-toolbar__button--text')\n    }\n    btn.setAttribute('aria-label', label)\n    // Icon-only buttons get the floating tooltip; text labels self-describe.\n    if (iconMarkup) {\n      const detach = attachTooltip(btn, label)\n      tooltipDetaches?.push(detach)\n    }\n  } else {\n    btn.className = 'media-toolbar__menu-item' + (active ? ' media-toolbar__menu-item--active' : '')\n    if (iconMarkup) btn.innerHTML = iconMarkup\n    btn.append(labelSpan())\n  }\n  return btn\n}\n\nexport type OpenMediaPopoverOptions = {\n  kind: string\n  content: HTMLElement\n  /** Click target — ignored for light-dismiss so toggle works. */\n  trigger: HTMLElement\n  /** Position surface; defaults to `trigger`. Overflow menus pass the toolbar bar. */\n  positionReference?: HTMLElement\n  variant?: 'menu' | 'dialog'\n  role?: string\n  ariaLabel?: string\n  /** When false, skip toggle-close if the same kind is already open (dialogs). Default true for menu. */\n  toggle?: boolean\n}\n\n/**\n * Media toolbar popover adapter — owns toggle, menu offset/shift, and dismiss-ignore.\n * Callers pass content + kind + anchors only.\n */\nexport function openMediaPopover(options: OpenMediaPopoverOptions): void {\n  const {\n    kind,\n    content,\n    trigger,\n    positionReference = trigger,\n    variant = 'menu',\n    role,\n    ariaLabel,\n    toggle = variant === 'menu'\n  } = options\n\n  const controller = getDefaultController()\n  if (toggle) {\n    const state = controller.getState()\n    if (state.kind === 'mounted' && state.popoverKind === kind) {\n      controller.close()\n      return\n    }\n  }\n\n  const popover = createPopover({\n    referenceElement: positionReference,\n    content,\n    ...POPOVER_BY_VARIANT[variant],\n    role,\n    ariaLabel,\n    ignoreOutsideClickOn: trigger\n  })\n\n  controller.adopt(popover, kind, {\n    element: popover.element,\n    referenceElement: positionReference\n  })\n  popover.show()\n}\n\nexport type OpenToolbarPopoverOptions = {\n  /** Position against this surface; defaults to `trigger`. Overflow menus pass the toolbar bar. */\n  positionReference?: HTMLElement\n}\n\n/** Toggle an anchored menu popover on the shared controller; one open at a time. */\nexport function openToolbarPopover(\n  trigger: HTMLElement,\n  body: HTMLElement,\n  kind: string,\n  options?: OpenToolbarPopoverOptions\n): void {\n  openMediaPopover({\n    kind,\n    content: body,\n    trigger,\n    positionReference: options?.positionReference,\n    variant: 'menu'\n  })\n}\n\n/** Close the popover opened by `openToolbarPopover` / `openMediaPopover`, if any. */\nexport function closeToolbarPopover(): void {\n  getDefaultController().close()\n}\n\n/** Vertical overflow menu: action rows + inline-expanded submenu sections. */\nexport function buildOverflowMenu(\n  ctx: MediaActionContext,\n  menuActions: MediaAction[],\n  icons?: MediaToolbarIconsResolver | null\n): HTMLElement {\n  const menu = document.createElement('div')\n  menu.className = 'media-toolbar__menu'\n  for (const action of menuActions) {\n    if (action.renderSubmenu) {\n      const section = document.createElement('div')\n      section.className = 'media-toolbar__menu-section'\n      const heading = document.createElement('p')\n      heading.className = 'media-toolbar__menu-heading'\n      heading.textContent = action.label(ctx)\n      section.append(heading, action.renderSubmenu(ctx))\n      menu.append(section)\n      continue\n    }\n    const row = actionButton(action, ctx, 'row', icons)\n    row.onclick = () => action.run?.(ctx)\n    menu.append(row)\n  }\n  return menu\n}\n","export type XEmbedSizeId = 'compact' | 'standard' | 'wide'\nexport type XEmbedTheme = 'light' | 'dark'\n\nexport const X_EMBED_DEFAULT_MAXWIDTH = 400\n\nexport const X_EMBED_SIZE_OPTIONS: ReadonlyArray<{\n  id: XEmbedSizeId\n  label: string\n  maxwidth: number\n}> = [\n  { id: 'compact', label: 'Compact', maxwidth: 280 },\n  { id: 'standard', label: 'Standard', maxwidth: 400 },\n  { id: 'wide', label: 'Wide', maxwidth: 550 }\n]\n\nexport const X_EMBED_THEME_OPTIONS: ReadonlyArray<{ id: XEmbedTheme; label: string }> = [\n  { id: 'light', label: 'Light' },\n  { id: 'dark', label: 'Dark' }\n]\n\nexport const resolveXEmbedSizeId = (maxwidth: number | null | undefined): XEmbedSizeId => {\n  const width = maxwidth ?? X_EMBED_DEFAULT_MAXWIDTH\n  return X_EMBED_SIZE_OPTIONS.find((option) => option.maxwidth === width)?.id ?? 'standard'\n}\n\nexport interface XOEmbedKitOptions {\n  theme: XEmbedTheme\n  lang: string\n  dnt: boolean\n  hide_media: boolean\n  hide_thread: boolean\n}\n\nexport const buildXOEmbedParams = (\n  attrs: Record<string, unknown>,\n  options: XOEmbedKitOptions\n): Record<string, string | number> => {\n  const params: Record<string, string | number> = {\n    url: String(attrs.src ?? ''),\n    theme: (attrs.theme as XEmbedTheme | undefined) ?? options.theme,\n    maxwidth: Number(attrs.maxwidth ?? X_EMBED_DEFAULT_MAXWIDTH),\n    lang: String(attrs.lang ?? options.lang),\n    dnt: attrs.dnt === false ? 0 : 1,\n    omit_script: 1\n  }\n\n  if (attrs.hide_media ?? options.hide_media) {\n    params.hide_media = 1\n  }\n\n  if (attrs.hide_thread ?? options.hide_thread) {\n    params.hide_thread = 1\n  }\n\n  if (typeof attrs.align === 'string' && attrs.align !== 'none') {\n    params.align = attrs.align\n  }\n\n  return params\n}\n","import { Fragment, Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Transaction } from '@tiptap/pm/state'\nimport { ReplaceAroundStep, ReplaceStep } from '@tiptap/pm/transform'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nexport const HIDE_RESIZE_GRIPPER_META = 'hideResizeGripper'\n\nexport type BuildDecorationsFunction = (doc: ProseMirrorNode) => DecorationSet\n\nexport function shouldHideResizeGripper(tr: Transaction): boolean {\n  return tr.getMeta(HIDE_RESIZE_GRIPPER_META) === true\n}\n\nfunction fragmentHasTrackedType(fragment: Fragment, targetNodeTypes: string[]): boolean {\n  let found = false\n  fragment.descendants((node) => {\n    if (found) return false\n    if (targetNodeTypes.includes(node.type.name)) found = true\n    return !found\n  })\n  return found\n}\n\nfunction rangeHasTrackedType(\n  doc: ProseMirrorNode,\n  from: number,\n  to: number,\n  targetNodeTypes: string[]\n): boolean {\n  if (from >= to) return false\n  let found = false\n  doc.nodesBetween(from, to, (node) => {\n    if (found) return false\n    if (targetNodeTypes.includes(node.type.name)) found = true\n    return !found\n  })\n  return found\n}\n\n/**\n * O(k) replacement for a whole-doc count diff: a tracked node added, removed, or\n * replaced shows up in some step's changed range or inserted slice. Scans each\n * step against its own before-doc (`tr.docs[i]`); mark/attr steps can't change\n * node structure, so they're skipped.\n */\nexport function transactionAffectsTrackedNodes(\n  tr: Transaction,\n  targetNodeTypes: string[]\n): boolean {\n  if (!tr.docChanged) return false\n  for (let i = 0; i < tr.steps.length; i++) {\n    const step = tr.steps[i]\n    if (!(step instanceof ReplaceStep) && !(step instanceof ReplaceAroundStep)) continue\n    if (fragmentHasTrackedType(step.slice.content, targetNodeTypes)) return true\n    // tr.docs[i] is the doc before step i (PM keeps docs.length === steps.length),\n    // so step.from/to are in its coordinate space — never tr.before's.\n    if (rangeHasTrackedType(tr.docs[i], step.from, step.to, targetNodeTypes)) return true\n  }\n  return false\n}\n","import type { StyleLayoutOptions } from './layoutStyle'\n\nexport type { StyleLayoutOptions } from './layoutStyle'\n\nexport const applyStyles = (dom: HTMLElement, styles: StyleLayoutOptions): void => {\n  const style = dom.style as unknown as Record<string, string>\n  for (const [key, value] of Object.entries(styles)) {\n    if (value !== undefined && value !== null) {\n      style[key] = typeof value === 'number' ? `${value}px` : value\n    }\n  }\n}\n\nexport const generateShortId = (): string => {\n  const randomPart = Math.random().toString(36).slice(2, 7) // 5 random characters\n  const timePart = Date.now().toString(36).slice(-5) // last 5 characters of the current time\n  return `${randomPart}-${timePart}`\n}\n\n/** Drops null/false entries before writing boolean HTML attributes onto media tags. */\nexport function omitNullishAndFalse(attrs: Record<string, unknown>): Record<string, unknown> {\n  const result: Record<string, unknown> = {}\n  for (const [key, value] of Object.entries(attrs)) {\n    if (value !== null && value !== false) result[key] = value\n  }\n  return result\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { HIDE_RESIZE_GRIPPER_META } from '../extensions/decorationHelpers'\nimport { generateShortId } from './utils'\n\n/**\n * Gripper and toolbar resolve a node by first-match `[data-key-id]`, so parsing\n * mints a fresh id: pasted HTML would otherwise aim them at the original node.\n */\nexport const keyIdAttribute = (): { default: string | null; parseHTML: () => string } => ({\n  default: null,\n  parseHTML: () => generateShortId()\n})\n\nexport const applyNodeAttributes = (\n  editor: Editor,\n  nodePos: number,\n  attributes: Record<string, string | number | null>\n): void => {\n  if (!editor.isEditable) return\n  const { state, dispatch } = editor.view\n  const transaction = state.tr\n  const node = transaction.doc.nodeAt(nodePos)\n\n  if (!node) return\n\n  Object.entries(attributes).forEach(([key, value]) => {\n    transaction.setNodeAttribute(nodePos, key, value)\n  })\n  transaction.setMeta(HIDE_RESIZE_GRIPPER_META, true)\n  dispatch(transaction)\n}\n\n/** Commit caption text without the hide-gripper meta — typing must not tear down the shell. */\nexport const setMediaCaption = (editor: Editor, nodePos: number, caption: string | null): void => {\n  if (!editor.isEditable) return\n  const { state, dispatch } = editor.view\n  if (!state.doc.nodeAt(nodePos)) return\n  dispatch(state.tr.setNodeAttribute(nodePos, 'caption', caption))\n}\n","import { DOMSerializer } from '@tiptap/pm/model'\n\nimport { getKitStorage } from '../kitStorage'\nimport { isDownloadableMedia, mediaKind } from '../utils/media-target'\nimport type { MediaActionContext } from './types'\n\n/** Downloadable local-asset kinds (image/audio/video); see `media-target.mediaKind`. */\nexport function isDownloadable(nodeType: string): boolean {\n  return isDownloadableMedia(nodeType)\n}\n\n/** Provider embeds (youtube/vimeo/soundcloud/loom/x) whose `src` is an external URL. */\nexport function isEmbedNode(nodeType: string): boolean {\n  return mediaKind(nodeType) === 'embed'\n}\n\nexport function isUploadedMedia(ctx: MediaActionContext): boolean {\n  return getKitStorage(ctx.editor).isUploadedMedia?.(ctx) ?? false\n}\n\n/** View Original: embeds always; uploaded image/video/audio hidden. */\nexport function canViewOriginal(ctx: MediaActionContext): boolean {\n  if (!ctx.attrs.src) return false\n  if (isEmbedNode(ctx.nodeType)) return true\n  return !isUploadedMedia(ctx)\n}\n\n/**\n * Guards the `window.open` sink. Not `isSafeMediaSrc`: that rejects `blob:`, a\n * legitimate src for uploaded media. Collab attrs are never re-validated.\n */\nconst isOpenableSrc = (src: string): boolean => /^(https?:|blob:|\\/)/i.test(src)\n\nexport function viewOriginalMedia(ctx: MediaActionContext): void {\n  const src = String(ctx.attrs.src ?? '')\n  if (isOpenableSrc(src)) window.open(src, '_blank', 'noopener,noreferrer')\n  ctx.close()\n}\n\nexport function removeMediaNode(ctx: MediaActionContext): void {\n  const node = ctx.editor.state.doc.nodeAt(ctx.nodePos)\n  if (!node || node.type.name !== ctx.nodeType) return\n  ctx.editor.view.dispatch(ctx.editor.state.tr.delete(ctx.nodePos, ctx.nodePos + node.nodeSize))\n  ctx.close()\n}\n\nfunction serializeNodeHTML(ctx: MediaActionContext): string | null {\n  const node = ctx.editor.state.doc.nodeAt(ctx.nodePos)\n  if (!node) return null\n  const serializer = DOMSerializer.fromSchema(ctx.editor.schema)\n  const wrapper = document.createElement('div')\n  wrapper.append(serializer.serializeNode(node))\n  return wrapper.innerHTML\n}\n\nexport async function copyMediaNode(ctx: MediaActionContext): Promise<void> {\n  const html = serializeNodeHTML(ctx)\n  const src = String(ctx.attrs.src ?? '')\n  try {\n    if (html && typeof ClipboardItem !== 'undefined' && navigator.clipboard?.write) {\n      await navigator.clipboard.write([\n        new ClipboardItem({\n          'text/html': new Blob([html], { type: 'text/html' }),\n          'text/plain': new Blob([src], { type: 'text/plain' })\n        })\n      ])\n    } else {\n      await navigator.clipboard.writeText(src)\n    }\n  } catch {\n    if (src) await navigator.clipboard?.writeText(src).catch(() => {})\n  }\n  ctx.close()\n}\n\nfunction filenameFromSrc(src: string): string {\n  try {\n    const name = new URL(src, window.location.href).pathname.split('/').pop()\n    return name && name.includes('.') ? name : 'media'\n  } catch {\n    return 'media'\n  }\n}\n\n/** Same-origin / blob URLs download; cross-origin without CORS falls back to opening a tab. */\nexport async function downloadMedia(ctx: MediaActionContext): Promise<void> {\n  const src = String(ctx.attrs.src ?? '')\n  if (!src) return\n  try {\n    const res = await fetch(src)\n    if (!res.ok) throw new Error(`download failed: ${res.status}`)\n    const blob = await res.blob()\n    const objectUrl = URL.createObjectURL(blob)\n    const a = document.createElement('a')\n    a.href = objectUrl\n    a.download = filenameFromSrc(src)\n    document.body.append(a)\n    a.click()\n    a.remove()\n    URL.revokeObjectURL(objectUrl)\n  } catch {\n    if (isOpenableSrc(src)) window.open(src, '_blank', 'noopener,noreferrer')\n  }\n  ctx.close()\n}\n\n/** Reveal + focus the inline caption editor (created by the node view). */\nexport function focusCaption(ctx: MediaActionContext): void {\n  const caption = ctx.wrapper.querySelector<HTMLElement>('.hm-caption')\n  if (!caption) return\n  caption.classList.remove('hm-caption--empty')\n  caption.focus()\n  const range = document.createRange()\n  range.selectNodeContents(caption)\n  range.collapse(false)\n  const sel = window.getSelection()\n  sel?.removeAllRanges()\n  sel?.addRange(range)\n}\n","const CONTENT_CLASS = /hypermultimedia--([a-z]+)__content/\nconst GRIPPER_CLASS = 'hypermultimedia__resize-gripper'\nconst GRIPPER_SELECTOR = `.${GRIPPER_CLASS}`\n\nfunction queryHTMLElement(root: ParentNode, selector: string): HTMLElement | null {\n  const el = root.querySelector(selector)\n  return el instanceof HTMLElement ? el : null\n}\n\nfunction queryGripperByKeyId(keyId: string, root: ParentNode): HTMLElement | null {\n  return queryHTMLElement(root, `${GRIPPER_SELECTOR}[data-media-key-id=\"${CSS.escape(keyId)}\"]`)\n}\n\nfunction queryMediaByKeyId(keyId: string, root: ParentNode): HTMLElement | null {\n  return queryHTMLElement(root, `[data-key-id=\"${CSS.escape(keyId)}\"]`)\n}\n\nfunction isGripper(el: Element | null): el is HTMLElement {\n  return el instanceof HTMLElement && el.classList.contains(GRIPPER_CLASS)\n}\n\nfunction isMediaWrapper(el: Element | null): el is HTMLElement {\n  // Inline the gripper class check: isGripper's type predicate would narrow `el` to never.\n  if (!(el instanceof HTMLElement) || el.classList.contains(GRIPPER_CLASS)) return false\n  if (el.classList.contains('hypermultimedia--image__content')) return true\n  if (el.hasAttribute('data-key-id')) return true\n  return CONTENT_CLASS.test(el.className)\n}\n\n/** Gripper widget for a media wrapper — keyId link first, validated sibling fallback. */\nexport function findGripperForMedia(\n  target: HTMLElement,\n  root: ParentNode = document\n): HTMLElement | null {\n  const keyId = target.getAttribute('data-key-id')\n  if (keyId) {\n    const linked = queryGripperByKeyId(keyId, root)\n    if (linked) return linked\n  }\n  const prev = target.previousElementSibling\n  return isGripper(prev) ? prev : null\n}\n\n/** Media wrapper for a gripper widget — keyId link first, validated sibling fallback. */\nexport function resolveMediaFromGripper(\n  gripper: HTMLElement,\n  root: ParentNode\n): HTMLElement | null {\n  const keyId = gripper.dataset.mediaKeyId\n  if (keyId) {\n    const linked = queryMediaByKeyId(keyId, root)\n    if (linked) return linked\n  }\n  const next = gripper.nextElementSibling\n  return isMediaWrapper(next) ? next : null\n}\n\n/** Block wrapper or image element that owns resize controls. */\nexport function findMediaTarget(\n  el: HTMLElement | null,\n  root: ParentNode = document\n): HTMLElement | null {\n  if (!el) return null\n\n  const gripper = el.closest(GRIPPER_SELECTOR)\n  if (gripper instanceof HTMLElement) {\n    const media = resolveMediaFromGripper(gripper, root)\n    if (media) return media\n  }\n\n  const imageRoot = el.closest('.hypermultimedia--image__content')\n  if (imageRoot instanceof HTMLElement) return imageRoot\n\n  const wrapper = el.closest('[class*=\"hypermultimedia--\"][class*=\"__content\"]')\n  return wrapper instanceof HTMLElement ? wrapper : null\n}\n\nexport function getMediaNodeType(target: HTMLElement): string | null {\n  if (target.closest('.hypermultimedia--image__content')) return 'image'\n  const fromClass = target.className.match(CONTENT_CLASS)?.[1]\n  if (fromClass) return fromClass\n  return target.getAttribute('data-node-name')\n}\n\n/** `asset` = downloadable local media; everything else is a provider embed. */\nconst ASSET_NODE_TYPES = new Set(['image', 'audio', 'video'])\n\nexport type MediaKind = 'asset' | 'embed'\n\nexport function mediaKind(nodeType: string | null): MediaKind {\n  return nodeType != null && ASSET_NODE_TYPES.has(nodeType) ? 'asset' : 'embed'\n}\n\nexport function isDownloadableMedia(nodeType: string | null): boolean {\n  return mediaKind(nodeType) === 'asset'\n}\n\n/** Embeds with in-frame controls (play, scrub) — provider embeds plus `video`. */\nexport function isInteractiveEmbed(nodeType: string | null): boolean {\n  if (!nodeType) return false\n  return mediaKind(nodeType) === 'embed' || nodeType === 'video'\n}\n\nexport function isInsideMediaControlsUI(el: HTMLElement | null): boolean {\n  // `[data-hm-toolbar]` is the structural lifecycle marker (mount.ts stamps it\n  // on built-in and custom bars alike), so custom factories are covered too.\n  return !!el?.closest('[data-hm-toolbar], .floating-popover, .hypermultimedia__resize-gripper')\n}\n\n// Cache the query: hover/out/click fire on every element boundary, so a fresh\n// matchMedia() per event is pure waste.\nconst finePointerQuery = typeof matchMedia === 'function' ? matchMedia('(pointer: fine)') : null\n\nexport function hasFinePointer(): boolean {\n  return finePointerQuery?.matches ?? false\n}\n","import type { Editor } from '@tiptap/core'\n\nexport interface FitBounds {\n  maxWidth: number\n  maxHeight?: number\n}\n\nexport function fitDimensionsToBounds(\n  width: number,\n  height: number,\n  bounds: FitBounds\n): { width: number; height: number } {\n  if (width <= 0 || height <= 0 || bounds.maxWidth <= 0) {\n    return { width, height }\n  }\n\n  const aspectRatio = width / height\n  let targetWidth = width\n  let targetHeight = height\n  const maxHeight = bounds.maxHeight ?? Number.POSITIVE_INFINITY\n\n  if (targetWidth > bounds.maxWidth) {\n    targetWidth = bounds.maxWidth\n    targetHeight = Math.round(targetWidth / aspectRatio)\n  }\n\n  if (targetHeight > maxHeight) {\n    targetHeight = maxHeight\n    targetWidth = Math.round(targetHeight * aspectRatio)\n  }\n\n  return { width: Math.round(targetWidth), height: Math.round(targetHeight) }\n}\n\n/** ProseMirror content width — caps insert + resize to the editable column. */\nexport function getEditorContentWidth(editor: Editor): number {\n  const root = editor.view.dom\n  const prose =\n    root instanceof HTMLElement && root.classList.contains('ProseMirror')\n      ? root\n      : (root.querySelector('.ProseMirror') as HTMLElement | null)\n  const target = prose ?? (root instanceof HTMLElement ? root : null)\n  if (target?.clientWidth) return target.clientWidth\n\n  return root.parentElement?.clientWidth ?? 0\n}\n\n/** Scale layout down when width exceeds the editable column (insert + resize). */\nexport function fitLayoutToEditorColumn(\n  editor: Editor,\n  width: number,\n  height: number\n): { width: number; height: number } {\n  const maxWidth = getEditorContentWidth(editor)\n  if (maxWidth <= 0 || width <= 0 || height <= 0) {\n    return { width, height }\n  }\n  return fitDimensionsToBounds(width, height, { maxWidth })\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { fitLayoutToEditorColumn } from './fitImageDimensions'\nimport type { StyleLayoutOptions } from './utils'\n\nexport interface EmbedNodeOptions {\n  inline: boolean\n  addPasteHandler: boolean\n  HTMLAttributes: Record<string, unknown>\n}\n\nexport interface FullscreenIframeKitOptions {\n  allow: string\n  frameborder: number\n  allowfullscreen: boolean\n}\n\nexport const EMBED_BASE_DEFAULTS = {\n  justifyContent: 'start',\n  margin: 'auto',\n  clear: 'none',\n  float: null,\n  display: 'block',\n  inline: false,\n  addPasteHandler: true,\n  HTMLAttributes: {}\n} as const satisfies StyleLayoutOptions &\n  Pick<EmbedNodeOptions, 'inline' | 'addPasteHandler' | 'HTMLAttributes'>\n\nexport const EMBED_BLOCK_LAYOUT_DEFAULTS = {\n  ...EMBED_BASE_DEFAULTS,\n  height: 480,\n  width: 640\n} as const satisfies StyleLayoutOptions &\n  Pick<EmbedNodeOptions, 'inline' | 'addPasteHandler' | 'HTMLAttributes'>\n\n/** Layout attrs on embed wrappers — shared by schema defaults and iframe node-view diffs. */\nexport const EMBED_LAYOUT_ATTR_KEYS = [\n  'height',\n  'width',\n  'display',\n  'float',\n  'clear',\n  'margin',\n  'justifyContent'\n] as const satisfies readonly (keyof StyleLayoutOptions)[]\n\n/** Live iframe element attrs; remount when these change on the node. */\nexport const FULLSCREEN_IFRAME_DOM_ATTR_KEYS = ['allow', 'frameborder', 'allowfullscreen'] as const\n\nexport const SOUNDCLOUD_IFRAME_DOM_ATTR_KEYS = ['scrolling', 'frameborder', 'allow'] as const\n\nexport function resolveEmbedOption<TOptions extends object, TKey extends keyof TOptions>(\n  attrs: Record<string, unknown>,\n  options: TOptions,\n  key: TKey\n): TOptions[TKey] | undefined {\n  const fromAttrs = attrs[key as string]\n  if (fromAttrs !== null && fromAttrs !== undefined) return fromAttrs as TOptions[TKey]\n  return options[key]\n}\n\nexport function embedAttrsEqual(\n  left: Record<string, unknown>,\n  right: Record<string, unknown>,\n  keys: readonly string[]\n): boolean {\n  return keys.every((key) => left[key] === right[key])\n}\n\nexport function defaultsFromOptions<TOptions extends object>(\n  options: TOptions,\n  keys: readonly (keyof TOptions)[]\n): Record<string, { default: unknown }> {\n  const result: Record<string, { default: unknown }> = {}\n  for (const key of keys) {\n    result[key as string] = { default: options[key] }\n  }\n  return result\n}\n\nexport function layoutAttrDefaults(\n  options: StyleLayoutOptions\n): Record<string, { default: unknown }> {\n  return defaultsFromOptions(options, EMBED_LAYOUT_ATTR_KEYS)\n}\n\n/** Node schema keys mirror `kitDefaults` — extend the kit interface and its defaults object together. */\nexport function kitAttrDefaults<TKit extends object>(\n  options: TKit,\n  kitDefaults: TKit\n): Record<string, { default: unknown }> {\n  return defaultsFromOptions(options, Object.keys(kitDefaults) as (keyof TKit)[])\n}\n\nexport function resolveEmbedLayoutDimensions(\n  editor: Editor,\n  options: { width?: number | string | null; height?: number | string | null },\n  defaults: Pick<StyleLayoutOptions, 'width' | 'height'>\n): { width: number; height: number } {\n  const width = Number(options.width ?? defaults.width)\n  const height = Number(options.height ?? defaults.height)\n  return fitLayoutToEditorColumn(editor, width, height)\n}\n\nexport function resolveFullscreenIframeAttributes(\n  attrs: Record<string, unknown>,\n  options: FullscreenIframeKitOptions,\n  width: number,\n  height: number\n): Record<string, string | number | boolean> {\n  return {\n    width,\n    height,\n    allow: String(resolveEmbedOption(attrs, options, 'allow') ?? options.allow),\n    frameborder: Number(resolveEmbedOption(attrs, options, 'frameborder') ?? options.frameborder),\n    allowfullscreen: Boolean(\n      resolveEmbedOption(attrs, options, 'allowfullscreen') ?? options.allowfullscreen\n    )\n  }\n}\n","import {\n  FULLSCREEN_IFRAME_DOM_ATTR_KEYS,\n  resolveEmbedOption,\n  resolveFullscreenIframeAttributes\n} from '../../utils/embedKit'\n\nexport interface LoomEmbedKitOptions {\n  autoplay: 0 | 1\n  muted: 0 | 1\n  hideOwner: boolean\n  hideShare: boolean\n  hideTitle: boolean\n  hideEmbedTopBar: boolean\n  allow: string\n  frameborder: number\n  allowfullscreen: boolean\n  scrolling: string\n}\n\ntype LoomEmbedParamKey = keyof Pick<\n  LoomEmbedKitOptions,\n  'autoplay' | 'muted' | 'hideOwner' | 'hideShare' | 'hideTitle' | 'hideEmbedTopBar'\n>\n\nconst LOOM_QUERY_PARAM: Record<LoomEmbedParamKey, string> = {\n  autoplay: 'autoplay',\n  muted: 'muted',\n  hideOwner: 'hide_owner',\n  hideShare: 'hide_share',\n  hideTitle: 'hide_title',\n  hideEmbedTopBar: 'hideEmbedTopBar'\n}\n\nconst LOOM_EMBED_PARAM_KEYS = Object.keys(LOOM_QUERY_PARAM) as LoomEmbedParamKey[]\n\nexport const LOOM_EMBED_KIT_DEFAULTS: LoomEmbedKitOptions = {\n  autoplay: 0,\n  muted: 0,\n  hideOwner: false,\n  hideShare: false,\n  hideTitle: false,\n  hideEmbedTopBar: false,\n  allow: 'fullscreen; picture-in-picture',\n  frameborder: 0,\n  allowfullscreen: true,\n  scrolling: 'no'\n}\n\nconst LOOM_IFRAME_DOM_ATTR_KEYS = ['scrolling', ...FULLSCREEN_IFRAME_DOM_ATTR_KEYS] as const\n\nexport const LOOM_EMBED_ATTR_KEYS = [\n  'src',\n  ...LOOM_EMBED_PARAM_KEYS,\n  ...LOOM_IFRAME_DOM_ATTR_KEYS\n] as const\n\nexport const buildLoomEmbedUrl = (\n  url: string,\n  attrs: Record<string, unknown>,\n  options: LoomEmbedKitOptions,\n  normalize: (input: string) => string | null\n): string | null => {\n  const base = normalize(url)\n  if (!base) return null\n\n  const embedUrl = new URL(base)\n\n  for (const optionKey of LOOM_EMBED_PARAM_KEYS) {\n    const value = resolveEmbedOption(attrs, options, optionKey)\n    if (value === undefined || value === null) continue\n\n    const formatted =\n      optionKey === 'autoplay' || optionKey === 'muted' ? String(value) : value ? 'true' : 'false'\n    embedUrl.searchParams.set(LOOM_QUERY_PARAM[optionKey], formatted)\n  }\n\n  return embedUrl.toString()\n}\n\nexport const resolveLoomIframeAttributes = (\n  attrs: Record<string, unknown>,\n  options: LoomEmbedKitOptions,\n  width: number,\n  height: number\n): Record<string, string | number | boolean> => ({\n  ...resolveFullscreenIframeAttributes(attrs, options, width, height),\n  scrolling: String(resolveEmbedOption(attrs, options, 'scrolling') ?? options.scrolling)\n})\n","const LOOM_REGEX = /https?:\\/\\/(www\\.)?loom\\.com\\/(share|embed)\\/[a-zA-Z0-9]+(?:\\?[^\\s]*)?/\nexport const LOOM_REGEX_GLOBAL = new RegExp(LOOM_REGEX.source, 'g')\n\nexport const isValidLoomUrl = (url: string): boolean => LOOM_REGEX.test(url)\n\nexport const getLoomVideoId = (url: string | null | undefined): string | null => {\n  if (!url) return null\n  const match = url.match(/loom\\.com\\/(?:share|embed)\\/([a-zA-Z0-9]+)/)\n  return match ? match[1] : null\n}\n\n/** Normalize any Loom share/embed URL to the canonical embed form (id only). */\nexport const getLoomEmbedUrl = (url: string | null | undefined): string | null => {\n  const id = getLoomVideoId(url)\n  return id ? `https://www.loom.com/embed/${id}` : null\n}\n\nexport {\n  buildLoomEmbedUrl,\n  LOOM_EMBED_ATTR_KEYS,\n  LOOM_EMBED_KIT_DEFAULTS,\n  type LoomEmbedKitOptions\n} from './embedOptions'\n","const SOUNDCLOUD_URL_PATTERN =\n  /^(https?:\\/\\/)?(www\\.)?soundcloud\\.com\\/[A-Za-z0-9_-]+(\\/[A-Za-z0-9_-]+)*(\\/sets)?(\\/[A-Za-z0-9_-]+)?(\\/?)?(\\?[A-Za-z0-9_=&-]+)?$/\n\nexport const SOUNDCLOUD_URL_REGEX_GLOBAL = new RegExp(SOUNDCLOUD_URL_PATTERN.source, 'g')\n\nexport function isValidSoundCloudUrl(url: string): boolean {\n  return SOUNDCLOUD_URL_PATTERN.test(url)\n}\n","import { isFloatedMedia, maxWidthForFloatedMedia } from './media-placement'\n\n/** Layout attrs shared by node options, live DOM, and export HTML. */\nexport type StyleLayoutOptions = {\n  width?: number | string | null\n  height?: number | string | null\n  float?: string | null\n  clear?: string | null\n  margin?: string | null\n  display?: string | null\n  justifyContent?: string | null\n}\n\nexport type MediaLayoutStyleMode = 'live' | 'export-embed' | 'export-html' | 'export-image-block'\n\n/** CamelCase bag — live DOM assigns directly; CSS export maps to kebab. */\ntype LayoutStyleBag = {\n  display: string | null\n  width: string | null\n  height: string | null\n  float: string | null\n  clear: string | null\n  margin: string | null\n  justifyContent: string | null\n  maxWidth: string | null\n}\n\nconst CSS_PROP: { [K in keyof LayoutStyleBag]: string } = {\n  display: 'display',\n  width: 'width',\n  height: 'height',\n  float: 'float',\n  clear: 'clear',\n  margin: 'margin',\n  justifyContent: 'justify-content',\n  maxWidth: 'max-width'\n}\n\nconst EMPTY_BAG: LayoutStyleBag = {\n  display: null,\n  width: null,\n  height: null,\n  float: null,\n  clear: null,\n  margin: null,\n  justifyContent: null,\n  maxWidth: null\n}\n\nconst pixelOrNull = (value: number | string | null | undefined): string | null => {\n  const parsed = parseInt(String(value), 10)\n  return Number.isFinite(parsed) ? `${parsed}px` : null\n}\n\nfunction resolvePlacement(attrs: StyleLayoutOptions, defaults?: StyleLayoutOptions) {\n  const float = attrs.float ?? defaults?.float ?? null\n  const margin = attrs.margin ?? defaults?.margin ?? null\n  const clear = attrs.clear ?? defaults?.clear ?? null\n  const floated = isFloatedMedia(float)\n  return {\n    float,\n    margin,\n    clear,\n    floated,\n    maxWidth: floated ? maxWidthForFloatedMedia(margin) : null\n  }\n}\n\n/** Attrs → layout bag. Float max-width and layout props live here only. */\nexport function mediaLayoutStyleProps(\n  attrs: StyleLayoutOptions,\n  mode: MediaLayoutStyleMode,\n  defaults?: StyleLayoutOptions\n): LayoutStyleBag {\n  const placement = resolvePlacement(attrs, defaults)\n\n  switch (mode) {\n    case 'live':\n      return {\n        ...EMPTY_BAG,\n        display: attrs.display ?? null,\n        width: pixelOrNull(attrs.width),\n        float: placement.float,\n        clear: placement.clear,\n        margin: placement.margin,\n        justifyContent: attrs.justifyContent ?? null,\n        maxWidth: placement.maxWidth\n      }\n    case 'export-embed':\n      return {\n        ...EMPTY_BAG,\n        display: attrs.display ?? 'block',\n        height: pixelOrNull(attrs.height),\n        width: pixelOrNull(attrs.width),\n        float: placement.float,\n        clear: placement.clear,\n        margin: placement.margin,\n        justifyContent: attrs.justifyContent ?? null,\n        maxWidth: placement.maxWidth\n      }\n    case 'export-html':\n      // Attrs come first: a committed per-node width/height must beat the kit default.\n      return {\n        ...EMPTY_BAG,\n        height: pixelOrNull(attrs.height) ?? pixelOrNull(defaults?.height),\n        width: pixelOrNull(attrs.width) ?? pixelOrNull(defaults?.width),\n        float: placement.float || null,\n        clear: placement.clear || null,\n        margin: placement.margin || null,\n        maxWidth: placement.maxWidth\n      }\n    case 'export-image-block': {\n      // Image block HTML omits unset / default sentinels.\n      return {\n        ...EMPTY_BAG,\n        float: placement.float && placement.float !== 'unset' ? placement.float : null,\n        clear: placement.clear && placement.clear !== 'none' ? placement.clear : null,\n        margin: placement.margin && placement.margin !== '0in' ? placement.margin : null,\n        maxWidth: placement.floated ? placement.maxWidth : null\n      }\n    }\n    default: {\n      const _exhaustive: never = mode\n      return _exhaustive\n    }\n  }\n}\n\n// `margin` and friends are read verbatim off pasted elements, so a value carrying\n// `;`, `}` or `url(` would inject extra declarations into the serialized style.\n// No `CSS.supports` here: renderHTML also runs under Node in the export pipeline.\nconst CSS_INJECTION_RE = /[;}]|url\\(/i\n\nexport function formatMediaLayoutCss(props: LayoutStyleBag): string {\n  return (Object.keys(CSS_PROP) as Array<keyof LayoutStyleBag>)\n    .filter((key) => props[key] != null && !CSS_INJECTION_RE.test(String(props[key])))\n    .map((key) => `${CSS_PROP[key]}:${props[key]};`)\n    .join(' ')\n}\n\nexport function mediaLayoutCss(\n  attrs: StyleLayoutOptions,\n  mode: MediaLayoutStyleMode,\n  defaults?: StyleLayoutOptions\n): string {\n  return formatMediaLayoutCss(mediaLayoutStyleProps(attrs, mode, defaults))\n}\n\n/**\n * Live node-view wrapper: commit layout attrs.\n * Clear `max-width` when not floated — otherwise wrap→inline keeps the float cap.\n */\nexport function applyMediaLayoutToDom(\n  dom: HTMLElement,\n  attrs: StyleLayoutOptions,\n  dims: { width: number },\n  options?: { justifyContent?: string }\n): void {\n  const bag = mediaLayoutStyleProps(\n    {\n      ...attrs,\n      width: dims.width,\n      justifyContent: options?.justifyContent ?? attrs.justifyContent ?? 'start'\n    },\n    'live'\n  )\n\n  if (bag.display) dom.style.display = bag.display\n  if (bag.width) dom.style.width = bag.width\n  if (bag.float != null) dom.style.float = bag.float\n  if (bag.clear) dom.style.clear = bag.clear\n  if (bag.margin) dom.style.margin = bag.margin\n  if (bag.justifyContent) dom.style.justifyContent = bag.justifyContent\n\n  if (bag.maxWidth) dom.style.maxWidth = bag.maxWidth\n  else dom.style.removeProperty('max-width')\n}\n\n/** Pick layout fields from ProseMirror attrs / HTMLAttribute bags. */\nexport function styleLayoutFromAttrs(attrs: Record<string, unknown>): StyleLayoutOptions {\n  return {\n    width: attrs.width as StyleLayoutOptions['width'],\n    height: attrs.height as StyleLayoutOptions['height'],\n    float: attrs.float as StyleLayoutOptions['float'],\n    clear: attrs.clear as StyleLayoutOptions['clear'],\n    margin: attrs.margin as StyleLayoutOptions['margin'],\n    display: attrs.display as StyleLayoutOptions['display'],\n    justifyContent: attrs.justifyContent as StyleLayoutOptions['justifyContent']\n  }\n}\n","import { EMBED_LAYOUT_ATTR_KEYS } from '../utils/embedKit'\nimport { applyMediaLayoutToDom, styleLayoutFromAttrs } from '../utils/layoutStyle'\n\nconst DEFAULT_LAYOUT_FALLBACK = { width: 640, height: 480 } as const\nexport const IMAGE_LAYOUT_FALLBACK = { width: 320, height: 240 } as const\nexport const AUDIO_LAYOUT_FALLBACK = { width: 450, height: 120 } as const\n\n/** Loading shell: fill the wrapper up to committed attrs; shrink with the column. */\nexport function syncResponsiveMediaHost(el: HTMLElement, width: number, height: number): void {\n  el.style.width = '100%'\n  el.style.maxWidth = `${width}px`\n  el.style.height = 'auto'\n  if (width > 0 && height > 0) {\n    el.style.aspectRatio = `${width} / ${height}`\n  } else {\n    el.style.removeProperty('aspect-ratio')\n  }\n}\n\n/** Media surface inside `.hm-media-slot` — host carries aspect-ratio. */\nexport function syncMediaSurfaceFill(el: HTMLElement, width: number, height: number): void {\n  el.style.width = '100%'\n  el.style.height = '100%'\n  el.style.removeProperty('max-width')\n  el.style.removeProperty('aspect-ratio')\n  if (el instanceof HTMLIFrameElement) {\n    el.setAttribute('width', String(width))\n    el.setAttribute('height', String(height))\n  }\n}\n\nexport function parseLayoutDimensions(\n  attrs: Record<string, unknown>,\n  fallback: { width: number; height: number } = DEFAULT_LAYOUT_FALLBACK\n): { width: number; height: number } {\n  const width = parseInt(String(attrs.width), 10)\n  const height = parseInt(String(attrs.height), 10)\n  return {\n    width: Number.isFinite(width) ? width : fallback.width,\n    height: Number.isFinite(height) ? height : fallback.height\n  }\n}\n\nexport function layoutAttrsChanged(\n  left: Record<string, unknown>,\n  right: Record<string, unknown>,\n  keys: readonly string[] = EMBED_LAYOUT_ATTR_KEYS\n): boolean {\n  return keys.some((key) => left[key] !== right[key])\n}\n\nexport interface SyncResizableMediaLayoutOptions {\n  width: number\n  height: number\n  loadingHost: HTMLElement\n  surfaces: HTMLElement[]\n  syncLoadingHost?: (el: HTMLElement, width: number, height: number) => void\n  syncSurface?: (el: HTMLElement, width: number, height: number) => void\n}\n\n/** Mirror committed layout attrs onto the loading shell and rendered media surfaces. */\nexport function syncResizableMediaLayout(options: SyncResizableMediaLayoutOptions): void {\n  const {\n    width,\n    height,\n    loadingHost,\n    surfaces,\n    syncLoadingHost = syncResponsiveMediaHost,\n    syncSurface = syncMediaSurfaceFill\n  } = options\n  syncLoadingHost(loadingHost, width, height)\n  for (const surface of surfaces) {\n    syncSurface(surface, width, height)\n  }\n}\n\nexport interface SyncMediaNodeLayoutOptions {\n  wrapper: HTMLElement\n  attrs: Record<string, unknown>\n  loadingHost: HTMLElement\n  surface: HTMLElement\n  dims?: { width: number; height: number }\n  fallback?: { width: number; height: number }\n  justifyContent?: string\n  syncLoadingHost?: (el: HTMLElement, width: number, height: number) => void\n  syncSurface?: (el: HTMLElement, width: number, height: number) => void\n}\n\n/** Mirror committed attrs onto wrapper, loading shell, and the rendered media surface. */\nexport function syncMediaNodeLayout(options: SyncMediaNodeLayoutOptions): void {\n  const dims =\n    options.dims ??\n    parseLayoutDimensions(options.attrs, options.fallback ?? DEFAULT_LAYOUT_FALLBACK)\n  // No fixed `height` on the wrapper: the loading shell + media surface carry the\n  // pixel height (syncResizableMediaLayout). The wrapper then grows to fit the\n  // caption below the media, instead of clipping the caption into the following\n  // paragraph.\n  applyMediaLayoutToDom(\n    options.wrapper,\n    styleLayoutFromAttrs(options.attrs),\n    { width: dims.width },\n    options.justifyContent ? { justifyContent: options.justifyContent } : undefined\n  )\n  syncResizableMediaLayout({\n    width: dims.width,\n    height: dims.height,\n    loadingHost: options.loadingHost,\n    surfaces: [options.surface],\n    syncLoadingHost: options.syncLoadingHost,\n    syncSurface: options.syncSurface\n  })\n}\n\nexport function syncImageNodeLayout(options: {\n  wrapper: HTMLElement\n  attrs: Record<string, unknown>\n  loadingHost: HTMLElement\n  img: HTMLImageElement\n  dims?: { width: number; height: number }\n}): void {\n  syncMediaNodeLayout({\n    wrapper: options.wrapper,\n    attrs: options.attrs,\n    loadingHost: options.loadingHost,\n    surface: options.img,\n    dims: options.dims,\n    fallback: IMAGE_LAYOUT_FALLBACK,\n    justifyContent: 'start'\n  })\n}\n\nexport function syncIframeNodeLayout(options: {\n  wrapper: HTMLElement\n  attrs: Record<string, unknown>\n  loadingHost: HTMLElement\n  iframe: HTMLIFrameElement\n  dims?: { width: number; height: number }\n  syncLoadingHost?: (el: HTMLElement, width: number, height: number) => void\n}): void {\n  syncMediaNodeLayout({\n    wrapper: options.wrapper,\n    attrs: options.attrs,\n    loadingHost: options.loadingHost,\n    surface: options.iframe,\n    dims: options.dims,\n    syncLoadingHost: options.syncLoadingHost\n  })\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { syncResponsiveMediaHost } from '../../loading/syncLayout'\nimport {\n  FULLSCREEN_IFRAME_DOM_ATTR_KEYS,\n  type FullscreenIframeKitOptions,\n  resolveEmbedOption\n} from '../../utils/embedKit'\nimport { fitLayoutToEditorColumn } from '../../utils/fitImageDimensions'\n\n/** Embeddable Spotify entities — the `{type}` segment of an `open.spotify.com` URL. */\nexport type SpotifyEntityType = 'track' | 'album' | 'playlist' | 'artist' | 'show' | 'episode'\n\nexport const SPOTIFY_ENTITY_TYPES = [\n  'track',\n  'album',\n  'playlist',\n  'artist',\n  'show',\n  'episode'\n] as const satisfies readonly SpotifyEntityType[]\n\n/** Spotify's two embed themes — `0` dark (default), `1` light. No other values exist. */\nexport type SpotifyTheme = 0 | 1\n\nconst SPOTIFY_ID = /^[A-Za-z0-9]+$/\n\nconst isSpotifyEntityType = (value: string): value is SpotifyEntityType =>\n  (SPOTIFY_ENTITY_TYPES as readonly string[]).includes(value)\n\n/**\n * Resolve a Spotify share URL or `spotify:` URI to its `{ type, id }`, or null.\n * Accepts `open.spotify.com/{type}/{id}`, localized `intl-xx`, already-`embed`\n * paths, and `spotify:type:id` URIs; rejects every other host (exact match).\n */\nexport const parseSpotifyEntity = (\n  input: string\n): { type: SpotifyEntityType; id: string } | null => {\n  const trimmed = input.trim()\n  if (!trimmed) return null\n\n  const uri = /^spotify:(track|album|playlist|artist|show|episode):([A-Za-z0-9]+)$/.exec(trimmed)\n  if (uri) return { type: uri[1] as SpotifyEntityType, id: uri[2] }\n\n  try {\n    const url = new URL(trimmed)\n    if (url.hostname.replace(/^www\\./i, '') !== 'open.spotify.com') return null\n\n    const segments = url.pathname.split('/').filter(Boolean)\n    let i = 0\n    if (segments[i] === 'embed') i += 1\n    if (segments[i]?.startsWith('intl-')) i += 1\n\n    const type = segments[i]\n    const id = segments[i + 1]\n    if (!type || !id || !isSpotifyEntityType(type) || !SPOTIFY_ID.test(id)) return null\n    return { type, id }\n  } catch {\n    return null\n  }\n}\n\n/** Canonical share URL for an entity — the stored `src`, without `/embed` or query. */\nexport const canonicalSpotifyUrl = (entity: { type: SpotifyEntityType; id: string }): string =>\n  `https://open.spotify.com/${entity.type}/${entity.id}`\n\nexport interface SpotifyEmbedKitOptions extends FullscreenIframeKitOptions {\n  /** `0` dark / `1` light. Unset uses Spotify's default (dark). */\n  theme?: SpotifyTheme\n}\n\nexport const SPOTIFY_DEFAULT_WIDTH = 640\n/** Full-art player (album / playlist / artist / show / episode). */\nexport const SPOTIFY_FULL_HEIGHT = 352\n/** Compact single-track player. */\nconst SPOTIFY_TRACK_HEIGHT = 152\n\n/** Spotify's generator uses the compact height for tracks and the full-art height for everything else. */\nexport const defaultSpotifyHeight = (type: SpotifyEntityType): number =>\n  type === 'track' ? SPOTIFY_TRACK_HEIGHT : SPOTIFY_FULL_HEIGHT\n\n/** Gripper + layout floor — entity type from stored share URL, else attrs.height, else full-art default. */\nexport function resolveSpotifyLayoutMinHeight(attrs: Record<string, unknown>): number {\n  const src = typeof attrs.src === 'string' ? attrs.src : ''\n  const entity = parseSpotifyEntity(src)\n  if (entity) return defaultSpotifyHeight(entity.type)\n\n  const height = attrs.height\n  if (typeof height === 'number' && height > 0) return height\n  return SPOTIFY_FULL_HEIGHT\n}\n\nexport const SPOTIFY_EMBED_KIT_DEFAULTS: SpotifyEmbedKitOptions = {\n  theme: undefined,\n  allow: 'autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture',\n  frameborder: 0,\n  allowfullscreen: true\n}\n\nexport const SPOTIFY_EMBED_ATTR_KEYS = ['src', 'theme', ...FULLSCREEN_IFRAME_DOM_ATTR_KEYS] as const\n\nexport const buildSpotifyEmbedUrl = (\n  src: string,\n  attrs: Record<string, unknown>,\n  options: SpotifyEmbedKitOptions\n): string | null => {\n  const entity = parseSpotifyEntity(src)\n  if (!entity) return null\n\n  const embedUrl = new URL(`https://open.spotify.com/embed/${entity.type}/${entity.id}`)\n  embedUrl.searchParams.set('utm_source', 'generator')\n\n  const theme = resolveEmbedOption(attrs, options, 'theme')\n  if (theme !== undefined) {\n    embedUrl.searchParams.set('theme', String(theme))\n  }\n\n  return embedUrl.toString()\n}\n\n/** The player has a fixed height and reflows by width — fit width to the column, keep the height. */\nexport const fitSpotifyLayoutToEditorColumn = (\n  editor: Editor,\n  width: number,\n  height: number\n): { width: number; height: number } => {\n  const fitted = fitLayoutToEditorColumn(editor, width, height)\n  return { width: fitted.width, height }\n}\n\n/** Spotify's player UI does not scale — pin host height so a narrow column never squashes it. */\nexport const syncSpotifyResponsiveHost = (el: HTMLElement, width: number, height: number): void => {\n  syncResponsiveMediaHost(el, width, height)\n  el.style.minHeight = `${height}px`\n}\n","import { parseSpotifyEntity } from './embedOptions'\n\nconst SPOTIFY_URL_REGEX =\n  /^https?:\\/\\/(www\\.)?open\\.spotify\\.com\\/(embed\\/)?(intl-[a-z-]+\\/)?(track|album|playlist|artist|show|episode)\\/[A-Za-z0-9]+(\\?\\S*)?$/i\nexport const SPOTIFY_URL_REGEX_GLOBAL = new RegExp(SPOTIFY_URL_REGEX.source, 'gi')\n\n/** Matches a pasted Spotify \"Copy embed\" `<iframe>`; capture group 1 is the embed src. */\nexport const SPOTIFY_EMBED_IFRAME_REGEX =\n  /<iframe[^>]*\\bsrc=[\"']([^\"']*open\\.spotify\\.com\\/embed\\/[^\"']+)[\"'][^>]*>(?:\\s*<\\/iframe>)?/gi\n\n/** True for any URL or `spotify:` URI that resolves to an embeddable entity. */\nexport const isValidSpotifyUrl = (url: string): boolean => parseSpotifyEntity(url) !== null\n\nexport {\n  buildSpotifyEmbedUrl,\n  canonicalSpotifyUrl,\n  defaultSpotifyHeight,\n  fitSpotifyLayoutToEditorColumn,\n  parseSpotifyEntity,\n  SPOTIFY_DEFAULT_WIDTH,\n  SPOTIFY_EMBED_ATTR_KEYS,\n  SPOTIFY_EMBED_KIT_DEFAULTS,\n  SPOTIFY_ENTITY_TYPES,\n  type SpotifyEmbedKitOptions,\n  type SpotifyEntityType,\n  type SpotifyTheme,\n  syncSpotifyResponsiveHost\n} from './embedOptions'\n","import { FULLSCREEN_IFRAME_DOM_ATTR_KEYS, resolveEmbedOption } from '../../utils/embedKit'\n\n/** Kit defaults and node attrs merged into Vimeo player iframe query params. */\n\nexport type VimeoBylinePortrait = boolean | 'site-default'\nexport type VimeoQuality = '240p' | '360p' | '540p' | '720p' | '1080p' | '2k' | '4k' | 'auto'\n\nexport interface VimeoPlayerKitOptions {\n  autopause: boolean\n  autoplay: boolean\n  background: boolean\n  byline: VimeoBylinePortrait\n  color?: string\n  controls: boolean\n  dnt: boolean\n  keyboard: boolean\n  loop: boolean\n  muted: boolean\n  pip: boolean\n  playsinline: boolean\n  portrait: VimeoBylinePortrait\n  quality: VimeoQuality\n  speed: boolean\n  texttrack?: string | false\n  title: boolean\n  transparent: boolean\n  endTime?: number\n  allow: string\n  frameborder: number\n  allowfullscreen: boolean\n}\n\ntype VimeoEmbedParamKey = keyof Pick<\n  VimeoPlayerKitOptions,\n  | 'autopause'\n  | 'autoplay'\n  | 'background'\n  | 'byline'\n  | 'color'\n  | 'controls'\n  | 'dnt'\n  | 'keyboard'\n  | 'loop'\n  | 'muted'\n  | 'pip'\n  | 'playsinline'\n  | 'portrait'\n  | 'quality'\n  | 'speed'\n  | 'texttrack'\n  | 'title'\n  | 'transparent'\n  | 'endTime'\n>\n\n/** Internal keys → query names in [Vimeo embed options](https://developer.vimeo.com/player/sdk/embed). */\nconst VIMEO_QUERY_PARAM: Record<VimeoEmbedParamKey, string> = {\n  autopause: 'autopause',\n  autoplay: 'autoplay',\n  background: 'background',\n  byline: 'byline',\n  color: 'color',\n  controls: 'controls',\n  dnt: 'dnt',\n  keyboard: 'keyboard',\n  loop: 'loop',\n  muted: 'muted',\n  pip: 'pip',\n  playsinline: 'playsinline',\n  portrait: 'portrait',\n  quality: 'quality',\n  speed: 'speed',\n  texttrack: 'texttrack',\n  title: 'title',\n  transparent: 'transparent',\n  endTime: 'end_time'\n}\n\nexport const VIMEO_PLAYER_KIT_DEFAULTS: VimeoPlayerKitOptions = {\n  autopause: true,\n  autoplay: false,\n  background: false,\n  byline: true,\n  color: undefined,\n  controls: true,\n  dnt: false,\n  keyboard: true,\n  loop: false,\n  muted: false,\n  pip: true,\n  playsinline: true,\n  portrait: true,\n  quality: 'auto',\n  speed: false,\n  texttrack: undefined,\n  title: true,\n  transparent: true,\n  endTime: undefined,\n  allow: 'autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media',\n  frameborder: 0,\n  allowfullscreen: true\n}\n\nexport const VIMEO_EMBED_ATTR_KEYS = [\n  'src',\n  'start',\n  ...(Object.keys(VIMEO_QUERY_PARAM) as VimeoEmbedParamKey[]),\n  ...FULLSCREEN_IFRAME_DOM_ATTR_KEYS\n] as const\n\nexport const parseVimeoVideoRef = (url: string): { id: string; h?: string } | null => {\n  try {\n    const parsed = new URL(url.trim())\n    const host = parsed.hostname.replace(/^www\\./i, '')\n\n    if (host === 'player.vimeo.com') {\n      const id = parsed.pathname.match(/\\/video\\/(\\d+)/)?.[1]\n      if (!id) return null\n      const h = parsed.searchParams.get('h') ?? undefined\n      return { id, h }\n    }\n\n    if (host === 'vimeo.com') {\n      const id = parsed.pathname.match(/\\/(\\d+)/)?.[1]\n      if (!id) return null\n      const h = parsed.searchParams.get('h') ?? undefined\n      return { id, h }\n    }\n  } catch {\n    return null\n  }\n\n  return null\n}\n\nconst formatVimeoParam = (value: boolean | string | number): string => {\n  if (value === 'site-default') return 'site-default'\n  if (typeof value === 'boolean') return value ? '1' : '0'\n  return String(value)\n}\n\nconst normalizeVimeoColor = (color: string): string => color.replace(/^#/, '')\n\nexport const buildVimeoEmbedUrl = (\n  url: string,\n  attrs: Record<string, unknown>,\n  options: VimeoPlayerKitOptions\n): string | null => {\n  const ref = parseVimeoVideoRef(url)\n  if (!ref) return null\n\n  const embedUrl = new URL(`https://player.vimeo.com/video/${ref.id}`)\n  if (ref.h) embedUrl.searchParams.set('h', ref.h)\n  ;(Object.keys(VIMEO_QUERY_PARAM) as VimeoEmbedParamKey[]).forEach((optionKey) => {\n    const value = resolveEmbedOption(attrs, options, optionKey)\n    if (value === undefined || value === null || value === '') return\n\n    const queryKey = VIMEO_QUERY_PARAM[optionKey]\n    if (optionKey === 'color' && typeof value === 'string') {\n      embedUrl.searchParams.set(queryKey, normalizeVimeoColor(value))\n      return\n    }\n    embedUrl.searchParams.set(queryKey, formatVimeoParam(value))\n  })\n\n  const start = attrs.start\n  if (typeof start === 'number' && start > 0) {\n    embedUrl.searchParams.set('start_time', String(start))\n  }\n\n  const width = attrs.width\n  const height = attrs.height\n  if (typeof width === 'number' && width > 0) {\n    embedUrl.searchParams.set('width', String(width))\n  }\n  if (typeof height === 'number' && height > 0) {\n    embedUrl.searchParams.set('height', String(height))\n  }\n\n  return embedUrl.toString()\n}\n","import { parseVimeoVideoRef } from './embedOptions'\n\n// Optional [/?#] tail keeps unlisted-video `?h=` params in the match; the (?=\\s|$)\n// boundary stops partial-ID claims like vimeo.com/123abc matching as /123.\nexport const VIMEO_REGEX_GLOBAL =\n  /https?:\\/\\/(www\\.)?(vimeo\\.com\\/\\d+|player\\.vimeo\\.com\\/video\\/\\d+)(?:[/?#][^\\s]*)?(?=\\s|$)/g\n\nexport const isValidVimeoUrl = (url: string): boolean => parseVimeoVideoRef(url) !== null\n\nexport {\n  buildVimeoEmbedUrl,\n  parseVimeoVideoRef,\n  VIMEO_EMBED_ATTR_KEYS,\n  VIMEO_PLAYER_KIT_DEFAULTS,\n  type VimeoBylinePortrait,\n  type VimeoPlayerKitOptions,\n  type VimeoQuality\n} from './embedOptions'\n","// Production logger kept (not stripped): sanitize/render failures must stay\n// visible in the field, and `console.error` survives the tsup `pure` policy.\nexport class Logger {\n  static error(message: string, error?: unknown): void {\n    console.error(`[HyperMultimedia] ${message}`, error)\n  }\n}\n","import { Logger } from './logger'\n\n// Tag/attribute allowlist sanitizer for untrusted embed HTML (X (formerly Twitter) oEmbed\n// before `innerHTML`). Disallowed tags collapse to a text-only span; on any\n// failure it falls back to text content so nothing executable survives.\nexport class HTMLSanitizer {\n  private static allowedTags = [\n    'div',\n    'span',\n    'button',\n    'a',\n    'strong',\n    'em',\n    'i',\n    'b',\n    'small',\n    'blockquote',\n    'p',\n    'br'\n  ]\n  private static allowedAttributes = ['class', 'href', 'title', 'data-*', 'aria-*', 'role']\n  // URL-bearing attrs must use a safe scheme even when the attr NAME is allowed.\n  private static SAFE_URL = /^(?:https?:|mailto:|tel:|\\/|#)/i\n\n  private static isSafeUrl(value: string): boolean {\n    const trimmed = value.trim()\n    if (/^\\/\\//.test(trimmed)) return false\n    return this.SAFE_URL.test(trimmed)\n  }\n\n  private static isAllowedHrefHost(value: string, allowedHosts: readonly string[]): boolean {\n    try {\n      const host = new URL(value.trim()).hostname.replace(/^www\\./i, '').replace(/^mobile\\./i, '')\n      return allowedHosts.some((allowed) => host === allowed)\n    } catch {\n      return false\n    }\n  }\n\n  static sanitize(html: string, options?: { hrefHosts?: readonly string[] }): string {\n    try {\n      // Parse inertly: DOMParser does not execute scripts or fire `<img onerror>`\n      // / `<svg onload>` handlers, so nothing runs before the scrub. [security: S3/S4]\n      const doc = new DOMParser().parseFromString(html, 'text/html')\n      this.sanitizeElement(doc.body, options?.hrefHosts)\n      return doc.body.innerHTML\n    } catch (error) {\n      Logger.error('HTML sanitization failed', error)\n      const div = document.createElement('div')\n      div.textContent = html\n      return div.innerHTML\n    }\n  }\n\n  private static sanitizeElement(element: Element, hrefHosts?: readonly string[]): void {\n    const children = Array.from(element.children)\n\n    children.forEach((child) => {\n      if (!this.allowedTags.includes(child.tagName.toLowerCase())) {\n        const span = document.createElement('span')\n        span.textContent = child.textContent || ''\n        child.parentNode?.replaceChild(span, child)\n        return\n      }\n\n      Array.from(child.attributes).forEach((attr) => {\n        const name = attr.name.toLowerCase()\n        if (!this.isAttributeAllowed(name)) {\n          child.removeAttribute(attr.name)\n          return\n        }\n        if (name === 'href') {\n          if (!this.isSafeUrl(attr.value)) {\n            child.removeAttribute(attr.name)\n            return\n          }\n          if (hrefHosts && !this.isAllowedHrefHost(attr.value, hrefHosts)) {\n            child.removeAttribute(attr.name)\n          }\n        }\n      })\n\n      this.sanitizeElement(child, hrefHosts)\n    })\n  }\n\n  private static isAttributeAllowed(attrName: string): boolean {\n    return this.allowedAttributes.some((allowed) => {\n      if (allowed.endsWith('*')) {\n        return attrName.startsWith(allowed.slice(0, -1))\n      }\n      return attrName === allowed\n    })\n  }\n}\n","import { HTMLSanitizer } from '../../utils/sanitizeHtml'\n\n/** Matches x.com / twitter.com status URLs, including share params like `?s=20`;\n * the (?=\\s|$) boundary lets bare mid-sentence URLs match without eating trailing text. */\nexport const X_URL_REGEX_GLOBAL =\n  /(?:https?:\\/\\/)?(?:www\\.|mobile\\.)?(?:x\\.com|twitter\\.com)\\/[A-Za-z0-9_]{1,15}\\/status\\/[0-9]+(?:[/?#][^\\s]*)?(?=\\s|$)/gi\n\nconst X_OEMBED_HREF_HOSTS = ['x.com', 'twitter.com'] as const\n\n/** Canonical `https://x.com/{user}/status/{id}` or null when not a status URL. */\nexport function normalizeXUrl(raw: string): string | null {\n  const trimmed = raw.trim()\n  if (!trimmed) return null\n\n  try {\n    const withProtocol = /^https?:\\/\\//i.test(trimmed) ? trimmed : `https://${trimmed}`\n    const parsed = new URL(withProtocol)\n    const host = parsed.hostname.replace(/^www\\./i, '').replace(/^mobile\\./i, '')\n    if (host !== 'x.com' && host !== 'twitter.com') return null\n\n    const match = parsed.pathname.match(/^\\/([A-Za-z0-9_]{1,15})\\/status\\/([0-9]+)\\/?$/i)\n    if (!match) return null\n\n    return `https://x.com/${match[1]}/status/${match[2]}`\n  } catch {\n    return null\n  }\n}\n\nexport function isValidXUrl(url: string): boolean {\n  return normalizeXUrl(url) !== null\n}\n\ninterface TwttrWidgets {\n  load: (element: HTMLElement) => void\n}\n\ninterface Twttr {\n  ready: (callback: () => void) => void\n  widgets: TwttrWidgets\n}\n\n// No `declare global` Window augmentation: the shipped .d.ts must not mutate\n// consumers' Window type or collide with their own `twttr` declarations.\nconst getTwttr = (): Twttr | undefined => (window as Window & { twttr?: Twttr }).twttr\n\nconst X_SCRIPT_POLL_MS = 200\nconst X_SCRIPT_TIMEOUT_MS = 10_000\nconst X_LAYOUT_TIMEOUT_MS = 8_000\nconst X_LAYOUT_MIN_HEIGHT_PX = 50\nconst X_LAYOUT_STABLE_FRAMES = 2\n\nfunction hasRenderableXEmbed(wrapper: HTMLElement): boolean {\n  const blockquote = wrapper.querySelector('blockquote.twitter-tweet')\n  if (blockquote) {\n    const href = blockquote.querySelector('a')?.getAttribute('href') ?? ''\n    if (normalizeXUrl(href)) return true\n  }\n  return wrapper.querySelector('iframe') != null\n}\n\nfunction measureEmbedHeight(element: HTMLElement): number {\n  return Math.max(\n    Math.ceil(element.scrollHeight),\n    Math.ceil(element.getBoundingClientRect().height)\n  )\n}\n\ninterface XEmbedHeightSyncOptions {\n  /** Reserved shell height before the widget reports a real size. */\n  minHeight: number\n  /** When true, host uses fluid/auto height and only min-height tracks growth. */\n  isFluid?: () => boolean\n}\n\n/** Tracks widget layout until dispose — pending fixed height, then fluid min-height. */\nexport function watchXEmbedHeight(\n  wrapper: HTMLElement,\n  host: HTMLElement,\n  options: XEmbedHeightSyncOptions\n): () => void {\n  const apply = (height: number) => {\n    const floor = Math.max(options.minHeight, height)\n    if (options.isFluid?.()) {\n      host.style.minHeight = `${floor}px`\n      return\n    }\n    host.style.height = `${floor}px`\n  }\n\n  const measure = () => {\n    const h = measureEmbedHeight(wrapper)\n    if (h > 0) apply(h)\n  }\n\n  const ro = new ResizeObserver(measure)\n  ro.observe(wrapper)\n  const mo = new MutationObserver(measure)\n  mo.observe(wrapper, { childList: true, subtree: true, attributes: true })\n  measure()\n\n  return () => {\n    ro.disconnect()\n    mo.disconnect()\n  }\n}\n\nfunction delayMs(ms: number, signal?: AbortSignal): Promise<void> {\n  if (signal?.aborted) return Promise.resolve()\n  return new Promise((resolve) => {\n    const id = window.setTimeout(() => {\n      signal?.removeEventListener('abort', onAbort)\n      resolve()\n    }, ms)\n    const onAbort = () => {\n      window.clearTimeout(id)\n      resolve()\n    }\n    signal?.addEventListener('abort', onAbort, { once: true })\n  })\n}\n\nasync function resolveXEmbedMeasureTarget(\n  wrapper: HTMLElement,\n  signal?: AbortSignal\n): Promise<HTMLElement> {\n  const deadline = Date.now() + 6_000\n  while (Date.now() < deadline) {\n    if (signal?.aborted) return wrapper\n    const iframe = wrapper.querySelector('iframe')\n    if (iframe instanceof HTMLIFrameElement) return iframe\n    await delayMs(100, signal)\n  }\n  return wrapper\n}\n\nasync function waitForStableSize(element: HTMLElement, signal?: AbortSignal): Promise<void> {\n  if (signal?.aborted) return\n\n  await new Promise<void>((resolve) => {\n    let lastHeight = 0\n    let stableFrames = 0\n    let finished = false\n\n    const finish = () => {\n      if (finished) return\n      finished = true\n      ro.disconnect()\n      window.clearTimeout(timeoutId)\n      signal?.removeEventListener('abort', finish)\n      resolve()\n    }\n\n    const timeoutId = window.setTimeout(finish, X_LAYOUT_TIMEOUT_MS)\n\n    const ro = new ResizeObserver(() => {\n      const h = Math.ceil(element.getBoundingClientRect().height)\n      if (h < X_LAYOUT_MIN_HEIGHT_PX) return\n      if (h === lastHeight) {\n        stableFrames++\n        if (stableFrames >= X_LAYOUT_STABLE_FRAMES) finish()\n      } else {\n        lastHeight = h\n        stableFrames = 0\n      }\n    })\n\n    ro.observe(element)\n    signal?.addEventListener('abort', finish, { once: true })\n  })\n}\n\nasync function waitForXEmbedStable(wrapper: HTMLElement, signal?: AbortSignal): Promise<void> {\n  const target = await resolveXEmbedMeasureTarget(wrapper, signal)\n  await waitForStableSize(target, signal)\n}\n\nfunction whenTwttrReady(signal?: AbortSignal): Promise<Twttr> {\n  if (signal?.aborted) {\n    return Promise.reject(new DOMException('Aborted', 'AbortError'))\n  }\n\n  const existing = getTwttr()\n  if (existing?.widgets) {\n    return Promise.resolve(existing)\n  }\n\n  return new Promise((resolve, reject) => {\n    let pollId: ReturnType<typeof setInterval> | undefined\n    let timeoutId: ReturnType<typeof setTimeout> | undefined\n    let settled = false\n\n    const cleanup = () => {\n      if (pollId != null) clearInterval(pollId)\n      if (timeoutId != null) clearTimeout(timeoutId)\n      signal?.removeEventListener('abort', onAbort)\n    }\n\n    const finish = (fn: () => void) => {\n      if (settled) return\n      settled = true\n      cleanup()\n      fn()\n    }\n\n    const onAbort = () => finish(() => reject(new DOMException('Aborted', 'AbortError')))\n\n    timeoutId = setTimeout(() => {\n      finish(() => reject(new Error('X widgets script timed out')))\n    }, X_SCRIPT_TIMEOUT_MS)\n\n    signal?.addEventListener('abort', onAbort, { once: true })\n\n    const resolveIfReady = () => {\n      const twttr = getTwttr()\n      if (twttr?.widgets) {\n        finish(() => resolve(twttr))\n        return true\n      }\n      return false\n    }\n\n    if (resolveIfReady()) return\n\n    getTwttr()?.ready?.(() => {\n      if (signal?.aborted) {\n        finish(() => reject(new DOMException('Aborted', 'AbortError')))\n        return\n      }\n      if (!resolveIfReady()) {\n        finish(() => reject(new Error('X widgets script loaded without twttr.widgets')))\n      }\n    })\n\n    pollId = setInterval(() => {\n      resolveIfReady()\n    }, X_SCRIPT_POLL_MS)\n  })\n}\n\nfunction loadXScript(signal?: AbortSignal): Promise<Twttr> {\n  if (signal?.aborted) {\n    return Promise.reject(new DOMException('Aborted', 'AbortError'))\n  }\n\n  const existingScript = document.querySelector(\n    'script[src=\"https://platform.twitter.com/widgets.js\"]'\n  )\n\n  if (existingScript || getTwttr()) {\n    return whenTwttrReady(signal)\n  }\n\n  return new Promise((resolve, reject) => {\n    const script = document.createElement('script')\n    script.src = 'https://platform.twitter.com/widgets.js'\n    script.async = true\n    script.onload = () => {\n      whenTwttrReady(signal).then(resolve).catch(reject)\n    }\n    script.onerror = () => reject(new Error('Failed to load X widgets script'))\n    document.head.append(script)\n  })\n}\n\nfunction sanitizeXEmbedHtml(html: string): string {\n  return HTMLSanitizer.sanitize(html, { hrefHosts: X_OEMBED_HREF_HOSTS })\n}\n\n/** Minimal blockquote widgets.js needs when oEmbed is empty or scrubbed away. */\nfunction seedXEmbedMarkup(wrapper: HTMLElement, statusUrl: string): void {\n  if (hasRenderableXEmbed(wrapper)) return\n\n  const url = normalizeXUrl(statusUrl)\n  if (!url) return\n\n  wrapper.replaceChildren()\n  const blockquote = document.createElement('blockquote')\n  blockquote.className = 'twitter-tweet'\n  const anchor = document.createElement('a')\n  anchor.href = url\n  anchor.textContent = url\n  blockquote.append(anchor)\n  wrapper.append(blockquote)\n}\n\nasync function fetchOEmbedHtml(\n  params: Record<string, string | number>,\n  signal?: AbortSignal\n): Promise<string> {\n  const urlParams = new URLSearchParams(\n    Object.entries(params).map(([key, value]) => [key, String(value)])\n  )\n  const urlWithParams = `https://publish.x.com/oembed?${urlParams.toString()}`\n\n  const response = await fetch(urlWithParams, { signal })\n\n  if (!response.ok) {\n    throw new Error('Failed to fetch oEmbed HTML')\n  }\n\n  const data = (await response.json()) as { html?: string }\n  return data.html ?? ''\n}\n\n/** Returns true when the wrapper has embed markup after oEmbed and/or widgets.js. */\nexport async function mountXEmbed(\n  wrapper: HTMLDivElement,\n  params: Record<string, string | number>,\n  signal?: AbortSignal\n): Promise<boolean> {\n  if (signal?.aborted) return false\n\n  const statusUrl = String(params.url ?? '')\n\n  try {\n    const html = await fetchOEmbedHtml(params, signal)\n    if (signal?.aborted) return false\n    wrapper.innerHTML = sanitizeXEmbedHtml(html)\n  } catch {\n    // oEmbed may fail; fall back to a blockquote seed for widgets.js.\n  }\n\n  seedXEmbedMarkup(wrapper, statusUrl)\n\n  if (signal?.aborted) return false\n\n  try {\n    const twttr = await loadXScript(signal)\n    if (signal?.aborted) return false\n    twttr.widgets.load(wrapper)\n    await waitForXEmbedStable(wrapper, signal)\n  } catch {\n    if (signal?.aborted) return false\n    return hasRenderableXEmbed(wrapper)\n  }\n\n  if (signal?.aborted) return false\n  return hasRenderableXEmbed(wrapper)\n}\n","import { FULLSCREEN_IFRAME_DOM_ATTR_KEYS, resolveEmbedOption } from '../../utils/embedKit'\n\n/** Kit defaults and node attrs merged into official YouTube iframe query params. */\n\nexport type YoutubeEmbedColor = 'red' | 'white'\nexport type YoutubeListType = 'playlist' | 'user_uploads'\n\nexport interface YoutubePlayerKitOptions {\n  autoplay: 0 | 1\n  ccLanguage?: string\n  ccLoadPolicy?: 0 | 1\n  color?: YoutubeEmbedColor\n  controls: 0 | 1\n  disableKBcontrols: 0 | 1\n  enableIFrameApi: 0 | 1\n  endTime?: number\n  fs: 0 | 1\n  interfaceLanguage?: string\n  ivLoadPolicy: 1 | 3\n  loop: 0 | 1\n  modestbranding: 0 | 1\n  nocookie: boolean\n  origin?: string\n  playlist?: string\n  playsinline: 0 | 1\n  rel: 0 | 1\n  list?: string\n  listType?: YoutubeListType\n  allow: string\n  frameborder: number\n  allowfullscreen: boolean\n}\n\ntype YoutubeEmbedParamKey = keyof Pick<\n  YoutubePlayerKitOptions,\n  | 'autoplay'\n  | 'ccLanguage'\n  | 'ccLoadPolicy'\n  | 'color'\n  | 'controls'\n  | 'disableKBcontrols'\n  | 'enableIFrameApi'\n  | 'endTime'\n  | 'fs'\n  | 'interfaceLanguage'\n  | 'ivLoadPolicy'\n  | 'loop'\n  | 'modestbranding'\n  | 'origin'\n  | 'playlist'\n  | 'playsinline'\n  | 'rel'\n  | 'list'\n  | 'listType'\n>\n\n/** Internal option keys → query names documented by YouTube. */\nconst YOUTUBE_QUERY_PARAM: Record<YoutubeEmbedParamKey, string> = {\n  autoplay: 'autoplay',\n  ccLanguage: 'cc_lang_pref',\n  ccLoadPolicy: 'cc_load_policy',\n  color: 'color',\n  controls: 'controls',\n  disableKBcontrols: 'disablekb',\n  enableIFrameApi: 'enablejsapi',\n  endTime: 'end',\n  fs: 'fs',\n  interfaceLanguage: 'hl',\n  ivLoadPolicy: 'iv_load_policy',\n  loop: 'loop',\n  modestbranding: 'modestbranding',\n  origin: 'origin',\n  playlist: 'playlist',\n  playsinline: 'playsinline',\n  rel: 'rel',\n  list: 'list',\n  listType: 'listType'\n}\n\nexport const YOUTUBE_PLAYER_KIT_DEFAULTS: YoutubePlayerKitOptions = {\n  autoplay: 0,\n  ccLanguage: undefined,\n  ccLoadPolicy: undefined,\n  color: undefined,\n  controls: 1,\n  disableKBcontrols: 0,\n  enableIFrameApi: 0,\n  endTime: undefined,\n  fs: 1,\n  interfaceLanguage: undefined,\n  ivLoadPolicy: 1,\n  loop: 0,\n  modestbranding: 0,\n  nocookie: false,\n  origin: undefined,\n  playlist: undefined,\n  playsinline: 0,\n  rel: 1,\n  list: undefined,\n  listType: undefined,\n  allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',\n  frameborder: 0,\n  allowfullscreen: true\n}\n\nexport const parseYoutubeVideoId = (url: string): string | null => {\n  try {\n    const parsed = new URL(url.trim())\n    const host = parsed.hostname.replace(/^www\\./i, '')\n\n    if (host === 'youtu.be') {\n      const id = parsed.pathname.split('/').filter(Boolean)[0]\n      return id ?? null\n    }\n\n    // Exact-host match only: `includes` would accept youtube.com.evil.example.\n    if (host === 'youtube.com' || host.endsWith('.youtube.com')) {\n      if (parsed.pathname.startsWith('/embed/') || parsed.pathname.startsWith('/shorts/')) {\n        return parsed.pathname.split('/')[2] ?? null\n      }\n      const fromQuery = parsed.searchParams.get('v')\n      if (fromQuery) return fromQuery\n    }\n  } catch {\n    return null\n  }\n\n  return null\n}\n\n/** Reads `t`, `start`, or `#t=` from a pasted watch URL (seconds). */\nexport const parseYoutubeStartSeconds = (url: string): number | undefined => {\n  try {\n    const parsed = new URL(url.trim())\n    const startParam = parsed.searchParams.get('start') ?? parsed.searchParams.get('t')\n    if (startParam) {\n      const asNumber = Number(startParam)\n      if (!Number.isNaN(asNumber)) return asNumber\n    }\n    const hashMatch = parsed.hash.match(/t=(\\d+)/)\n    if (hashMatch?.[1]) return Number(hashMatch[1])\n  } catch {\n    return undefined\n  }\n  return undefined\n}\n\nexport const getYoutubeEmbedHost = (nocookie: boolean): string =>\n  nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'\n\nexport const YOUTUBE_EMBED_ATTR_KEYS = [\n  'src',\n  'start',\n  'nocookie',\n  ...(Object.keys(YOUTUBE_QUERY_PARAM) as YoutubeEmbedParamKey[]),\n  ...FULLSCREEN_IFRAME_DOM_ATTR_KEYS\n] as const\n\nexport const buildYoutubeEmbedUrl = (\n  url: string,\n  attrs: Record<string, unknown>,\n  options: YoutubePlayerKitOptions\n): string | null => {\n  const videoId = parseYoutubeVideoId(url)\n  if (!videoId) return null\n\n  const nocookie = Boolean(resolveEmbedOption(attrs, options, 'nocookie'))\n  const embedUrl = new URL(`${getYoutubeEmbedHost(nocookie)}${videoId}`)\n\n  const appendParam = (queryKey: string, value: string | number) => {\n    embedUrl.searchParams.set(queryKey, String(value))\n  }\n\n  ;(Object.keys(YOUTUBE_QUERY_PARAM) as YoutubeEmbedParamKey[]).forEach((optionKey) => {\n    const value = resolveEmbedOption(attrs, options, optionKey)\n    if (value === undefined || value === null || value === '') return\n    appendParam(YOUTUBE_QUERY_PARAM[optionKey], value)\n  })\n\n  const startAttr = attrs.start\n  if (typeof startAttr === 'number' && startAttr > 0) {\n    appendParam('start', startAttr)\n  }\n\n  const loop = resolveEmbedOption(attrs, options, 'loop') ?? 0\n  if (loop === 1 && !embedUrl.searchParams.has('playlist')) {\n    appendParam('playlist', videoId)\n  }\n\n  return embedUrl.toString()\n}\n","import { parseYoutubeVideoId } from './embedOptions'\n\nexport const YOUTUBE_REGEX_GLOBAL =\n  /^(https?:\\/\\/)?(www\\.|music\\.)?(youtube\\.com|youtu\\.be)(?!.*\\/channel\\/)(?!\\/@)(.+)?$/g\n\nexport const isValidYoutubeUrl = (url: string): boolean => parseYoutubeVideoId(url) !== null\n\nexport {\n  buildYoutubeEmbedUrl,\n  getYoutubeEmbedHost,\n  parseYoutubeStartSeconds,\n  parseYoutubeVideoId,\n  YOUTUBE_EMBED_ATTR_KEYS,\n  YOUTUBE_PLAYER_KIT_DEFAULTS,\n  type YoutubeEmbedColor,\n  type YoutubeListType,\n  type YoutubePlayerKitOptions\n} from './embedOptions'\n","// File-extension matchers for raw video/audio URLs. No `g` flag: a sticky\n// lastIndex would make repeat `.test()` calls flaky. These back `detectMediaType`\n// but not `isMediaUrl` — pasted `.mp4`/`.mp3` URLs stay links (see isMediaUrl).\nconst videoUrlRegex =\n  /^(?:(?:https?|ftp):\\/\\/(?:www\\.)?[^/]+\\/|\\/|\\.\\.?\\/)?[\\w\\-/\\\\]+\\.(mp4|avi|mov|wmv|flv|mkv|3gp|3g2|asf|divx|m4v|mpg|m2v|m4p|mts|m2ts|ogv|rm|rmvb|ts|vob|webm|qt|f4v)$/i\n\nconst audioUrlRegex =\n  /^(?:(?:https?|ftp):\\/\\/(?:www\\.)?[^/]+\\/|\\/|\\.\\.?\\/)?[\\w\\-/\\\\]+\\.(mp3|wav|aac|flac|ogg|m4a|aiff|ape|asf|m4p|m4b|mp2|mpc|wma|webm|opus|ra|rm|wavpack|wv)$/i\n\nexport const isVideoUrl = (url: string): boolean => videoUrlRegex.test(url)\n\nexport const isAudioUrl = (url: string): boolean => audioUrlRegex.test(url)\n\n// Twin of `extension-hyperlink`'s copy. Adding a scheme to one and not the\n// other opens a hole in the twin, so preflight diffs them byte-for-byte.\nconst DANGEROUS_SCHEME_RE = /^\\s*(javascript|data|vbscript|file|blob):/i\n\n/** Inline image payloads, the one `data:` form a media node accepts. SVG is excluded — it carries script. */\nconst INLINE_IMAGE_DATA_RE = /^data:image\\/(?!svg\\+xml)/i\n\n/**\n * Scheme gate for every stored media `src`. Embed builders refuse to render a bad\n * URL, but the raw value still lands in the collaborative document and `window.open`.\n * `allowInlineImage` opts a path into base64 images; `Image.allowBase64` still decides.\n */\nexport const isSafeMediaSrc = (\n  src: string | null | undefined,\n  { allowInlineImage = false }: { allowInlineImage?: boolean } = {}\n): src is string => {\n  if (typeof src !== 'string' || src.length === 0) return false\n  if (allowInlineImage && INLINE_IMAGE_DATA_RE.test(src)) return true\n  // Test a control-stripped copy. The URL parser drops ASCII tab/LF/CR.\n  // So `java\\tscript:` would otherwise smuggle a dangerous scheme past the regex.\n  // eslint-disable-next-line no-control-regex -- matching C0 controls is the point\n  return !DANGEROUS_SCHEME_RE.test(src.replace(/[\\u0000-\\u0020]+/g, ''))\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { getKitStorage } from '../kitStorage'\nimport { isValidLoomUrl } from '../nodes/loom/helper'\nimport { isValidSoundCloudUrl } from '../nodes/soundcloud/helper'\nimport { isValidSpotifyUrl } from '../nodes/spotify/helper'\nimport { isValidVimeoUrl } from '../nodes/vimeo/helper'\nimport { normalizeXUrl } from '../nodes/x/helper'\nimport { isValidYoutubeUrl } from '../nodes/youtube/helper'\nimport { applyNodeAttributes } from '../utils/media-node-attrs'\nimport { isSafeMediaSrc } from '../utils/mediaUrl'\nimport { closeToolbarPopover, openMediaPopover } from './menu'\nimport { type MediaToolbarIconScope, resolveMediaToolbarIcon } from './resolveIcon'\nimport type { MediaActionContext } from './types'\n\nconst validatedUrl =\n  (isValid: (url: string) => boolean) =>\n  (url: string): string | null =>\n    isValid(url) ? url : null\n\n/** Same-type replacement only — assets (image/video/audio) accept any non-empty src. */\nconst REPLACE_URL_GUARDS: Record<\n  string,\n  { label: string; resolve: (url: string) => string | null }\n> = {\n  youtube: { label: 'YouTube', resolve: validatedUrl(isValidYoutubeUrl) },\n  vimeo: { label: 'Vimeo', resolve: validatedUrl(isValidVimeoUrl) },\n  soundcloud: { label: 'SoundCloud', resolve: validatedUrl(isValidSoundCloudUrl) },\n  spotify: { label: 'Spotify', resolve: validatedUrl(isValidSpotifyUrl) },\n  loom: { label: 'Loom', resolve: validatedUrl(isValidLoomUrl) },\n  x: { label: 'X post', resolve: normalizeXUrl }\n}\n\n// Nodes without a provider guard (image/video/audio) still get the scheme gate,\n// widened for inline images so a pasted base64 src stays replaceable.\nconst ANY_URL = {\n  label: 'media',\n  resolve: (url: string) => (isSafeMediaSrc(url, { allowInlineImage: true }) ? url : null)\n}\n\n/** Contract handed to a `replaceUrlPopover` factory. `nodePos` is a snapshot at open; `apply` re-validates, commits the normalized URL, and closes. */\nexport interface ReplaceUrlPopoverOptions {\n  editor: Editor\n  nodeType: string\n  nodePos: number\n  /** Current `src`, for prefilling the editor UI. */\n  src: string\n  /** Error message for an invalid URL, `null` when it passes the node's guard. */\n  validate: (url: string) => string | null\n  apply: (url: string) => void\n  close: () => void\n}\n\n/** Return the popover content, or `null` to opt out so the host renders its own surface. */\nexport type ReplaceUrlPopoverFactory = (options: ReplaceUrlPopoverOptions) => HTMLElement | null\n\n/** Prebuilt popover content: prefilled URL field, Replace confirm, inline error. */\nexport const createReplaceUrlPopover: ReplaceUrlPopoverFactory = (options) => {\n  const body = document.createElement('div')\n  body.className = 'media-toolbar__submenu'\n\n  const input = document.createElement('input')\n  input.type = 'url'\n  input.className = 'media-toolbar__input'\n  input.value = options.src\n  input.setAttribute('aria-label', 'Media URL')\n\n  const error = document.createElement('p')\n  error.className = 'media-toolbar__error'\n  error.hidden = true\n\n  const submit = () => {\n    const value = input.value.trim()\n    if (!value || value === options.src) {\n      options.close()\n      return\n    }\n    const message = options.validate(value)\n    if (message) {\n      error.textContent = message\n      error.hidden = false\n      input.setAttribute('aria-invalid', 'true')\n      return\n    }\n    options.apply(value)\n  }\n\n  input.onkeydown = (event) => {\n    if (event.key !== 'Enter') return\n    event.preventDefault()\n    submit()\n  }\n\n  const confirm = document.createElement('button')\n  confirm.type = 'button'\n  confirm.className = 'media-toolbar__submenu-item'\n  const iconScope: MediaToolbarIconScope = {\n    editor: options.editor,\n    nodeType: options.nodeType\n  }\n  const icon = resolveMediaToolbarIcon(iconScope, 'replace') ?? ''\n  confirm.innerHTML = `${icon}<span>Replace</span>`\n  confirm.onclick = submit\n\n  body.append(input, confirm, error)\n  return body\n}\n\n/** Open the URL editor in a dialog popover anchored to the media node (bottom, flipping to top). A `replaceUrlPopover` factory returning `null` opts out entirely. */\nexport function openReplaceUrlPopover(ctx: MediaActionContext): void {\n  const factory = getKitStorage(ctx.editor).replaceUrlPopover ?? createReplaceUrlPopover\n  const guard = REPLACE_URL_GUARDS[ctx.nodeType] ?? ANY_URL\n  const resolve = (url: string): string | null => {\n    const value = url.trim()\n    return value ? guard.resolve(value) : null\n  }\n  const close = () => closeToolbarPopover()\n\n  const content = factory({\n    editor: ctx.editor,\n    nodeType: ctx.nodeType,\n    nodePos: ctx.nodePos,\n    src: String(ctx.attrs.src ?? ''),\n    validate: (url) => (resolve(url) === null ? `Enter a valid ${guard.label} URL` : null),\n    apply: (url) => {\n      const src = resolve(url)\n      if (src === null) return\n      applyNodeAttributes(ctx.editor, ctx.nodePos, { src })\n      close()\n    },\n    close\n  })\n  // Null = host surface; still dismiss the overflow menu the action was invoked from.\n  if (!content) {\n    close()\n    return\n  }\n\n  openMediaPopover({\n    kind: 'media-replace',\n    content,\n    trigger: ctx.wrapper,\n    variant: 'dialog',\n    role: 'dialog',\n    ariaLabel: 'Replace URL',\n    toggle: false\n  })\n  content.querySelector('input')?.focus()\n}\n","import {\n  resolveXEmbedSizeId,\n  X_EMBED_SIZE_OPTIONS,\n  X_EMBED_THEME_OPTIONS,\n  type XEmbedTheme\n} from '../nodes/x/embedOptions'\nimport { applyNodeAttributes } from '../utils/media-node-attrs'\nimport {\n  getCurrentMediaPlacement,\n  getMediaPlacementAttrs,\n  MEDIA_MARGIN_OPTIONS,\n  MEDIA_PLACEMENT_OPTIONS\n} from '../utils/media-placement'\nimport type { MediaActionsBuilder } from './compose'\nimport {\n  canViewOriginal,\n  copyMediaNode,\n  downloadMedia,\n  focusCaption,\n  isDownloadable,\n  removeMediaNode,\n  viewOriginalMedia\n} from './handlers'\nimport { openReplaceUrlPopover } from './replaceUrl'\nimport { resolveMediaToolbarIcon } from './resolveIcon'\nimport type { MediaAction, MediaActionContext, MediaActionList } from './types'\n\nconst DEFAULT_WRAP_MARGIN = '0.5in'\n\nconst isPresetMargin = (value: string): boolean =>\n  MEDIA_MARGIN_OPTIONS.some((option) => option.value === value)\n\nfunction submenuItem(active: boolean): HTMLButtonElement {\n  const btn = document.createElement('button')\n  btn.type = 'button'\n  btn.className =\n    'media-toolbar__submenu-item' + (active ? ' media-toolbar__submenu-item--active' : '')\n  btn.setAttribute('aria-pressed', active ? 'true' : 'false')\n  return btn\n}\n\nfunction textItem(label: string, active: boolean, onClick: () => void): HTMLButtonElement {\n  const btn = submenuItem(active)\n  btn.textContent = label\n  btn.onclick = onClick\n  return btn\n}\n\nfunction renderAlignmentSubmenu(ctx: MediaActionContext): HTMLElement {\n  const list = document.createElement('div')\n  list.className = 'media-toolbar__submenu'\n  const current = getCurrentMediaPlacement(ctx.attrs)\n  // Non-preset margins ('auto' from center, '0' from inline) would collapse the wrap gap.\n  const rawMargin = String(ctx.attrs.margin ?? '')\n  const margin = isPresetMargin(rawMargin) ? rawMargin : DEFAULT_WRAP_MARGIN\n  for (const { id, label } of MEDIA_PLACEMENT_OPTIONS) {\n    const btn = submenuItem(current === id)\n    btn.innerHTML = `${resolveMediaToolbarIcon(ctx, `align:${id}`) ?? ''}<span>${label}</span>`\n    btn.onclick = () => {\n      applyNodeAttributes(ctx.editor, ctx.nodePos, getMediaPlacementAttrs(id, margin))\n      ctx.close()\n    }\n    list.append(btn)\n  }\n  return list\n}\n\n/** Margin only matters when text wraps around the media, so the control is float-only. */\nfunction isWrapPlacement(attrs: Record<string, unknown>): boolean {\n  const placement = getCurrentMediaPlacement(attrs)\n  return placement === 'float-left' || placement === 'float-right'\n}\n\nfunction currentMargin(attrs: Record<string, unknown>): string {\n  return String(attrs.margin ?? DEFAULT_WRAP_MARGIN)\n}\n\nfunction marginLabel(ctx: MediaActionContext): string {\n  const current = currentMargin(ctx.attrs)\n  return MEDIA_MARGIN_OPTIONS.find((option) => option.value === current)?.label ?? current\n}\n\n/** Preset gaps for wrap placements; a legacy non-preset margin surfaces as the active extra row. */\nfunction renderMarginSubmenu(ctx: MediaActionContext): HTMLElement {\n  const list = document.createElement('div')\n  list.className = 'media-toolbar__submenu'\n  const current = currentMargin(ctx.attrs)\n  const apply = (value: string) => {\n    applyNodeAttributes(\n      ctx.editor,\n      ctx.nodePos,\n      getMediaPlacementAttrs(getCurrentMediaPlacement(ctx.attrs), value)\n    )\n    ctx.close()\n  }\n  const marginItem = (value: string, label: string) =>\n    list.append(textItem(label, current === value, () => apply(value)))\n  for (const { value, label } of MEDIA_MARGIN_OPTIONS) marginItem(value, label)\n  if (!isPresetMargin(current)) marginItem(current, current)\n  return list\n}\n\nfunction renderXOptionsSubmenu(ctx: MediaActionContext): HTMLElement {\n  const list = document.createElement('div')\n  list.className = 'media-toolbar__submenu'\n  const activeSize = resolveXEmbedSizeId(ctx.attrs.maxwidth as number | null | undefined)\n  const activeTheme = (ctx.attrs.theme as XEmbedTheme | undefined) ?? 'light'\n\n  const section = (title: string, children: HTMLElement[]) => {\n    const wrap = document.createElement('div')\n    wrap.className = 'media-toolbar__submenu-section'\n    const heading = document.createElement('p')\n    heading.className = 'media-toolbar__submenu-heading'\n    heading.textContent = title\n    wrap.append(heading, ...children)\n    return wrap\n  }\n  list.append(\n    section(\n      'Size',\n      X_EMBED_SIZE_OPTIONS.map(({ id, label, maxwidth }) =>\n        textItem(label, activeSize === id, () => {\n          applyNodeAttributes(ctx.editor, ctx.nodePos, { maxwidth })\n          ctx.close()\n        })\n      )\n    ),\n    section(\n      'Theme',\n      X_EMBED_THEME_OPTIONS.map(({ id, label }) =>\n        textItem(label, activeTheme === id, () => {\n          applyNodeAttributes(ctx.editor, ctx.nodePos, { theme: id })\n          ctx.close()\n        })\n      )\n    )\n  )\n  return list\n}\n\n/** Authored in display order — array position is the source of truth (no numeric `order`). */\nexport const BASE_ACTIONS: MediaActionList = [\n  {\n    id: 'align',\n    label: () => 'Align',\n    placement: 'inline',\n    renderSubmenu: renderAlignmentSubmenu\n  },\n  {\n    id: 'margin',\n    label: marginLabel,\n    placement: 'inline',\n    isVisible: (ctx) => isWrapPlacement(ctx.attrs),\n    renderSubmenu: renderMarginSubmenu,\n    dividerAfter: true\n  },\n  {\n    id: 'caption',\n    label: () => 'Caption',\n    placement: 'inline',\n    run: focusCaption\n  },\n  {\n    id: 'view-original',\n    label: () => 'View original',\n    placement: 'inline',\n    isVisible: canViewOriginal,\n    run: viewOriginalMedia\n  },\n  {\n    id: 'download',\n    label: () => 'Download',\n    placement: 'inline',\n    isVisible: (ctx) => isDownloadable(ctx.nodeType) && Boolean(ctx.attrs.src),\n    run: (ctx) => void downloadMedia(ctx)\n  },\n  {\n    id: 'replace',\n    label: () => 'Replace URL',\n    placement: 'overflow',\n    run: openReplaceUrlPopover\n  },\n  {\n    id: 'copy',\n    label: () => 'Copy',\n    placement: 'overflow',\n    run: (ctx) => void copyMediaNode(ctx)\n  },\n  {\n    id: 'delete',\n    label: () => 'Delete',\n    placement: 'overflow',\n    run: removeMediaNode\n  }\n]\n\nconst X_OPTIONS_ACTION: MediaAction = {\n  id: 'x-options',\n  label: () => 'Post options',\n  placement: 'overflow',\n  renderSubmenu: renderXOptionsSubmenu\n}\n\n/** Per-node recipes snap extra bricks onto the base set via the builder (replaces the old `order` interleave). */\nexport const NODE_ACTION_RECIPES: Record<\n  string,\n  (builder: MediaActionsBuilder) => MediaActionsBuilder\n> = {\n  x: (builder) => builder.add(X_OPTIONS_ACTION, { after: 'replace' })\n}\n","import type {\n  MediaAction,\n  MediaActionList,\n  MediaActionPlacement,\n  MediaActionsResolver\n} from './types'\n\n/** Where a brick snaps relative to another by id; omit both to append to the end. */\nexport interface MediaActionAnchor {\n  before?: string\n  after?: string\n}\n\n/** Immutable lego-style editor for the resolved action list — every method returns a new builder. */\nexport interface MediaActionsBuilder {\n  /** Insert (or move, if the id already exists) a brick at the anchor. */\n  add(action: MediaAction, at?: MediaActionAnchor): MediaActionsBuilder\n  remove(...ids: string[]): MediaActionsBuilder\n  /** Reposition an existing brick; no-op if the id is absent. */\n  move(id: string, at: MediaActionAnchor): MediaActionsBuilder\n  /** Swap a brick's definition in place; no-op if the id is absent. */\n  replace(id: string, action: MediaAction): MediaActionsBuilder\n  setPlacement(id: string, placement: MediaActionPlacement): MediaActionsBuilder\n  toInline(id: string): MediaActionsBuilder\n  toOverflow(id: string): MediaActionsBuilder\n  /** Order listed ids first (in the given sequence); unlisted bricks keep their relative order after. */\n  order(ids: string[]): MediaActionsBuilder\n  has(id: string): boolean\n  result(): MediaActionList\n}\n\nconst withoutId = (list: MediaActionList, id: string): MediaActionList =>\n  list.filter((action) => action.id !== id)\n\nfunction anchorIndex(list: MediaActionList, at?: MediaActionAnchor): number {\n  if (at?.before) {\n    const i = list.findIndex((action) => action.id === at.before)\n    if (i !== -1) return i\n  }\n  if (at?.after) {\n    const i = list.findIndex((action) => action.id === at.after)\n    if (i !== -1) return i + 1\n  }\n  return list.length\n}\n\nfunction spliced(\n  list: MediaActionList,\n  action: MediaAction,\n  at?: MediaActionAnchor\n): MediaActionList {\n  const base = withoutId(list, action.id)\n  const i = anchorIndex(base, at)\n  return [...base.slice(0, i), action, ...base.slice(i)]\n}\n\nfunction mapById(\n  list: MediaActionList,\n  id: string,\n  fn: (action: MediaAction) => MediaAction\n): MediaActionList {\n  const i = list.findIndex((action) => action.id === id)\n  if (i === -1) return list\n  const next = list.slice()\n  next[i] = fn(next[i])\n  return next\n}\n\nfunction make(list: MediaActionList): MediaActionsBuilder {\n  const setPlacement = (id: string, placement: MediaAction['placement']): MediaActionsBuilder =>\n    make(mapById(list, id, (action) => ({ ...action, placement })))\n\n  return {\n    add: (action, at) => make(spliced(list, action, at)),\n    remove: (...ids) => {\n      const drop = new Set(ids)\n      return make(list.filter((action) => !drop.has(action.id)))\n    },\n    move: (id, at) => {\n      const found = list.find((action) => action.id === id)\n      return found ? make(spliced(list, found, at)) : make(list)\n    },\n    replace: (id, action) => make(mapById(list, id, () => action)),\n    setPlacement,\n    toInline: (id) => setPlacement(id, 'inline'),\n    toOverflow: (id) => setPlacement(id, 'overflow'),\n    order: (ids) => {\n      const rank = new Map(ids.map((id, i) => [id, i]))\n      const last = Number.MAX_SAFE_INTEGER\n      const next = list\n        .map((action, i) => ({ action, i }))\n        .sort((a, b) => {\n          const ra = rank.get(a.action.id) ?? last\n          const rb = rank.get(b.action.id) ?? last\n          return ra === rb ? a.i - b.i : ra - rb\n        })\n        .map((entry) => entry.action)\n      return make(next)\n    },\n    has: (id) => list.some((action) => action.id === id),\n    result: () => list\n  }\n}\n\n/** Start a lego-style edit over a resolved action list (typically the `mediaActions` `defaults`). */\nexport function composeMediaActions(actions: MediaActionList): MediaActionsBuilder {\n  return make(actions.slice())\n}\n\n/** Declarative row layout by id; unlisted known actions keep their placement and append after. */\nexport interface MediaToolbarLayout {\n  inline?: string[]\n  overflow?: string[]\n}\n\n/** Sugar: a `mediaActions` resolver that arranges the inline and overflow rows by id. */\nexport function layoutMediaActions(\n  layout: MediaToolbarLayout | ((ctx: { nodeType: string }) => MediaToolbarLayout)\n): MediaActionsResolver {\n  return (defaults, ctx) => {\n    const { inline = [], overflow = [] } = typeof layout === 'function' ? layout(ctx) : layout\n    let builder = composeMediaActions(defaults)\n    for (const id of inline) builder = builder.toInline(id)\n    for (const id of overflow) builder = builder.toOverflow(id)\n    return builder.order([...inline, ...overflow]).result()\n  }\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { getKitStorage } from '../kitStorage'\nimport { BASE_ACTIONS, NODE_ACTION_RECIPES } from './actions'\nimport { composeMediaActions } from './compose'\nimport type { MediaActionList, MediaActionsResolver } from './types'\n\nfunction computeMediaActions(\n  nodeType: string,\n  mediaActions: MediaActionsResolver | undefined\n): MediaActionList {\n  const recipe = NODE_ACTION_RECIPES[nodeType]\n  const defaults = recipe ? recipe(composeMediaActions(BASE_ACTIONS)).result() : BASE_ACTIONS\n  return mediaActions ? mediaActions(defaults, { nodeType }) : defaults\n}\n\nconst actionsCache = new WeakMap<\n  Editor,\n  {\n    doc: Editor['state']['doc']\n    mediaActions: MediaActionsResolver | undefined\n    byType: Map<string, MediaActionList>\n  }\n>()\n\n/** Base bricks + per-node recipe (array order is final) → host `mediaActions` override. */\nexport function resolveMediaActions(editor: Editor, nodeType: string): MediaActionList {\n  const doc = editor.state.doc\n  const mediaActions = getKitStorage(editor).mediaActions\n  let cache = actionsCache.get(editor)\n  if (!cache || cache.doc !== doc || cache.mediaActions !== mediaActions) {\n    cache = { doc, mediaActions, byType: new Map() }\n    actionsCache.set(editor, cache)\n  }\n  const hit = cache.byType.get(nodeType)\n  if (hit) return hit\n  const actions = computeMediaActions(nodeType, mediaActions)\n  cache.byType.set(nodeType, actions)\n  return actions\n}\n","import { attachTooltip } from '@docs.plus/floating-tooltip'\n\nimport { getKitStorage } from '../kitStorage'\nimport { actionButton, bindToolbarTooltips, buildOverflowMenu, openToolbarPopover } from './menu'\nimport { resolveMediaToolbarIcon } from './resolveIcon'\nimport type { MediaAction, MediaActionContext } from './types'\n\n/** Build the in-place toolbar element from a resolved action list. */\nexport function renderMediaToolbar(ctx: MediaActionContext, actions: MediaAction[]): HTMLElement {\n  const kitIcons = getKitStorage(ctx.editor).mediaToolbarIcons\n  const tooltipDetaches: (() => void)[] = []\n  const inline: MediaAction[] = []\n  const overflow: MediaAction[] = []\n  for (const action of actions) {\n    if (!(action.isVisible?.(ctx) ?? true)) continue\n    if (action.placement === 'inline') inline.push(action)\n    else overflow.push(action)\n  }\n\n  const bar = document.createElement('div')\n  bar.className = 'media-toolbar'\n  bar.setAttribute('data-node-type', ctx.nodeType)\n  bar.setAttribute('role', 'toolbar')\n  bar.setAttribute('aria-label', 'Media toolbar')\n\n  for (const action of inline) {\n    const btn = actionButton(action, ctx, 'inline', kitIcons, tooltipDetaches)\n    if (action.renderSubmenu) {\n      btn.onclick = () => openToolbarPopover(btn, action.renderSubmenu!(ctx), `media-${action.id}`)\n    } else {\n      btn.onclick = () => action.run?.(ctx)\n    }\n    bar.append(btn)\n    if (action.dividerAfter) {\n      const divider = document.createElement('span')\n      divider.className = 'media-toolbar__divider'\n      divider.setAttribute('role', 'separator')\n      divider.setAttribute('aria-orientation', 'vertical')\n      bar.append(divider)\n    }\n  }\n\n  if (overflow.length > 0) {\n    const more = document.createElement('button')\n    more.type = 'button'\n    more.className = 'media-toolbar__button media-toolbar__more'\n    more.setAttribute('aria-label', 'More actions')\n    more.innerHTML = resolveMediaToolbarIcon(ctx, 'more', kitIcons) ?? ''\n    tooltipDetaches.push(attachTooltip(more, 'More actions'))\n    more.onclick = () =>\n      openToolbarPopover(more, buildOverflowMenu(ctx, overflow, kitIcons), 'media-menu', {\n        positionReference: bar\n      })\n    bar.append(more)\n  }\n\n  bindToolbarTooltips(bar, tooltipDetaches)\n  return bar\n}\n","import { registerToolbarContext } from './contextRegistry'\nimport { closeToolbarPopover } from './menu'\nimport { resolveMediaActions } from './registry'\nimport { renderMediaToolbar } from './renderToolbar'\nimport type { MediaActionContext, MediaToolbarFactory } from './types'\n\n/** Default desktop toolbar: resolve actions for the node, render the in-place bar. */\nexport const createMediaToolbar: MediaToolbarFactory = ({ target, editor, nodeType, nodePos }) => {\n  const node = editor.state.doc.nodeAt(nodePos)\n  if (!node || node.type.name !== nodeType) return null\n\n  const ctx: MediaActionContext = {\n    editor,\n    nodeType,\n    nodePos,\n    attrs: node.attrs,\n    wrapper: target,\n    close: closeToolbarPopover\n  }\n\n  const bar = renderMediaToolbar(ctx, resolveMediaActions(editor, nodeType))\n  registerToolbarContext(bar, ctx)\n  return bar\n}\n","import { hideTooltip } from '@docs.plus/floating-tooltip'\n\nimport { getKitStorage } from '../kitStorage'\nimport { createMediaToolbar } from './createMediaToolbar'\nimport { closeToolbarPopover, releaseToolbarTooltips } from './menu'\nimport type { MediaToolbarFactory, MediaToolbarOptions } from './types'\n\n// 80ms CSS opacity fade plus a short buffer before DOM removal; closing bars are not reused.\nconst CLOSE_REMOVE_DELAY_MS = 100\n\nconst pendingRemoval = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>()\n\nfunction cancelToolbarRemoval(el: HTMLElement): void {\n  const timer = pendingRemoval.get(el)\n  if (timer == null) return\n  clearTimeout(timer)\n  pendingRemoval.delete(el)\n  delete el.dataset.hmClosing\n}\n\nfunction teardownToolbar(el: HTMLElement): void {\n  cancelToolbarRemoval(el)\n  releaseToolbarTooltips(el)\n}\n\nfunction removeToolbarElement(el: HTMLElement): void {\n  teardownToolbar(el)\n  el.remove()\n}\n\nfunction scheduleToolbarRemoval(el: HTMLElement): void {\n  teardownToolbar(el)\n  el.dataset.hmClosing = 'true'\n  pendingRemoval.set(\n    el,\n    setTimeout(() => {\n      pendingRemoval.delete(el)\n      el.remove()\n    }, CLOSE_REMOVE_DELAY_MS)\n  )\n}\n\nfunction existingToolbar(wrapper: HTMLElement): HTMLElement | null {\n  const live = wrapper.querySelector<HTMLElement>(\n    ':scope > [data-hm-toolbar]:not([data-hm-closing])'\n  )\n  if (live) return live\n\n  // Re-hover before removal fires can stack a second bar; purge closing siblings first.\n  wrapper\n    .querySelectorAll(':scope > [data-hm-toolbar][data-hm-closing]')\n    .forEach((el) => removeToolbarElement(el as HTMLElement))\n  wrapper.classList.remove('hm-has-toolbar')\n  return null\n}\n\n/** Mount the toolbar inside the media wrapper (absolute top-right). `null` ⇒ host surface. */\nexport function openMediaToolbar(\n  options: MediaToolbarOptions,\n  factory?: MediaToolbarFactory\n): HTMLElement | null {\n  const reused = existingToolbar(options.target)\n  if (reused) return reused\n\n  const resolved: MediaToolbarFactory =\n    factory ?? getKitStorage(options.editor).mediaToolbar ?? createMediaToolbar\n\n  const content = resolved(options)\n  if (!content) return null\n\n  // Structural lifecycle marker: reuse and removal key on it, so custom\n  // factories get both without adopting the built-in `.media-toolbar` skin.\n  content.dataset.hmToolbar = ''\n  // Enter fades via the hm-toolbar-in keyframe (fires on class match at mount);\n  // the exit fade + reuse-safety live on the data-hm-closing guard below.\n  options.target.classList.add('hm-has-toolbar')\n  options.target.append(content)\n  return content\n}\n\n/** Remove the mounted toolbar, any open menu popover, and a lingering tooltip. */\nexport function closeMediaToolbar(wrapper?: HTMLElement | null): void {\n  closeToolbarPopover()\n  hideTooltip()\n  const root = wrapper ?? document\n  root.querySelectorAll<HTMLElement>('[data-hm-toolbar]').forEach((el) => {\n    el.closest('.hm-has-toolbar')?.classList.remove('hm-has-toolbar')\n    scheduleToolbarRemoval(el)\n  })\n}\n","import { Extension } from '@tiptap/core'\nimport { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { buildOptimizedDecorations } from './decoration'\nimport { abortActiveGripperDrag } from './decoration/gripperDrag'\nimport {\n  BuildDecorationsFunction,\n  HIDE_RESIZE_GRIPPER_META,\n  transactionAffectsTrackedNodes\n} from './decorationHelpers'\n\nexport interface MediaResizeGripperOptions {\n  acceptedNodes: string[]\n}\n\nexport const MediaResizeGripper = Extension.create<MediaResizeGripperOptions>({\n  name: 'MediaResizeGripper',\n\n  addOptions() {\n    return {\n      acceptedNodes: ['image']\n    }\n  },\n\n  addProseMirrorPlugins() {\n    const { acceptedNodes } = this.options\n    const editor = this.editor\n\n    const buildDecorations: BuildDecorationsFunction = (doc: ProseMirrorNode) => {\n      return buildOptimizedDecorations(acceptedNodes, doc, editor)\n    }\n\n    const key = new PluginKey<DecorationSet>('MediaResizeGripper')\n\n    return [\n      new Plugin<DecorationSet>({\n        key,\n        state: {\n          init: (_, { doc }) => buildDecorations(doc),\n          apply: (tr, old) => {\n            // The meta check must stay ahead of the structural scan: an attr\n            // commit ships only an AttrStep, which that scan skips by design.\n            if (\n              tr.getMeta(HIDE_RESIZE_GRIPPER_META) !== undefined ||\n              transactionAffectsTrackedNodes(tr, acceptedNodes)\n            ) {\n              return buildDecorations(tr.doc)\n            }\n            return tr.docChanged ? old.map(tr.mapping, tr.doc) : old\n          }\n        },\n        props: {\n          decorations(state) {\n            return key.getState(state)\n          }\n        },\n        // Editor torn down mid-drag would otherwise leak the drag's window/\n        // document listeners + pointer capture; abort releases them.\n        view: () => ({ destroy: () => abortActiveGripperDrag(editor) })\n      })\n    ]\n  }\n})\n","import { Editor } from '@tiptap/core'\nimport { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nimport { collectMediaGripperInfo, createMediaResizeGripper } from './media-resize-gripper'\n\n/** One widget-decoration gripper per resizable media node, keyed for stable reuse across maps. */\nexport function buildOptimizedDecorations(\n  nodeNames: string[],\n  doc: ProseMirrorNode,\n  editor: Editor\n): DecorationSet {\n  const contentWrappers = collectMediaGripperInfo(nodeNames, doc)\n\n  const decorations = contentWrappers.map((gripperInfo) => {\n    const options = {\n      side: -1,\n      key: gripperInfo.keyId || `gripper-${gripperInfo.from}`\n    }\n    // Lazy `toDOM`: prosemirror-view only reuses a keyed widget whose previous\n    // `toDOM` has no parentNode. An eagerly built element therefore rebuilds every\n    // gripper on every decoration pass — 9 elements and 8 listeners per node.\n    return Decoration.widget(\n      gripperInfo.from,\n      () => createMediaResizeGripper(gripperInfo, editor),\n      options\n    )\n  })\n\n  return DecorationSet.create(doc, decorations)\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { syncResponsiveMediaHost } from '../../loading/syncLayout'\nimport { resolveEmbedOption, SOUNDCLOUD_IFRAME_DOM_ATTR_KEYS } from '../../utils/embedKit'\nimport { fitLayoutToEditorColumn } from '../../utils/fitImageDimensions'\n\n/** Page URL or `w.soundcloud.com/player/?url=…` from serialized HTML → canonical track URL. */\nexport function parseSoundCloudTrackUrl(url: string): string | null {\n  try {\n    const trimmed = url.trim()\n    if (!trimmed) return null\n\n    const parsed = new URL(trimmed)\n    const host = parsed.hostname.replace(/^www\\./i, '')\n\n    if (host === 'w.soundcloud.com') {\n      const track = parsed.searchParams.get('url')\n      return track ? decodeURIComponent(track) : null\n    }\n\n    if (host === 'soundcloud.com') return trimmed\n  } catch {\n    return null\n  }\n\n  return null\n}\n\n/** SoundCloud HTML5 widget params — [widget docs](https://developers.soundcloud.com/docs/api/html5-widget). */\n\nexport interface SoundCloudPlayerKitOptions {\n  auto_play: boolean\n  hide_related: boolean\n  show_comments: boolean\n  show_user: boolean\n  show_reposts: boolean\n  visual: boolean\n  color?: string\n  buying: boolean\n  sharing: boolean\n  download: boolean\n  show_artwork: boolean\n  show_playcount: boolean\n  start_track?: number\n  single_active: boolean\n  scrolling: string\n  frameborder: string\n  allow: string\n}\n\nconst SOUNDCLOUD_WIDGET_PARAM_KEYS = [\n  'auto_play',\n  'hide_related',\n  'show_comments',\n  'show_user',\n  'show_reposts',\n  'color',\n  'buying',\n  'sharing',\n  'download',\n  'show_artwork',\n  'show_playcount',\n  'start_track',\n  'single_active'\n] as const satisfies readonly (keyof SoundCloudPlayerKitOptions)[]\n\nconst SOUNDCLOUD_VISUAL_PLAYER_MIN_HEIGHT = 130\n\n/** Compact widget shell needs ~120px; visual waveform player needs ~166px. */\nconst SOUNDCLOUD_COMPACT_PLAYER_MIN_HEIGHT = 120\nconst SOUNDCLOUD_VISUAL_PLAYER_LAYOUT_MIN_HEIGHT = 166\n\nexport const SOUNDCLOUD_EMBED_ATTR_KEYS = [\n  'src',\n  'height',\n  ...SOUNDCLOUD_WIDGET_PARAM_KEYS,\n  'visual',\n  ...SOUNDCLOUD_IFRAME_DOM_ATTR_KEYS\n] as const\n\nexport const SOUNDCLOUD_PLAYER_KIT_DEFAULTS: SoundCloudPlayerKitOptions = {\n  auto_play: false,\n  hide_related: false,\n  show_comments: true,\n  show_user: true,\n  show_reposts: true,\n  visual: false,\n  color: undefined,\n  buying: true,\n  sharing: true,\n  download: true,\n  show_artwork: true,\n  show_playcount: true,\n  start_track: undefined,\n  single_active: true,\n  scrolling: 'no',\n  frameborder: 'no',\n  allow: 'autoplay'\n}\n\nfunction formatWidgetValue(value: boolean | number | string): string {\n  if (typeof value === 'boolean') return value ? 'true' : 'false'\n  return String(value)\n}\n\n/** Heights above ~130px use the visual (waveform) player unless `visual` is set explicitly. */\nfunction resolveSoundCloudVisual(\n  attrs: Record<string, unknown>,\n  options: SoundCloudPlayerKitOptions\n): boolean {\n  if (attrs.visual != null) return Boolean(attrs.visual)\n  const height = attrs.height\n  if (typeof height === 'number' && height > SOUNDCLOUD_VISUAL_PLAYER_MIN_HEIGHT) return true\n  return options.visual\n}\n\nexport function resolveSoundCloudLayoutMinHeight(\n  attrs: Record<string, unknown>,\n  options: SoundCloudPlayerKitOptions\n): number {\n  return resolveSoundCloudVisual(attrs, options)\n    ? SOUNDCLOUD_VISUAL_PLAYER_LAYOUT_MIN_HEIGHT\n    : SOUNDCLOUD_COMPACT_PLAYER_MIN_HEIGHT\n}\n\nexport function resolveSoundCloudExtensionOptions(editor: Editor): SoundCloudPlayerKitOptions {\n  const extension = editor.extensionManager.extensions.find((e) => e.name === 'soundcloud')\n  return (\n    (extension?.options as SoundCloudPlayerKitOptions | undefined) ?? SOUNDCLOUD_PLAYER_KIT_DEFAULTS\n  )\n}\n\n/** Scale layout down when width exceeds the column; never shrink below widget minimum height. */\nexport function fitSoundCloudLayoutToEditorColumn(\n  editor: Editor,\n  width: number,\n  height: number,\n  attrs: Record<string, unknown>,\n  options: SoundCloudPlayerKitOptions\n): { width: number; height: number } {\n  const fitted = fitLayoutToEditorColumn(editor, width, height)\n  const minHeight = resolveSoundCloudLayoutMinHeight({ ...attrs, height: fitted.height }, options)\n  return fitted.height >= minHeight ? fitted : { ...fitted, height: minHeight }\n}\n\n/** SoundCloud iframe UI does not scale — floor host height when the column shrinks width. */\nexport function syncSoundCloudResponsiveHost(\n  el: HTMLElement,\n  width: number,\n  height: number,\n  attrs: Record<string, unknown>,\n  options: SoundCloudPlayerKitOptions\n): void {\n  const hostHeight = Math.max(height, resolveSoundCloudLayoutMinHeight(attrs, options))\n  syncResponsiveMediaHost(el, width, hostHeight)\n  el.style.minHeight = `${hostHeight}px`\n}\n\nexport function buildSoundCloudEmbedUrl(\n  trackUrl: string,\n  attrs: Record<string, unknown>,\n  options: SoundCloudPlayerKitOptions\n): string {\n  const canonical = parseSoundCloudTrackUrl(trackUrl) ?? trackUrl.trim()\n  const embedUrl = new URL('https://w.soundcloud.com/player/')\n  embedUrl.searchParams.set('url', canonical)\n\n  for (const key of SOUNDCLOUD_WIDGET_PARAM_KEYS) {\n    const value = resolveEmbedOption(attrs, options, key)\n    if (value == null || value === '') continue\n    embedUrl.searchParams.set(key, formatWidgetValue(value))\n  }\n\n  embedUrl.searchParams.set('visual', formatWidgetValue(resolveSoundCloudVisual(attrs, options)))\n  return embedUrl.toString()\n}\n\nexport function resolveSoundCloudIframeAttributes(\n  attrs: Record<string, unknown>,\n  options: SoundCloudPlayerKitOptions,\n  width: number,\n  height: number\n): Record<string, string | number> {\n  return {\n    width,\n    height,\n    scrolling: String(resolveEmbedOption(attrs, options, 'scrolling') ?? options.scrolling),\n    frameborder: String(resolveEmbedOption(attrs, options, 'frameborder') ?? options.frameborder),\n    allow: String(resolveEmbedOption(attrs, options, 'allow') ?? options.allow)\n  }\n}\n","import { Editor } from '@tiptap/core'\n\nimport {\n  resolveSoundCloudExtensionOptions,\n  resolveSoundCloudLayoutMinHeight\n} from '../../nodes/soundcloud/embedOptions'\nimport { resolveSpotifyLayoutMinHeight } from '../../nodes/spotify/embedOptions'\nimport { fitDimensionsToBounds, getEditorContentWidth } from '../../utils/fitImageDimensions'\nimport { PointerPosition, ResizeConstraints } from './types'\n\nconst DEFAULT_CONSTRAINTS: ResizeConstraints = {\n  minWidth: 160,\n  minHeight: 80\n}\n\nexport function getPointerPosition(e: MouseEvent | TouchEvent | PointerEvent): PointerPosition {\n  if (e instanceof PointerEvent) {\n    return { x: e.clientX, y: e.clientY }\n  }\n  if (e.type.startsWith('touch') && 'touches' in e && e.touches.length > 0) {\n    return { x: e.touches[0].clientX, y: e.touches[0].clientY }\n  }\n  const mouseEvent = e as MouseEvent\n  return { x: mouseEvent.clientX, y: mouseEvent.clientY }\n}\n\n/** Aspect-locked resize: the larger pointer delta drives the axis the other follows. */\nexport function calculateAspectRatioDimensions(\n  deltaX: number,\n  deltaY: number,\n  initialWidth: number,\n  initialHeight: number,\n  aspectRatio: number,\n  corner: string\n): { width: number; height: number } {\n  let newWidth: number\n  let newHeight: number\n  const absX = Math.abs(deltaX)\n  const absY = Math.abs(deltaY)\n\n  if (absX > absY) {\n    switch (corner) {\n      case 'topRight':\n      case 'bottomRight':\n        newWidth = initialWidth + deltaX\n        break\n      case 'topLeft':\n      case 'bottomLeft':\n        newWidth = initialWidth - deltaX\n        break\n      default:\n        newWidth = initialWidth + deltaX\n    }\n    newHeight = newWidth / aspectRatio\n  } else {\n    switch (corner) {\n      case 'topRight':\n      case 'topLeft':\n        newHeight = initialHeight - deltaY\n        break\n      case 'bottomRight':\n      case 'bottomLeft':\n        newHeight = initialHeight + deltaY\n        break\n      default:\n        newHeight = initialHeight + deltaY\n    }\n    newWidth = newHeight * aspectRatio\n  }\n\n  return { width: newWidth, height: newHeight }\n}\n\nfunction resolveResizeConstraints(editor: Editor): ResizeConstraints {\n  const maxWidth = getEditorContentWidth(editor)\n  return {\n    ...DEFAULT_CONSTRAINTS,\n    ...(maxWidth > 0 ? { maxWidth } : {})\n  }\n}\n\nexport function resolveMediaNodeConstraints(\n  editor: Editor,\n  node?: { type: { name: string }; attrs: Record<string, unknown> } | null\n): ResizeConstraints {\n  const base = resolveResizeConstraints(editor)\n  if (!node) return base\n\n  if (node.type.name === 'soundcloud') {\n    const options = resolveSoundCloudExtensionOptions(editor)\n    const minHeight = resolveSoundCloudLayoutMinHeight(node.attrs, options)\n    return { ...base, minHeight: Math.max(base.minHeight, minHeight) }\n  }\n\n  if (node.type.name === 'spotify') {\n    const minHeight = resolveSpotifyLayoutMinHeight(node.attrs)\n    return { ...base, minHeight: Math.max(base.minHeight, minHeight) }\n  }\n\n  return base\n}\n\nexport function clampDimensionsToConstraints(\n  width: number,\n  height: number,\n  constraints: ResizeConstraints\n): { width: number; height: number } {\n  const maxWidth = constraints.maxWidth ?? Number.POSITIVE_INFINITY\n  const maxHeight = constraints.maxHeight ?? Number.POSITIVE_INFINITY\n  const fitted = fitDimensionsToBounds(width, height, { maxWidth, maxHeight })\n  return {\n    width: Math.max(constraints.minWidth, fitted.width),\n    height: Math.max(constraints.minHeight, fitted.height)\n  }\n}\n\nexport function updateNodeDimensions(\n  editor: Editor,\n  nodePos: number,\n  width: number,\n  height: number\n): void {\n  const { state, dispatch } = editor.view\n  const { tr } = state\n\n  // A stale position must never throw (nodeAt RangeErrors past doc end) or resize\n  // a foreign node. Resizable media nodes are the ones carrying keyId + width attrs.\n  if (nodePos < 0 || nodePos > state.doc.content.size) return\n  const nodeAtPos = state.doc.nodeAt(nodePos)\n  if (!nodeAtPos || !('keyId' in nodeAtPos.attrs) || !('width' in nodeAtPos.attrs)) return\n\n  const clamped = clampDimensionsToConstraints(\n    width,\n    height,\n    resolveMediaNodeConstraints(editor, nodeAtPos)\n  )\n  tr.setNodeMarkup(nodePos, null, {\n    ...nodeAtPos.attrs,\n    width: clamped.width,\n    height: clamped.height\n  })\n\n  tr.setMeta('resizeMedia', true)\n  tr.setMeta('addToHistory', true)\n  dispatch(tr)\n}\n\n/** Style pixels are the source of truth: the gripper may be detached/`display:none` post-rebuild. */\nexport function readGripperDimensions(gripper: HTMLElement): {\n  width: number\n  height: number\n} {\n  const fromStyle = (axis: 'width' | 'height') => parseFloat(gripper.style[axis])\n  return {\n    width: fromStyle('width') || gripper.offsetWidth,\n    height: fromStyle('height') || gripper.offsetHeight\n  }\n}\n\nexport function resetGripperPosition(\n  gripper: HTMLElement,\n  initialLeft: number,\n  initialTop: number\n): void {\n  gripper.style.left = `${initialLeft}px`\n  gripper.style.top = `${initialTop}px`\n}\n\n/** Drag-scoped Shift tracking; caller must `cleanup()` on drag end to avoid leaking listeners. */\nexport function setupKeyboardListeners(): {\n  isShiftPressed: () => boolean\n  cleanup: () => void\n} {\n  let shiftPressed = false\n\n  const handleKeyDown = (e: KeyboardEvent) => {\n    if (e.key === 'Shift') {\n      shiftPressed = true\n    }\n  }\n\n  const handleKeyUp = (e: KeyboardEvent) => {\n    if (e.key === 'Shift') {\n      shiftPressed = false\n    }\n  }\n\n  document.addEventListener('keydown', handleKeyDown)\n  document.addEventListener('keyup', handleKeyUp)\n\n  return {\n    isShiftPressed: () => shiftPressed,\n    cleanup: () => {\n      document.removeEventListener('keydown', handleKeyDown)\n      document.removeEventListener('keyup', handleKeyUp)\n    }\n  }\n}\n","import { getDefaultController } from '@docs.plus/floating-popover'\nimport { Editor } from '@tiptap/core'\n\nimport {\n  resolveMediaNodePos,\n  restoreControlsAfterResize,\n  setMediaResizing\n} from '../../utils/media-resize-controls'\nimport { getMediaNodeType, resolveMediaFromGripper } from '../../utils/media-target'\nimport { Corner, ResizeConstraints, ResizeState } from './types'\nimport {\n  calculateAspectRatioDimensions,\n  clampDimensionsToConstraints,\n  getPointerPosition,\n  readGripperDimensions,\n  resetGripperPosition,\n  resolveMediaNodeConstraints,\n  setupKeyboardListeners,\n  updateNodeDimensions\n} from './utils'\n\n// One in-flight drag per editor. The gripper plugin's `view().destroy()` calls\n// `abortActiveGripperDrag` so an editor torn down mid-drag drops the window/\n// document listeners and pointer capture instead of leaking them. WeakMap keys\n// are GC-safe, so a destroyed editor needs no explicit removal.\nconst activeDragByEditor = new WeakMap<Editor, () => void>()\n\nexport function abortActiveGripperDrag(editor: Editor): void {\n  activeDragByEditor.get(editor)?.()\n}\n\ninterface GripperBox {\n  width: number\n  height: number\n  top: number\n  left: number\n}\n\ninterface GripperDragContext {\n  deltaX: number\n  deltaY: number\n  state: ResizeState\n  clamp: HTMLElement\n}\n\ntype ComputeGripperBox = (ctx: GripperDragContext) => GripperBox\n\nfunction isLeftEdge(clamp: HTMLElement): boolean {\n  return (\n    clamp.classList.contains('media-resize-clamp--left') ||\n    clamp.classList.contains('media-resize-clamp--top-left') ||\n    clamp.classList.contains('media-resize-clamp--bottom-left')\n  )\n}\n\nfunction isTopEdge(clamp: HTMLElement): boolean {\n  return (\n    clamp.classList.contains('media-resize-clamp--top') ||\n    clamp.classList.contains('media-resize-clamp--top-left') ||\n    clamp.classList.contains('media-resize-clamp--top-right')\n  )\n}\n\nfunction clampGripperBox(\n  box: GripperBox,\n  state: ResizeState,\n  clamp: HTMLElement,\n  constraints: ResizeConstraints\n): GripperBox {\n  const clamped = clampDimensionsToConstraints(box.width, box.height, constraints)\n  let { top, left } = box\n\n  if (isLeftEdge(clamp)) {\n    left = state.initialLeft + (state.initialWidth - clamped.width)\n  }\n  if (isTopEdge(clamp)) {\n    top = state.initialTop + (state.initialHeight - clamped.height)\n  }\n\n  return { width: clamped.width, height: clamped.height, top, left }\n}\n\ninterface GripperDragConfig {\n  clamp: HTMLElement\n  gripper: HTMLElement\n  editor: Editor\n  computeBox: ComputeGripperBox\n}\n\n/** Pointer-capture class on the gripper widget during a drag — never mutate node-view DOM. */\nfunction setGripperDragging(gripper: HTMLElement, dragging: boolean): void {\n  gripper.classList.toggle('hypermultimedia__resize-gripper--dragging', dragging)\n  document.documentElement.classList.toggle('hypermultimedia--resize-dragging', dragging)\n}\n\n/**\n * Keyed-widget DOM reuse keeps this gripper (and its decoration-time positions)\n * alive across edits above the node. The doc position must therefore be\n * re-resolved from the DOM at drag end. Never trust `MediaGripperInfo.from`.\n */\nfunction resolveDragTargetPos(editor: Editor, gripper: HTMLElement): number | null {\n  const media = resolveMediaFromGripper(gripper, editor.view.dom)\n  if (!media) return null\n  const nodeType = getMediaNodeType(media)\n  if (!nodeType) return null\n  return resolveMediaNodePos(editor.view, media, nodeType)\n}\n\n/** Wire one clamp's pointer drag onto the shared resize lifecycle; `computeBox` owns the per-handle math. */\nexport function attachGripperDrag(config: GripperDragConfig): void {\n  const { clamp, gripper, editor, computeBox } = config\n\n  function handleStart(event: Event) {\n    if (!(event instanceof PointerEvent)) return\n    if (event.button !== 0) return\n\n    event.preventDefault()\n    event.stopPropagation()\n\n    const pointerId = event.pointerId\n    let ended = false\n    let pointerMoveActive = false\n\n    setMediaResizing(editor, true)\n    setGripperDragging(gripper, true)\n    getDefaultController().close()\n\n    try {\n      clamp.setPointerCapture(pointerId)\n    } catch {\n      setGripperDragging(gripper, false)\n      setMediaResizing(editor, false)\n      return\n    }\n\n    const keyboard = setupKeyboardListeners()\n    const start = getPointerPosition(event)\n    const nodePos = resolveDragTargetPos(editor, gripper)\n    const node = nodePos != null ? editor.state.doc.nodeAt(nodePos) : null\n    const constraints = resolveMediaNodeConstraints(editor, node)\n\n    const state: ResizeState = {\n      initialX: start.x,\n      initialY: start.y,\n      initialWidth: gripper.offsetWidth,\n      initialHeight: gripper.offsetHeight,\n      initialTop: gripper.offsetTop,\n      initialLeft: gripper.offsetLeft,\n      aspectRatio: gripper.offsetWidth / gripper.offsetHeight,\n      isShiftPressed: keyboard.isShiftPressed()\n    }\n\n    function applyMove(clientX: number, clientY: number) {\n      state.isShiftPressed = keyboard.isShiftPressed()\n      const raw = computeBox({\n        deltaX: clientX - state.initialX,\n        deltaY: clientY - state.initialY,\n        state,\n        clamp\n      })\n      const box = clampGripperBox(raw, state, clamp, constraints)\n\n      gripper.style.width = `${box.width}px`\n      gripper.style.height = `${box.height}px`\n      gripper.style.top = `${box.top}px`\n      gripper.style.left = `${box.left}px`\n    }\n\n    function ensurePointerCapture() {\n      if (ended || clamp.hasPointerCapture(pointerId)) return\n      try {\n        clamp.setPointerCapture(pointerId)\n      } catch {\n        // window listeners keep the drag alive until pointer/mouse up\n      }\n    }\n\n    function onPointerMove(ev: PointerEvent) {\n      if (ended || ev.pointerId !== pointerId) return\n      ev.preventDefault()\n      pointerMoveActive = true\n      ensurePointerCapture()\n      applyMove(ev.clientX, ev.clientY)\n    }\n\n    function onMouseMove(ev: MouseEvent) {\n      if (ended || pointerMoveActive || (ev.buttons & 1) === 0) return\n      ev.preventDefault()\n      applyMove(ev.clientX, ev.clientY)\n    }\n\n    function finish(ev?: Event) {\n      if (ended) return\n      if (ev instanceof PointerEvent && ev.pointerId !== pointerId) return\n      ended = true\n\n      const { width, height } = readGripperDimensions(gripper)\n      resetGripperPosition(gripper, state.initialLeft, state.initialTop)\n      const nodePos = resolveDragTargetPos(editor, gripper)\n\n      try {\n        if (nodePos !== null) {\n          updateNodeDimensions(editor, nodePos, width, height)\n        }\n      } finally {\n        // Listeners and pointer capture must release even when the commit throws.\n        teardown()\n        setGripperDragging(gripper, false)\n        activeDragByEditor.delete(editor)\n        restoreControlsAfterResize(editor, gripper, nodePos ?? undefined)\n      }\n    }\n\n    // Escape drops the preview without committing: restore the gripper box, then re-arm hover controls.\n    function cancel() {\n      if (ended) return\n      ended = true\n\n      gripper.style.width = `${state.initialWidth}px`\n      gripper.style.height = `${state.initialHeight}px`\n      resetGripperPosition(gripper, state.initialLeft, state.initialTop)\n\n      teardown()\n      setGripperDragging(gripper, false)\n      activeDragByEditor.delete(editor)\n      restoreControlsAfterResize(editor, gripper)\n    }\n\n    function onWindowBlur() {\n      finish()\n    }\n\n    function onKeyDown(ev: KeyboardEvent) {\n      if (ev.key === 'Escape') cancel()\n    }\n\n    type ListenerBinding = [EventTarget, string, EventListener]\n\n    // Window pointer + mouse fallbacks keep drag alive across iframe overlays and Cypress.\n    const bindings: ListenerBinding[] = [\n      [clamp, 'pointermove', onPointerMove as EventListener],\n      [clamp, 'pointerup', finish],\n      [clamp, 'pointercancel', finish],\n      [window, 'pointermove', onPointerMove as EventListener],\n      [window, 'pointerup', finish],\n      [window, 'pointercancel', finish],\n      [window, 'mousemove', onMouseMove as EventListener],\n      [window, 'mouseup', finish],\n      [window, 'blur', onWindowBlur],\n      [document, 'keydown', onKeyDown as EventListener]\n    ]\n\n    function setDragListeners(active: boolean): void {\n      for (const [target, type, listener] of bindings) {\n        target[active ? 'addEventListener' : 'removeEventListener'](type, listener)\n      }\n    }\n\n    function teardown() {\n      setDragListeners(false)\n      keyboard.cleanup()\n      try {\n        if (clamp.hasPointerCapture(pointerId)) clamp.releasePointerCapture(pointerId)\n      } catch {\n        // capture may already be released\n      }\n    }\n\n    // Editor destroyed mid-drag: release listeners + visual state without\n    // touching the now-dead view (finish() dispatches a transaction and would\n    // throw). A later stray pointerup hits the `ended` guard and no-ops.\n    function abort() {\n      if (ended) return\n      ended = true\n      teardown()\n      setGripperDragging(gripper, false)\n      setMediaResizing(editor, false)\n      activeDragByEditor.delete(editor)\n    }\n\n    setDragListeners(true)\n    activeDragByEditor.set(editor, abort)\n  }\n\n  clamp.addEventListener('pointerdown', handleStart)\n}\n\n/** Left/right/top/bottom handles: one axis, no aspect ratio. */\nexport const computeSideBox: ComputeGripperBox = ({ deltaX, deltaY, state, clamp }) => {\n  let width = state.initialWidth\n  let height = state.initialHeight\n  let top = state.initialTop\n  let left = state.initialLeft\n\n  if (clamp.classList.contains('media-resize-clamp--left')) {\n    width = state.initialWidth - deltaX\n    left = state.initialLeft + deltaX\n  } else if (clamp.classList.contains('media-resize-clamp--right')) {\n    width = state.initialWidth + deltaX\n  } else if (clamp.classList.contains('media-resize-clamp--top')) {\n    height = state.initialHeight - deltaY\n    top = state.initialTop + deltaY\n  } else if (clamp.classList.contains('media-resize-clamp--bottom')) {\n    height = state.initialHeight + deltaY\n  }\n\n  return { width, height, top, left }\n}\n\n/** Corner handles: free resize, or aspect-locked while Shift is held. */\nexport function computeCornerBox(corner: Corner): ComputeGripperBox {\n  return ({ deltaX, deltaY, state }) => {\n    let width: number\n    let height: number\n    let top = state.initialTop\n    let left = state.initialLeft\n\n    if (state.isShiftPressed && state.aspectRatio) {\n      const dimensions = calculateAspectRatioDimensions(\n        deltaX,\n        deltaY,\n        state.initialWidth,\n        state.initialHeight,\n        state.aspectRatio,\n        corner\n      )\n      width = dimensions.width\n      height = dimensions.height\n\n      switch (corner) {\n        case 'topRight':\n          top = state.initialTop + (state.initialHeight - height)\n          break\n        case 'bottomLeft':\n          left = state.initialLeft + (state.initialWidth - width)\n          break\n        case 'topLeft':\n          top = state.initialTop + (state.initialHeight - height)\n          left = state.initialLeft + (state.initialWidth - width)\n          break\n        case 'bottomRight':\n      }\n    } else {\n      switch (corner) {\n        case 'topRight':\n          width = state.initialWidth + deltaX\n          height = state.initialHeight - deltaY\n          top = state.initialTop + deltaY\n          break\n        case 'bottomLeft':\n          width = state.initialWidth - deltaX\n          height = state.initialHeight + deltaY\n          left = state.initialLeft + deltaX\n          break\n        case 'topLeft':\n          width = state.initialWidth - deltaX\n          height = state.initialHeight - deltaY\n          top = state.initialTop + deltaY\n          left = state.initialLeft + deltaX\n          break\n        case 'bottomRight':\n          width = state.initialWidth + deltaX\n          height = state.initialHeight + deltaY\n          break\n      }\n    }\n\n    return { width, height, top, left }\n  }\n}\n","import { Editor } from '@tiptap/core'\nimport { Node as ProseMirrorNode } from '@tiptap/pm/model'\n\nimport { attachGripperDrag, computeCornerBox, computeSideBox } from './gripperDrag'\nimport { ClampType, Corner, MediaGripperInfo } from './types'\n\nfunction createClamp(extraClass: ClampType): HTMLDivElement {\n  const clamp = document.createElement('div')\n  clamp.classList.add('media-resize-clamp', extraClass)\n  return clamp\n}\n\nfunction createClamps() {\n  return {\n    sides: [\n      createClamp(ClampType.Left),\n      createClamp(ClampType.Right),\n      createClamp(ClampType.Top),\n      createClamp(ClampType.Bottom)\n    ],\n    corners: {\n      topRight: createClamp(ClampType.TopRight),\n      topLeft: createClamp(ClampType.TopLeft),\n      bottomRight: createClamp(ClampType.BottomRight),\n      bottomLeft: createClamp(ClampType.BottomLeft)\n    }\n  }\n}\n\nexport function collectMediaGripperInfo(\n  nodeNames: string[],\n  doc: ProseMirrorNode\n): MediaGripperInfo[] {\n  const result: MediaGripperInfo[] = []\n  doc.descendants((node, pos) => {\n    if (nodeNames.includes(node.type.name)) {\n      result.push({ from: pos, keyId: node.attrs.keyId })\n    }\n  })\n  return result\n}\n\nexport const createMediaResizeGripper = (\n  mediaInfo: MediaGripperInfo,\n  editor: Editor\n): HTMLDivElement => {\n  const gripper = document.createElement('div')\n  gripper.classList.add('hypermultimedia__resize-gripper')\n  if (mediaInfo.keyId) {\n    gripper.dataset.mediaKeyId = mediaInfo.keyId\n  }\n\n  const clamps = createClamps()\n  gripper.append(...clamps.sides, ...Object.values(clamps.corners))\n\n  clamps.sides.forEach((clamp) => {\n    attachGripperDrag({ clamp, gripper, editor, computeBox: computeSideBox })\n  })\n\n  Object.entries(clamps.corners).forEach(([corner, clamp]) => {\n    attachGripperDrag({\n      clamp,\n      gripper,\n      editor,\n      computeBox: computeCornerBox(corner as Corner)\n    })\n  })\n\n  return gripper\n}\n","import { Node, nodeInputRule } from '@tiptap/core'\n\nimport { captionAttribute } from '../../caption'\nimport { AUDIO_LAYOUT_FALLBACK } from '../../loading'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport { layoutAttrDefaults } from '../../utils/embedKit'\nimport {\n  createHtmlMediaNodeView,\n  type HtmlMediaNodeConfig,\n  renderHtmlMediaHTML\n} from '../../utils/htmlMediaNode'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { isSafeMediaSrc } from '../../utils/mediaUrl'\nimport { generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport { inputRegex } from './helper'\n\ninterface NodeOptions {\n  HTMLAttributes: Record<string, unknown>\n}\n\nexport interface AudioOptions extends StyleLayoutOptions, NodeOptions {\n  // Node attributes\n  inline?: boolean\n\n  // Html attributes\n  controls?: boolean\n  autoplay?: boolean\n  loop?: boolean\n  muted?: boolean\n  preload?: 'none' | 'metadata' | 'auto'\n  src: string | null\n}\n\nexport type SetAudioOptions = {\n  src: string\n  controls?: boolean\n  autoplay?: boolean\n  loop?: boolean\n  muted?: boolean\n  preload?: 'none' | 'metadata' | 'auto'\n} & StyleLayoutOptions\n\n/** `<audio>` element attrs; remount the node view when any change. */\nconst AUDIO_REMOUNT_ATTR_KEYS = ['src', 'controls', 'autoplay', 'loop', 'muted', 'preload'] as const\n\nconst AUDIO_MEDIA_CONFIG: HtmlMediaNodeConfig = {\n  tag: 'audio',\n  elementAttrKeys: AUDIO_REMOUNT_ATTR_KEYS,\n  layoutFallback: AUDIO_LAYOUT_FALLBACK\n}\n\nexport const Audio = Node.create<AudioOptions>({\n  name: 'audio',\n  draggable: true,\n\n  ...createTypedMediaMarkdownHooks('audio', { withDimensions: true }),\n\n  addOptions() {\n    return {\n      src: null,\n      controls: true,\n      autoplay: false,\n      loop: false,\n      muted: false,\n      preload: 'metadata',\n      margin: 'auto',\n      clear: 'none',\n      float: null,\n      display: 'block',\n      justifyContent: 'start',\n      HTMLAttributes: {},\n      inline: false\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        default: null,\n        parseHTML: (element) => {\n          const src = element.getAttribute('src')\n          return isSafeMediaSrc(src) ? src : null\n        }\n      },\n      controls: {\n        default: this.options.controls\n      },\n      autoplay: {\n        default: this.options.autoplay\n      },\n      loop: {\n        default: this.options.loop\n      },\n      muted: {\n        default: this.options.muted\n      },\n      preload: {\n        default: this.options.preload\n      },\n      ...layoutAttrDefaults(this.options),\n      // Audio options omit dims; persist explicit nulls, not `undefined` defaults.\n      width: {\n        default: null\n      },\n      height: {\n        default: null\n      },\n      caption: captionAttribute()\n    }\n  },\n\n  addNodeView() {\n    return createHtmlMediaNodeView(AUDIO_MEDIA_CONFIG, () => ({\n      name: this.name,\n      editor: this.editor\n    }))\n  },\n\n  parseHTML() {\n    return [\n      {\n        tag: 'div[data-audio] audio[src]'\n      }\n    ]\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return renderHtmlMediaHTML(AUDIO_MEDIA_CONFIG, this.options, { node, HTMLAttributes })\n  },\n\n  addInputRules() {\n    return [\n      nodeInputRule({\n        find: inputRegex,\n        type: this.type,\n        getAttributes: (match) => {\n          const [, , src, width, height] = match\n\n          return { src, width, height }\n        }\n      })\n    ]\n  },\n\n  addCommands() {\n    return {\n      setAudio:\n        (options) =>\n        ({ commands }) => {\n          if (!options.src) return false\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...options, keyId: generateShortId() }\n          })\n        }\n    }\n  }\n})\n","import type { Editor } from '@tiptap/core'\nimport type { DOMOutputSpec, Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { NodeSelection, TextSelection } from '@tiptap/pm/state'\n\nimport { setMediaCaption } from './utils/media-node-attrs'\n\n/** Single reader for the nullable `caption` attr so node views don't repeat the cast. */\nexport function readCaption(node: ProseMirrorNode): string | null {\n  return (node.attrs.caption as string | null) ?? null\n}\n\n/** NodeSelection can outlive caption DOM focus; collapse it before typing replaces the node. */\nfunction releaseMediaNodeSelectionForCaption(\n  editor: Editor,\n  getPos: () => number | undefined\n): void {\n  const pos = getPos()\n  if (pos == null || !editor.isEditable) return\n\n  const { state } = editor\n  const { selection } = state\n  if (!(selection instanceof NodeSelection) || selection.from !== pos) return\n\n  const after = state.doc.resolve(Math.min(pos + selection.node.nodeSize, state.doc.content.size))\n  editor.view.dispatch(state.tr.setSelection(TextSelection.near(after, -1)))\n}\n\nexport interface CaptionHandle {\n  el: HTMLElement\n  sync: (caption: string | null) => void\n  destroy: () => void\n}\n\ninterface CaptionParams {\n  editor: Editor\n  getPos: () => number | undefined\n  initial: string | null\n}\n\n/** Editable figcaption committed to the `caption` attr on blur/Enter. Shell, not PM content. */\nexport function createCaptionElement({ editor, getPos, initial }: CaptionParams): CaptionHandle {\n  const el = document.createElement('figcaption')\n  el.className = 'hm-caption'\n  el.dataset.placeholder = 'Add a caption…'\n  el.setAttribute('contenteditable', editor.isEditable ? 'true' : 'false')\n  el.textContent = initial ?? ''\n  if (!initial) el.classList.add('hm-caption--empty')\n\n  const commit = () => {\n    if (!editor.isEditable) return\n    const pos = getPos()\n    if (pos == null) return\n    setMediaCaption(editor, pos, el.textContent?.trim() || null)\n  }\n  // editable toggles (read ↔ edit mode) don't rebuild this island, so reject edits live.\n  const onFocus = () => {\n    if (!editor.isEditable) {\n      el.blur()\n      return\n    }\n    el.classList.remove('hm-caption--empty')\n    releaseMediaNodeSelectionForCaption(editor, getPos)\n  }\n  const onBlur = () => {\n    commit()\n    if (!el.textContent?.trim()) el.classList.add('hm-caption--empty')\n  }\n  const onKeyDown = (event: KeyboardEvent) => {\n    event.stopPropagation()\n    if (!editor.isEditable) {\n      event.preventDefault()\n      return\n    }\n    if (event.key === 'Enter') {\n      event.preventDefault()\n      commit()\n      el.blur()\n      editor.commands.focus()\n    }\n  }\n  const stop = (event: Event) => event.stopPropagation()\n\n  el.addEventListener('focus', onFocus)\n  el.addEventListener('blur', onBlur)\n  el.addEventListener('keydown', onKeyDown)\n  el.addEventListener('beforeinput', stop)\n  el.addEventListener('mousedown', stop)\n  el.addEventListener('paste', stop)\n\n  return {\n    el,\n    sync: (caption) => {\n      el.setAttribute('contenteditable', editor.isEditable ? 'true' : 'false')\n      if (el === document.activeElement) return\n      const next = caption ?? ''\n      if (el.textContent !== next) el.textContent = next\n      el.classList.toggle('hm-caption--empty', !next)\n    },\n    destroy: () => {\n      el.removeEventListener('focus', onFocus)\n      el.removeEventListener('blur', onBlur)\n      el.removeEventListener('keydown', onKeyDown)\n      el.removeEventListener('beforeinput', stop)\n      el.removeEventListener('mousedown', stop)\n      el.removeEventListener('paste', stop)\n    }\n  }\n}\n\n/** Figure-aware element finder for attribute parseHTML (handles bare media or figure>media). */\nexport function mediaElementFrom(el: HTMLElement, selector: string): HTMLElement | null {\n  if (el.matches(selector)) return el\n  return el.querySelector<HTMLElement>(selector)\n}\n\n/** Reusable `caption` attribute spec: parses figcaption text; never emits an HTML attribute. */\nexport function captionAttribute(): {\n  default: string | null\n  parseHTML: (element: HTMLElement) => string | null\n  renderHTML: () => Record<string, never>\n} {\n  return {\n    default: null as string | null,\n    parseHTML: (element: HTMLElement) =>\n      element.tagName === 'FIGURE'\n        ? element.querySelector('figcaption')?.textContent?.trim() || null\n        : null,\n    renderHTML: () => ({})\n  }\n}\n\n/** Wrap a rendered media spec in `<figure>…<figcaption>` when caption is present. */\nexport function wrapRenderWithCaption(\n  rendered: DOMOutputSpec,\n  caption: string | null,\n  figureStyle = ''\n): DOMOutputSpec {\n  if (!caption) return rendered\n  return [\n    'figure',\n    { 'data-hm-figure': '', class: 'hypermultimedia--figure', style: figureStyle },\n    rendered,\n    ['figcaption', { class: 'hm-caption' }, caption]\n  ] as DOMOutputSpec\n}\n","import type { MediaLoadingKind, MediaLoadingShellContext } from './types'\n\nconst DEFAULT_MESSAGES: Record<MediaLoadingKind, string> = {\n  image: 'Loading image…',\n  video: 'Loading video…',\n  audio: 'Loading audio…',\n  embed: 'Loading embed…'\n}\n\nexport function createDefaultMediaLoadingShell(context: MediaLoadingShellContext): HTMLElement {\n  const overlay = document.createElement('div')\n  overlay.className = 'hm-loading-shell__overlay'\n  // No role=\"status\"/aria-live inside the editor. A persistent live-region makes\n  // focus-trap libraries (e.g. Floating UI markOthers) treat the overlay as a\n  // keep-target and recurse in, stamping `inert` across the doc. ProseMirror\n  // reconciles the inert stamping by recreating the media node views (the embed reloads).\n\n  const shimmer = document.createElement('div')\n  shimmer.className = 'hm-loading-shell__shimmer'\n  shimmer.setAttribute('aria-hidden', 'true')\n  overlay.append(shimmer)\n\n  const body = document.createElement('div')\n  body.className = 'hm-loading-shell__body'\n\n  if (context.provider) {\n    const provider = document.createElement('span')\n    provider.className = 'hm-loading-shell__provider'\n    provider.textContent = context.provider\n    body.append(provider)\n  }\n\n  const message = document.createElement('span')\n  message.className = 'hm-loading-shell__message'\n  message.textContent = context.message ?? DEFAULT_MESSAGES[context.kind]\n  body.append(message)\n\n  const spinner = document.createElement('span')\n  spinner.className = 'hm-loading-shell__spinner'\n  spinner.setAttribute('aria-hidden', 'true')\n  body.append(spinner)\n\n  overlay.append(body)\n  return overlay\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { getKitStorage } from '../kitStorage'\nimport { createDefaultMediaLoadingShell } from './defaultShell'\nimport { syncResponsiveMediaHost } from './syncLayout'\nimport type {\n  MediaLoadingController,\n  MediaLoadingShellContext,\n  MediaLoadingShellFactory,\n  MediaLoadingShellOption,\n  MediaLoadingShellWrapOptions\n} from './types'\n\nconst MEDIA_READY_EVENTS = ['load', 'loadeddata', 'canplay'] as const\n\nconst NOOP_CONTROLLER: MediaLoadingController = {\n  markReady: () => {},\n  markError: () => {},\n  destroy: () => {}\n}\n\nfunction resolveFactory(\n  option: MediaLoadingShellOption | undefined\n): MediaLoadingShellFactory | null {\n  if (option === false) return null\n  if (typeof option === 'function') return option\n  return createDefaultMediaLoadingShell\n}\n\nfunction createPlainMediaHost(content: HTMLElement): {\n  dom: HTMLElement\n  controller: MediaLoadingController\n} {\n  const host = document.createElement('div')\n  host.className = 'hm-media-host hm-media-host--plain'\n  host.append(content)\n  return { dom: host, controller: NOOP_CONTROLLER }\n}\n\nfunction stampLoadingOverlay(overlay: HTMLElement): void {\n  overlay.classList.add('hm-loading-shell', 'hm-loading-shell__overlay')\n}\n\nfunction bindMediaElementLoad(\n  element: HTMLElement,\n  controller: MediaLoadingController,\n  isAlreadyReady: () => boolean\n): () => void {\n  // A src-less element fires no load or error event, so the shell would sit on\n  // `pending` forever. Settle to `error` so Replace URL can repair the node.\n  if (!element.getAttribute('src')) {\n    controller.markError()\n    return () => {}\n  }\n\n  if (isAlreadyReady()) {\n    controller.markReady()\n    return () => {}\n  }\n\n  function teardown(): void {\n    for (const event of MEDIA_READY_EVENTS) {\n      element.removeEventListener(event, onReady)\n    }\n    element.removeEventListener('error', onError)\n  }\n\n  const onReady = () => {\n    teardown()\n    controller.markReady()\n  }\n\n  const onError = () => {\n    teardown()\n    controller.markError()\n  }\n\n  for (const event of MEDIA_READY_EVENTS) {\n    element.addEventListener(event, onReady, { once: true })\n  }\n  element.addEventListener('error', onError, { once: true })\n\n  return teardown\n}\n\nexport function wrapMediaWithLoadingShell(\n  editor: Editor,\n  context: MediaLoadingShellContext,\n  content: HTMLElement,\n  options?: MediaLoadingShellWrapOptions\n): { dom: HTMLElement; controller: MediaLoadingController } {\n  const factory = resolveFactory(getKitStorage(editor).loadingShell)\n  if (!factory) return createPlainMediaHost(content)\n\n  const host = document.createElement('div')\n  host.className = 'hm-media-host'\n  host.dataset.hmLoading = 'pending'\n  const syncHost = options?.syncLoadingHost ?? syncResponsiveMediaHost\n  syncHost(host, context.width, context.height)\n\n  const overlay = factory(context)\n  stampLoadingOverlay(overlay)\n\n  const slot = document.createElement('div')\n  slot.className = 'hm-media-slot'\n  slot.append(content)\n\n  host.append(overlay, slot)\n\n  let settled = false\n  let unbindLoad: (() => void) | undefined\n\n  const settle = (): boolean => {\n    if (settled) return false\n    unbindLoad?.()\n    unbindLoad = undefined\n    settled = true\n    return true\n  }\n\n  const controller: MediaLoadingController = {\n    markReady: () => {\n      if (!settle()) return\n      host.dataset.hmLoading = 'ready'\n    },\n    markError: (message) => {\n      if (!settle()) return\n      host.dataset.hmLoading = 'error'\n      const text = message ?? 'Could not load media'\n      const messageEl = overlay.querySelector('.hm-loading-shell__message')\n      if (messageEl) messageEl.textContent = text\n      else {\n        overlay.setAttribute('aria-label', text)\n        host.setAttribute('aria-label', text)\n      }\n    },\n    destroy: () => {\n      settle()\n    }\n  }\n\n  const bindLoad = options?.bindLoad\n  if (bindLoad) {\n    unbindLoad = bindMediaElementLoad(\n      bindLoad.element,\n      controller,\n      bindLoad.isAlreadyReady ?? (() => false)\n    )\n  }\n\n  return { dom: host, controller }\n}\n","import type {\n  JSONContent,\n  MarkdownLexerConfiguration,\n  MarkdownParseHelpers,\n  MarkdownRendererHelpers,\n  MarkdownToken,\n  RenderContext\n} from '@tiptap/core'\n\nimport { isValidLoomUrl } from '../nodes/loom/helper'\nimport { isValidSoundCloudUrl } from '../nodes/soundcloud/helper'\nimport { isValidSpotifyUrl } from '../nodes/spotify/helper'\nimport { isValidVimeoUrl } from '../nodes/vimeo/helper'\nimport { isValidXUrl } from '../nodes/x/helper'\nimport { isValidYoutubeUrl } from '../nodes/youtube/helper'\nimport { isAudioUrl, isVideoUrl } from '../utils/mediaUrl'\n\n/** Reserved `![alt]` literals routed to non-image media nodes. */\nexport const TYPED_MEDIA_MARKDOWN_ALTS = [\n  'audio',\n  'video',\n  'youtube',\n  'vimeo',\n  'soundcloud',\n  'spotify',\n  'loom',\n  'x'\n] as const\n\nexport type TypedMediaMarkdownAlt = (typeof TYPED_MEDIA_MARKDOWN_ALTS)[number]\n\n// Gate routing on the src. Then `![x](photo.png)`, an image whose alt collides\n// with a reserved literal, stays an image instead of round-tripping into a\n// broken embed.\nconst TYPED_MEDIA_SRC_VALIDATORS: Record<TypedMediaMarkdownAlt, (url: string) => boolean> = {\n  audio: isAudioUrl,\n  video: isVideoUrl,\n  youtube: isValidYoutubeUrl,\n  vimeo: isValidVimeoUrl,\n  soundcloud: isValidSoundCloudUrl,\n  spotify: isValidSpotifyUrl,\n  loom: isValidLoomUrl,\n  x: isValidXUrl\n}\n\ntype TypedMediaMarkdownOptions = {\n  withDimensions?: boolean\n}\n\ntype TypedMediaMarkdownHooks = {\n  markdownTokenName: string\n  markdownTokenizer: {\n    name: string\n    level: 'inline'\n    start: (src: string) => number\n    tokenize: (\n      src: string,\n      tokens: MarkdownToken[],\n      lexer: MarkdownLexerConfiguration\n    ) => MarkdownToken | undefined\n  }\n  parseMarkdown: (token: MarkdownToken, helpers: MarkdownParseHelpers) => JSONContent\n  renderMarkdown: (\n    node: JSONContent,\n    helpers: MarkdownRendererHelpers,\n    ctx: RenderContext\n  ) => string\n}\n\n/** `![{nodeName}](src)` import/export hooks shared by every non-image media node. */\nexport function createTypedMediaMarkdownHooks(\n  nodeName: TypedMediaMarkdownAlt,\n  options: TypedMediaMarkdownOptions = {}\n): TypedMediaMarkdownHooks {\n  const tokenName = `hm_${nodeName}`\n  const dimPattern = options.withDimensions\n    ? String.raw`(?:\\s+width=(\\d+))?(?:\\s+height=(\\d+))?`\n    : ''\n  const pattern = new RegExp(String.raw`^!\\[${nodeName}\\]\\((\\S+)${dimPattern}\\)`)\n\n  return {\n    markdownTokenName: tokenName,\n\n    markdownTokenizer: {\n      name: tokenName,\n      level: 'inline' as const,\n      start: (src: string) => src.indexOf(`![${nodeName}]`),\n      tokenize: (src: string, _tokens: MarkdownToken[], _lexer: MarkdownLexerConfiguration) => {\n        const match = pattern.exec(src)\n        if (!match) return undefined\n        if (!TYPED_MEDIA_SRC_VALIDATORS[nodeName](match[1])) return undefined\n\n        return {\n          type: tokenName,\n          raw: match[0],\n          href: match[1],\n          ...(options.withDimensions && { width: match[2], height: match[3] })\n        }\n      }\n    },\n\n    parseMarkdown: (token: MarkdownToken, _helpers: MarkdownParseHelpers) => {\n      const attrs: Record<string, unknown> = { src: token.href || '' }\n      if (options.withDimensions) {\n        if (token.width) attrs.width = Number(token.width)\n        if (token.height) attrs.height = Number(token.height)\n      }\n      return { type: nodeName, attrs }\n    },\n\n    renderMarkdown: (node: JSONContent, _helpers: MarkdownRendererHelpers, _ctx: RenderContext) => {\n      const src = (node.attrs?.src || '').replace(/\\)/g, '%29')\n      if (!options.withDimensions) {\n        return `![${nodeName}](${src})`\n      }\n\n      const width = node.attrs?.width\n      const height = node.attrs?.height\n      let out = `![${nodeName}](${src}`\n      if (width != null && width !== '') out += ` width=${width}`\n      if (height != null && height !== '') out += ` height=${height}`\n      return `${out})`\n    }\n  }\n}\n","import { type Editor, mergeAttributes, type NodeViewRendererProps } from '@tiptap/core'\nimport type { NodeView, ViewMutationRecord } from '@tiptap/pm/view'\n\nimport { type CaptionHandle, createCaptionElement, readCaption } from '../caption'\nimport {\n  layoutAttrsChanged,\n  parseLayoutDimensions,\n  syncMediaNodeLayout,\n  wrapMediaWithLoadingShell\n} from '../loading'\nimport { EMBED_LAYOUT_ATTR_KEYS, embedAttrsEqual } from './embedKit'\nimport { ignoreNodeViewSubtreeMutation } from './ignoreNodeViewMutation'\nimport { mediaLayoutCss } from './layoutStyle'\nimport { omitNullishAndFalse, type StyleLayoutOptions } from './utils'\n\nexport interface HtmlMediaNodeConfig {\n  tag: 'video' | 'audio'\n  /** Written onto the media element; doubles as the update() remount-key list. */\n  elementAttrKeys: readonly string[]\n  /** Pixel box when attrs carry no dimensions (audio has no intrinsic size). */\n  layoutFallback?: { width: number; height: number }\n}\n\n/** `src` falls back to the rendered attr so parsed nodes keep their source. */\nfunction pickMediaElementAttrs(\n  node: { attrs: Record<string, unknown> },\n  HTMLAttributes: Record<string, unknown>,\n  keys: readonly string[]\n): Record<string, unknown> {\n  const picked: Record<string, unknown> = {}\n  for (const key of keys) {\n    picked[key] = key === 'src' ? (node.attrs.src ?? HTMLAttributes.src) : node.attrs[key]\n  }\n  return omitNullishAndFalse(picked)\n}\n\nexport function createHtmlMediaNodeView(\n  config: HtmlMediaNodeConfig,\n  getExtension: () => { name: string; editor: Editor }\n) {\n  return function htmlMediaNodeView({\n    node,\n    HTMLAttributes,\n    getPos\n  }: NodeViewRendererProps): NodeView {\n    const { name, editor } = getExtension()\n    let attrsSnapshot = node.attrs\n    const dom = document.createElement('div')\n    const content = document.createElement('div')\n    const media = document.createElement(config.tag)\n\n    dom.classList.add(`hypermultimedia--${config.tag}__content`)\n    if (node.attrs.keyId) {\n      dom.setAttribute('data-key-id', String(node.attrs.keyId))\n    }\n\n    const dims = parseLayoutDimensions(node.attrs, config.layoutFallback)\n    const htmlAttributes = pickMediaElementAttrs(node, HTMLAttributes, config.elementAttrKeys)\n\n    Object.entries(mergeAttributes(htmlAttributes, { 'data-node-name': name })).forEach(\n      ([key, value]) => {\n        if (value !== undefined && value !== null && value !== false) {\n          media.setAttribute(key, String(value))\n        }\n      }\n    )\n\n    content.append(media)\n\n    const { dom: loadingHost, controller } = wrapMediaWithLoadingShell(\n      editor,\n      { kind: config.tag, width: dims.width, height: dims.height },\n      content,\n      {\n        bindLoad: {\n          element: media,\n          // `preload: 'none'` fetches nothing and fires no ready event, so the\n          // controls are all there is to paint — settle now or the shell never does.\n          isAlreadyReady: () =>\n            media.preload === 'none' || media.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA\n        }\n      }\n    )\n\n    dom.append(loadingHost)\n    syncMediaNodeLayout({\n      wrapper: dom,\n      attrs: node.attrs,\n      loadingHost,\n      surface: media,\n      dims,\n      fallback: config.layoutFallback\n    })\n\n    const caption: CaptionHandle = createCaptionElement({\n      editor,\n      getPos,\n      initial: readCaption(node)\n    })\n    dom.append(caption.el)\n\n    return {\n      dom,\n      // Caption is a nested contenteditable the node view owns; keep PM out of its events.\n      stopEvent: (event: Event) => caption.el.contains(event.target as Node | null),\n      destroy: () => {\n        controller.destroy()\n        caption.destroy()\n      },\n      ignoreMutation: (mutation: ViewMutationRecord) =>\n        ignoreNodeViewSubtreeMutation(mutation, dom),\n      update: (updatedNode: typeof node) => {\n        if (updatedNode.type.name !== name) return false\n\n        if (layoutAttrsChanged(updatedNode.attrs, attrsSnapshot)) {\n          syncMediaNodeLayout({\n            wrapper: dom,\n            attrs: updatedNode.attrs,\n            loadingHost,\n            surface: media,\n            fallback: config.layoutFallback\n          })\n        }\n\n        if (!embedAttrsEqual(updatedNode.attrs, attrsSnapshot, config.elementAttrKeys)) {\n          return false\n        }\n\n        caption.sync(readCaption(updatedNode))\n        attrsSnapshot = updatedNode.attrs\n        return true\n      }\n    }\n  }\n}\n\nexport function renderHtmlMediaHTML(\n  config: HtmlMediaNodeConfig,\n  options: StyleLayoutOptions,\n  {\n    node,\n    HTMLAttributes\n  }: {\n    node: { attrs: Record<string, unknown> }\n    HTMLAttributes: Record<string, unknown>\n  }\n): [string, Record<string, unknown>, [string, Record<string, unknown>]] {\n  const mediaClass = `hypermultimedia--${config.tag}__content`\n  const style = mediaLayoutCss(\n    {\n      height: parseInt(String(HTMLAttributes.height)),\n      width: parseInt(String(HTMLAttributes.width)),\n      float: HTMLAttributes.float as string | null | undefined,\n      clear: HTMLAttributes.clear as string | undefined,\n      margin: HTMLAttributes.margin as string | undefined\n    },\n    'export-html',\n    options\n  )\n\n  const htmlAttributes = pickMediaElementAttrs(node, HTMLAttributes, config.elementAttrKeys)\n\n  // The parse rule targets the inner element and the wrapper's inline style is\n  // never read back, so size and placement must ride the element itself. Kept out\n  // of `elementAttrKeys`, which drives node-view remounts — resizing must not\n  // reload the media.\n  const layout = omitNullishAndFalse(\n    Object.fromEntries(EMBED_LAYOUT_ATTR_KEYS.map((key) => [key, node.attrs[key]]))\n  )\n\n  // Leaf node: no content hole — a trailing `0` makes DOMSerializer throw on getHTML/copy.\n  return [\n    'div',\n    { [`data-${config.tag}`]: '', class: mediaClass, style },\n    [config.tag, mergeAttributes(htmlAttributes, layout, { class: mediaClass, style })]\n  ]\n}\n","import type { ViewMutationRecord } from '@tiptap/pm/view'\n\n/** Ignore PM reconciliation for DOM the node view owns (media load, oEmbed, widgets). */\nexport function ignoreNodeViewSubtreeMutation(\n  mutation: ViewMutationRecord,\n  root: HTMLElement\n): boolean {\n  const target = mutation.target\n  return target instanceof globalThis.Node && root.contains(target)\n}\n","// ![audio](http://example.com/path-to-audio.mp3 width=640 height=480)\n// ![audio](http://example.com/path-to-audio.mp3)\n// No `g` flag — input rules reuse the regex across keystrokes and a sticky lastIndex makes matches flaky.\nexport const inputRegex = /(?:^|\\s)(!\\[audio]\\((\\S+)(?:\\s+width=(\\d+))?(?:\\s+height=(\\d+))?\\))$/\n","import type {\n  JSONContent,\n  MarkdownParseHelpers,\n  MarkdownRendererHelpers,\n  MarkdownToken,\n  RenderContext\n} from '@tiptap/core'\nimport { Editor, mergeAttributes, Node, nodeInputRule } from '@tiptap/core'\nimport type { DOMOutputSpec } from '@tiptap/pm/model'\nimport { Transaction } from '@tiptap/pm/state'\n\nimport {\n  captionAttribute,\n  mediaElementFrom,\n  readCaption,\n  wrapRenderWithCaption\n} from '../../caption'\nimport { fitLayoutToEditorColumn } from '../../utils/fitImageDimensions'\nimport { mediaLayoutCss } from '../../utils/layoutStyle'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { isSafeMediaSrc } from '../../utils/mediaUrl'\nimport { generateShortId } from '../../utils/utils'\nimport { inputRegex, isImageUrl } from './helper'\nimport { createImageNodeView } from './nodeView'\nimport { HyperImagePastePlugin } from './plugin'\nimport type { ImageOptions } from './types'\n\nexport const Image = Node.create<ImageOptions>({\n  name: 'image',\n  draggable: true,\n\n  // Markdown round-trip lives on the node so a single\n  // `HyperMultimediaKit.configure({ Image })` is the only entry a host needs.\n  // Hooks are inert unless the host also loads @tiptap/markdown.\n  markdownTokenName: 'image',\n\n  parseMarkdown: (token: MarkdownToken, _helpers: MarkdownParseHelpers) => {\n    const alt = token.text || ''\n    // Markdown import is a write boundary like paste and parseHTML: an\n    // imported `![alt](javascript:…)` must not reach a stored node attribute.\n    // Inline images pass — a `.md` that embeds base64 is the case `allowBase64`\n    // exists for, and the HTML parse rule stays the authority on that policy.\n    const href = isSafeMediaSrc(token.href, { allowInlineImage: true }) ? token.href : ''\n    // Reserved alts belong to each node's own `hm_*` tokenizer, which is registered\n    // only when that node is enabled. Naming a disabled type here blanks the document.\n    return {\n      type: 'image',\n      attrs: { src: href, alt }\n    }\n  },\n\n  renderMarkdown: (node: JSONContent, _helpers: MarkdownRendererHelpers, _ctx: RenderContext) => {\n    const alt = (node.attrs?.alt || '').replace(/\\]/g, '\\\\]')\n    const src = (node.attrs?.src || '').replace(/\\)/g, '%29')\n    return `![${alt}](${src})`\n  },\n\n  addOptions() {\n    return {\n      allowBase64: false,\n      margin: 'auto',\n      clear: 'none',\n      float: null,\n      display: 'block',\n      HTMLAttributes: {},\n      inline: false,\n      width: null,\n      height: null\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      // A captioned image parses from its `<figure>`, so every layout attribute\n      // has to read the inner `<img>` it was rendered onto. That is the same\n      // reader `width`/`height`/`src` already use. Without that reader a wrapped\n      // image comes back centred after a copy/paste.\n      margin: {\n        default: this.options.margin,\n        parseHTML: (element) =>\n          mediaElementFrom(element, 'img')?.getAttribute('margin') ?? this.options.margin\n      },\n      clear: {\n        default: this.options.clear,\n        parseHTML: (element) =>\n          mediaElementFrom(element, 'img')?.getAttribute('clear') ?? this.options.clear\n      },\n      float: {\n        default: this.options.float,\n        parseHTML: (element) =>\n          mediaElementFrom(element, 'img')?.getAttribute('float') ?? this.options.float\n      },\n      display: {\n        default: this.options.display,\n        parseHTML: (element) =>\n          mediaElementFrom(element, 'img')?.getAttribute('display') ?? this.options.display\n      },\n      width: {\n        default: this.options.width,\n        parseHTML: (element) =>\n          mediaElementFrom(element, 'img')?.getAttribute('width') || this.options.width,\n        renderHTML: (attributes) => (attributes.width ? { width: attributes.width } : {})\n      },\n      height: {\n        default: this.options.height,\n        parseHTML: (element) =>\n          mediaElementFrom(element, 'img')?.getAttribute('height') || this.options.height,\n        renderHTML: (attributes) => (attributes.height ? { height: attributes.height } : {})\n      },\n      src: {\n        default: null,\n        parseHTML: (element) => mediaElementFrom(element, 'img')?.getAttribute('src') ?? null\n      },\n      alt: {\n        default: null,\n        parseHTML: (element) => mediaElementFrom(element, 'img')?.getAttribute('alt') ?? null\n      },\n      title: {\n        default: null,\n        parseHTML: (element) => mediaElementFrom(element, 'img')?.getAttribute('title') ?? null\n      },\n      caption: captionAttribute()\n    }\n  },\n\n  parseHTML() {\n    const acceptSrc = (src: string | null): boolean =>\n      !!src && (this.options.allowBase64 || !src.startsWith('data:')) && isImageUrl(src)\n    return [\n      {\n        tag: 'figure[data-hm-figure]',\n        getAttrs: (element) => {\n          const img = (element as HTMLElement).querySelector('img[src]')\n          return acceptSrc(img?.getAttribute('src') ?? null) ? {} : false\n        }\n      },\n      {\n        tag: this.options.allowBase64 ? 'img[src]' : 'img[src]:not([src^=\"data:\"])',\n        getAttrs: (element) => (acceptSrc(element.getAttribute('src')) ? {} : false)\n      }\n    ]\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    const keyId = HTMLAttributes.keyId\n\n    const width = HTMLAttributes.width ? parseInt(HTMLAttributes.width) : null\n    const height = HTMLAttributes.height ? parseInt(HTMLAttributes.height) : null\n\n    const { float, clear, margin } = HTMLAttributes\n    const caption = readCaption(node)\n\n    let imgStyle =\n      'background-color: #f3f4f6; max-width: 100%; height: auto; box-sizing: border-box;'\n    if (width && height) imgStyle += `width: ${width}px; aspect-ratio: ${width} / ${height}; `\n    else if (width) imgStyle += `width: ${width}px; `\n\n    const blockStyle = mediaLayoutCss({ float, clear, margin }, 'export-image-block')\n\n    const img = [\n      'img',\n      mergeAttributes(this.options.HTMLAttributes, {\n        ...HTMLAttributes,\n        'data-key-id': keyId,\n        class: 'hypermultimedia--image__content',\n        style: (imgStyle + (caption ? '' : blockStyle)).trim()\n      })\n    ] as DOMOutputSpec\n    return wrapRenderWithCaption(img, caption, blockStyle.trim())\n  },\n\n  addCommands() {\n    return {\n      setImage:\n        (options) =>\n        ({ commands }) => {\n          if (!options.src) return false\n\n          const keyId = generateShortId()\n          let { width, height, ...rest } = options\n\n          if (width && height) {\n            const fitted = fitLayoutToEditorColumn(this.editor, width, height)\n            width = fitted.width\n            height = fitted.height\n          }\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...rest, width, height, keyId }\n          })\n        },\n\n      updateImageDimensions:\n        ({\n          keyId,\n          width,\n          height\n        }: {\n          keyId: string\n          width?: number | null\n          height?: number | null\n        }) =>\n        ({ tr, dispatch }: { tr: Transaction; dispatch?: (tr: Transaction) => void }) => {\n          // The scan runs even without dispatch so `editor.can()` reports a miss\n          // instead of always answering true.\n          let found = false\n          tr.doc.descendants((node, pos) => {\n            if (node.type.name === this.name && node.attrs.keyId === keyId) {\n              found = true\n              if (dispatch) {\n                tr.setNodeMarkup(pos, undefined, {\n                  ...node.attrs,\n                  width: width !== undefined ? width : node.attrs.width,\n                  height: height !== undefined ? height : node.attrs.height\n                })\n              }\n              return false\n            }\n          })\n          return found\n        }\n    }\n  },\n\n  addNodeView() {\n    return createImageNodeView(this.options, this.editor)\n  },\n\n  addInputRules() {\n    return [\n      nodeInputRule({\n        find: inputRegex,\n        type: this.type,\n        getAttributes: (match) => {\n          const [, , alt, src] = match\n\n          return { src, alt }\n        }\n      })\n    ]\n  },\n\n  addProseMirrorPlugins() {\n    const editor = this.editor as Editor\n    return [\n      HyperImagePastePlugin(editor, {\n        nodeName: this.name,\n        allowBase64: this.options.allowBase64\n      })\n    ]\n  }\n})\n","// Bang is required so plain `[text](url)` links stay text. No `g` flag: input\n// rules reuse the regex across keystrokes, and a sticky lastIndex makes matches flaky.\nexport const inputRegex =\n  /(?:^|\\s)(!\\[([^\\]]*)\\]\\(((?:https?:\\/\\/|\\/\\/).*\\.(jpe?g|png|gif|webp|svg|bmp|ico|tiff|avif|heic|heif|jxl)(?:\\?.*)?(?:#.*)?)\\))$/i\n\nconst httpImageUrlRegex =\n  /^(?:https?:\\/\\/|\\/\\/).*\\.(jpe?g|png|gif|webp|svg|bmp|ico|tiff|avif|heic|heif|jxl)(?:\\?.*)?(?:#.*)?$/i\n\nexport const isImageUrl = (url: string): boolean => {\n  if (url.startsWith('data:image/')) return true\n  if (url.startsWith('blob:')) return true\n  return httpImageUrlRegex.test(url)\n}\n","import type { Editor } from '@tiptap/core'\nimport { mergeAttributes } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport type { NodeView, ViewMutationRecord } from '@tiptap/pm/view'\n\nimport { type CaptionHandle, createCaptionElement, readCaption } from '../../caption'\nimport {\n  IMAGE_LAYOUT_FALLBACK,\n  layoutAttrsChanged,\n  parseLayoutDimensions,\n  syncImageNodeLayout,\n  wrapMediaWithLoadingShell\n} from '../../loading'\nimport { ignoreNodeViewSubtreeMutation } from '../../utils/ignoreNodeViewMutation'\nimport type { ImageOptions } from './types'\n\nexport function createImageNodeView(options: ImageOptions, editor: Editor) {\n  return ({\n    node,\n    HTMLAttributes,\n    getPos\n  }: {\n    node: ProseMirrorNode\n    HTMLAttributes: Record<string, unknown>\n    getPos: () => number | undefined\n  }): NodeView => {\n    let attrsSnapshot = node.attrs\n    const layoutRoot = document.createElement('div')\n    layoutRoot.classList.add('hypermultimedia--image__content')\n    if (node.attrs.keyId) {\n      layoutRoot.setAttribute('data-key-id', String(node.attrs.keyId))\n    }\n\n    const dims = parseLayoutDimensions(node.attrs, IMAGE_LAYOUT_FALLBACK)\n\n    const img = document.createElement('img')\n    Object.entries(\n      mergeAttributes(options.HTMLAttributes, HTMLAttributes, {\n        src: node.attrs.src,\n        alt: node.attrs.alt,\n        title: node.attrs.title\n      })\n    ).forEach(([key, value]) => {\n      if (value !== undefined && value !== null) img.setAttribute(key, String(value))\n    })\n\n    const { dom: loadingHost, controller } = wrapMediaWithLoadingShell(\n      editor,\n      { kind: 'image', width: dims.width, height: dims.height },\n      img,\n      {\n        bindLoad: {\n          element: img,\n          isAlreadyReady: () => img.complete && img.naturalWidth > 0\n        }\n      }\n    )\n\n    layoutRoot.append(loadingHost)\n    syncImageNodeLayout({ wrapper: layoutRoot, attrs: node.attrs, loadingHost, img, dims })\n\n    const caption: CaptionHandle = createCaptionElement({\n      editor,\n      getPos,\n      initial: readCaption(node)\n    })\n    layoutRoot.append(caption.el)\n\n    return {\n      dom: layoutRoot,\n      destroy: () => {\n        controller.destroy()\n        caption.destroy()\n      },\n      // The caption is a nested contenteditable the node view owns; PM must not\n      // process its input/key/selection events or it deletes the selected node.\n      stopEvent: (event: Event) => caption.el.contains(event.target as Node | null),\n      ignoreMutation: (mutation: ViewMutationRecord) =>\n        ignoreNodeViewSubtreeMutation(mutation, layoutRoot),\n      update: (updatedNode: ProseMirrorNode) => {\n        if (updatedNode.type.name !== 'image') return false\n        if (updatedNode.attrs.src !== attrsSnapshot.src) return false\n\n        if (layoutAttrsChanged(updatedNode.attrs, attrsSnapshot)) {\n          syncImageNodeLayout({\n            wrapper: layoutRoot,\n            attrs: updatedNode.attrs,\n            loadingHost,\n            img\n          })\n        }\n\n        caption.sync(readCaption(updatedNode))\n        attrsSnapshot = updatedNode.attrs\n        return true\n      }\n    }\n  }\n}\n","import { Editor } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { isImageUrl } from './helper'\n\nexport const HyperImagePastePlugin = (\n  editor: Editor,\n  options: { nodeName: string; allowBase64: boolean }\n): Plugin => {\n  return new Plugin({\n    key: new PluginKey('ImagePasteHandler'),\n    props: {\n      handlePaste: (view, event, slice) => {\n        if (event.clipboardData?.files?.length) {\n          const imageFiles = Array.from(event.clipboardData.files).filter((file) =>\n            file.type.startsWith('image/')\n          )\n\n          if (imageFiles.length > 0) {\n            event.preventDefault()\n            triggerFileUpload(imageFiles, editor)\n            // Claim the paste even though the host inserts: a screenshot populates\n            // both `files` and `items`, and the branch below would fire again.\n            return true\n          }\n        }\n\n        if (event.clipboardData?.items?.length) {\n          const imageFiles = Array.from(event.clipboardData.items)\n            .filter((item) => item.kind === 'file' && item.type.startsWith('image/'))\n            .map((item) => item.getAsFile())\n            .filter((file): file is File => file !== null)\n\n          if (imageFiles.length > 0) {\n            event.preventDefault()\n            triggerFileUpload(imageFiles, editor)\n            return true\n          }\n        }\n\n        const content = slice.content\n        if (content.childCount === 1) {\n          const firstChild = content.firstChild\n\n          if (firstChild?.type.name === 'paragraph' && firstChild.childCount === 1) {\n            const textNode = firstChild.firstChild\n\n            if (textNode?.type.name === 'text' && textNode.text) {\n              const text = textNode.text.trim()\n\n              // Mirror parseHTML's allowBase64 gate: pasted data: URLs are rejected too.\n              if (!options.allowBase64 && text.startsWith('data:')) return false\n\n              if (isImageUrl(text)) {\n                event.preventDefault()\n\n                const imageNode = view.state.schema.nodes[options.nodeName].create({\n                  src: text,\n                  alt: 'Pasted Image'\n                })\n\n                const tr = view.state.tr.replaceSelectionWith(imageNode)\n                view.dispatch(tr)\n\n                return true\n              }\n            }\n          }\n        }\n\n        return false\n      }\n    }\n  })\n}\n\nconst triggerFileUpload = (files: File[], editor: Editor) => {\n  document.dispatchEvent(new CustomEvent('editorFileUpload', { detail: { files, editor } }))\n}\n","import { Node, nodePasteRule } from '@tiptap/core'\n\nimport { captionAttribute } from '../../caption'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport {\n  EMBED_BLOCK_LAYOUT_DEFAULTS,\n  type EmbedNodeOptions,\n  kitAttrDefaults,\n  layoutAttrDefaults,\n  resolveEmbedLayoutDimensions\n} from '../../utils/embedKit'\nimport { createIframeEmbedNodeView, renderIframeEmbedHTML } from '../../utils/iframeEmbedNode'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport {\n  buildLoomEmbedUrl,\n  LOOM_EMBED_ATTR_KEYS,\n  LOOM_EMBED_KIT_DEFAULTS,\n  type LoomEmbedKitOptions,\n  resolveLoomIframeAttributes\n} from './embedOptions'\nimport { getLoomEmbedUrl, isValidLoomUrl, LOOM_REGEX_GLOBAL } from './helper'\n\nexport interface LoomOptions extends StyleLayoutOptions, EmbedNodeOptions, LoomEmbedKitOptions {}\n\nexport type SetLoomOptions = {\n  src: string\n} & StyleLayoutOptions &\n  Partial<LoomEmbedKitOptions>\n\nconst LOOM_IFRAME_CONFIG = {\n  wrapperClass: 'hypermultimedia--loom__content',\n  dataVideoAttr: 'data-loom-video',\n  renderWrapperClass: 'loom-video',\n  embedAttrKeys: LOOM_EMBED_ATTR_KEYS,\n  loadingProvider: 'Loom',\n  buildEmbedUrl: (src: string, attrs: Record<string, unknown>, options: LoomOptions) =>\n    buildLoomEmbedUrl(src, attrs, options, getLoomEmbedUrl),\n  resolveIframeAttributes: resolveLoomIframeAttributes\n} satisfies Parameters<typeof createIframeEmbedNodeView<LoomOptions>>[0]\n\nexport const Loom = Node.create<LoomOptions>({\n  name: 'loom',\n  draggable: true,\n\n  ...createTypedMediaMarkdownHooks('loom'),\n\n  addOptions() {\n    return {\n      ...LOOM_EMBED_KIT_DEFAULTS,\n      ...EMBED_BLOCK_LAYOUT_DEFAULTS\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        default: null,\n        parseHTML: (element) => {\n          const src = element.getAttribute('src')\n          return src && isValidLoomUrl(src) ? src : null\n        }\n      },\n      caption: captionAttribute(),\n      ...layoutAttrDefaults(this.options),\n      ...kitAttrDefaults(this.options, LOOM_EMBED_KIT_DEFAULTS)\n    }\n  },\n\n  addNodeView() {\n    return createIframeEmbedNodeView(LOOM_IFRAME_CONFIG, () => ({\n      name: this.name,\n      options: this.options,\n      editor: this.editor\n    }))\n  },\n\n  parseHTML() {\n    return [{ tag: 'div[data-loom-video] iframe' }]\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return renderIframeEmbedHTML(LOOM_IFRAME_CONFIG, this.options, { node, HTMLAttributes })\n  },\n\n  addCommands() {\n    return {\n      setLoom:\n        (options: SetLoomOptions) =>\n        ({ commands }) => {\n          if (!isValidLoomUrl(options.src)) return false\n\n          const { width, height, ...rest } = options\n          const layout = resolveEmbedLayoutDimensions(this.editor, { width, height }, this.options)\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...rest, ...layout, keyId: generateShortId() }\n          })\n        }\n    }\n  },\n\n  addPasteRules() {\n    if (!this.options.addPasteHandler) return []\n\n    return [\n      nodePasteRule({\n        find: LOOM_REGEX_GLOBAL,\n        type: this.type,\n        getAttributes: (match) => {\n          const src = match[0]?.trim()\n          if (!src || !isValidLoomUrl(src)) return false\n          return { src }\n        }\n      })\n    ]\n  }\n})\n\nexport type { LoomEmbedKitOptions }\n","import { type Editor, mergeAttributes, type NodeViewRendererProps } from '@tiptap/core'\nimport type { NodeView, ViewMutationRecord } from '@tiptap/pm/view'\n\nimport { type CaptionHandle, createCaptionElement, readCaption } from '../caption'\nimport {\n  layoutAttrsChanged,\n  parseLayoutDimensions,\n  syncIframeNodeLayout,\n  wrapMediaWithLoadingShell\n} from '../loading'\nimport {\n  EMBED_LAYOUT_ATTR_KEYS,\n  embedAttrsEqual,\n  type FullscreenIframeKitOptions,\n  resolveFullscreenIframeAttributes\n} from './embedKit'\nimport { ignoreNodeViewSubtreeMutation } from './ignoreNodeViewMutation'\nimport { mediaLayoutCss } from './layoutStyle'\nimport type { StyleLayoutOptions } from './utils'\n\nexport type IframeAttrs = Record<string, unknown>\n\nexport interface IframeEmbedNodeOptions extends StyleLayoutOptions {\n  HTMLAttributes: Record<string, unknown>\n}\n\nexport interface IframeEmbedConfig<TOptions extends IframeEmbedNodeOptions> {\n  wrapperClass: string\n  dataVideoAttr: string\n  renderWrapperClass: string\n  embedAttrKeys: readonly string[]\n  contentEditableFalse?: boolean\n  buildEmbedUrl: (src: string, attrs: IframeAttrs, options: TOptions) => string | null\n  /** Label in the default loading shell, e.g. \"YouTube\". */\n  loadingProvider?: string\n  syncLoadingHost?: (\n    el: HTMLElement,\n    width: number,\n    height: number,\n    attrs: IframeAttrs,\n    options: TOptions\n  ) => void\n  resolveIframeAttributes: (\n    attrs: IframeAttrs,\n    options: TOptions,\n    width: number,\n    height: number\n  ) => Record<string, string | number | boolean>\n}\n\ntype FullscreenIframeEmbedConfigInput<TOptions extends IframeEmbedNodeOptions> = Pick<\n  IframeEmbedConfig<TOptions>,\n  | 'wrapperClass'\n  | 'dataVideoAttr'\n  | 'renderWrapperClass'\n  | 'embedAttrKeys'\n  | 'buildEmbedUrl'\n  | 'contentEditableFalse'\n  | 'loadingProvider'\n>\n\nexport function defineFullscreenIframeEmbedConfig<\n  TOptions extends IframeEmbedNodeOptions & FullscreenIframeKitOptions\n>(config: FullscreenIframeEmbedConfigInput<TOptions>): IframeEmbedConfig<TOptions> {\n  return {\n    ...config,\n    resolveIframeAttributes: (attrs, options, width, height) =>\n      resolveFullscreenIframeAttributes(attrs, options, width, height)\n  }\n}\n\nfunction bindIframeSyncLoadingHost<TOptions extends IframeEmbedNodeOptions>(\n  config: IframeEmbedConfig<TOptions>,\n  attrs: IframeAttrs,\n  options: TOptions\n): ((el: HTMLElement, width: number, height: number) => void) | undefined {\n  if (!config.syncLoadingHost) return undefined\n  return (el, width, height) => config.syncLoadingHost!(el, width, height, attrs, options)\n}\n\nfunction writeIframeAttributes(\n  iframe: HTMLIFrameElement,\n  embedUrl: string,\n  iframeAttrs: Record<string, string | number | boolean>\n): void {\n  iframe.setAttribute('src', embedUrl)\n  // Document Referrer-Policy is same-origin (no Referer cross-origin), which makes\n  // YouTube/Vimeo reject the embed; element-level policy still sends the origin.\n  iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin')\n\n  for (const [key, value] of Object.entries(iframeAttrs)) {\n    if (key === 'allowfullscreen') {\n      if (value) iframe.setAttribute('allowfullscreen', '')\n      else iframe.removeAttribute('allowfullscreen')\n      continue\n    }\n    if (key === 'width' || key === 'height') continue\n    iframe.setAttribute(key, String(value))\n  }\n}\n\nexport function createIframeEmbedNodeView<TOptions extends IframeEmbedNodeOptions>(\n  config: IframeEmbedConfig<TOptions>,\n  getExtension: () => { name: string; options: TOptions; editor: Editor }\n) {\n  return function iframeEmbedNodeView({\n    node,\n    HTMLAttributes,\n    getPos\n  }: NodeViewRendererProps): NodeView {\n    const { name, options, editor } = getExtension()\n    let attrsSnapshot = node.attrs\n    const dom = document.createElement('div')\n    const content = document.createElement('div')\n    content.style.display = 'block'\n    content.style.width = '100%'\n    content.style.height = '100%'\n    const iframe = document.createElement('iframe')\n\n    if (config.contentEditableFalse) dom.contentEditable = 'false'\n    dom.classList.add(config.wrapperClass)\n    if (node.attrs.keyId) {\n      dom.setAttribute('data-key-id', String(node.attrs.keyId))\n    }\n\n    const dims = parseLayoutDimensions(node.attrs)\n\n    const src = String(HTMLAttributes.src ?? node.attrs.src ?? '')\n    const embedUrl = config.buildEmbedUrl(src, node.attrs, options) ?? ''\n    const resolvedAttrs = config.resolveIframeAttributes(\n      node.attrs,\n      options,\n      dims.width,\n      dims.height\n    )\n\n    writeIframeAttributes(iframe, embedUrl, resolvedAttrs)\n\n    Object.entries(mergeAttributes(options.HTMLAttributes, { 'data-node-name': name })).forEach(\n      ([key, value]) => {\n        if (value !== undefined && value !== null) {\n          iframe.setAttribute(key, String(value))\n        }\n      }\n    )\n\n    content.append(iframe)\n    const syncLoadingHost = bindIframeSyncLoadingHost(config, node.attrs, options)\n    const { dom: loadingHost, controller } = wrapMediaWithLoadingShell(\n      editor,\n      {\n        kind: 'embed',\n        provider: config.loadingProvider,\n        width: dims.width,\n        height: dims.height\n      },\n      content,\n      { bindLoad: { element: iframe }, syncLoadingHost }\n    )\n\n    dom.append(loadingHost)\n    syncIframeNodeLayout({\n      wrapper: dom,\n      attrs: node.attrs,\n      loadingHost,\n      iframe,\n      dims,\n      syncLoadingHost\n    })\n\n    const caption: CaptionHandle = createCaptionElement({\n      editor,\n      getPos,\n      initial: readCaption(node)\n    })\n    dom.append(caption.el)\n\n    return {\n      dom,\n      // Leaf node: no contentDOM — PM would re-parse async iframe mutations and reload embeds.\n      // Caption is nested contenteditable; PM must not handle its events or it deletes the node.\n      stopEvent: (event: Event) => caption.el.contains(event.target as Node | null),\n      destroy: () => {\n        controller.destroy()\n        caption.destroy()\n      },\n      ignoreMutation: (mutation: ViewMutationRecord) =>\n        ignoreNodeViewSubtreeMutation(mutation, dom),\n      update: (updatedNode: typeof node) => {\n        if (updatedNode.type.name !== name) return false\n\n        if (layoutAttrsChanged(updatedNode.attrs, attrsSnapshot, EMBED_LAYOUT_ATTR_KEYS)) {\n          syncIframeNodeLayout({\n            wrapper: dom,\n            attrs: updatedNode.attrs,\n            loadingHost,\n            iframe,\n            syncLoadingHost: bindIframeSyncLoadingHost(config, updatedNode.attrs, options)\n          })\n        }\n\n        if (!embedAttrsEqual(updatedNode.attrs, attrsSnapshot, config.embedAttrKeys)) {\n          return false\n        }\n\n        caption.sync(readCaption(updatedNode))\n        attrsSnapshot = updatedNode.attrs\n        return true\n      }\n    }\n  }\n}\n\nexport function renderIframeEmbedHTML<TOptions extends IframeEmbedNodeOptions>(\n  config: IframeEmbedConfig<TOptions>,\n  options: TOptions,\n  {\n    node,\n    HTMLAttributes\n  }: {\n    node: { attrs: IframeAttrs }\n    HTMLAttributes: Record<string, unknown>\n  }\n): [string, Record<string, unknown>, [string, Record<string, unknown>]] {\n  const src = String(HTMLAttributes.src ?? node.attrs.src ?? '')\n  const embedUrl = config.buildEmbedUrl(src, node.attrs, options) ?? ''\n\n  const { width, height } = parseLayoutDimensions({\n    width: HTMLAttributes.width ?? node.attrs.width,\n    height: HTMLAttributes.height ?? node.attrs.height\n  })\n\n  const style = mediaLayoutCss(\n    {\n      display: String(HTMLAttributes.display ?? node.attrs.display ?? 'block'),\n      height,\n      width,\n      float: (HTMLAttributes.float ?? node.attrs.float) as string | null,\n      clear: String(HTMLAttributes.clear ?? node.attrs.clear ?? 'none'),\n      margin: String(HTMLAttributes.margin ?? node.attrs.margin ?? 'auto'),\n      justifyContent: String(HTMLAttributes.justifyContent ?? node.attrs.justifyContent ?? 'start')\n    },\n    'export-embed'\n  )\n\n  const iframeAttrs = config.resolveIframeAttributes(node.attrs, options, width, height)\n  const { allowfullscreen, ...restIframeAttrs } = iframeAttrs\n\n  return [\n    'div',\n    { [config.dataVideoAttr]: '', class: config.renderWrapperClass, style },\n    [\n      'iframe',\n      mergeAttributes(options.HTMLAttributes, HTMLAttributes, {\n        // Match the live node-view: element-level policy overrides the document's\n        // same-origin Referrer-Policy so YouTube/Vimeo accept the serialized embed.\n        referrerpolicy: 'strict-origin-when-cross-origin',\n        src: embedUrl,\n        ...restIframeAttrs,\n        ...(allowfullscreen ? { allowfullscreen: '' } : {})\n      })\n    ]\n  ]\n}\n","import { Node, nodePasteRule } from '@tiptap/core'\n\nimport { captionAttribute } from '../../caption'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport {\n  EMBED_BLOCK_LAYOUT_DEFAULTS,\n  type EmbedNodeOptions,\n  kitAttrDefaults,\n  layoutAttrDefaults\n} from '../../utils/embedKit'\nimport {\n  createIframeEmbedNodeView,\n  type IframeEmbedConfig,\n  renderIframeEmbedHTML\n} from '../../utils/iframeEmbedNode'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport {\n  buildSoundCloudEmbedUrl,\n  fitSoundCloudLayoutToEditorColumn,\n  parseSoundCloudTrackUrl,\n  resolveSoundCloudIframeAttributes,\n  SOUNDCLOUD_EMBED_ATTR_KEYS,\n  SOUNDCLOUD_PLAYER_KIT_DEFAULTS,\n  type SoundCloudPlayerKitOptions,\n  syncSoundCloudResponsiveHost\n} from './embedOptions'\nimport { isValidSoundCloudUrl, SOUNDCLOUD_URL_REGEX_GLOBAL } from './helper'\n\nexport interface SoundCloudOptions\n  extends StyleLayoutOptions, EmbedNodeOptions, SoundCloudPlayerKitOptions {}\n\nexport type SetSoundCloudOptions = {\n  src: string\n} & StyleLayoutOptions &\n  Partial<SoundCloudPlayerKitOptions>\n\nconst SOUNDCLOUD_IFRAME_CONFIG: IframeEmbedConfig<SoundCloudOptions> = {\n  wrapperClass: 'hypermultimedia--soundcloud__content',\n  dataVideoAttr: 'data-soundcloud-video',\n  renderWrapperClass: 'soundcloud-video',\n  embedAttrKeys: SOUNDCLOUD_EMBED_ATTR_KEYS,\n  loadingProvider: 'SoundCloud',\n  buildEmbedUrl: buildSoundCloudEmbedUrl,\n  resolveIframeAttributes: resolveSoundCloudIframeAttributes,\n  syncLoadingHost: syncSoundCloudResponsiveHost\n}\n\nexport const SoundCloud = Node.create<SoundCloudOptions>({\n  name: 'soundcloud',\n  draggable: true,\n\n  ...createTypedMediaMarkdownHooks('soundcloud'),\n\n  addOptions() {\n    return {\n      ...SOUNDCLOUD_PLAYER_KIT_DEFAULTS,\n      ...EMBED_BLOCK_LAYOUT_DEFAULTS,\n      width: 450,\n      height: 120\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        default: null,\n        parseHTML: (element) => {\n          const raw = element.getAttribute('src')\n          if (!raw) return null\n          const track = parseSoundCloudTrackUrl(raw) ?? raw\n          return isValidSoundCloudUrl(track) ? track : null\n        }\n      },\n      caption: captionAttribute(),\n      ...layoutAttrDefaults(this.options),\n      ...kitAttrDefaults(this.options, SOUNDCLOUD_PLAYER_KIT_DEFAULTS)\n    }\n  },\n\n  addNodeView() {\n    return createIframeEmbedNodeView(SOUNDCLOUD_IFRAME_CONFIG, () => ({\n      name: this.name,\n      options: this.options,\n      editor: this.editor\n    }))\n  },\n\n  parseHTML() {\n    return [{ tag: 'div[data-soundcloud-video] iframe' }]\n  },\n\n  addCommands() {\n    return {\n      setSoundCloud:\n        (options: SetSoundCloudOptions) =>\n        ({ commands }) => {\n          if (!isValidSoundCloudUrl(options.src)) return false\n\n          const { width, height, ...rest } = options\n          const layout = fitSoundCloudLayoutToEditorColumn(\n            this.editor,\n            Number(width ?? this.options.width),\n            Number(height ?? this.options.height),\n            rest,\n            this.options\n          )\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...rest, ...layout, keyId: generateShortId() }\n          })\n        }\n    }\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return renderIframeEmbedHTML(SOUNDCLOUD_IFRAME_CONFIG, this.options, { node, HTMLAttributes })\n  },\n\n  addPasteRules() {\n    if (!this.options.addPasteHandler) return []\n\n    return [\n      nodePasteRule({\n        find: SOUNDCLOUD_URL_REGEX_GLOBAL,\n        type: this.type,\n        getAttributes: (match) => {\n          const src = match.input?.trim()\n          if (!src || !isValidSoundCloudUrl(src)) return false\n          const layout = fitSoundCloudLayoutToEditorColumn(\n            this.editor,\n            Number(this.options.width),\n            Number(this.options.height),\n            { src },\n            this.options\n          )\n          return { src, ...layout }\n        }\n      })\n    ]\n  }\n})\n","import { Node, nodePasteRule } from '@tiptap/core'\n\nimport { captionAttribute } from '../../caption'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport {\n  EMBED_BLOCK_LAYOUT_DEFAULTS,\n  type EmbedNodeOptions,\n  kitAttrDefaults,\n  layoutAttrDefaults\n} from '../../utils/embedKit'\nimport {\n  createIframeEmbedNodeView,\n  defineFullscreenIframeEmbedConfig,\n  type IframeEmbedConfig,\n  renderIframeEmbedHTML\n} from '../../utils/iframeEmbedNode'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport {\n  buildSpotifyEmbedUrl,\n  canonicalSpotifyUrl,\n  defaultSpotifyHeight,\n  fitSpotifyLayoutToEditorColumn,\n  parseSpotifyEntity,\n  SPOTIFY_DEFAULT_WIDTH,\n  SPOTIFY_EMBED_ATTR_KEYS,\n  SPOTIFY_EMBED_KIT_DEFAULTS,\n  SPOTIFY_FULL_HEIGHT,\n  type SpotifyEmbedKitOptions,\n  syncSpotifyResponsiveHost\n} from './embedOptions'\nimport { SPOTIFY_EMBED_IFRAME_REGEX, SPOTIFY_URL_REGEX_GLOBAL } from './helper'\n\nexport interface SpotifyOptions\n  extends StyleLayoutOptions, EmbedNodeOptions, SpotifyEmbedKitOptions {}\n\nexport type SetSpotifyOptions = {\n  src: string\n} & StyleLayoutOptions &\n  Partial<SpotifyEmbedKitOptions>\n\nconst SPOTIFY_IFRAME_CONFIG: IframeEmbedConfig<SpotifyOptions> = {\n  ...defineFullscreenIframeEmbedConfig<SpotifyOptions>({\n    wrapperClass: 'hypermultimedia--spotify__content',\n    dataVideoAttr: 'data-spotify-media',\n    renderWrapperClass: 'spotify-media',\n    embedAttrKeys: SPOTIFY_EMBED_ATTR_KEYS,\n    loadingProvider: 'Spotify',\n    buildEmbedUrl: buildSpotifyEmbedUrl\n  }),\n  // Spotify's player is fixed-height: pin the host instead of aspect-scaling it.\n  syncLoadingHost: syncSpotifyResponsiveHost\n}\n\n/** Keep an integer px dimension, else null → schema default (bare embed iframes use `width=\"100%\"`). */\nconst integerPxAttr = (raw: string | null): number | null => {\n  const px = raw ? Number.parseInt(raw, 10) : NaN\n  return Number.isFinite(px) && String(px) === raw ? px : null\n}\n\nexport const Spotify = Node.create<SpotifyOptions>({\n  name: 'spotify',\n  draggable: true,\n\n  ...createTypedMediaMarkdownHooks('spotify'),\n\n  addOptions() {\n    return {\n      ...SPOTIFY_EMBED_KIT_DEFAULTS,\n      ...EMBED_BLOCK_LAYOUT_DEFAULTS,\n      width: SPOTIFY_DEFAULT_WIDTH,\n      height: SPOTIFY_FULL_HEIGHT\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        default: null,\n        // Serialized iframes carry the `embed/` URL; normalize back to the canonical share URL.\n        parseHTML: (element) => {\n          const entity = parseSpotifyEntity(element.getAttribute('src') ?? '')\n          return entity ? canonicalSpotifyUrl(entity) : null\n        }\n      },\n      caption: captionAttribute(),\n      ...layoutAttrDefaults(this.options),\n      // Tiptap auto-reads (and merge-wins) a bare embed iframe's `width=\"100%\"` over\n      // a parse rule's getAttrs → a 100px sliver. Sanitize both dims to an integer\n      // or the default.\n      width: {\n        default: this.options.width,\n        parseHTML: (el) => integerPxAttr(el.getAttribute('width'))\n      },\n      height: {\n        default: this.options.height,\n        parseHTML: (el) => integerPxAttr(el.getAttribute('height'))\n      },\n      ...kitAttrDefaults(this.options, SPOTIFY_EMBED_KIT_DEFAULTS)\n    }\n  },\n\n  addNodeView() {\n    return createIframeEmbedNodeView(SPOTIFY_IFRAME_CONFIG, () => ({\n      name: this.name,\n      options: this.options,\n      editor: this.editor\n    }))\n  },\n\n  parseHTML() {\n    return [\n      { tag: 'div[data-spotify-media] iframe' },\n      {\n        // Pasted/imported \"Copy embed\" iframe (bare, no wrapper div).\n        tag: 'iframe[src*=\"open.spotify.com/embed\"]',\n        getAttrs: (node: string | HTMLElement) =>\n          typeof node !== 'string' && parseSpotifyEntity(node.getAttribute('src') ?? '')\n            ? {}\n            : false\n      }\n    ]\n  },\n\n  addCommands() {\n    return {\n      setSpotify:\n        (options: SetSpotifyOptions) =>\n        ({ commands }) => {\n          const entity = parseSpotifyEntity(options.src)\n          if (!entity) return false\n\n          const { width, height, ...rest } = options\n          const layout = fitSpotifyLayoutToEditorColumn(\n            this.editor,\n            Number(width ?? this.options.width),\n            Number(height ?? defaultSpotifyHeight(entity.type))\n          )\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...rest, ...layout, keyId: generateShortId() }\n          })\n        }\n    }\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return renderIframeEmbedHTML(SPOTIFY_IFRAME_CONFIG, this.options, { node, HTMLAttributes })\n  },\n\n  addPasteRules() {\n    if (!this.options.addPasteHandler) return []\n\n    return [\n      nodePasteRule({\n        find: SPOTIFY_URL_REGEX_GLOBAL,\n        type: this.type,\n        getAttributes: (match) => {\n          const src = match.input?.trim()\n          const entity = src ? parseSpotifyEntity(src) : null\n          if (!src || !entity) return false\n          return { src, height: defaultSpotifyHeight(entity.type) }\n        }\n      }),\n      // Pasting the \"Copy embed\" iframe markup (plain text) — pull out the embed URL.\n      nodePasteRule({\n        find: SPOTIFY_EMBED_IFRAME_REGEX,\n        type: this.type,\n        getAttributes: (match) => {\n          const entity = parseSpotifyEntity(match[1] ?? '')\n          if (!entity) return false\n          return { src: canonicalSpotifyUrl(entity), height: defaultSpotifyHeight(entity.type) }\n        }\n      })\n    ]\n  }\n})\n","import { Node, nodeInputRule } from '@tiptap/core'\n\nimport { captionAttribute } from '../../caption'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport { layoutAttrDefaults, resolveEmbedLayoutDimensions } from '../../utils/embedKit'\nimport {\n  createHtmlMediaNodeView,\n  type HtmlMediaNodeConfig,\n  renderHtmlMediaHTML\n} from '../../utils/htmlMediaNode'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { isSafeMediaSrc } from '../../utils/mediaUrl'\nimport { generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport { inputRegex } from './helper'\n\ninterface NodeOptions {\n  HTMLAttributes: Record<string, unknown>\n}\n\nexport interface VideoOptions extends StyleLayoutOptions, NodeOptions {\n  // Node attributes\n  inline?: boolean\n\n  // Html attributes\n  controls?: boolean\n  autoplay?: boolean\n  loop?: boolean\n  muted?: boolean\n  poster?: string | null\n  preload?: 'none' | 'metadata' | 'auto'\n  src: string | null\n}\n\nexport type SetVideoOptions = {\n  src: string\n  controls?: boolean\n  autoplay?: boolean\n  loop?: boolean\n  muted?: boolean\n  poster?: string | null\n  preload?: 'none' | 'metadata' | 'auto'\n} & StyleLayoutOptions\n\n/** `<video>` element attrs; remount the node view when any change. */\nconst VIDEO_REMOUNT_ATTR_KEYS = [\n  'src',\n  'controls',\n  'autoplay',\n  'loop',\n  'muted',\n  'preload',\n  'poster'\n] as const\n\nconst VIDEO_MEDIA_CONFIG: HtmlMediaNodeConfig = {\n  tag: 'video',\n  elementAttrKeys: VIDEO_REMOUNT_ATTR_KEYS\n}\n\nexport const Video = Node.create<VideoOptions>({\n  name: 'video',\n  draggable: true,\n\n  ...createTypedMediaMarkdownHooks('video', { withDimensions: true }),\n\n  addOptions() {\n    return {\n      src: null,\n      margin: 'auto',\n      clear: 'none',\n      float: null,\n      display: 'block',\n      justifyContent: 'start',\n      HTMLAttributes: {},\n      inline: false,\n      controls: true,\n      autoplay: false,\n      loop: false,\n      muted: false,\n      poster: null,\n      preload: 'metadata',\n      height: 480,\n      width: 640\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        default: null,\n        parseHTML: (element) => {\n          const src = element.getAttribute('src')\n          return isSafeMediaSrc(src) ? src : null\n        }\n      },\n      controls: {\n        default: this.options.controls\n      },\n      autoplay: {\n        default: this.options.autoplay\n      },\n      loop: {\n        default: this.options.loop\n      },\n      muted: {\n        default: this.options.muted\n      },\n      poster: {\n        default: this.options.poster\n      },\n      preload: {\n        default: this.options.preload\n      },\n      ...layoutAttrDefaults(this.options),\n      caption: captionAttribute()\n    }\n  },\n\n  addNodeView() {\n    return createHtmlMediaNodeView(VIDEO_MEDIA_CONFIG, () => ({\n      name: this.name,\n      editor: this.editor\n    }))\n  },\n\n  parseHTML() {\n    return [\n      {\n        tag: 'div[data-video] video[src]'\n      }\n    ]\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return renderHtmlMediaHTML(VIDEO_MEDIA_CONFIG, this.options, { node, HTMLAttributes })\n  },\n\n  addInputRules() {\n    return [\n      nodeInputRule({\n        find: inputRegex,\n        type: this.type,\n        getAttributes: (match) => {\n          const [, , src, width, height] = match\n\n          return { src, width, height }\n        }\n      })\n    ]\n  },\n\n  addCommands() {\n    return {\n      setVideo:\n        (options) =>\n        ({ commands }) => {\n          if (!options.src) return false\n\n          const { width, height, ...rest } = options\n          const layout = resolveEmbedLayoutDimensions(this.editor, { width, height }, this.options)\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...rest, ...layout, keyId: generateShortId() }\n          })\n        }\n    }\n  }\n})\n","// ![video](http://example.com/path-to-video.mp4 width=640 height=480)\n// ![video](http://example.com/path-to-video.mp4)\n// No `g` flag — input rules reuse the regex across keystrokes and a sticky lastIndex makes matches flaky.\nexport const inputRegex = /(?:^|\\s)(!\\[video]\\((\\S+)(?:\\s+width=(\\d+))?(?:\\s+height=(\\d+))?\\))$/\n","import { Node, nodePasteRule } from '@tiptap/core'\n\nimport { captionAttribute } from '../../caption'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport {\n  EMBED_BLOCK_LAYOUT_DEFAULTS,\n  type EmbedNodeOptions,\n  kitAttrDefaults,\n  layoutAttrDefaults,\n  resolveEmbedLayoutDimensions\n} from '../../utils/embedKit'\nimport {\n  createIframeEmbedNodeView,\n  defineFullscreenIframeEmbedConfig,\n  renderIframeEmbedHTML\n} from '../../utils/iframeEmbedNode'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { isSafeMediaSrc } from '../../utils/mediaUrl'\nimport { generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport {\n  buildVimeoEmbedUrl,\n  parseVimeoVideoRef,\n  VIMEO_EMBED_ATTR_KEYS,\n  VIMEO_PLAYER_KIT_DEFAULTS,\n  type VimeoBylinePortrait,\n  type VimeoPlayerKitOptions,\n  type VimeoQuality\n} from './embedOptions'\nimport { isValidVimeoUrl, VIMEO_REGEX_GLOBAL } from './helper'\n\nexport interface VimeoOptions extends StyleLayoutOptions, EmbedNodeOptions, VimeoPlayerKitOptions {}\n\nexport type SetVimeoOptions = {\n  src: string\n  start?: number\n} & StyleLayoutOptions &\n  Partial<VimeoPlayerKitOptions>\n\nconst VIMEO_IFRAME_CONFIG = defineFullscreenIframeEmbedConfig<VimeoOptions>({\n  wrapperClass: 'hypermultimedia--vimeo__content',\n  dataVideoAttr: 'data-vimeo-video',\n  renderWrapperClass: 'vimeo-video',\n  embedAttrKeys: VIMEO_EMBED_ATTR_KEYS,\n  loadingProvider: 'Vimeo',\n  buildEmbedUrl: buildVimeoEmbedUrl\n})\n\nexport const Vimeo = Node.create<VimeoOptions>({\n  name: 'vimeo',\n  draggable: true,\n\n  ...createTypedMediaMarkdownHooks('vimeo'),\n\n  addOptions() {\n    return {\n      ...VIMEO_PLAYER_KIT_DEFAULTS,\n      ...EMBED_BLOCK_LAYOUT_DEFAULTS\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        default: null,\n        parseHTML: (element) => {\n          const src = element.getAttribute('src')\n          if (!isSafeMediaSrc(src)) return null\n          // renderHTML emits the player.vimeo.com widget URL; unwrap it back to the\n          // page URL. `h` must survive — it is the unlisted-video access token.\n          const ref = parseVimeoVideoRef(src)\n          if (!ref) return src\n          return `https://vimeo.com/${ref.id}${ref.h ? `?h=${ref.h}` : ''}`\n        }\n      },\n      start: { default: 0 },\n      caption: captionAttribute(),\n      ...layoutAttrDefaults(this.options),\n      ...kitAttrDefaults(this.options, VIMEO_PLAYER_KIT_DEFAULTS)\n    }\n  },\n\n  addNodeView() {\n    return createIframeEmbedNodeView(VIMEO_IFRAME_CONFIG, () => ({\n      name: this.name,\n      options: this.options,\n      editor: this.editor\n    }))\n  },\n\n  parseHTML() {\n    return [{ tag: 'div[data-vimeo-video] iframe' }]\n  },\n\n  addCommands() {\n    return {\n      setVimeo:\n        (options: SetVimeoOptions) =>\n        ({ commands }) => {\n          if (!isValidVimeoUrl(options.src)) return false\n\n          const { width, height, ...rest } = options\n          const layout = resolveEmbedLayoutDimensions(this.editor, { width, height }, this.options)\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...rest, ...layout, keyId: generateShortId() }\n          })\n        }\n    }\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return renderIframeEmbedHTML(VIMEO_IFRAME_CONFIG, this.options, { node, HTMLAttributes })\n  },\n\n  addPasteRules() {\n    if (!this.options.addPasteHandler) return []\n\n    return [\n      nodePasteRule({\n        find: VIMEO_REGEX_GLOBAL,\n        type: this.type,\n        getAttributes: (match) => {\n          const src = match[0]?.trim()\n          if (!src || !isValidVimeoUrl(src)) return false\n          return { src }\n        }\n      })\n    ]\n  }\n})\n\nexport type { VimeoBylinePortrait, VimeoQuality }\n","import { mergeAttributes, Node, nodePasteRule } from '@tiptap/core'\n\nimport {\n  captionAttribute,\n  type CaptionHandle,\n  createCaptionElement,\n  readCaption\n} from '../../caption'\nimport { layoutAttrsChanged, wrapMediaWithLoadingShell } from '../../loading'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport {\n  defaultsFromOptions,\n  EMBED_BASE_DEFAULTS,\n  embedAttrsEqual,\n  type EmbedNodeOptions\n} from '../../utils/embedKit'\nimport { ignoreNodeViewSubtreeMutation } from '../../utils/ignoreNodeViewMutation'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { applyStyles, generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport {\n  buildXOEmbedParams,\n  X_EMBED_DEFAULT_MAXWIDTH,\n  type XEmbedTheme,\n  type XOEmbedKitOptions\n} from './embedOptions'\nimport { mountXEmbed, normalizeXUrl, watchXEmbedHeight, X_URL_REGEX_GLOBAL } from './helper'\n\nconst EMBED_ATTR_KEYS = ['src', 'maxwidth', 'theme', 'lang', 'hide_media', 'hide_thread'] as const\n\n/** oEmbed params seeded from kit options; per-node attrs override them. */\nconst X_OEMBED_ATTR_KEYS = ['theme', 'lang', 'hide_media', 'hide_thread', 'dnt'] as const\n\n/** Wrapper placement attrs; update() re-applies layout in place when they change. */\nconst X_LAYOUT_ATTR_KEYS = ['display', 'float', 'clear', 'margin', 'justifyContent'] as const\n\nexport interface XOptions extends StyleLayoutOptions, EmbedNodeOptions, XOEmbedKitOptions {}\n\nexport type AddXOptions = {\n  src: string\n  maxwidth?: number\n  theme?: XEmbedTheme\n  lang?: string\n  hide_media?: boolean\n  hide_thread?: boolean\n} & StyleLayoutOptions\n\nexport const X = Node.create<XOptions>({\n  name: 'x',\n  draggable: true,\n  // Above StarterKit's default (100) so `blockquote.twitter-tweet` parses as an X\n  // embed before the generic blockquote rule claims it.\n  priority: 101,\n\n  ...createTypedMediaMarkdownHooks('x'),\n\n  addOptions() {\n    return {\n      theme: 'light' as XEmbedTheme,\n      lang: 'en',\n      dnt: true,\n      hide_media: false,\n      hide_thread: false,\n      ...EMBED_BASE_DEFAULTS\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        // Without a per-attribute parser Tiptap reads a literal `src=` off the\n        // blockquote and it overwrites whatever `getAttrs` normalized below.\n        default: null,\n        parseHTML: (element) => normalizeXUrl(element.getAttribute('src') ?? '')\n      },\n      maxwidth: {\n        default: X_EMBED_DEFAULT_MAXWIDTH\n      },\n      align: {\n        default: null\n      },\n      ...defaultsFromOptions(this.options, X_OEMBED_ATTR_KEYS),\n      ...defaultsFromOptions(this.options, X_LAYOUT_ATTR_KEYS),\n      caption: captionAttribute()\n    }\n  },\n\n  parseHTML() {\n    return [\n      {\n        tag: 'blockquote.twitter-tweet',\n        getAttrs: (node: string | HTMLElement) => {\n          if (typeof node === 'string') return {}\n\n          const href = (node as HTMLElement).querySelector('a')?.getAttribute('href') ?? ''\n          const src = normalizeXUrl(href)\n          // Reject invalid/hostile hrefs outright — a `src: null` node would later\n          // throw in renderHTML on the null text child.\n          return src ? { src } : false\n        }\n      }\n    ]\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    // Documents persisted before the parse gate landed can still carry a hostile\n    // src, and collaborative attrs are never re-validated — gate the read too.\n    const url = normalizeXUrl(String(HTMLAttributes.src ?? node.attrs.src ?? '')) ?? ''\n\n    // Serialize the standard twitter-tweet markup only; the per-node oEmbed and layout\n    // attrs are runtime nodeView concerns and would be invalid attributes on <blockquote>.\n    return [\n      'blockquote',\n      mergeAttributes(this.options.HTMLAttributes, { class: 'twitter-tweet' }),\n      ['a', { href: url }, url]\n    ]\n  },\n\n  addNodeView() {\n    const editor = this.editor\n    const kitOptions = this.options\n\n    return ({ node, getPos }) => {\n      const layoutRoot = document.createElement('div')\n      let attrsSnapshot = node.attrs\n      layoutRoot.classList.add('hypermultimedia--x__content')\n\n      const embedTarget = document.createElement('div')\n      const shellWidth = Number(node.attrs.maxwidth ?? X_EMBED_DEFAULT_MAXWIDTH)\n      const shellHeight = Math.min(420, Math.round(shellWidth * 0.72))\n\n      const applyLayout = (attrs: typeof node.attrs) => {\n        const width = Number(attrs.maxwidth ?? X_EMBED_DEFAULT_MAXWIDTH)\n        applyStyles(layoutRoot, {\n          display: attrs.display,\n          width,\n          float: attrs.float,\n          clear: attrs.clear,\n          margin: attrs.margin,\n          justifyContent: attrs.justifyContent\n        })\n        layoutRoot.style.maxWidth = '100%'\n        layoutRoot.dataset.xTheme = (attrs.theme as XEmbedTheme | undefined) ?? kitOptions.theme\n      }\n\n      applyLayout(node.attrs)\n\n      const { dom: loadingHost, controller } = wrapMediaWithLoadingShell(\n        editor,\n        { kind: 'embed', provider: 'X', width: shellWidth, height: shellHeight },\n        embedTarget\n      )\n\n      layoutRoot.append(loadingHost)\n\n      const mountAbort = new AbortController()\n      let isFluid = false\n      let disposeHeightSync = watchXEmbedHeight(embedTarget, loadingHost, {\n        minHeight: shellHeight,\n        isFluid: () => isFluid\n      })\n\n      void mountXEmbed(embedTarget, buildXOEmbedParams(node.attrs, kitOptions), mountAbort.signal)\n        .then((ok) => {\n          if (mountAbort.signal.aborted) return\n          if (!ok) {\n            controller.markError()\n            return\n          }\n          isFluid = true\n          loadingHost.classList.add('hm-media-host--fluid')\n          loadingHost.style.width = '100%'\n          loadingHost.style.height = 'auto'\n          controller.markReady()\n        })\n        .catch(() => {\n          if (mountAbort.signal.aborted) return\n          controller.markError()\n        })\n\n      const caption: CaptionHandle = createCaptionElement({\n        editor,\n        getPos,\n        initial: readCaption(node)\n      })\n      layoutRoot.append(caption.el)\n\n      return {\n        dom: layoutRoot,\n        // Caption is a nested contenteditable the node view owns; keep PM out of its events.\n        stopEvent: (event: Event) => caption.el.contains(event.target as globalThis.Node | null),\n        destroy: () => {\n          disposeHeightSync()\n          mountAbort.abort()\n          controller.destroy()\n          caption.destroy()\n        },\n        ignoreMutation: (mutation) => ignoreNodeViewSubtreeMutation(mutation, layoutRoot),\n        update: (updatedNode) => {\n          if (updatedNode.type.name !== this.name) return false\n\n          if (layoutAttrsChanged(updatedNode.attrs, attrsSnapshot, X_LAYOUT_ATTR_KEYS)) {\n            applyLayout(updatedNode.attrs)\n          }\n\n          if (!embedAttrsEqual(updatedNode.attrs, attrsSnapshot, EMBED_ATTR_KEYS)) {\n            return false\n          }\n\n          caption.sync(readCaption(updatedNode))\n          attrsSnapshot = updatedNode.attrs\n          return true\n        }\n      }\n    }\n  },\n\n  addCommands() {\n    return {\n      setX:\n        (options) =>\n        ({ commands }) => {\n          const src = normalizeXUrl(options.src)\n          if (!src) return false\n\n          // Omitted attrs fall back to schema defaults via ProseMirror computeAttrs.\n          return commands.insertContent({\n            type: this.name,\n            attrs: { ...options, src, keyId: generateShortId() }\n          })\n        }\n    }\n  },\n\n  addPasteRules() {\n    if (!this.options.addPasteHandler) return []\n\n    return [\n      nodePasteRule({\n        find: X_URL_REGEX_GLOBAL,\n        type: this.type,\n        getAttributes: (match) => {\n          const src = normalizeXUrl(match[0])\n          if (!src) return false\n          // Schema defaults (maxwidth, theme, …) fill in via ProseMirror computeAttrs.\n          return { src }\n        }\n      })\n    ]\n  }\n})\n","import { Node, nodePasteRule } from '@tiptap/core'\n\nimport { captionAttribute } from '../../caption'\nimport { createTypedMediaMarkdownHooks } from '../../markdown/typedMediaMarkdown'\nimport {\n  EMBED_BLOCK_LAYOUT_DEFAULTS,\n  type EmbedNodeOptions,\n  kitAttrDefaults,\n  layoutAttrDefaults,\n  resolveEmbedLayoutDimensions\n} from '../../utils/embedKit'\nimport {\n  createIframeEmbedNodeView,\n  defineFullscreenIframeEmbedConfig,\n  renderIframeEmbedHTML\n} from '../../utils/iframeEmbedNode'\nimport { keyIdAttribute } from '../../utils/media-node-attrs'\nimport { isSafeMediaSrc } from '../../utils/mediaUrl'\nimport { generateShortId, type StyleLayoutOptions } from '../../utils/utils'\nimport {\n  buildYoutubeEmbedUrl,\n  parseYoutubeStartSeconds,\n  parseYoutubeVideoId,\n  YOUTUBE_EMBED_ATTR_KEYS,\n  YOUTUBE_PLAYER_KIT_DEFAULTS,\n  type YoutubeEmbedColor,\n  type YoutubeListType,\n  type YoutubePlayerKitOptions\n} from './embedOptions'\nimport { isValidYoutubeUrl, YOUTUBE_REGEX_GLOBAL } from './helper'\n\nexport interface YoutubeOptions\n  extends StyleLayoutOptions, EmbedNodeOptions, YoutubePlayerKitOptions {}\n\nexport type SetYoutubeVideoOptions = {\n  src: string\n  start?: number\n} & StyleLayoutOptions &\n  Partial<YoutubePlayerKitOptions>\n\nconst YOUTUBE_IFRAME_CONFIG = defineFullscreenIframeEmbedConfig<YoutubeOptions>({\n  wrapperClass: 'hypermultimedia--youtube__content',\n  dataVideoAttr: 'data-youtube-video',\n  renderWrapperClass: 'youtube-video',\n  embedAttrKeys: YOUTUBE_EMBED_ATTR_KEYS,\n  contentEditableFalse: true,\n  loadingProvider: 'YouTube',\n  buildEmbedUrl: buildYoutubeEmbedUrl\n})\n\nexport const Youtube = Node.create<YoutubeOptions>({\n  name: 'youtube',\n  draggable: true,\n\n  ...createTypedMediaMarkdownHooks('youtube'),\n\n  addOptions() {\n    return {\n      ...YOUTUBE_PLAYER_KIT_DEFAULTS,\n      ...EMBED_BLOCK_LAYOUT_DEFAULTS\n    }\n  },\n\n  inline() {\n    return this.options.inline\n  },\n\n  group() {\n    return this.options.inline ? 'inline' : 'block'\n  },\n\n  addAttributes() {\n    return {\n      keyId: keyIdAttribute(),\n      src: {\n        default: null,\n        parseHTML: (element) => {\n          const src = element.getAttribute('src')\n          if (!isSafeMediaSrc(src)) return null\n          // renderHTML emits the `/embed/` widget URL, so a plain read-back would\n          // store the player as the node's src. `nocookie` is worse: that host is\n          // not a watch host, so the next embed build would return an empty src.\n          const id = parseYoutubeVideoId(src.replace(/youtube-nocookie\\.com/i, 'youtube.com'))\n          return id ? `https://www.youtube.com/watch?v=${id}` : src\n        }\n      },\n      start: { default: 0 },\n      caption: captionAttribute(),\n      ...layoutAttrDefaults(this.options),\n      ...kitAttrDefaults(this.options, YOUTUBE_PLAYER_KIT_DEFAULTS)\n    }\n  },\n\n  addNodeView() {\n    return createIframeEmbedNodeView(YOUTUBE_IFRAME_CONFIG, () => ({\n      name: this.name,\n      options: this.options,\n      editor: this.editor\n    }))\n  },\n\n  parseHTML() {\n    return [{ tag: 'div[data-youtube-video] iframe' }]\n  },\n\n  addCommands() {\n    return {\n      setYoutubeVideo:\n        (options: SetYoutubeVideoOptions) =>\n        ({ commands }) => {\n          if (!isValidYoutubeUrl(options.src)) return false\n\n          const startFromUrl = parseYoutubeStartSeconds(options.src)\n          const start = options.start ?? (startFromUrl !== undefined ? startFromUrl : 0)\n          const { width, height, ...rest } = options\n          const layout = resolveEmbedLayoutDimensions(this.editor, { width, height }, this.options)\n\n          return commands.insertContent({\n            type: this.name,\n            attrs: {\n              ...rest,\n              ...layout,\n              start,\n              keyId: generateShortId()\n            }\n          })\n        }\n    }\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    return renderIframeEmbedHTML(YOUTUBE_IFRAME_CONFIG, this.options, { node, HTMLAttributes })\n  },\n\n  addPasteRules() {\n    if (!this.options.addPasteHandler) return []\n\n    return [\n      nodePasteRule({\n        find: YOUTUBE_REGEX_GLOBAL,\n        type: this.type,\n        getAttributes: (match) => {\n          const src = match.input?.trim()\n          if (!src || !isValidYoutubeUrl(src)) return false\n\n          const start = parseYoutubeStartSeconds(src)\n\n          return {\n            src,\n            ...(start !== undefined && start > 0 ? { start } : {})\n          }\n        }\n      })\n    ]\n  }\n})\n\nexport type { YoutubeEmbedColor, YoutubeListType }\n","import { isImageUrl } from '../nodes/image/helper'\nimport { isValidLoomUrl } from '../nodes/loom/helper'\nimport { isValidSoundCloudUrl } from '../nodes/soundcloud/helper'\nimport { isValidSpotifyUrl } from '../nodes/spotify/helper'\nimport { isValidVimeoUrl } from '../nodes/vimeo/helper'\nimport { isValidXUrl } from '../nodes/x/helper'\nimport { isValidYoutubeUrl } from '../nodes/youtube/helper'\nimport { isAudioUrl, isVideoUrl } from './mediaUrl'\n\n/** Every media node `.name`, the source of truth for which node a URL inserts. */\nexport type MediaNodeType =\n  'image' | 'video' | 'audio' | 'youtube' | 'vimeo' | 'soundcloud' | 'spotify' | 'loom' | 'x'\n\n/**\n * Resolve a URL to the media node that should render it, or null when none does.\n * Specific providers and image win before the generic video/audio extension\n * matchers, so a `youtube.com/...webm` URL maps to youtube, not video.\n */\nexport const detectMediaType = (url: string): MediaNodeType | null => {\n  if (isValidYoutubeUrl(url)) return 'youtube'\n  if (isValidVimeoUrl(url)) return 'vimeo'\n  if (isValidSoundCloudUrl(url)) return 'soundcloud'\n  if (isValidSpotifyUrl(url)) return 'spotify'\n  if (isValidLoomUrl(url)) return 'loom'\n  if (isValidXUrl(url)) return 'x'\n  if (isImageUrl(url)) return 'image'\n  if (isVideoUrl(url)) return 'video'\n  if (isAudioUrl(url)) return 'audio'\n  return null\n}\n","import { detectMediaType } from './detectMediaType'\n\n/**\n * True if `url` is one this kit auto-converts to a media node on paste.\n * A host running a link extension alongside it should veto these so media\n * URLs become nodes, not links: `Hyperlink.configure({ shouldAutoLink: (url)\n * => !isMediaUrl(url) })`. Resolves the paste precedence between the two.\n */\nexport const isMediaUrl = (url: string): boolean => {\n  const kind = detectMediaType(url)\n  // Raw video/audio URLs are excluded on purpose so pasted `.mp4`/`.mp3` stay links.\n  return kind !== null && kind !== 'video' && kind !== 'audio'\n}\n"],"mappings":"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","names":["index_exports","__export","HyperMultimediaKit","MEDIA_MARGIN_OPTIONS","MEDIA_PLACEMENT_OPTIONS","SPOTIFY_ENTITY_TYPES","X_EMBED_DEFAULT_MAXWIDTH","X_EMBED_SIZE_OPTIONS","X_EMBED_THEME_OPTIONS","applyNodeAttributes","a","buildXOEmbedParams","canViewOriginal","closeMediaToolbar","closeToolbarPopover","composeMediaActions","copyMediaNode","createDefaultMediaLoadingShell","createMediaToolbar","createReplaceUrlPopover","detectMediaType","downloadMedia","fitDimensionsToBounds","fitLayoutToEditorColumn","getCurrentMediaPlacement","getEditorContentWidth","getMediaPlacementAttrs","n","isAudioUrl","isDownloadable","isImageUrl","isMediaUrl","isValidLoomUrl","isValidSoundCloudUrl","isValidSpotifyUrl","isValidVimeoUrl","isValidXUrl","isValidYoutubeUrl","isVideoUrl","layoutMediaActions","openMediaPopover","openMediaToolbar","openReplaceUrlPopover","openToolbarPopover","parseSpotifyEntity","parseYoutubeVideoId","removeMediaNode","resolveMediaActions","resolveMediaNodePos","resolveXEmbedSizeId","viewOriginalMedia","wrapMediaWithLoadingShell","__toCommonJS","import_core","import_core","import_state","import_state","contextByToolbar","registerToolbarContext","bar","ctx","updateToolbarContextNodePos","wrapper","nodePos","import_dom","SHOW_DELAY_MS","VISIBLE_CLASS","bubble","showTimer","ensureBubble","cancelShow","show","target","label","el","x","y","computePosition","offset","flip","shift","hideTooltip","attachTooltip","schedule","scheduleOnFocus","HYPER_MULTIMEDIA_KIT_EXTENSION_NAME","getKitStorage","editor","import_dom","createPopoverController","current","listeners","snapshot","notify","state","listener","popover","kind","metadata","previous","referenceElement","coordinates","defaultController","getDefaultController","DEFAULT_OFFSET","DEFAULT_Z_INDEX","SHIFT_PADDING","ARROW_STATIC_OFFSET","OUTSIDE_CLICK_DEFER_MS","EXIT_FALLBACK_MS","ORIGIN_BY_SIDE","createVirtualReference","coords","x","y","width","height","setupKeyboardNav","container","focusable","removers","el","i","onKeydown","e","next","createPopover","options","controller","getDefaultController","referenceElement","coordinates","content","placement","offsetVal","showArrow","className","zIndex","role","ariaLabel","ignoreOutsideClickOn","crossAxisShift","onShow","onHide","reference","outsideIgnore","cleanups","visible","terminated","autoUpdateCleanup","isOutsideDismissTarget","target","root","arrowEl","contentContainer","navListeners","clearNavListeners","setContent","updatePosition","middleware","offset","flip","shift","arrow","hideMiddleware","actual","middlewareData","computePosition","side","ax","ay","staticSide","unregister","removalTimer","onExitEnd","cancelPendingRemoval","finishHide","hide","fn","popover","autoUpdate","onMouseDown","onTouchStart","onKeyDown","ref","icons_exports","__export","AlignCenter","AlignLeft","AlignRight","Caption","Copy","Download","ExternalLink","ImageLeft","ImageRight","More","Replace","Trash","VIEW_BOX","materialIcon","path","size","fill","className","PATHS","icon","props","MEDIA_MARGIN_OPTIONS","MEDIA_PLACEMENT_OPTIONS","getMediaPlacementAttrs","id","margin","getCurrentMediaPlacement","attrs","isZeroLength","value","horizontalMarginExpression","trimmed","parts","right","left","maxWidthForFloatedMedia","sum","isFloatedMedia","float","ICON_SIZE","ALIGN_MATERIAL","BUILTIN_MATERIAL","Caption","ExternalLink","Download","Replace","Copy","Trash","More","materialPlacementIcon","key","id","icon","icons_exports","normalizeIconKey","attrs","getCurrentMediaPlacement","defaultMaterialIcon","resolveMediaToolbarIcon","scope","icons","resolvedKey","iconCtx","override","getKitStorage","TOOLBAR_MENU_OFFSET","POPOVER_BY_VARIANT","z","toolbarTooltipDetaches","bindToolbarTooltips","bar","detaches","releaseToolbarTooltips","detach","actionButton","action","ctx","variant","icons","tooltipDetaches","btn","active","iconMarkup","resolveMediaToolbarIcon","label","labelSpan","span","a","openMediaPopover","options","kind","content","trigger","positionReference","role","ariaLabel","toggle","controller","O","state","popover","ve","openToolbarPopover","body","closeToolbarPopover","buildOverflowMenu","menuActions","menu","section","heading","row","X_EMBED_DEFAULT_MAXWIDTH","X_EMBED_SIZE_OPTIONS","X_EMBED_THEME_OPTIONS","resolveXEmbedSizeId","maxwidth","width","option","buildXOEmbedParams","attrs","options","params","import_transform","HIDE_RESIZE_GRIPPER_META","shouldHideResizeGripper","tr","fragmentHasTrackedType","fragment","targetNodeTypes","found","node","rangeHasTrackedType","doc","from","to","transactionAffectsTrackedNodes","i","step","applyStyles","dom","styles","style","key","value","generateShortId","randomPart","timePart","omitNullishAndFalse","attrs","result","keyIdAttribute","generateShortId","applyNodeAttributes","editor","nodePos","attributes","state","dispatch","transaction","key","value","HIDE_RESIZE_GRIPPER_META","setMediaCaption","caption","import_model","CONTENT_CLASS","GRIPPER_CLASS","GRIPPER_SELECTOR","queryHTMLElement","root","selector","el","queryGripperByKeyId","keyId","queryMediaByKeyId","isGripper","isMediaWrapper","findGripperForMedia","target","linked","prev","resolveMediaFromGripper","gripper","next","findMediaTarget","media","imageRoot","wrapper","getMediaNodeType","fromClass","ASSET_NODE_TYPES","mediaKind","nodeType","isDownloadableMedia","isInteractiveEmbed","isInsideMediaControlsUI","finePointerQuery","hasFinePointer","isDownloadable","nodeType","isDownloadableMedia","isEmbedNode","mediaKind","isUploadedMedia","ctx","getKitStorage","canViewOriginal","isOpenableSrc","src","viewOriginalMedia","removeMediaNode","node","serializeNodeHTML","serializer","wrapper","copyMediaNode","html","filenameFromSrc","name","downloadMedia","res","blob","objectUrl","a","focusCaption","caption","range","sel","fitDimensionsToBounds","width","height","bounds","aspectRatio","targetWidth","targetHeight","maxHeight","getEditorContentWidth","editor","root","target","fitLayoutToEditorColumn","maxWidth","EMBED_BASE_DEFAULTS","EMBED_BLOCK_LAYOUT_DEFAULTS","EMBED_LAYOUT_ATTR_KEYS","FULLSCREEN_IFRAME_DOM_ATTR_KEYS","SOUNDCLOUD_IFRAME_DOM_ATTR_KEYS","resolveEmbedOption","attrs","options","key","fromAttrs","embedAttrsEqual","left","right","keys","defaultsFromOptions","result","layoutAttrDefaults","kitAttrDefaults","kitDefaults","resolveEmbedLayoutDimensions","editor","defaults","width","height","fitLayoutToEditorColumn","resolveFullscreenIframeAttributes","LOOM_QUERY_PARAM","LOOM_EMBED_PARAM_KEYS","LOOM_EMBED_KIT_DEFAULTS","LOOM_IFRAME_DOM_ATTR_KEYS","FULLSCREEN_IFRAME_DOM_ATTR_KEYS","LOOM_EMBED_ATTR_KEYS","buildLoomEmbedUrl","url","attrs","options","normalize","base","embedUrl","optionKey","value","resolveEmbedOption","formatted","resolveLoomIframeAttributes","width","height","resolveFullscreenIframeAttributes","LOOM_REGEX","LOOM_REGEX_GLOBAL","isValidLoomUrl","url","getLoomVideoId","match","getLoomEmbedUrl","id","SOUNDCLOUD_URL_PATTERN","SOUNDCLOUD_URL_REGEX_GLOBAL","isValidSoundCloudUrl","url","CSS_PROP","EMPTY_BAG","pixelOrNull","value","parsed","resolvePlacement","attrs","defaults","float","margin","clear","floated","isFloatedMedia","maxWidthForFloatedMedia","mediaLayoutStyleProps","mode","placement","CSS_INJECTION_RE","formatMediaLayoutCss","props","key","mediaLayoutCss","applyMediaLayoutToDom","dom","dims","options","bag","styleLayoutFromAttrs","DEFAULT_LAYOUT_FALLBACK","IMAGE_LAYOUT_FALLBACK","AUDIO_LAYOUT_FALLBACK","syncResponsiveMediaHost","el","width","height","syncMediaSurfaceFill","parseLayoutDimensions","attrs","fallback","layoutAttrsChanged","left","right","keys","EMBED_LAYOUT_ATTR_KEYS","key","syncResizableMediaLayout","options","loadingHost","surfaces","syncLoadingHost","syncSurface","surface","syncMediaNodeLayout","dims","applyMediaLayoutToDom","styleLayoutFromAttrs","syncImageNodeLayout","syncIframeNodeLayout","SPOTIFY_ENTITY_TYPES","SPOTIFY_ID","isSpotifyEntityType","value","parseSpotifyEntity","input","trimmed","uri","url","segments","type","id","canonicalSpotifyUrl","entity","SPOTIFY_DEFAULT_WIDTH","SPOTIFY_FULL_HEIGHT","SPOTIFY_TRACK_HEIGHT","defaultSpotifyHeight","resolveSpotifyLayoutMinHeight","attrs","src","height","SPOTIFY_EMBED_KIT_DEFAULTS","SPOTIFY_EMBED_ATTR_KEYS","FULLSCREEN_IFRAME_DOM_ATTR_KEYS","buildSpotifyEmbedUrl","options","embedUrl","theme","resolveEmbedOption","fitSpotifyLayoutToEditorColumn","editor","width","fitLayoutToEditorColumn","syncSpotifyResponsiveHost","el","syncResponsiveMediaHost","SPOTIFY_URL_REGEX","SPOTIFY_URL_REGEX_GLOBAL","SPOTIFY_EMBED_IFRAME_REGEX","isValidSpotifyUrl","url","parseSpotifyEntity","VIMEO_QUERY_PARAM","VIMEO_PLAYER_KIT_DEFAULTS","VIMEO_EMBED_ATTR_KEYS","FULLSCREEN_IFRAME_DOM_ATTR_KEYS","parseVimeoVideoRef","url","parsed","host","id","h","formatVimeoParam","value","normalizeVimeoColor","color","buildVimeoEmbedUrl","attrs","options","ref","embedUrl","optionKey","resolveEmbedOption","queryKey","start","width","height","VIMEO_REGEX_GLOBAL","isValidVimeoUrl","url","parseVimeoVideoRef","Logger","message","error","HTMLSanitizer","value","trimmed","allowedHosts","host","allowed","html","options","doc","error","Logger","div","element","hrefHosts","child","span","attr","name","attrName","X_URL_REGEX_GLOBAL","X_OEMBED_HREF_HOSTS","normalizeXUrl","raw","trimmed","withProtocol","parsed","host","match","isValidXUrl","url","getTwttr","X_SCRIPT_POLL_MS","X_SCRIPT_TIMEOUT_MS","X_LAYOUT_TIMEOUT_MS","X_LAYOUT_MIN_HEIGHT_PX","X_LAYOUT_STABLE_FRAMES","hasRenderableXEmbed","wrapper","blockquote","href","measureEmbedHeight","element","watchXEmbedHeight","options","apply","height","floor","measure","h","ro","mo","delayMs","ms","signal","resolve","id","onAbort","resolveXEmbedMeasureTarget","deadline","iframe","waitForStableSize","lastHeight","stableFrames","finished","finish","timeoutId","waitForXEmbedStable","target","whenTwttrReady","existing","reject","pollId","settled","cleanup","fn","resolveIfReady","twttr","loadXScript","script","sanitizeXEmbedHtml","html","HTMLSanitizer","seedXEmbedMarkup","statusUrl","anchor","fetchOEmbedHtml","params","urlWithParams","key","value","response","mountXEmbed","YOUTUBE_QUERY_PARAM","YOUTUBE_PLAYER_KIT_DEFAULTS","parseYoutubeVideoId","url","parsed","host","fromQuery","parseYoutubeStartSeconds","startParam","asNumber","hashMatch","getYoutubeEmbedHost","nocookie","YOUTUBE_EMBED_ATTR_KEYS","FULLSCREEN_IFRAME_DOM_ATTR_KEYS","buildYoutubeEmbedUrl","attrs","options","videoId","resolveEmbedOption","embedUrl","appendParam","queryKey","value","optionKey","startAttr","YOUTUBE_REGEX_GLOBAL","isValidYoutubeUrl","url","parseYoutubeVideoId","videoUrlRegex","audioUrlRegex","isVideoUrl","url","isAudioUrl","DANGEROUS_SCHEME_RE","INLINE_IMAGE_DATA_RE","isSafeMediaSrc","src","allowInlineImage","validatedUrl","isValid","url","REPLACE_URL_GUARDS","isValidYoutubeUrl","isValidVimeoUrl","isValidSoundCloudUrl","isValidSpotifyUrl","isValidLoomUrl","normalizeXUrl","ANY_URL","isSafeMediaSrc","createReplaceUrlPopover","options","body","input","error","submit","value","message","event","confirm","iconScope","icon","resolveMediaToolbarIcon","openReplaceUrlPopover","ctx","factory","getKitStorage","guard","resolve","close","closeToolbarPopover","content","src","applyNodeAttributes","openMediaPopover","DEFAULT_WRAP_MARGIN","isPresetMargin","value","MEDIA_MARGIN_OPTIONS","option","submenuItem","active","btn","textItem","label","onClick","renderAlignmentSubmenu","ctx","list","current","getCurrentMediaPlacement","rawMargin","margin","id","MEDIA_PLACEMENT_OPTIONS","resolveMediaToolbarIcon","applyNodeAttributes","getMediaPlacementAttrs","isWrapPlacement","attrs","placement","currentMargin","marginLabel","renderMarginSubmenu","apply","marginItem","renderXOptionsSubmenu","activeSize","resolveXEmbedSizeId","activeTheme","section","title","children","wrap","heading","X_EMBED_SIZE_OPTIONS","maxwidth","X_EMBED_THEME_OPTIONS","BASE_ACTIONS","focusCaption","canViewOriginal","viewOriginalMedia","isDownloadable","downloadMedia","openReplaceUrlPopover","copyMediaNode","removeMediaNode","X_OPTIONS_ACTION","NODE_ACTION_RECIPES","builder","withoutId","list","id","action","anchorIndex","at","i","spliced","base","mapById","fn","next","make","setPlacement","placement","ids","drop","found","rank","last","b","ra","rb","entry","composeMediaActions","actions","layoutMediaActions","layout","defaults","ctx","inline","overflow","builder","computeMediaActions","nodeType","mediaActions","recipe","NODE_ACTION_RECIPES","defaults","composeMediaActions","BASE_ACTIONS","actionsCache","resolveMediaActions","editor","doc","getKitStorage","cache","hit","actions","renderMediaToolbar","ctx","actions","kitIcons","getKitStorage","tooltipDetaches","inline","overflow","action","bar","btn","actionButton","openToolbarPopover","divider","more","resolveMediaToolbarIcon","a","buildOverflowMenu","bindToolbarTooltips","createMediaToolbar","target","editor","nodeType","nodePos","node","ctx","closeToolbarPopover","bar","renderMediaToolbar","resolveMediaActions","registerToolbarContext","CLOSE_REMOVE_DELAY_MS","pendingRemoval","cancelToolbarRemoval","el","timer","teardownToolbar","releaseToolbarTooltips","removeToolbarElement","scheduleToolbarRemoval","existingToolbar","wrapper","live","openMediaToolbar","options","factory","reused","content","getKitStorage","createMediaToolbar","closeMediaToolbar","closeToolbarPopover","n","HOVER_HIDE_DELAY_MS","controlsByEditor","createControlsState","editor","event","handlePointerOutside","handleMediaDeleteKey","handleToolbarEscape","hideMediaResizeControls","state","getControlsState","clearActiveState","canUseHoverControls","finePointer","isDeleteKey","isFormControlInMediaUI","element","isInsideMediaControlsUI","tag","isEditingMediaCaption","setMediaResizing","active","resolveMediaBox","target","host","positionResizeGripper","gripper","parent","parentRect","box","originLeft","originTop","activateResizeGripper","deactivateResizeGripper","purgeOrphanedResizeControls","root","node","media","resolveMediaFromGripper","findMediaTarget","cancelHoverHide","isPointerOverActiveControls","focused","hit","scheduleHoverHide","setPointerTracking","attach","findGripperForMedia","closeMediaToolbar","setOutsideListeners","remapActiveNodePos","transactions","pos","tr","mapped","updateToolbarContextNodePos","repositionActiveGripper","syncControlsToDoc","clickTarget","resolveMediaNodePos","view","nodeType","$pos","showMediaResizeControls","getMediaNodeType","nodePos","openMediaToolbar","lockMediaSelection","deleteActiveMediaNode","activeTarget","activeNodePos","mediaResizing","handleMediaHover","fromTarget","handleMediaUnhover","related","handleMediaClick","isInteractiveEmbed","restoreControlsAfterResize","hasFinePointer","MediaResizeControls","editor","trackedNodes","view","prevState","syncControlsToDoc","hideMediaResizeControls","transactions","tr","shouldHideResizeGripper","transactionAffectsTrackedNodes","remapActiveNodePos","event","handleMediaHover","hasFinePointer","handleMediaUnhover","handleMediaClick","import_core","import_state","import_view","parseSoundCloudTrackUrl","url","trimmed","parsed","host","track","SOUNDCLOUD_WIDGET_PARAM_KEYS","SOUNDCLOUD_VISUAL_PLAYER_MIN_HEIGHT","SOUNDCLOUD_COMPACT_PLAYER_MIN_HEIGHT","SOUNDCLOUD_VISUAL_PLAYER_LAYOUT_MIN_HEIGHT","SOUNDCLOUD_EMBED_ATTR_KEYS","SOUNDCLOUD_IFRAME_DOM_ATTR_KEYS","SOUNDCLOUD_PLAYER_KIT_DEFAULTS","formatWidgetValue","value","resolveSoundCloudVisual","attrs","options","height","resolveSoundCloudLayoutMinHeight","resolveSoundCloudExtensionOptions","editor","e","fitSoundCloudLayoutToEditorColumn","width","fitted","fitLayoutToEditorColumn","minHeight","syncSoundCloudResponsiveHost","el","hostHeight","syncResponsiveMediaHost","buildSoundCloudEmbedUrl","trackUrl","canonical","embedUrl","key","resolveEmbedOption","resolveSoundCloudIframeAttributes","DEFAULT_CONSTRAINTS","getPointerPosition","mouseEvent","calculateAspectRatioDimensions","deltaX","deltaY","initialWidth","initialHeight","aspectRatio","corner","newWidth","newHeight","absX","absY","resolveResizeConstraints","editor","maxWidth","getEditorContentWidth","resolveMediaNodeConstraints","node","base","options","resolveSoundCloudExtensionOptions","minHeight","resolveSoundCloudLayoutMinHeight","resolveSpotifyLayoutMinHeight","clampDimensionsToConstraints","width","height","constraints","maxHeight","fitted","fitDimensionsToBounds","updateNodeDimensions","nodePos","state","dispatch","tr","nodeAtPos","clamped","readGripperDimensions","gripper","fromStyle","axis","resetGripperPosition","initialLeft","initialTop","setupKeyboardListeners","shiftPressed","handleKeyDown","e","handleKeyUp","activeDragByEditor","abortActiveGripperDrag","editor","isLeftEdge","clamp","isTopEdge","clampGripperBox","box","state","constraints","clamped","clampDimensionsToConstraints","top","left","setGripperDragging","gripper","dragging","resolveDragTargetPos","media","resolveMediaFromGripper","nodeType","getMediaNodeType","resolveMediaNodePos","attachGripperDrag","config","computeBox","handleStart","event","pointerId","ended","pointerMoveActive","setMediaResizing","O","keyboard","setupKeyboardListeners","start","getPointerPosition","nodePos","node","resolveMediaNodeConstraints","applyMove","clientX","clientY","raw","ensurePointerCapture","onPointerMove","ev","onMouseMove","finish","width","height","readGripperDimensions","resetGripperPosition","updateNodeDimensions","teardown","restoreControlsAfterResize","cancel","onWindowBlur","onKeyDown","bindings","setDragListeners","active","target","type","listener","abort","computeSideBox","deltaX","deltaY","computeCornerBox","corner","dimensions","calculateAspectRatioDimensions","createClamp","extraClass","clamp","createClamps","collectMediaGripperInfo","nodeNames","doc","result","node","pos","createMediaResizeGripper","mediaInfo","editor","gripper","clamps","attachGripperDrag","computeSideBox","corner","computeCornerBox","buildOptimizedDecorations","nodeNames","doc","editor","decorations","collectMediaGripperInfo","gripperInfo","options","createMediaResizeGripper","MediaResizeGripper","acceptedNodes","editor","buildDecorations","doc","buildOptimizedDecorations","key","_","tr","old","HIDE_RESIZE_GRIPPER_META","transactionAffectsTrackedNodes","state","abortActiveGripperDrag","import_core","import_state","readCaption","node","releaseMediaNodeSelectionForCaption","editor","getPos","pos","state","selection","after","createCaptionElement","initial","el","commit","setMediaCaption","onFocus","onBlur","onKeyDown","event","stop","caption","next","mediaElementFrom","selector","captionAttribute","element","wrapRenderWithCaption","rendered","figureStyle","DEFAULT_MESSAGES","createDefaultMediaLoadingShell","context","overlay","shimmer","body","provider","message","spinner","MEDIA_READY_EVENTS","NOOP_CONTROLLER","resolveFactory","option","createDefaultMediaLoadingShell","createPlainMediaHost","content","host","stampLoadingOverlay","overlay","bindMediaElementLoad","element","controller","isAlreadyReady","teardown","event","onReady","onError","wrapMediaWithLoadingShell","editor","context","options","factory","getKitStorage","syncResponsiveMediaHost","slot","settled","unbindLoad","settle","message","text","messageEl","bindLoad","TYPED_MEDIA_SRC_VALIDATORS","isAudioUrl","isVideoUrl","isValidYoutubeUrl","isValidVimeoUrl","isValidSoundCloudUrl","isValidSpotifyUrl","isValidLoomUrl","isValidXUrl","createTypedMediaMarkdownHooks","nodeName","options","tokenName","dimPattern","pattern","src","_tokens","_lexer","match","token","_helpers","attrs","node","_ctx","width","height","out","import_core","ignoreNodeViewSubtreeMutation","mutation","root","target","pickMediaElementAttrs","node","HTMLAttributes","keys","picked","key","omitNullishAndFalse","createHtmlMediaNodeView","config","getExtension","getPos","name","editor","attrsSnapshot","dom","content","media","dims","parseLayoutDimensions","htmlAttributes","value","loadingHost","controller","wrapMediaWithLoadingShell","syncMediaNodeLayout","caption","createCaptionElement","readCaption","event","mutation","ignoreNodeViewSubtreeMutation","updatedNode","layoutAttrsChanged","embedAttrsEqual","renderHtmlMediaHTML","options","mediaClass","style","mediaLayoutCss","layout","EMBED_LAYOUT_ATTR_KEYS","inputRegex","AUDIO_REMOUNT_ATTR_KEYS","AUDIO_MEDIA_CONFIG","AUDIO_LAYOUT_FALLBACK","Audio","createTypedMediaMarkdownHooks","keyIdAttribute","element","src","isSafeMediaSrc","layoutAttrDefaults","captionAttribute","createHtmlMediaNodeView","node","HTMLAttributes","renderHtmlMediaHTML","inputRegex","match","width","height","options","commands","generateShortId","import_core","inputRegex","httpImageUrlRegex","isImageUrl","url","import_core","createImageNodeView","options","editor","node","HTMLAttributes","getPos","attrsSnapshot","layoutRoot","dims","parseLayoutDimensions","IMAGE_LAYOUT_FALLBACK","img","key","value","loadingHost","controller","wrapMediaWithLoadingShell","syncImageNodeLayout","caption","createCaptionElement","readCaption","event","mutation","ignoreNodeViewSubtreeMutation","updatedNode","layoutAttrsChanged","import_state","HyperImagePastePlugin","editor","options","view","event","slice","imageFiles","file","triggerFileUpload","item","content","firstChild","textNode","text","isImageUrl","imageNode","tr","files","Image","token","_helpers","alt","isSafeMediaSrc","node","_ctx","src","keyIdAttribute","element","mediaElementFrom","attributes","captionAttribute","acceptSrc","isImageUrl","img","HTMLAttributes","keyId","width","height","float","clear","margin","caption","readCaption","imgStyle","blockStyle","mediaLayoutCss","wrapRenderWithCaption","options","commands","generateShortId","rest","fitted","fitLayoutToEditorColumn","tr","dispatch","found","pos","createImageNodeView","inputRegex","match","editor","HyperImagePastePlugin","import_core","import_core","defineFullscreenIframeEmbedConfig","config","attrs","options","width","height","resolveFullscreenIframeAttributes","bindIframeSyncLoadingHost","el","writeIframeAttributes","iframe","embedUrl","iframeAttrs","key","value","createIframeEmbedNodeView","getExtension","node","HTMLAttributes","getPos","name","editor","attrsSnapshot","dom","content","dims","parseLayoutDimensions","src","resolvedAttrs","syncLoadingHost","loadingHost","controller","wrapMediaWithLoadingShell","syncIframeNodeLayout","caption","createCaptionElement","readCaption","event","mutation","ignoreNodeViewSubtreeMutation","updatedNode","layoutAttrsChanged","EMBED_LAYOUT_ATTR_KEYS","embedAttrsEqual","renderIframeEmbedHTML","style","mediaLayoutCss","allowfullscreen","restIframeAttrs","LOOM_IFRAME_CONFIG","LOOM_EMBED_ATTR_KEYS","src","attrs","options","buildLoomEmbedUrl","getLoomEmbedUrl","resolveLoomIframeAttributes","Loom","createTypedMediaMarkdownHooks","LOOM_EMBED_KIT_DEFAULTS","EMBED_BLOCK_LAYOUT_DEFAULTS","keyIdAttribute","element","isValidLoomUrl","captionAttribute","layoutAttrDefaults","kitAttrDefaults","createIframeEmbedNodeView","node","HTMLAttributes","renderIframeEmbedHTML","commands","width","height","rest","layout","resolveEmbedLayoutDimensions","generateShortId","LOOM_REGEX_GLOBAL","match","import_core","SOUNDCLOUD_IFRAME_CONFIG","SOUNDCLOUD_EMBED_ATTR_KEYS","buildSoundCloudEmbedUrl","resolveSoundCloudIframeAttributes","syncSoundCloudResponsiveHost","SoundCloud","createTypedMediaMarkdownHooks","SOUNDCLOUD_PLAYER_KIT_DEFAULTS","EMBED_BLOCK_LAYOUT_DEFAULTS","keyIdAttribute","element","raw","track","parseSoundCloudTrackUrl","isValidSoundCloudUrl","captionAttribute","layoutAttrDefaults","kitAttrDefaults","createIframeEmbedNodeView","options","commands","width","height","rest","layout","fitSoundCloudLayoutToEditorColumn","generateShortId","node","HTMLAttributes","renderIframeEmbedHTML","SOUNDCLOUD_URL_REGEX_GLOBAL","match","src","import_core","SPOTIFY_IFRAME_CONFIG","defineFullscreenIframeEmbedConfig","SPOTIFY_EMBED_ATTR_KEYS","buildSpotifyEmbedUrl","syncSpotifyResponsiveHost","integerPxAttr","raw","px","Spotify","createTypedMediaMarkdownHooks","SPOTIFY_EMBED_KIT_DEFAULTS","EMBED_BLOCK_LAYOUT_DEFAULTS","SPOTIFY_DEFAULT_WIDTH","SPOTIFY_FULL_HEIGHT","keyIdAttribute","element","entity","parseSpotifyEntity","canonicalSpotifyUrl","captionAttribute","layoutAttrDefaults","el","kitAttrDefaults","createIframeEmbedNodeView","node","options","commands","width","height","rest","layout","fitSpotifyLayoutToEditorColumn","defaultSpotifyHeight","generateShortId","HTMLAttributes","renderIframeEmbedHTML","SPOTIFY_URL_REGEX_GLOBAL","match","src","SPOTIFY_EMBED_IFRAME_REGEX","import_core","inputRegex","VIDEO_REMOUNT_ATTR_KEYS","VIDEO_MEDIA_CONFIG","Video","createTypedMediaMarkdownHooks","keyIdAttribute","element","src","isSafeMediaSrc","layoutAttrDefaults","captionAttribute","createHtmlMediaNodeView","node","HTMLAttributes","renderHtmlMediaHTML","inputRegex","match","width","height","options","commands","rest","layout","resolveEmbedLayoutDimensions","generateShortId","import_core","VIMEO_IFRAME_CONFIG","defineFullscreenIframeEmbedConfig","VIMEO_EMBED_ATTR_KEYS","buildVimeoEmbedUrl","Vimeo","createTypedMediaMarkdownHooks","VIMEO_PLAYER_KIT_DEFAULTS","EMBED_BLOCK_LAYOUT_DEFAULTS","keyIdAttribute","element","src","isSafeMediaSrc","ref","parseVimeoVideoRef","captionAttribute","layoutAttrDefaults","kitAttrDefaults","createIframeEmbedNodeView","options","commands","isValidVimeoUrl","width","height","rest","layout","resolveEmbedLayoutDimensions","generateShortId","node","HTMLAttributes","renderIframeEmbedHTML","VIMEO_REGEX_GLOBAL","match","import_core","EMBED_ATTR_KEYS","X_OEMBED_ATTR_KEYS","X_LAYOUT_ATTR_KEYS","X","createTypedMediaMarkdownHooks","EMBED_BASE_DEFAULTS","keyIdAttribute","element","normalizeXUrl","defaultsFromOptions","captionAttribute","node","href","src","HTMLAttributes","url","editor","kitOptions","getPos","layoutRoot","attrsSnapshot","embedTarget","shellWidth","shellHeight","applyLayout","attrs","width","applyStyles","loadingHost","controller","wrapMediaWithLoadingShell","mountAbort","isFluid","disposeHeightSync","watchXEmbedHeight","mountXEmbed","buildXOEmbedParams","ok","caption","createCaptionElement","readCaption","event","mutation","ignoreNodeViewSubtreeMutation","updatedNode","layoutAttrsChanged","embedAttrsEqual","options","commands","generateShortId","X_URL_REGEX_GLOBAL","match","import_core","YOUTUBE_IFRAME_CONFIG","defineFullscreenIframeEmbedConfig","YOUTUBE_EMBED_ATTR_KEYS","buildYoutubeEmbedUrl","Youtube","createTypedMediaMarkdownHooks","YOUTUBE_PLAYER_KIT_DEFAULTS","EMBED_BLOCK_LAYOUT_DEFAULTS","keyIdAttribute","element","src","isSafeMediaSrc","id","parseYoutubeVideoId","captionAttribute","layoutAttrDefaults","kitAttrDefaults","createIframeEmbedNodeView","options","commands","isValidYoutubeUrl","startFromUrl","parseYoutubeStartSeconds","start","width","height","rest","layout","resolveEmbedLayoutDimensions","generateShortId","node","HTMLAttributes","renderIframeEmbedHTML","YOUTUBE_REGEX_GLOBAL","match","HyperMultimediaKit","HYPER_MULTIMEDIA_KIT_EXTENSION_NAME","extensions","resizableMedia","mediaNodeNames","addMediaExtension","media","mediaOptions","Image","Video","Audio","Youtube","Vimeo","SoundCloud","Spotify","Loom","X","MediaResizeGripper","MediaResizeControls","detectMediaType","url","isValidYoutubeUrl","isValidVimeoUrl","isValidSoundCloudUrl","isValidSpotifyUrl","isValidLoomUrl","isValidXUrl","isImageUrl","isVideoUrl","isAudioUrl","isMediaUrl","url","kind","detectMediaType"]}