diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index 58783e6b..7c98e008 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -1,6 +1,6 @@ { "name": "simple-mind-map", - "version": "0.12.2", + "version": "0.13.0", "description": "一个简单的web在线思维导图", "authors": [ { diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index 179f6204..5e1381bf 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -3,7 +3,6 @@ export const CONSTANTS = { CHANGE_THEME: 'changeTheme', CHANGE_LAYOUT: 'changeLayout', SET_DATA: 'setData', - TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode', MODE: { READONLY: 'readonly', EDIT: 'edit' @@ -157,7 +156,7 @@ export const nodeDataNoStylePropList = [ 'isActive', 'generalization', 'richText', - 'resetRichText', + 'resetRichText',// 重新创建富文本内容,去掉原有样式 'uid', 'activeStyle', 'associativeLineTargets', @@ -174,7 +173,8 @@ export const nodeDataNoStylePropList = [ 'customTop', 'customTextWidth', 'checkbox', - 'dir' + 'dir', + 'needUpdate'// 重新创建节点内容 ] // 错误类型 @@ -226,3 +226,13 @@ export const selfCloseTagList = [ // 非富文本模式下的节点文本行高 export const noneRichTextNodeLineHeight = 1.2 + +// 富文本支持的样式列表 +export const richTextSupportStyleList = [ + 'fontFamily', + 'fontSize', + 'fontWeight', + 'fontStyle', + 'textDecoration', + 'color' +] diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 3355b731..8116a5df 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -30,7 +30,6 @@ import { createSmmFormatData, checkSmmFormatData, checkIsNodeStyleDataKey, - removeRichTextStyes, formatGetNodeGeneralization, sortNodeList, throttle, @@ -547,13 +546,6 @@ class Render { if (this.reRender) { this.reRender = false } - // 触发一次保存,因为修改了渲染树的数据 - if ( - this.hasRichTextPlugin() && - [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source) - ) { - this.mindMap.command.addHistory() - } } this.mindMap.emit('node_tree_render_end') }) @@ -561,13 +553,14 @@ class Render { this.emitNodeActiveEvent() } - // 给当前被收起来的节点数据添加文本复位标志 + // 给当前被收起来的节点数据添加更新标志 resetUnExpandNodeStyle() { - if (!this.renderTree || !this.hasRichTextPlugin()) return + if (!this.renderTree) return walk(this.renderTree, null, node => { if (!node.data.expand) { walk(node, null, node2 => { - node2.data.resetRichText = true + // 主要是触发数据新旧对比,不一样则会重新创建节点 + node2.data['needUpdate'] = true }) return true } @@ -750,11 +743,10 @@ class Render { richText: isRichText, isActive: focusNewNode // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true // 动态指定的子节点数据也需要添加相关属性 - appointChildren = addDataToAppointNodes(appointChildren, { - ...params - }) + appointChildren = addDataToAppointNodes(appointChildren, params) + const alreadyIsRichText = appointData && appointData.richText list.forEach(node => { if (node.isGeneralization || node.isRoot) { return @@ -767,6 +759,10 @@ class Render { : defaultInsertBelowSecondLevelNodeText // 计算插入位置 const index = getNodeDataIndex(node) + // 如果指定的数据就是富文本格式,那么不需要重新创建 + if (alreadyIsRichText && params.resetRichText) { + delete params.resetRichText + } const newNodeData = { inserting, data: { @@ -802,7 +798,7 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true nodeList = addDataToAppointNodes(nodeList, params) list.forEach(node => { if (node.isGeneralization || node.isRoot) { @@ -848,11 +844,10 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true // 动态指定的子节点数据也需要添加相关属性 - appointChildren = addDataToAppointNodes(appointChildren, { - ...params - }) + appointChildren = addDataToAppointNodes(appointChildren, params) + const alreadyIsRichText = appointData && appointData.richText list.forEach(node => { if (node.isGeneralization) { return @@ -863,6 +858,10 @@ class Render { const text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText + // 如果指定的数据就是富文本格式,那么不需要重新创建 + if (alreadyIsRichText && params.resetRichText) { + delete params.resetRichText + } const newNode = { inserting, data: { @@ -902,7 +901,7 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true childList = addDataToAppointNodes(childList, params) list.forEach(node => { if (node.isGeneralization) { @@ -947,7 +946,8 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true + const alreadyIsRichText = appointData && appointData.richText list.forEach(node => { if (node.isGeneralization || node.isRoot) { return @@ -956,6 +956,10 @@ class Render { node.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText + // 如果指定的数据就是富文本格式,那么不需要重新创建 + if (alreadyIsRichText && params.resetRichText) { + delete params.resetRichText + } const newNode = { inserting, data: { @@ -966,11 +970,6 @@ class Render { }, children: [node.nodeData] } - if (isRichText) { - node.setData({ - resetRichText: true - }) - } const parent = node.parent // 获取当前节点所在位置 const index = getNodeDataIndex(node) @@ -1046,7 +1045,6 @@ class Render { const index = getNodeIndexInNodeList(node, parent.children) const parentIndex = getNodeIndexInNodeList(parent, grandpa.children) // 节点数据 - this.checkNodeLayerChange(node, parent) parent.nodeData.children.splice(index, 1) grandpa.nodeData.children.splice(parentIndex + 1, 0, node.nodeData) this.mindMap.render() @@ -1061,10 +1059,10 @@ class Render { delete nodeData[key] } }) - // 如果是富文本,那么还要处理富文本内容 - if (hasCustomStyles && this.hasRichTextPlugin()) { + // 如果是富文本,那么直接全部重新创建,因为有些样式是通过标签来渲染的 + if (this.hasRichTextPlugin()) { + hasCustomStyles = true nodeData.resetRichText = true - nodeData.text = removeRichTextStyes(nodeData.text) } return hasCustomStyles } @@ -1302,7 +1300,6 @@ class Render { nodeList.reverse() } nodeList.forEach(item => { - this.checkNodeLayerChange(item, exist) // 移动节点 let nodeParent = item.parent let nodeBorthers = nodeParent.children @@ -1329,25 +1326,6 @@ class Render { this.mindMap.render() } - // 如果是富文本模式,那么某些层级变化需要更新样式 - checkNodeLayerChange(node, toNode, toNodeIsParent = false) { - if (this.hasRichTextPlugin()) { - // 如果设置了自定义样式那么不需要更新 - if (this.mindMap.richText.checkNodeHasCustomRichTextStyle(node)) { - return - } - const toIndex = toNodeIsParent ? toNode.layerIndex + 1 : toNode.layerIndex - let nodeLayerChanged = - (node.layerIndex === 1 && toIndex !== 1) || - (node.layerIndex !== 1 && toIndex === 1) - if (nodeLayerChanged) { - node.setData({ - resetRichText: true - }) - } - } - } - // 移除节点 removeNode(appointNodes = []) { appointNodes = formatDataToArray(appointNodes) @@ -1531,7 +1509,6 @@ class Render { return !item.isRoot }) nodeList.forEach(item => { - this.checkNodeLayerChange(item, toNode, true) this.removeNodeFromActiveList(item) removeFromParentNodeData(item) toNode.setData({ @@ -1558,18 +1535,7 @@ class Render { node.nodeData.children.push( ...data.map(item => { const newData = simpleDeepClone(item) - createUidForAppointNodes([newData], true, node => { - // 可能跨层级复制,那么富文本样式需要更新 - if (this.hasRichTextPlugin()) { - // 如果设置了自定义样式那么不需要更新 - if ( - this.mindMap.richText.checkNodeHasCustomRichTextStyle(node.data) - ) { - return - } - node.data.resetRichText = true - } - }) + createUidForAppointNodes([newData], true) return newData }) ) @@ -1582,13 +1548,6 @@ class Render { const data = { [prop]: value } - // 如果开启了富文本,则需要应用到富文本上 - if ( - this.hasRichTextPlugin() && - this.mindMap.richText.isHasRichTextStyle(data) - ) { - data.resetRichText = true - } this.setNodeDataRender(node, data) // 更新了连线的样式 if (lineStyleProps.includes(prop)) { @@ -1599,13 +1558,6 @@ class Render { // 设置节点多个样式 setNodeStyles(node, style) { const data = { ...style } - // 如果开启了富文本,则需要应用到富文本上 - if ( - this.hasRichTextPlugin() && - this.mindMap.richText.isHasRichTextStyle(data) - ) { - data.resetRichText = true - } this.setNodeDataRender(node, data) // 更新了连线的样式 let props = Object.keys(style) @@ -1826,6 +1778,7 @@ class Render { list.length > 1 ) let needRender = false + const alreadyIsRichText = data && data.richText list.forEach(item => { const newData = { inserting, @@ -1837,7 +1790,7 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) newData.resetRichText = isRichText + if (isRichText && !alreadyIsRichText) newData.resetRichText = isRichText let generalization = item.node.getData('generalization') generalization = generalization ? Array.isArray(generalization) diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js index 9c1e8da5..9c2ee4a9 100644 --- a/simple-mind-map/src/core/render/node/nodeCreateContents.js +++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js @@ -1,19 +1,17 @@ import { resizeImgSize, - removeHtmlStyle, - addHtmlStyle, + removeRichTextStyes, checkIsRichText, isUndef, createForeignObjectNode, addXmlns, - generateColorByContent + generateColorByContent, + camelCaseToHyphen, + getNodeRichTextStyles } from '../../../utils' import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js' import iconsSvg from '../../../svg/icons' -import { - CONSTANTS, - noneRichTextNodeLineHeight -} from '../../../constants/constant' +import { noneRichTextNodeLineHeight } from '../../../constants/constant' // 测量svg文本宽高 const measureText = (text, style) => { @@ -124,20 +122,6 @@ function createIconNode() { }) } -// 尝试给html指定标签添加内联样式 -function tryAddHtmlStyle(text, style) { - const tagList = ['span', 'strong', 's', 'em', 'u'] - // let _text = text - // for (let i = 0; i < tagList.length; i++) { - // text = addHtmlStyle(text, tagList[i], style) - // if (text !== _text) { - // break - // } - // } - // return text - return addHtmlStyle(text, tagList, style) -} - // 创建富文本节点 function createRichTextNode(specifyText) { const hasCustomWidth = this.hasCustomWidth() @@ -145,40 +129,32 @@ function createRichTextNode(specifyText) { typeof specifyText === 'string' ? specifyText : this.getData('text') let { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt textAutoWrapWidth = hasCustomWidth ? this.customTextWidth : textAutoWrapWidth - let g = new G() - // 重新设置富文本节点内容 + const g = new G() + // 创建富文本结构,或复位富文本样式 let recoverText = false if (this.getData('resetRichText')) { delete this.nodeData.data.resetRichText recoverText = true } - if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) { - // 如果自定义过样式则不允许覆盖 - // if (!this.hasCustomStyle() ) { - recoverText = true - // } - } if (recoverText && !isUndef(text)) { - // 判断节点内容是否是富文本 - const isRichText = checkIsRichText(text) - // 获取自定义样式 - const customStyle = this.style.getCustomStyle() - // 样式字符串 - const style = this.style.createStyleText(customStyle) - if (isRichText) { - // 如果是富文本那么线移除内联样式 - text = removeHtmlStyle(text) - // 再添加新的内联样式 - text = this.tryAddHtmlStyle(text, style) + if (checkIsRichText(text)) { + // 如果是富文本那么移除内联样式 + text = removeRichTextStyes(text) } else { - // 非富文本 - text = `
${text}
` + // 非富文本则改为富文本结构 + text = `${text}
` } this.setData({ - text: text + text }) } - let html = `
<\/p>$/, '')
}
- // 给html字符串中的节点样式按样式名首字母排序
- sortHtmlNodeStyles(html) {
- return html.replace(/(<[^<>]+\s+style=")([^"]+)("\s*>)/g, (_, a, b, c) => {
- let arr = b.match(/[^:]+:[^:]+;/g) || []
- arr = arr.map(item => {
- return item.trim()
- })
- arr.sort()
- return a + arr.join('') + c
- })
- }
-
// 隐藏文本编辑控件,即完成编辑
hideEditText(nodes) {
if (!this.showTextEdit) {
@@ -395,8 +350,7 @@ class RichText {
if (typeof beforeHideRichTextEdit === 'function') {
beforeHideRichTextEdit(this)
}
- let html = this.getEditText()
- html = this.sortHtmlNodeStyles(html)
+ const html = this.getEditText()
const list = nodes && nodes.length > 0 ? nodes : [this.node]
const node = this.node
this.textEditNode.style.display = 'none'
@@ -536,18 +490,6 @@ class RichText {
}
})
this.quill.on('text-change', () => {
- let contents = this.quill.getContents()
- let len = contents.ops.length
- // 如果编辑过程中删除所有字符,那么会丢失主题的样式
- if (len <= 0 || (len === 1 && contents.ops[0].insert === '\n')) {
- this.lostStyle = true
- // 需要删除节点的样式数据
- this.syncFormatToNodeConfig(null, true)
- } else if (this.lostStyle && !this.isCompositing) {
- // 如果处于样式丢失状态,那么需要进行格式化加回样式
- this.setTextStyleIfNotRichText(this.node)
- this.lostStyle = false
- }
this.mindMap.emit('node_text_edit_change', {
node: this.node,
text: this.getEditText(),
@@ -638,10 +580,6 @@ class RichText {
return
}
this.isCompositing = false
- if (!this.lostStyle) {
- return
- }
- this.setTextStyleIfNotRichText(this.node)
}
// 选中全部
@@ -651,16 +589,15 @@ class RichText {
// 聚焦
focus(start) {
- let len = this.quill.getLength()
+ const len = this.quill.getLength()
this.quill.setSelection(typeof start === 'number' ? start : len, len)
}
// 格式化当前选中的文本
- formatText(config = {}, clear = false, pure = false) {
+ formatText(config = {}, clear = false) {
if (!this.range && !this.lastRange) return
- if (!pure) this.syncFormatToNodeConfig(config, clear)
- let rangeLost = !this.range
- let range = rangeLost ? this.lastRange : this.range
+ const rangeLost = !this.range
+ const range = rangeLost ? this.lastRange : this.range
clear
? this.quill.removeFormat(range.index, range.length)
: this.quill.formatText(range.index, range.length, config)
@@ -671,56 +608,25 @@ class RichText {
// 清除当前选中文本的样式
removeFormat() {
- // 先移除全部样式
this.formatText({}, true)
- // 再将样式恢复为当前主题改节点的默认样式
- const style = {}
- if (this.node) {
- this.supportStyleProps.forEach(key => {
- style[key] = this.node.style.merge(key)
- })
- }
- const config = this.normalStyleToRichTextStyle(style)
- this.formatText(config, false, true)
}
// 格式化指定范围的文本
formatRangeText(range, config = {}) {
if (!range) return
- this.syncFormatToNodeConfig(config)
this.quill.formatText(range.index, range.length, config)
}
// 格式化所有文本
formatAllText(config = {}) {
- this.syncFormatToNodeConfig(config)
- this.pureFormatAllText(config)
- }
-
- // 纯粹的格式化所有文本
- pureFormatAllText(config = {}) {
this.quill.formatText(0, this.quill.getLength(), config)
}
- // 同步格式化到节点样式配置
- syncFormatToNodeConfig(config, clear) {
- if (!this.node) return
- if (clear) {
- // 清除文本样式
- this.supportStyleProps.forEach(prop => {
- delete this.node.nodeData.data[prop]
- })
- } else {
- let data = this.richTextStyleToNormalStyle(config)
- this.mindMap.execCommand('SET_NODE_DATA', this.node, data)
- }
- }
-
// 将普通节点样式对象转换成富文本样式对象
normalStyleToRichTextStyle(style) {
- let config = {}
+ const config = {}
Object.keys(style).forEach(prop => {
- let value = style[prop]
+ const value = style[prop]
switch (prop) {
case 'fontFamily':
config.font = value
@@ -750,9 +656,9 @@ class RichText {
// 将富文本样式对象转换成普通节点样式对象
richTextStyleToNormalStyle(config) {
- let data = {}
+ const data = {}
Object.keys(config).forEach(prop => {
- let value = config[prop]
+ const value = config[prop]
switch (prop) {
case 'font':
data.fontFamily = value
@@ -787,39 +693,50 @@ class RichText {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
- if (this.supportStyleProps.includes(key)) {
+ if (richTextSupportStyleList.includes(key)) {
return true
}
}
return false
}
- // 给未激活的节点设置富文本样式
- setNotActiveNodeStyle(node, style) {
- const config = this.normalStyleToRichTextStyle(style)
- if (Object.keys(config).length > 0) {
- this.showEditText({ node })
- this.formatAllText(config)
- this.hideEditText([node])
- }
- }
-
// 检查指定节点是否存在自定义的富文本样式
checkNodeHasCustomRichTextStyle(node) {
const nodeData = node instanceof MindMapNode ? node.getData() : node
- for (let i = 0; i < this.supportStyleProps.length; i++) {
- if (nodeData[this.supportStyleProps[i]] !== undefined) {
+ for (let i = 0; i < richTextSupportStyleList.length; i++) {
+ if (nodeData[richTextSupportStyleList[i]] !== undefined) {
return true
}
}
return false
}
+ // 转换数据后的渲染操作
+ afterHandleData() {
+ // 清空历史数据,并且触发数据变化
+ this.mindMap.command.clearHistory()
+ this.mindMap.command.addHistory()
+ this.mindMap.render()
+ }
+
+ // 插件实例化时处理思维导图数据,转换为富文本数据
+ handleDataToRichTextOnInit() {
+ // 处理数据,转成富文本格式
+ if (this.mindMap.renderer.renderTree) {
+ // 如果已经存在渲染树了,那么直接更新渲染树,并且触发重新渲染
+ this.handleSetData(this.mindMap.renderer.renderTree)
+ this.afterHandleData()
+ } else if (this.mindMap.opt.data) {
+ this.handleSetData(this.mindMap.opt.data)
+ }
+ }
+
// 将所有节点转换成非富文本节点
transformAllNodesToNormalNode() {
- if (!this.mindMap.renderer.renderTree) return
+ const renderTree = this.mindMap.renderer.renderTree
+ if (!renderTree) return
walk(
- this.mindMap.renderer.renderTree,
+ renderTree,
null,
node => {
if (node.data.richText) {
@@ -840,15 +757,12 @@ class RichText {
0,
0
)
- // 清空历史数据,并且触发数据变化
- this.mindMap.command.clearHistory()
- this.mindMap.command.addHistory()
- this.mindMap.render(null, CONSTANTS.TRANSFORM_TO_NORMAL_NODE)
+ this.afterHandleData()
}
// 处理导入数据
handleSetData(data) {
- let walk = root => {
+ const walk = root => {
if (root.data && !root.data.richText) {
root.data.richText = true
root.data.resetRichText = true
@@ -857,8 +771,10 @@ class RichText {
if (root.data) {
const generalizationList = formatGetNodeGeneralization(root.data)
generalizationList.forEach(item => {
- item.richText = true
- item.resetRichText = true
+ if (!item.richText) {
+ item.richText = true
+ item.resetRichText = true
+ }
})
}
if (root.children && root.children.length > 0) {
diff --git a/simple-mind-map/src/plugins/Search.js b/simple-mind-map/src/plugins/Search.js
index 9e061eca..0682bc26 100644
--- a/simple-mind-map/src/plugins/Search.js
+++ b/simple-mind-map/src/plugins/Search.js
@@ -228,7 +228,7 @@ class Search {
const keep = replaceText.includes(this.searchText)
const text = this.getReplacedText(currentNode, this.searchText, replaceText)
this.notResetSearchText = true
- currentNode.setText(text, currentNode.getData('richText'), true)
+ currentNode.setText(text, currentNode.getData('richText'))
if (keep) {
this.updateMatchNodeList(this.matchNodeList)
return
@@ -258,18 +258,15 @@ class Search {
// 如果当前搜索文本是替换文本的子串,那么该节点还是符合搜索结果的
const keep = replaceText.includes(this.searchText)
this.notResetSearchText = true
- const hasRichTextPlugin = this.mindMap.renderer.hasRichTextPlugin()
this.matchNodeList.forEach(node => {
const text = this.getReplacedText(node, this.searchText, replaceText)
if (this.isNodeInstance(node)) {
const data = {
text
}
- if (hasRichTextPlugin) data.resetRichText = !!node.getData('richText')
this.mindMap.renderer.setNodeDataRender(node, data, true)
} else {
node.data.text = text
- if (hasRichTextPlugin) node.data.resetRichText = !!node.data.richText
}
})
this.mindMap.render()
diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js
index 97a626b7..d3b7e897 100644
--- a/simple-mind-map/src/utils/index.js
+++ b/simple-mind-map/src/utils/index.js
@@ -1,7 +1,8 @@
import { v4 as uuidv4 } from 'uuid'
import {
nodeDataNoStylePropList,
- selfCloseTagList
+ selfCloseTagList,
+ richTextSupportStyleList
} from '../constants/constant'
import MersenneTwister from './mersenneTwister'
import { ForeignObject } from '@svgdotjs/svg.js'
@@ -481,7 +482,7 @@ export const loadImage = imgFile => {
// 移除字符串中的html实体
export const removeHTMLEntities = str => {
- ;[[' ', ' ']].forEach(item => {
+ [[' ', ' ']].forEach(item => {
str = str.replaceAll(item[0], item[1])
})
return str
@@ -949,6 +950,12 @@ export const selectAllInput = el => {
// 给指定的节点列表树数据添加附加数据,会修改原数据
export const addDataToAppointNodes = (appointNodes, data = {}) => {
+ data = { ...data }
+ const alreadyIsRichText = data && data.richText
+ // 如果指定的数据就是富文本格式,那么不需要重新创建
+ if (alreadyIsRichText && data.resetRichText) {
+ delete data.resetRichText
+ }
const walk = list => {
list.forEach(node => {
node.data = {
@@ -1027,7 +1034,7 @@ export const generateColorByContent = str => {
// html转义
export const htmlEscape = str => {
- ;[
+ [
['&', '&'],
['<', '<'],
['>', '>']
@@ -1639,3 +1646,16 @@ export const mergeTheme = (dest, source) => {
}
})
}
+
+// 获取节点实例的文本样式数据
+export const getNodeRichTextStyles = node => {
+ const res = {}
+ richTextSupportStyleList.forEach(prop => {
+ let value = node.style.merge(prop)
+ if (prop === 'fontSize') {
+ value = value + 'px'
+ }
+ res[prop] = value
+ })
+ return res
+}