@typex-core_model_formater.js
import { default as h } from '../view/vdom/createVnode'
import { setVdomOrPath } from '../mappings'
import { mergeTextPath } from '../utils'
/**
* @description 格式管理类
* @class Formater
*/
class Formater {
formatMap = new Map()
/**
* @description 注册格式
* @param {*} formats
* @memberof Formater
*/
register (formats) {
formats.forEach((format) => {
this.formatMap.set(format.name, format)
})
}
/**
* @description 依赖注入
* @param {*} propName
* @param {*} prop
* @memberof Formater
*/
inject (propName, prop) {
this[propName] = prop
}
renderRoot (rootPath) {
return this.render({ children: [rootPath] })
}
/**
* @description path 渲染
* @param {*} path
* @returns {*}
* @memberof Formater
*/
render (path) {
const gs = this._group(
{
paths: path.children,
restFormats: this.types,
},
0
)
const vn = this._generateGroups(gs, path.isBlock && path.length === 0)
return vn
}
/**
* @description 渲染调用
* @param {*} vn
* @param {*} current
* @returns {*}
* @memberof Formater
*/
_invokeRender (current, pv, ...others) {
const fmt = current.fmt
if (typeof fmt.nativeRender === 'function') {
return fmt.nativeRender(pv, current.value, ...others, h)
}
const vn = fmt.render(current.value, ...others, h)
if (!pv) return vn
switch (fmt.type) {
case "component":
case "tag": {
pv.children.push(vn)
return vn
}
case 'attribute': {
const attrNameArr = (fmt.attrName || `style.${fmt.name}`).split('.')
attrNameArr.reduce((init, currProp, index) => {
if (index === attrNameArr.length - 1) {
init[currProp] = current.value
} else {
if (!init[currProp]) init[currProp] = {}
return init[currProp]
}
}, pv.props)
return undefined
}
}
}
/**
* @description 格式分组
* @param {*} gs
* @param {*} isEmptyBlock
* @returns {*}
* @memberof Formater
*/
_generateGroups (gs, isEmptyBlock) {
return gs
.map((g) => {
let componentQuene
const formatQuene = this._getFormats(g.commonFormats)
// 无格式
if (g.commonFormats.length === 0) {
if (g.children.findIndex((path) => typeof path.node.data === 'object') !== -1)
throw '格式标记不合法,文本格式不可用于标记非文本的结构'
const mergedTextPath = mergeTextPath(g.children, this.editor)
let vtext = null
if (isEmptyBlock) {
vtext = h('br')
/*
这里借助指针的思想,设计比较巧妙抽象
改变path指向,从path层面看选区还在path-0位置
表现层因为text-0已经不存在了;需要添加br防止块塌陷,光标应该聚焦在父级-0
表现层看来这里产生了混乱,不符合编程直觉;从更加抽象的path层面看是统一的;
*/
setVdomOrPath(mergedTextPath, mergedTextPath.parent.vn)
mergedTextPath.clearFormat()
} else {
if (mergedTextPath.node.data === '') console.warn('非法空标签:', mergedTextPath)
// 输入内容时 重新指向创建vdom
vtext = h('text', {}, [mergedTextPath.node.data])
setVdomOrPath(mergedTextPath, vtext)
}
return vtext
// 有格式
} else if (
(componentQuene = formatQuene.filter((ele) => ele.fmt.type === 'component')).length
) {
// 组件类型单独占一个分组
const path = g.children[0]
const current = componentQuene[0]
const pv = this._invokeRender(current, null, { path, editor: this.editor })
// 为所有component类型的path映射vnode
setVdomOrPath(path, pv)
return pv
// 属性和标签格式
} else {
let pv = null // 最外层的vnode
let vn = null // 当前vnode
const attributeQueue = []
for (let index = 0; index < formatQuene.length; index++) {
const current = formatQuene[index]
// 属性类型的格式放在最后处理
if (current.fmt.type === 'attribute') {
attributeQueue.push(current)
continue
}
// 处理标签格式 嵌套处理
vn = this._invokeRender(current, vn)
if (!pv) pv = vn
}
// 处理属性格式
// 如果存在属性格式(vn)应该加在标签上面
// 如果不存在属性格式(vn)则创建一个span
for (let index = 0; index < attributeQueue.length; index++) {
const current = attributeQueue[index]
const res = this._invokeRender(current, vn)
if (res) vn = res
if (!pv) pv = res
}
if (g.children[0].commonFormats) {
vn.children = this._generateGroups(g.children, isEmptyBlock)
} else {
if (g.children.findIndex((ele) => typeof ele.data === 'object') !== -1)
throw '格式标记不合法,文本格式不可用于标记非文本的结构'
const mergedTextPath = mergeTextPath(g.children, this.editor)
let vtext = null
if (isEmptyBlock) {
vtext = h('br')
setVdomOrPath(mergedTextPath, vn)
mergedTextPath.clearFormat()
} else {
if (mergedTextPath.node.data === '') console.warn('非法空标签:', mergedTextPath)
vtext = h('text', {}, [mergedTextPath.node.data])
setVdomOrPath(mergedTextPath, vtext)
}
vn.children = [vtext]
}
return pv
}
})
.filter((i) => i)
}
/**
* @description 获取格式类型list
* @readonly
* @memberof Formater
*/
get types () {
return [...this.formatMap.keys()]
}
/**
* @description _getFormats
* @param {*} objs
* @returns {*}
* @memberof Formater
* @private
*/
_getFormats (objs) {
return objs.map((obj) => {
const key = Object.keys(obj)[0]
return {
fmt: this.formatMap.get(key),
value: obj[key],
}
})
}
/**
* @description 根据格式名获取格式
* @param {*} key
* @returns {*}
* @memberof Formater
*/
get (key) {
return this.formatMap.get(key) || {}
}
/**
* @description 判断是否可以增加
* @param {*} path
* @param {*} prevPath
* @param {*} key
* @returns {*} {boolean}
* @memberof Formater
* @private
*/
_canAdd (path, prevPath, key) {
/**
* 当前无格式
*/
if (!path.node.formats[key]) return false
/**
* 当前有格式,上一个没格式
*/
if (!prevPath.node.formats[key]) return true
/**
* 当前格式和上一个相同
*/
if (prevPath.node.formats[key] === path.node.formats[key]) return true
}
/**
* @description 公共格式提取分组法
* @param {*} group
* @param {*} index
* @param {*} [r=[]]
* @returns {*}
* @memberof Formater
* @private
*/
_group (group, index, r = []) {
const grouped = { commonFormats: [], children: [] }
let restFormats = []
let prevPath = null
let counter = {}
let prevMaxCounter = 0
for (index; index < group.paths.length; index++) {
let cacheCounter = { ...counter }
const path = group.paths[index]
group.restFormats.forEach((key) => {
if (!prevPath) {
counter[key] = 0
if (path.node.formats[key]) counter[key]++
} else if (this._canAdd(path, prevPath, key)) {
counter[key]++
}
})
const maxCounter = Math.max(...Object.values(counter))
/**
* 块格式 不嵌套
*/
if (
prevPath &&
Object.keys(path.node.formats).some((key) => this.get(key).type === 'component')
) {
prevPath = null
break
}
if (
prevPath &&
Object.keys(prevPath.node.formats).some((key) => this.get(key).type === 'component')
) {
prevPath = null
break
}
/**
* 上一个是纯文本,下一个有格式
*/
if (prevPath && prevMaxCounter === 0 && maxCounter > prevMaxCounter) {
counter = cacheCounter
break
}
/**
* 上一个和当前比没有格式增长
*/
if (prevPath && maxCounter === prevMaxCounter && maxCounter !== 0) {
counter = cacheCounter
break
}
// 无格式也是一个分组
grouped.children.push(path)
grouped.commonFormats = Object.entries(counter)
.filter((ele) => ele[1] && ele[1] === maxCounter)
.map((ele) => ({ [ele[0]]: group.paths[index].node.formats[ele[0]] }))
restFormats = group.restFormats.filter(
(ele) =>
!grouped.commonFormats.some((i) => {
return i[ele]
})
)
prevMaxCounter = maxCounter
prevPath = path
}
/**
* 递归边界
* 1.非空格式集长度小于1
* 2.空格式集
*/
if (grouped.commonFormats.length > 0 && grouped.children.length > 1) {
grouped.children = this._group(
{
paths: grouped.children,
restFormats,
},
0
)
}
r.push(grouped)
if (index < group.paths.length) {
this._group(group, index, r)
}
return r
}
}
export default Formater