@typex-core_model_path.js
import { getVdomOrElm, getVdomOrPath } from '../mappings'
import { computeLen, typeOf, positionCompare, isPrimitive, uuid } from '../utils'
/**
* @description 路径类
* @export
* @class Path
*/
const currentComponent = Symbol('currentComponent');
const $editor = Symbol('$editor');
export class Path {
/**
* @description 重建标记
* - 0:无操作 ;1:删除
* @protected
* @memberof Path
*/
rebuildFlag = 0
_uuid = uuid()
render () {
return this.$editor.formater.render(this)
}
/**
* @description 原始数据节点
* @type {Object}
* @memberof Path
*/
node = null
/**
* @description 父path
* @type {Path}
* @memberof Path
*/
parent = null
/**
* @description 同级相对索引
* @type {Number}
* @memberof Path
*/
index = 0
/**
* @description 前一个兄弟path
* @type {Path}
* @memberof Path
*/
prevSibling = null
/**
* @description 后一个兄弟path
* @type {Path}
* @memberof Path
*/
nextSibling = null
/**
* @description 子级path
* @type {Array.<Path>}
* @memberof Path
*/
children = []
/**
* Creates an instance of Path.
* @param {Object} options - The options object.
* @param {Object} options.node - 原始数据节点.
* @param {Path} options.parent - 父path.
* @param {number} options.index - 同级相对索引.
* @param {Path} options.prevSibling - 前一个兄弟path.
* @param {Path} options.nextSibling - 后一个兄弟path.
* @param {Array.<Path>} options.children - 子级path.
*/
constructor({ node, parent, index, prevSibling, nextSibling, children }) {
this.node = node
this.parent = parent
this.index = index
this.prevSibling = prevSibling
this.nextSibling = nextSibling
this.children = children
}
get rootPath () {
let root = this
while (root.parent) {
root = root.parent
}
return root
}
/**
* @description 判断是否是块路径
* @readonly
* @memberof Path
*/
get isBlock () {
return this === this?.currentComponent.$path && this?.currentComponent.displayType === 'block'
}
/**
* @description 获取编辑器对象
* @readonly
* @memberof Path
*/
get $editor () {
return this[$editor] || this.parent.$editor
}
/**
* @description 设置编辑器对象
* @readonly
* @memberof Path
*/
set $editor (value) {
this[$editor] = value
}
/**
* @description path所属组件的实例
* @readonly
* @memberof Path
*/
get currentComponent () {
return this[currentComponent] || this.parent?.currentComponent
}
/**
* @description 设置currentComponent
* @readonly
* @memberof Path
*/
set currentComponent (val) {
this[currentComponent] = val
}
/**
* @description path内容长度
* @readonly
* @memberof Path
*/
get length () {
return computeLen(this)
}
/**
* @description path对应的真实dom
* @readonly
* @memberof Path
*/
get elm () {
return getVdomOrElm(this.vn)
}
/**
* @description 数据类型
* @readonly
* @memberof Path
*/
get dataType () {
return typeOf(this.node.data)
}
/**
* @description path所属的块级组件实例
* @readonly
* @memberof Path
*/
get block () {
if (this.currentComponent?.displayType === 'block') return this.currentComponent
return this.parent.block
}
/**
* @description path对应的虚拟dom
* @readonly
* @memberof Path
*/
get vn () {
return getVdomOrPath(this)
}
/**
* @description 是否是叶子节点
* @readonly
* @memberof Path
*/
get isLeaf () {
return this.children.length === 0
}
/**
* @description 第一个叶子节点
* @readonly
* @memberof Path
*/
get firstLeaf () {
let path = this
while (path.children && path.children.length) {
path = path.children[0]
}
return path
}
/**
* @description 最后一个叶子节点
* @readonly
* @memberof Path
*/
get lastLeaf () {
let path = this
while (path.children && path.children.length) {
path = path.children[path.children.length - 1]
}
return path
}
/**
* @description 绝对路径
* @readonly
* @memberof Path
*/
get position () {
if (this.parent) return `${this.parent.position}-${this.index}`
return '0'
}
/**
* @description 上一个叶子节点
* @readonly
* @memberof Path
*/
get prevLeaf () {
return (this.prevSibling || this.parent?.prevLeaf)?.lastLeaf
}
/**
* @description 下一个叶子节点
* @readonly
* @memberof Path
*/
get nextLeaf () {
return (this.nextSibling || this.parent?.nextLeaf)?.firstLeaf
}
/**
* @description 获取最近共同节点
* @param {*} path
* @returns {*}
* @memberof Path
*/
queryCommonPath (path) {
if (this === path) return this
if (this.position === '0') return this
if (path.position === '0') return path
const rootPath = this.rootPath
const posArr1 = this.position.split('-')
const posArr2 = path.position.split('-')
const minLen = Math.min(posArr1.length, posArr2.length)
let i
for (i = 0; i < minLen; i++) {
const element1 = posArr1[i]
const element2 = posArr2[i]
if (element1 !== element2) break
}
const commonPosition = posArr1.slice(0, i).join('-')
return queryPathByPosition(commonPosition, rootPath)
}
/**
* @description 文本插入
* @param {String} pos 从偏移量,开始删除的位置
* @param {String} data 插入字符
* @memberof Path
*/
textInsert (offset, data) {
this.node.data = this.node.data.slice(0, offset) + data + this.node.data.slice(offset)
}
/**
* @description 内容删除
* @param {Number} offset 偏移量,开始删除的位置
* @param {Number} count 删除的字符数量
* @memberof Path
*/
textDelete (offset, count) {
const deleteText = this.node.data.slice(offset - count, offset)
this.node.data = this.node.data.slice(0, offset - count) + this.node.data.slice(offset)
return deleteText
}
/**
* @description 清除格式
* @memberof Path
*/
clearFormat () {
this.node.formats = {}
}
/**
* @description 设置节点
* @param {*} [{ data = '', formats = {} }={}]
* @memberof Path
*/
setNode ({ data = '', formats = {} } = {}) {
this.node.data = data
this.node.formats = formats
}
/**
* @description 设置格式(只会merge格式,不会强制覆盖其他格式)
* @param {Object} formats 格式
* @memberof Path
*/
setFormat (formats) {
Object.assign(this.node.formats, formats)
}
/**
* @description path分割
* @param {Number} index 分隔位置
* @returns {Path[]} path列表
* @memberof Path
*/
split (index) {
if (this.dataType === 'string') {
const newPath = createPath({
data: this.node.data.slice(index),
formats: { ...this.node.formats },
})
this.textDelete(this.length, this.length - index)
newPath.insertAfter(this)
return [this, newPath]
} else {
const newPath = createPath({
data: [],
formats: { ...this.node.formats },
})
this.children.slice(index).forEach((path) => {
path.moveTo(newPath)
})
newPath.insertAfter(this)
return [this, newPath]
}
}
/**
* @description 标记克隆
* @returns {Path}
* @memberof Path
*/
clone (cloneChild = false) {
let data
if (cloneChild) {
data = isPrimitive(this.node.data)
? this.node.data
: JSON.parse(JSON.stringify(this.node.data))
} else {
data = isPrimitive(this.node.data) ? '' : this.node.data ? [] : {}
}
return createPath({
data,
formats: { ...this.node.formats },
})
}
/**
* @description - 位置比较
* @example
* res = a.positionCompare(b),
* res=-1: a<b;
* res=0: a===b;
* res=1: a>b
* @param {Path} path
* @returns {Number}
* @memberof Path
*/
positionCompare (path) {
return positionCompare(this, path)
}
/**
* @description 是否源于 xxx
* @param {Path} path
* @returns {Boolean}
* @memberof Path
*/
originOf (path) {
return this.position.includes(path.position + '-')
}
/**
* @description 插入到path前面
* @param {Path} path
* @memberof Path
*/
insertBefore (path) {
this.delete()
path.parent.splice(path.index, 0, this)
}
/**
* @description 插入到path后面
* @param {Path} path
* @memberof Path
*/
insertAfter (path) {
this.delete()
path.parent.splice(path.index + 1, 0, this)
}
/**
* @description 移动到path的children
* @param {Path} path
* @memberof Path
*/
moveTo (path) {
this.delete()
path.push(this)
}
/**
* @description path删除
* @memberof Path
*/
delete () {
if (!this.parent) {
return
}
this.rebuildFlag = 1
this.parent.rebuild()
this.parent.currentComponent.update()
}
/**
* @description 尾部插入children
* @param {*} paths
* @memberof Path
*/
push (...paths) {
paths.forEach((i) => (i.rebuildFlag = 0))
this.children.push(...paths)
this.rebuild()
this.currentComponent.update()
}
/**
* @description 从尾部弹出元素
* @returns {*}
* @memberof Path
*/
pop () {
const item = this.children[this.children.length - 1]
item.rebuildFlag = 1
this.rebuild()
this.currentComponent.update()
return item
}
/**
* @description 从头部插入children
* @param {*} paths
* @memberof Path
*/
unshift (...paths) {
paths.forEach((i) => (i.rebuildFlag = 0))
this.children.unshift(...paths)
this.rebuild()
this.currentComponent.update()
}
/**
* @description 从头部弹出元素
* @returns {*}
* @memberof Path
*/
shift () {
const item = this.children[0]
item.rebuildFlag = 1
this.rebuild()
this.currentComponent.update()
return item
}
/**
* @description 通过移除或者替换已存在的元素和/或添加新元素就地改变一个数组的内容
* @param {*} start
* @param {*} deleteCount
* @param {*} additems
* @returns {deleteItems}
* @memberof Path
*/
splice (start, deleteCount, ...additems) {
const deleteItems = []
if (additems.length) {
additems.forEach((i) => (i.rebuildFlag = 0))
this.children.splice(start, 0, ...additems)
}
if (deleteCount > 0) {
for (let index = 0; index < deleteCount; index++) {
const element = this.children[index + start]
element.rebuildFlag = 1
deleteItems.push[element]
}
}
this.rebuild()
this.currentComponent.update()
return deleteItems
}
/**
* @description 删除两个节点之间的所有节点 不包含开始结束节点
* @param {Path} startPath 开始节点
* @param {Path} endPath 结束节点
* @memberof Path
*/
deleteBetween (startPath, endPath) {
if (this === startPath || this === endPath || startPath === endPath) return
const needRebuild = []
const traverse = (path) => {
for (var i = 0; i < path.children.length; i++) {
const ele = path.children[i]
if (startPath.originOf(ele) || endPath.originOf(ele)) {
traverse(ele)
} else if (ele.positionCompare(startPath) === 1 && ele.positionCompare(endPath) === -1) {
ele.rebuildFlag = 1
if (!needRebuild.includes(ele.parent)) {
needRebuild.push(ele.parent)
}
}
}
}
traverse(this)
needRebuild.forEach((path) => path.rebuild())
}
/**
* @description 重构链表树
* @memberof Path
*/
rebuild (deep = false) {
if (this.dataType !== 'array') return
let cachePath = null
this.children = this.children.filter((ele) => ele.rebuildFlag !== 1)
// 为了保持索引,使用length = 0来清空数组
this.node.data.length = 0
this.children.forEach((path, index) => {
// 更新父级
path.parent = this
// 同步mark子节点
this.node.data.push(path.node)
// 重新维护链表结构
path.prevSibling = cachePath
if (cachePath) {
cachePath.nextSibling = path
}
path.nextSibling = null
cachePath = path
path.index = index
// 更新位置坐标
// const newPosition = this.position + '-' + index
// if (path.position !== newPosition || deep) {
// path.position = path.node.position = newPosition
// path.rebuild(deep)
// }
})
}
}
/**
* @description 创建path
* @export
* @param {Object} node 标记
* @param node.data {String|Object}
* @param node.formats {Object}
* @param {Path} [parent=null]
* @param {Path} [prevSibling=null]
* @param {Path} [nextSibling=null]
* @param {Number} [index=0]
* @returns {Path}
*/
export function createPath (node, parent = null, prevSibling = null, nextSibling = null, index = 0) {
if (!node.formats) node.formats = {}
const config = {
node: node,
parent: parent,
index: index,
prevSibling: prevSibling,
nextSibling: nextSibling,
children: [],
}
const path = new Path(config)
if (node.data) {
let currPath = null
node.data.reduce?.((prevPath, currMark, index) => {
currPath = createPath(currMark, path, prevPath, null, index)
if (prevPath) {
prevPath.nextSibling = currPath
}
currPath.prevSibling = prevPath
path.children.push(currPath)
return currPath
}, null)
}
return path
}
/**
* @desc: path查找
* @param {elm|vn|position} target
* @param {path} path
* @return {path}
*/
export function queryPath (target, path) {
if (target instanceof Path) return target
if (target.nodeType) return queryPathByElm(target)
if (target.vnodeType) return queryPathByVn(target)
if (typeof target === 'string') return queryPathByPosition(target, path)
throw 'queryPath的参数必须是elm|vn|position'
}
/**
* @description 根据dom查询path
* @param {*} elm
* @returns {*}
*/
function queryPathByElm (elm) {
const vn = getVdomOrElm(elm)
if (!vn) return null
return queryPathByVn(vn)
}
/**
* @description 根据虚拟dom查询path
* @param {*} vn
* @returns {*}
*/
function queryPathByVn (vn) {
const path = getVdomOrPath(vn)
if (!path) return null
return path
}
/**
* @description 根据坐标查询path
* @param {*} position
* @param {*} rootPath
* @returns {*}
*/
function queryPathByPosition (position, rootPath) {
const posArr = position.split('-')
return posArr.slice(1).reduce((prev, index) => {
return prev.children[index]
}, rootPath)
}