@typex-core_transform_transaction.js

import { SplitText, SetFormats, TextDelete, TextInsert } from './step'


/**
 * @description 事务类
 * @export
 * @class Transaction
 */
export default class Transaction {

  /**
   * @description 步骤列表
   * @memberof Transaction
   */
  steps = []

  /**
   * @description 保存事务初始化时的选区快照
   * @memberof Transaction
   */
  startRanges = []

  /**
   * @description 事务提交之后的选区快照
   * @memberof Transaction
   */
  endRanges = []

  /**
   * @description 是否提交标志
   * @memberof Transaction
   */
  commited = false

  constructor(editor) {
    this.editor = editor
    this.init()
  }

  /**
   * @description 初始化开始状态
   * @memberof Transaction
   */
  init () {
    // 初始状态
    this.startRanges = this.editor.selection.rangesSnapshot
  }

  /**
   * @description 增加并且执行动作
   * @param {*} step
   * @returns {*}  
   * @memberof Transaction
   */
  addAndApplyStep (step) {
    this.steps.push(step)
    return step.apply(this.editor)
  }

  /**
   * @description 增加动作
   * @param {*} step
   * @memberof Transaction
   */
  addStep (...step) {
    this.steps.push(...step)
  }

  /**
   * @description 提交事务
   * @returns {*}  
   * @memberof Transaction
   */
  commit () {
    if (this.steps.length === 0) return this
    // 结尾状态
    this.endRanges = this.editor.selection.rangesSnapshot
    this.editor.history.push(this)
    this.commited = true
  }

  /**
   * @description 执行事务
   * @memberof Transaction
   */
  apply () {
    for (let index = 0; index < this.steps.length; index++) {
      const step = this.steps[index]
      step.apply(this.editor)
    }
    setTimeout(() => {
      this.editor.selection.recoverRangesFromSnapshot(this.endRanges)
    })
  }

  /**
   * @description 回滚事务
   * @memberof Transaction
   */
  rollback () {
    for (let index = this.steps.length; index > 0; index--) {
      const step = this.steps[index - 1]
      step.invert(this.editor)
    }
    setTimeout(() => {
      this.editor.selection.recoverRangesFromSnapshot(this.startRanges)
    })
  }
}

export function setFormat (editor, format) {
  const ts = new Transaction(editor)
  editor.selection.ranges.forEach((range) => {
    const commonPath = range.startContainer.queryCommonPath(range.endContainer)
    const paths = getLeafPathInRange(range, editor, ts)
    for (const item of paths) {
      const setFormatStep = new SetFormats(item, 0, format)
      ts.addAndApplyStep(setFormatStep)
    }
    ts.commit(commonPath)
    commonPath.currentComponent.update()
  })
}
export function deleteText (editor, count) {
  const ts = new Transaction(editor)
  editor.selection.ranges.forEach((range) => {
    const deleteTextStep = new TextDelete(range.container, range.offset, count)
    ts.addAndApplyStep(deleteTextStep)
    ts.commit(range.container.parent)
    range.container.parent.currentComponent.update()
  })
}

export function insertText (editor, data) {
  const ts = new Transaction(editor)
  editor.selection.ranges.forEach((range) => {
    const deleteTextStep = new TextInsert(range.container, range.offset, data)
    ts.addAndApplyStep(deleteTextStep)
    ts.commit(range.container.parent)
    range.container.parent.currentComponent.update()
  })
}

function getLeafPathInRange (range, editor, ts) {
  if (range.collapsed) return []
  let start,
    end,
    value,
    done = false
  if (range.collapsed) {
    done = true
  } else {
    if (range.startOffset === 0) {
      start = range.startContainer
    } else if (range.startOffset === range.startContainer.length) {
      start = range.startContainer.nextLeaf
    } else {
      const splitTextStep = new SplitText(range.startContainer, range.startOffset)
      const startSplits = ts.addAndApplyStep(splitTextStep)
      start = startSplits[1]
    }

    if (range.endOffset === 0) {
      end = range.endContainer.prevLeaf
    } else if (range.endOffset === range.endContainer.length) {
      end = range.endContainer
    } else {
      const splitTextStep = new SplitText(range.endContainer, range.endOffset)
      const endSplits = ts.addAndApplyStep(splitTextStep)
      end = endSplits[0]
    }
  }

  value = start
  return {
    length: 0,
    next: function () {
      if (!done) {
        const res = { value, done }
        done = value === end
        value = value.nextLeaf
        this.length++
        return res
      } else {
        return { value: undefined, done }
      }
    },
    [Symbol.iterator]: function () {
      return this
    },
  }
}