redoItem = (transaction, item, redoitems, itemsToDelete) => {
  const doc = transaction.doc;
  const store = doc.store;
  const ownClientID = doc.clientID;
  const redone = item.redone;

  if (redone !== null) {
    return getItemCleanStart(transaction, redone);
  }

  let parentItem =
  /** @type {AbstractType<any>} */
  item.parent._item;
  /**
   * @type {Item|null}
   */

  let left;
  /**
   * @type {Item|null}
   */

  let right;

  if (item.parentSub === null) {
    // Is an array item. Insert at the old position
    left = item.left;
    right = item;
  } else {
    // Is a map item. Insert as current value
    left = item;

    while (left.right !== null) {
      left = left.right;

      if (left.id.client !== ownClientID) {
        // It is not possible to redo this item because it conflicts with a
        // change from another client
        return null;
      }
    }

    if (left.right !== null) {
      left =
      /** @type {Item} */

      /** @type {AbstractType<any>} */
      item.parent._map.get(item.parentSub);
    }

    right = null;
  } // make sure that parent is redone


  if (parentItem !== null && parentItem.deleted === true && parentItem.redone === null) {
    // try to undo parent if it will be undone anyway
    if (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete) === null) {
      return null;
    }
  }

  if (parentItem !== null && parentItem.redone !== null) {
    while (parentItem.redone !== null) {
      parentItem = getItemCleanStart(transaction, parentItem.redone);
    } // find next cloned_redo items


    while (left !== null) {
      /**
       * @type {Item|null}
       */
      let leftTrace = left; // trace redone until parent matches

      while (leftTrace !== null &&
      /** @type {AbstractType<any>} */
      leftTrace.parent._item !== parentItem) {
        leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone);
      }

      if (leftTrace !== null &&
      /** @type {AbstractType<any>} */
      leftTrace.parent._item === parentItem) {
        left = leftTrace;
        break;
      }

      left = left.left;
    }

    while (right !== null) {
      /**
       * @type {Item|null}
       */
      let rightTrace = right; // trace redone until parent matches

      while (rightTrace !== null &&
      /** @type {AbstractType<any>} */
      rightTrace.parent._item !== parentItem) {
        rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone);
      }

      if (rightTrace !== null &&
      /** @type {AbstractType<any>} */
      rightTrace.parent._item === parentItem) {
        right = rightTrace;
        break;
      }

      right = right.right;
    } // Iterate right while right is in itemsToDelete
    // If it is intended to delete right while item is redone, we can expect that item should replace right.


    while (left !== null && left.right !== null && left.right !== right && itemsToDelete.findIndex(d => d ===
    /** @type {Item} */
    left.right) >= 0) {
      left = left.right;
    }
  }

  const nextClock = getState(store, ownClientID);
  const nextId = createID(ownClientID, nextClock);
  const redoneItem = new Item(nextId, left, left && left.lastId, right, right && right.id, parentItem === null ? item.parent :
  /** @type {ContentType} */
  parentItem.content.type, item.parentSub, item.content.copy());
  item.redone = nextId;
  keepItem(redoneItem, true);
  redoneItem.integrate(transaction, 0);
  return redoneItem;
}
Redoes the effect of this operation.

@param {Transaction} transaction The Yjs instance.
@param {Item} item
@param {Set<Item>} redoitems
@param {Array<Item>} itemsToDelete

@return {Item|null}

@private