simian 0.1.2

A command-line tool for exploring and implementing Machine Learning algorithms in Rust.
Documentation
'use client'

import { useLayoutEffect, useMemo } from 'react'
import _ from 'lodash'

import { useEditor } from '../../context'
import {
  AddonHandlerArgs,
  AddonHandlerContext,
  AddonHandlerReturn,
  leafAddon,
} from '../base'

import { TextContext } from './context'
import { Text } from './element'
import * as marks from './marks'
import { TextMenu } from './menu'
import { BaseMarkId, baseMarkIds, TextAddon, TextMark } from './types'

/**
 * Auxiliary function to run a handler for all available marks.
 */
export const runMarkHandler = <K extends keyof TextMark>(
  ctx: AddonHandlerContext<TextAddon, K>,
  handlerName: K,
  ...args: AddonHandlerArgs<K>
): {
  break?: boolean
  result?: AddonHandlerReturn<K> | undefined
} => {
  const { baseMarkIds } = ctx.addon

  for (const markId of baseMarkIds) {
    const mark = marks[`${_.upperFirst(markId)}Mark`] as
      | TextMark<object>
      | undefined

    if (!mark) {
      continue
    }

    if (typeof mark[handlerName] == 'function') {
      const rawResponse = mark[handlerName](ctx, ...args)
      const response =
        typeof rawResponse === 'boolean'
          ? { break: rawResponse }
          : (rawResponse as { break?: boolean })

      if (response.break) {
        return response
      }
    }
  }

  return { break: false }
}

/**
 * The render function.
 */
const render: TextAddon['render'] = (props) => {
  return <Text {...props} />
}

/**
 * The context provider component.
 */
const ContextProvider: TextAddon['ContextProvider'] = ({ addon, children }) => {
  const { editor } = useEditor()
  const markIds = useMemo(() => [...addon.baseMarkIds], [addon])

  // Handle Global Portal Host creation
  useLayoutEffect(() => {
    const hostId = `text-menu-root-${editor.id}`
    let hostElement = document.getElementById(hostId)

    if (!hostElement) {
      hostElement = document.createElement('div')
      hostElement.id = hostId
      document.body.appendChild(hostElement)
    }

    return () => {
      // Cleanup: Remove the div when the editor/provider unmounts
      if (hostElement && document.body.contains(hostElement)) {
        document.body.removeChild(hostElement)
      }
    }
  }, [editor.id])

  return (
    <TextContext.Provider value={{ markIds }}>{children}</TextContext.Provider>
  )
}

/**
 * The root element.
 */
const Companion: TextAddon['Companion'] = () => <TextMenu />

/**
 * Handle insert text.
 */
const insertText: TextAddon['insertText'] = (ctx, ...rest) => {
  if (runMarkHandler(ctx, 'insertText', ...rest).break) {
    return true
  }

  return false
}

/**
 * Handle keyboard events.
 */
const onKeyDown: TextAddon['onKeyDown'] = (ctx, ...rest) => {
  if (runMarkHandler(ctx, 'onKeyDown', ...rest).break) {
    return true
  }

  return false
}

/**
 * The addon builder.
 *
 * @param params - Initial set of params.
 */
export function text(params?: { marks?: BaseMarkId[] }): TextAddon {
  return leafAddon({
    id: 'text',
    baseMarkIds: params?.marks ?? [...baseMarkIds],
    render,
    insertText,
    onKeyDown,

    ContextProvider,
    Companion,
  })
}

export * from './schema'
export * from './types'