Repository: ianstormtaylor/slate
File: packages/slate/src/core/normalize-node.ts (TypeScript)

Code before:
  // Determine whether the node should have only block or only inline children.
  // - The editor should have only block children.
  // - Inline elements should have only inline children.
  // - Elements that begin with a text child or an inline element child should have only inline children.
  // - All other elements should have only block children.
  const shouldHaveInlines =
    !(element === editor) &&
    (editor.isInline(element) ||
      Text.isText(element.children[0]) ||
      editor.isInline(element.children[0]))

  if (shouldHaveInlines) {
    // Since we'll be applying operations while iterating, we also modify `n` when adding/removing nodes.
    for (let n = 0; n < element.children.length; n++) {
      const child = element.children[n]
      const prev = element.children[n - 1] as Descendant | undefined

      if (Text.isText(child)) {
        if (prev != null && Text.isText(prev)) {
          // Merge adjacent text nodes that are empty or match.
          if (child.text === '') {
            Transforms.removeNodes(editor, {
              at: path.concat(n),
              voids: true,
            })
            element = Node.get(editor, path) as Element
            n--
          } else if (prev.text === '') {
            Transforms.removeNodes(editor, {
              at: path.concat(n - 1),
              voids: true,
            })
            element = Node.get(editor, path) as Element
            n--
          } else if (Text.equals(child, prev, { loose: true })) {
            Transforms.mergeNodes(editor, { at: path.concat(n), voids: true })
            element = Node.get(editor, path) as Element
            n--
          }
        }
      } else {
        if (editor.isInline(child)) {
          // Ensure that inline nodes are surrounded by text nodes.
          if (prev == null || !Text.isText(prev)) {
            const newChild = { text: '' }
            Transforms.insertNodes(editor, newChild, {
              at: path.concat(n),
              voids: true,
            })
            element = Node.get(editor, path) as Element
            n++

Review comment (bug):
pretty sure there's an invisible bug if this runs. we add some number of new child nodes and step back to read them--but we still only run the loop a number of times equal to the number of original children (everywhere else we add a child we skip over it and num iterations stay the same)

I dont think it caused issues due to the multi-pass nature that `normalizeNode` gets called but we can avoid running it more times than we need to by accounting for it.

Code after:
  // Determine whether the node should have only block or only inline children.
  // - The editor should have only block children.
  // - Inline elements should have only inline children.
  // - Elements that begin with a text child or an inline element child should have only inline children.
  // - All other elements should have only block children.
  const shouldHaveInlines =
    !(element === editor) &&
    (editor.isInline(element) ||
      Node.isText(element.children[0]) ||
      editor.isInline(element.children[0]))

  if (shouldHaveInlines) {
    // Since we'll be applying operations while iterating, we also modify `n` when adding/removing nodes.
    for (let n = 0; n < element.children.length; n++) {
      const child = element.children[n]
      const prev = element.children[n - 1] as Descendant | undefined

      if (Node.isText(child)) {
        if (prev != null && Node.isText(prev)) {
          // Merge adjacent text nodes that are empty or match.
          if (child.text === '') {
            Transforms.removeNodes(editor, {
              at: path.concat(n),
              voids: true,
            })
            element = Node.get(editor, path) as Element
            n--
          } else if (prev.text === '') {
            Transforms.removeNodes(editor, {
              at: path.concat(n - 1),
              voids: true,
            })
            element = Node.get(editor, path) as Element
            n--
          } else if (Text.equals(child, prev, { loose: true })) {
            Transforms.mergeNodes(editor, { at: path.concat(n), voids: true })
            element = Node.get(editor, path) as Element
            n--
          }
        }
      } else {
        if (editor.isInline(child)) {
          // Ensure that inline nodes are surrounded by text nodes.
          if (prev == null || !Node.isText(prev)) {
            const newChild = { text: '' }
            Transforms.insertNodes(editor, newChild, {
              at: path.concat(n),
              voids: true,
            })
            element = Node.get(editor, path) as Element
            n++