feat(P1-1本地/P2-1): beforeUpdate草稿转发布合并旧文档fail-closed / es.json ASCII安全西语(备份.zcode/bak)

This commit is contained in:
2026-09-13 07:12:29 +08:00
parent 25307a755e
commit 39d69108f7
2 changed files with 426 additions and 408 deletions
@@ -1,403 +1,421 @@
// 获取配置 // 获取配置
const createConfig = safeRequire('uni-config-center') const createConfig = safeRequire('uni-config-center')
const {QuillDeltaToHtmlConverter, QuillDeltaToJSONConverter} = safeRequire('quill-delta-converter') const {QuillDeltaToHtmlConverter, QuillDeltaToJSONConverter} = safeRequire('quill-delta-converter')
const config = createConfig({ const config = createConfig({
pluginId: 'uni-cms' pluginId: 'uni-cms'
}).config() }).config()
// 获取数据库实例 // 获取数据库实例
const db = uniCloud.database() const db = uniCloud.database()
// 文章数据库名称 // 文章数据库名称
const articleDBName = 'uni-cms-articles' const articleDBName = 'uni-cms-articles'
// 解锁内容数据库名称 // 解锁内容数据库名称
const unlockContentDBName = 'uni-cms-unlock-record' const unlockContentDBName = 'uni-cms-unlock-record'
function cmsLstText(list) { function cmsLstText(list) {
if (!Array.isArray(list)) return '' if (!Array.isArray(list)) return ''
return list.map(item => { return list.map(item => {
if (!item || typeof item !== 'object') return '' if (!item || typeof item !== 'object') return ''
if (item.type === 'text' || item.type === 'title') return item.text || item.html || '' if (item.type === 'text' || item.type === 'title') return item.text || item.html || ''
if (item.type === 'video') return item.text || '' if (item.type === 'video') return item.text || ''
if (item.type === 'vote' && item.vote) { if (item.type === 'vote' && item.vote) {
return [item.vote.voteTitle, ...(Array.isArray(item.vote.voteLst) ? item.vote.voteLst.map(option => option && option.value) : [])] return [item.vote.voteTitle, ...(Array.isArray(item.vote.voteLst) ? item.vote.voteLst.map(option => option && option.value) : [])]
.filter(Boolean).join(' ') .filter(Boolean).join(' ')
} }
return '' return ''
}).filter(Boolean).join('\n') }).filter(Boolean).join('\n')
} }
function cmsLstImages(list) { function cmsLstImages(list) {
if (!Array.isArray(list)) return [] if (!Array.isArray(list)) return []
return list.reduce((images, item) => { return list.reduce((images, item) => {
if (!item || typeof item !== 'object') return images if (!item || typeof item !== 'object') return images
if (item.type === 'image' && item.image && item.image.src) images.push(item.image.src) if (item.type === 'image' && item.image && item.image.src) images.push(item.image.src)
if (item.type === 'video' && item.video && item.video.poster) images.push(item.video.poster) if (item.type === 'video' && item.video && item.video.poster) images.push(item.video.poster)
return images return images
}, []) }, [])
} }
function contentText(content, cmsLst) { function contentText(content, cmsLst) {
const deltaText = content && Array.isArray(content.ops) const deltaText = content && Array.isArray(content.ops)
? content.ops.map(op => typeof op.insert === 'string' ? op.insert : '').join(' ') ? content.ops.map(op => typeof op.insert === 'string' ? op.insert : '').join(' ')
: '' : ''
return [deltaText, cmsLstText(cmsLst)].filter(Boolean).join('\n').trim() return [deltaText, cmsLstText(cmsLst)].filter(Boolean).join('\n').trim()
} }
function contentImages(content, cmsLst) { function contentImages(content, cmsLst) {
const deltaImages = content && Array.isArray(content.ops) const deltaImages = content && Array.isArray(content.ops)
? content.ops.filter(op => op && op.insert && typeof op.insert.image === 'string').map(op => op.insert.image) ? content.ops.filter(op => op && op.insert && typeof op.insert.image === 'string').map(op => op.insert.image)
: [] : []
return [...new Set([...deltaImages, ...cmsLstImages(cmsLst)])] return [...new Set([...deltaImages, ...cmsLstImages(cmsLst)])]
} }
// 安全检测文本内容 // 安全检测文本内容
async function checkContentSec(content, requestId, errorMsg) { async function checkContentSec(content, requestId, errorMsg) {
// 安全引入内容安全检测模块 // 安全引入内容安全检测模块
const UniSecCheck = safeRequire('uni-sec-check') const UniSecCheck = safeRequire('uni-sec-check')
// 创建内容安全检测实例 // 创建内容安全检测实例
const uniSecCheck = new UniSecCheck({ const uniSecCheck = new UniSecCheck({
provider: 'mp-weixin', provider: 'mp-weixin',
requestId requestId
}) })
// 调用文本安全检测接口 // 调用文本安全检测接口
const res = await uniSecCheck.textSecCheck({ const res = await uniSecCheck.textSecCheck({
content, // 待检测的文本内容 content, // 待检测的文本内容
scene: 1, // 表示资料类场景 scene: 1, // 表示资料类场景
version: 1 // 调用检测API的版本号 version: 1 // 调用检测API的版本号
}) })
// 如果存在敏感词,抛出异常 // 如果存在敏感词,抛出异常
if (res.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) { if (res.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) {
throw new Error(errorMsg || '存在敏感词,请修改后提交') throw new Error(errorMsg || '存在敏感词,请修改后提交')
} else if (res.errCode !== 0) { } else if (res.errCode !== 0) {
console.error(res) console.error(res)
throw new Error('内容安全检测异常:' + res.errCode) throw new Error('内容安全检测异常:' + res.errCode)
} }
} }
// 安全检测图片内容 // 安全检测图片内容
async function checkImageSec(image, requestId, errorMsg) { async function checkImageSec(image, requestId, errorMsg) {
// 安全引入内容安全检测模块 // 安全引入内容安全检测模块
const UniSecCheck = safeRequire('uni-sec-check') const UniSecCheck = safeRequire('uni-sec-check')
// 创建内容安全检测实例 // 创建内容安全检测实例
const uniSecCheck = new UniSecCheck({ const uniSecCheck = new UniSecCheck({
provider: 'mp-weixin', provider: 'mp-weixin',
requestId requestId
}) })
const images = typeof image === "string" ? [image]: image const images = typeof image === "string" ? [image]: image
for (let item of images) { for (let item of images) {
// 处理cloud://开头的链接 // 处理cloud://开头的链接
if (item.startsWith('cloud://')) { if (item.startsWith('cloud://')) {
const res = await uniCloud.getTempFileURL({ const res = await uniCloud.getTempFileURL({
fileList: [item] fileList: [item]
}) })
if (res.fileList && res.fileList.length > 0) { if (res.fileList && res.fileList.length > 0) {
item = res.fileList[0].tempFileURL item = res.fileList[0].tempFileURL
} }
} }
// 调用图片安全检测接口 // 调用图片安全检测接口
const res = await uniSecCheck.imgSecCheck({ const res = await uniSecCheck.imgSecCheck({
image: item, // 待检测的图片URL image: item, // 待检测的图片URL
scene: 1, // 表示资料类场景 scene: 1, // 表示资料类场景
version: 1 // 调用检测API的版本号 version: 1 // 调用检测API的版本号
}) })
// 如果存在违规内容,抛出异常 // 如果存在违规内容,抛出异常
if (res.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) { if (res.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) {
throw new Error(errorMsg || '图片违规,请修改后提交') throw new Error(errorMsg || '图片违规,请修改后提交')
} else if (res.errCode !== 0) { } else if (res.errCode !== 0) {
console.error(res) console.error(res)
throw new Error('内容安全检测异常:' + res.errCode) throw new Error('内容安全检测异常:' + res.errCode)
} }
} }
} }
// 检测内容安全开关 // 检测内容安全开关
function checkContentSecurityEnable(field) { function checkContentSecurityEnable(field) {
// 1. 从配置中心获取配置 // 1. 从配置中心获取配置
return config.contentSecurity && config.contentSecurity.allowCheckType && config.contentSecurity.allowCheckType.includes(field) return config.contentSecurity && config.contentSecurity.allowCheckType && config.contentSecurity.allowCheckType.includes(field)
} }
// 安全require // 安全require
function safeRequire(module) { function safeRequire(module) {
try { try {
return require(module) return require(module)
} catch (e) { } catch (e) {
if (e.code === 'MODULE_NOT_FOUND') { if (e.code === 'MODULE_NOT_FOUND') {
throw new Error(`${module} 公共模块不存在,请在 uniCloud/database 目录右击"配置schema扩展公共模块"添加 ${module} 模块`) throw new Error(`${module} 公共模块不存在,请在 uniCloud/database 目录右击"配置schema扩展公共模块"添加 ${module} 模块`)
} }
} }
} }
module.exports = { module.exports = {
trigger: { trigger: {
// 创建文章前触发 // 创建文章前触发
beforeCreate: async function ({clientInfo, addDataList}) { beforeCreate: async function ({clientInfo, addDataList}) {
// addDataList 是一个数组,因为可以一次性创建多条数据 // addDataList 是一个数组,因为可以一次性创建多条数据
if (addDataList.length <= 0) return if (addDataList.length <= 0) return
// 检测内容安全开关 // 检测内容安全开关
const allowCheckContent = checkContentSecurityEnable('content') const allowCheckContent = checkContentSecurityEnable('content')
const allowCheckImage = checkContentSecurityEnable('image') const allowCheckImage = checkContentSecurityEnable('image')
// 遍历数组,对每一条数据进行安全检测 // 遍历数组,对每一条数据进行安全检测
for (const addData of addDataList) { for (const addData of addDataList) {
// 如果是草稿,不检测 // 如果是草稿,不检测
if (addData.article_status !== 1) continue if (addData.article_status !== 1) continue
// 并行检测 // 并行检测
const parallel = [] const parallel = []
// 检测标题 // 检测标题
if (allowCheckContent && addData.title) { if (allowCheckContent && addData.title) {
parallel.push(checkContentSec(addData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交')) parallel.push(checkContentSec(addData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交'))
} }
// 检测摘要 // 检测摘要
if (allowCheckContent && addData.excerpt) { if (allowCheckContent && addData.excerpt) {
parallel.push(checkContentSec(addData.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交')) parallel.push(checkContentSec(addData.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交'))
} }
// 检测内容:兼容标准 Delta 与移动端 cmsLst // 检测内容:兼容标准 Delta 与移动端 cmsLst
const text = contentText(addData.content, addData.cmsLst) const text = contentText(addData.content, addData.cmsLst)
if (allowCheckContent && text) { if (allowCheckContent && text) {
parallel.push(checkContentSec(text, clientInfo.requestId, '内容存在敏感字,请修改后提交')) parallel.push(checkContentSec(text, clientInfo.requestId, '内容存在敏感字,请修改后提交'))
} }
// 检测封面、正文图片和视频封面 // 检测封面、正文图片和视频封面
const images = [...(Array.isArray(addData.thumbnail) ? addData.thumbnail : []), ...contentImages(addData.content, addData.cmsLst)] const images = [...(Array.isArray(addData.thumbnail) ? addData.thumbnail : []), ...contentImages(addData.content, addData.cmsLst)]
if (allowCheckImage && images.length) { if (allowCheckImage && images.length) {
parallel.push(checkImageSec(images, clientInfo.requestId, '图片存在违规,请修改后提交')) parallel.push(checkImageSec(images, clientInfo.requestId, '图片存在违规,请修改后提交'))
} }
// 等待所有并行检测完成 // 等待所有并行检测完成
await Promise.all(parallel) await Promise.all(parallel)
} }
}, },
// 更新文章前触发 // 更新文章前触发
beforeUpdate: async function ({clientInfo, where, updateData}) { beforeUpdate: async function ({clientInfo, where, updateData}) {
const id = where && where._id const id = where && where._id
if (!id) return if (!id) return
// 如果是草稿,不检测 // 如果是草稿,不检测
if (updateData.article_status !== 1) return if (updateData.article_status !== 1) return
// 检测内容安全开关 // P1-1 草稿转发布合并旧文档:仅带 article_status=1 而无内容字段时,
const allowCheckContent = checkContentSecurityEnable('content') // 直接审 updateData 会审空漏过。读库合并标题/摘要/正文/封面后再审。
const allowCheckImage = checkContentSecurityEnable('image') // 读库失败则拒绝放行(fail-closed),避免静默漏审。
let auditSrc = updateData;
// 并行检测 const hasContentField = updateData.title !== undefined || updateData.excerpt !== undefined ||
const parallel = [] updateData.content !== undefined || updateData.cmsLst !== undefined || updateData.thumbnail !== undefined;
if (updateData.article_status === 1 && !hasContentField) {
// 检测标题 let oldDoc = null;
if (allowCheckContent && updateData.title) { try {
parallel.push(checkContentSec(updateData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交')) const found = await db.collection(articleDBName).doc(id).get();
} oldDoc = found && found.data && found.data[0];
// 检测摘要 } catch (e) {
if (allowCheckContent && updateData.excerpt) { throw new Error("发布审核需读取原文失败,请重试");
parallel.push(checkContentSec(updateData.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交')) }
} if (!oldDoc) throw new Error("发布审核需读取原文失败,请重试");
// 检测内容:兼容标准 Delta 与移动端 cmsLst auditSrc = Object.assign({}, oldDoc, updateData);
const text = contentText(updateData.content, updateData.cmsLst) }
if (allowCheckContent && text) {
parallel.push(checkContentSec(text, clientInfo.requestId, '内容存在敏感字,请修改后提交')) // 检测内容安全开关
} const allowCheckContent = checkContentSecurityEnable('content')
// 检测封面、正文图片和视频封面 const allowCheckImage = checkContentSecurityEnable('image')
const images = [...(Array.isArray(updateData.thumbnail) ? updateData.thumbnail : []), ...contentImages(updateData.content, updateData.cmsLst)]
if (allowCheckImage && images.length) { // 并行检测
parallel.push(checkImageSec(images, clientInfo.requestId, '图片存在违规,请修改后提交')) const parallel = []
}
// 检测标题
// 等待所有并行检测完成 if (allowCheckContent && auditSrc.title) {
await Promise.all(parallel) parallel.push(checkContentSec(auditSrc.title, clientInfo.requestId, '标题存在敏感字,请修改后提交'))
}
}, // 检测摘要
// 读取文章后触发 if (allowCheckContent && auditSrc.excerpt) {
afterRead: async function ({userInfo, clientInfo, result, where, field}) { parallel.push(checkContentSec(auditSrc.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交'))
const isAdmin = field && field.length && field.includes('is_admin') }
// 检查是否配置了clientAppIds字段,如果没有则抛出错误 // 检测内容:兼容标准 Delta 与移动端 cmsLst
if ((!config.clientAppIds || !config.clientAppIds.length) && !isAdmin) { const text = contentText(auditSrc.content, auditSrc.cmsLst)
throw new Error('请在 uni-cms 配置文件中配置 clientAppIds 字段后访问,详见:https://uniapp.dcloud.net.cn/uniCloud/uni-cms.html#uni-cms-config') if (allowCheckContent && text) {
} parallel.push(checkContentSec(text, clientInfo.requestId, '内容存在敏感字,请修改后提交'))
}
// 如果clientAppIds字段未配置或当前appId不在clientAppIds中,则返回 // 检测封面、正文图片和视频封面
if (!config.clientAppIds || !config.clientAppIds.includes(clientInfo.appId)) return const images = [...(Array.isArray(auditSrc.thumbnail) ? auditSrc.thumbnail : []), ...contentImages(auditSrc.content, auditSrc.cmsLst)]
if (allowCheckImage && images.length) {
// 获取广告配置 parallel.push(checkImageSec(images, clientInfo.requestId, '图片存在违规,请修改后提交'))
const adConfig = config.adConfig || {} }
// 获取文章id // 等待所有并行检测完成
const id = where && where._id await Promise.all(parallel)
// 如果id不存在或者field不包含content,则返回 },
if (id && field.includes('content')) { // 读取文章后触发
// 读取了content字段后view_count加1 afterRead: async function ({userInfo, clientInfo, result, where, field}) {
await db.collection(articleDBName).where(where).update({ const isAdmin = field && field.length && field.includes('is_admin')
view_count: db.command.inc(1) // 检查是否配置了clientAppIds字段,如果没有则抛出错误
}) if ((!config.clientAppIds || !config.clientAppIds.length) && !isAdmin) {
} throw new Error('请在 uni-cms 配置文件中配置 clientAppIds 字段后访问,详见:https://uniapp.dcloud.net.cn/uniCloud/uni-cms.html#uni-cms-config')
}
// 如果查询结果为空,则返回
if (!result.data || result.data.length <= 0) return // 如果clientAppIds字段未配置或当前appId不在clientAppIds中,则返回
if (!config.clientAppIds || !config.clientAppIds.includes(clientInfo.appId)) return
// 获取文章
const article = result.data[0] // 获取广告配置
const adConfig = config.adConfig || {}
// 移动编辑器保留 cmsLst,标准渲染器使用 content;移动文章不依赖 content 才能展示
if (article.edit_type === 'mobile') { // 获取文章id
article.content_images = contentImages(article.content, article.cmsLst) const id = where && where._id
return
} // 如果id不存在或者field不包含content,则返回
if (id && field.includes('content')) {
// 如果文章内容不存在或格式异常,则返回空内容,避免 afterRead 直接抛异常 // 读取了content字段后view_count加1
if (!article.content || !Array.isArray(article.content.ops)) { await db.collection(articleDBName).where(where).update({
article.content_images = [] view_count: db.command.inc(1)
article.content = { ops: [] } })
return }
}
// 如果查询结果为空,则返回
let needUnlock = false if (!result.data || result.data.length <= 0) return
let unlockContent = []
// 获取文章
// 获取文章内容中的图片 const article = result.data[0]
article.content_images = article.content.ops.reduce((imageBlocks, block) => {
if (!block || !block.insert || typeof block.insert.image !== 'string') return imageBlocks // 移动编辑器保留 cmsLst,标准渲染器使用 content;移动文章不依赖 content 才能展示
if (article.edit_type === 'mobile') {
const {attributes} = block article.content_images = contentImages(article.content, article.cmsLst)
const {'data-custom': custom = ""} = attributes || {} return
const parseCustom = custom.split('&').reduce((obj, item) => { }
const [key, value] = item.split('=')
if (key) obj[key] = value // 如果文章内容不存在或格式异常,则返回空内容,避免 afterRead 直接抛异常
return obj if (!article.content || !Array.isArray(article.content.ops)) {
}, {}) article.content_images = []
article.content = { ops: [] }
return imageBlocks.concat( return
parseCustom.source || }
block.insert.image
) let needUnlock = false
}, []) let unlockContent = []
for (const op of article.content.ops) { // 获取文章内容中的图片
if (!op || !op.insert) continue article.content_images = article.content.ops.reduce((imageBlocks, block) => {
unlockContent.push(op) if (!block || !block.insert || typeof block.insert.image !== 'string') return imageBlocks
// 遍历文章内容,找到解锁内容
if (op.insert.unlockContent) { const {attributes} = block
needUnlock = true const {'data-custom': custom = ""} = attributes || {}
break const parseCustom = custom.split('&').reduce((obj, item) => {
} const [key, value] = item.split('=')
} if (key) obj[key] = value
return obj
// 如果文章不需要解锁,则返回 }, {})
if (!needUnlock) {
article.content = getRenderableArticleContent(article.content, clientInfo) return imageBlocks.concat(
return parseCustom.source ||
} block.insert.image
)
// 获取唯一标识符 }, [])
const uniqueId = adConfig.watchAdUniqueType === 'user' ? userInfo.uid : clientInfo.deviceId
for (const op of article.content.ops) {
// 如果未登录或者文章未解锁,则返回解锁内容 if (!op || !op.insert) continue
if (!uniqueId || !article._id) { unlockContent.push(op)
article.content = getRenderableArticleContent({ // 遍历文章内容,找到解锁内容
ops: unlockContent if (op.insert.unlockContent) {
}, clientInfo) needUnlock = true
return break
} }
}
// 查询解锁记录
const unlockRecord = await db.collection(unlockContentDBName).where({ // 如果文章不需要解锁,则返回
unique_id: uniqueId, if (!needUnlock) {
article_id: article._id article.content = getRenderableArticleContent(article.content, clientInfo)
}).get() return
}
// 如果未解锁,则返回解锁内容
if (unlockRecord.data && unlockRecord.data.length <= 0) { // 获取唯一标识符
article.content = getRenderableArticleContent({ const uniqueId = adConfig.watchAdUniqueType === 'user' ? userInfo.uid : clientInfo.deviceId
ops: unlockContent
}, clientInfo) // 如果未登录或者文章未解锁,则返回解锁内容
return if (!uniqueId || !article._id) {
} article.content = getRenderableArticleContent({
ops: unlockContent
// 将文章解锁替换为行结束符 \n }, clientInfo)
article.content = getRenderableArticleContent({ return
ops: article.content.ops.map(op => { }
if (op.insert.unlockContent) {
op.insert = "\n" // 查询解锁记录
} const unlockRecord = await db.collection(unlockContentDBName).where({
return op unique_id: uniqueId,
}) article_id: article._id
}, clientInfo) }).get()
}
} // 如果未解锁,则返回解锁内容
} if (unlockRecord.data && unlockRecord.data.length <= 0) {
article.content = getRenderableArticleContent({
function getRenderableArticleContent (rawArticleContent, clientInfo) { ops: unlockContent
const isUniAppX = /uni-app-x/i.test(clientInfo.userAgent) }, clientInfo)
return
if (!isUniAppX) { }
const quillDeltaConverter = new QuillDeltaToJSONConverter(rawArticleContent.ops)
return quillDeltaConverter.convert() // 将文章解锁替换为行结束符 \n
} article.content = getRenderableArticleContent({
ops: article.content.ops.map(op => {
const deltaOps = [] if (op.insert.unlockContent) {
op.insert = "\n"
for (let i = 0; i < rawArticleContent.ops.length; i++) { }
const op = rawArticleContent.ops[i] return op
})
if (typeof op.insert === 'object') { }, clientInfo)
const insertType = Object.keys(op.insert) }
const blockRenderList = ['image', 'divider', 'unlockContent', 'mediaVideo'] }
if (insertType && insertType.length > 0 && blockRenderList.includes(insertType[0])) { }
deltaOps.push({
type: insertType[0], function getRenderableArticleContent (rawArticleContent, clientInfo) {
ops: [op] const isUniAppX = /uni-app-x/i.test(clientInfo.userAgent)
})
if (!isUniAppX) {
// 一般块级节点后面都跟一个换行,需要把这个换行给去掉 const quillDeltaConverter = new QuillDeltaToJSONConverter(rawArticleContent.ops)
const nextOps = rawArticleContent.ops[i + 1] return quillDeltaConverter.convert()
if (nextOps && nextOps.insert === '\n') { }
i ++
} const deltaOps = []
continue for (let i = 0; i < rawArticleContent.ops.length; i++) {
} const op = rawArticleContent.ops[i]
}
if (typeof op.insert === 'object') {
const currentIndex = deltaOps.length > 0 ? deltaOps.length - 1: 0 const insertType = Object.keys(op.insert)
if ( const blockRenderList = ['image', 'divider', 'unlockContent', 'mediaVideo']
typeof deltaOps[currentIndex] !== "object" || if (insertType && insertType.length > 0 && blockRenderList.includes(insertType[0])) {
(deltaOps[currentIndex] && deltaOps[currentIndex].type !== 'rich-text') deltaOps.push({
) { type: insertType[0],
deltaOps.push({ ops: [op]
type: 'rich-text', })
ops: []
}) // 一般块级节点后面都跟一个换行,需要把这个换行给去掉
} const nextOps = rawArticleContent.ops[i + 1]
if (nextOps && nextOps.insert === '\n') {
deltaOps[deltaOps.length - 1].ops.push(op) i ++
} }
return deltaOps.reduce((content, item) => { continue
const isRichText = item.type === 'rich-text' }
let block = { }
type: item.type,
data: isRichText ? item.ops: item.ops[0] const currentIndex = deltaOps.length > 0 ? deltaOps.length - 1: 0
} if (
typeof deltaOps[currentIndex] !== "object" ||
if (item.type === 'rich-text') { (deltaOps[currentIndex] && deltaOps[currentIndex].type !== 'rich-text')
const lastOp = item.ops.length > 0 ? item.ops[item.ops.length - 1]: null ) {
deltaOps.push({
if (lastOp !== null && lastOp.insert === "\n") { type: 'rich-text',
item.ops.pop() ops: []
} })
}
const quillDeltaConverter = new QuillDeltaToHtmlConverter(item.ops)
deltaOps[deltaOps.length - 1].ops.push(op)
block.data = quillDeltaConverter.convert() }
}
return deltaOps.reduce((content, item) => {
return content.concat(block) const isRichText = item.type === 'rich-text'
}, []) let block = {
} type: item.type,
data: isRichText ? item.ops: item.ops[0]
}
if (item.type === 'rich-text') {
const lastOp = item.ops.length > 0 ? item.ops[item.ops.length - 1]: null
if (lastOp !== null && lastOp.insert === "\n") {
item.ops.pop()
}
const quillDeltaConverter = new QuillDeltaToHtmlConverter(item.ops)
block.data = quillDeltaConverter.convert()
}
return content.concat(block)
}, [])
}
@@ -1,5 +1,5 @@
{ {
"uni-pagination.prevText": "anterior", "uni-pagination.prevText": "anterior",
"uni-pagination.nextText": "prxima", "uni-pagination.nextText": "siguiente",
"uni-pagination.piecePerPage": "Art¨ªculo/P¨¢gina" "uni-pagination.piecePerPage": "elementos/pagina"
} }