258 lines
7.1 KiB
JavaScript
258 lines
7.1 KiB
JavaScript
const {
|
||
QuillDeltaToHtmlConverter: QuillDeltaToHtmlConverterBase,
|
||
BlockGroup,
|
||
ListGroup,
|
||
BlotBlock,
|
||
} = require('./core')
|
||
|
||
const jsonRules = {
|
||
list(list) {
|
||
const firstItem = list.items[0];
|
||
|
||
return {
|
||
type: "list",
|
||
data: {
|
||
type: firstItem.item.op.attributes.list,
|
||
items: list.items.map((item) => jsonRules.listItem(item)),
|
||
},
|
||
attributes: firstItem.item.op.attributes,
|
||
class: attributes2class(firstItem.item.op.attributes, 'block'),
|
||
style: attributes2style(firstItem.item.op.attributes, 'block'),
|
||
};
|
||
},
|
||
listItem(listItem) {
|
||
// listItem.item.op.attributes.indent = 0;
|
||
const inlines = jsonRules.inlines(listItem.item.op, listItem.item.ops, false).map((v) => v.ops)
|
||
|
||
return {
|
||
data: inlines[0] || [],
|
||
children: listItem.innerList ? jsonRules.list(listItem.innerList): [],
|
||
}
|
||
},
|
||
block (op, ops) {
|
||
const type = ops[0].insert.type
|
||
|
||
if (type === 'text') {
|
||
return jsonRules.inlines(op, ops)
|
||
}
|
||
|
||
return {
|
||
type: ops[0].insert.type,
|
||
data: ops.map(bop => jsonRules.inline(bop)),
|
||
attributes: op.attributes,
|
||
class: attributes2class(op.attributes, 'block'),
|
||
style: attributes2style(op.attributes, 'block'),
|
||
}
|
||
},
|
||
inlines(op = {}, ops, isInlineGroup = true) {
|
||
const opsLen = ops.length - 1;
|
||
const br = {
|
||
type: 'br'
|
||
}
|
||
const texts = ops.reduce((acc, op, i) => {
|
||
if (i > 0 && i === opsLen && op.isJustNewline()) {
|
||
acc[acc.length - 1].op = op
|
||
return acc;
|
||
}
|
||
|
||
if (!acc[acc.length - 1]) {
|
||
acc.push({ops: [], op: {}});
|
||
}
|
||
|
||
if (op.isJustNewline()) {
|
||
const nextOp = ops[i + 1];
|
||
acc[acc.length - 1].op = op
|
||
if (nextOp && nextOp.isJustNewline()) {
|
||
acc.push({ops: [br], op: {}});
|
||
} else {
|
||
acc.push({ops: [], op: {}});
|
||
}
|
||
return acc;
|
||
} else {
|
||
acc[acc.length - 1].ops.push(jsonRules.inline(op));
|
||
return acc;
|
||
}
|
||
}, []);
|
||
|
||
if (!isInlineGroup) {
|
||
return texts;
|
||
}
|
||
|
||
return texts.map((v) => {
|
||
return {
|
||
type: "paragraph",
|
||
data: v.ops,
|
||
class: attributes2class(op.attributes || v.op.attributes, 'block'),
|
||
style: attributes2style(op.attributes || v.op.attributes, 'block'),
|
||
};
|
||
});
|
||
},
|
||
inline (op) {
|
||
const data = {
|
||
value: op.insert.value,
|
||
attributes: op.attributes,
|
||
class: attributes2class(op.attributes, 'inline'),
|
||
style: attributes2style(op.attributes, 'inline'),
|
||
}
|
||
|
||
if (op.isCustomEmbed()) {
|
||
return {
|
||
type: op.insert.type,
|
||
data
|
||
}
|
||
}
|
||
|
||
if (op.isLink()) {
|
||
return {
|
||
type: 'link',
|
||
data
|
||
}
|
||
}
|
||
|
||
return {
|
||
type: op.isImage() ? "image": "text",
|
||
data
|
||
}
|
||
},
|
||
blotBlock (op) {
|
||
return {
|
||
type: op.insert.type,
|
||
data: {
|
||
value: op.insert.value,
|
||
attributes: op.attributes,
|
||
class: attributes2class(op.attributes, 'block'),
|
||
style: attributes2style(op.attributes, 'block'),
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
function attributes2style(attributes, type) {
|
||
if (!attributes) return ''
|
||
// 定义允许的属性
|
||
const allowAttr = {
|
||
inline: ['align', 'color', 'background', 'font', 'fontSize', 'fontStyle', 'fontVariant', 'fontWeight', 'fontFamily', 'lineHeight', 'letterSpacing', 'textDecoration', 'textIndent', 'wordWrap', 'wordBreak', 'whiteSpace'],
|
||
block: ['align', 'margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight', 'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'lineHeight', 'textIndent']
|
||
}[type]
|
||
|
||
// 定义属性适配器
|
||
const adp = {
|
||
align: 'text-align',
|
||
}
|
||
|
||
// 如果不支持该类型的属性,则抛出错误
|
||
if (!allowAttr) throw new Error('type not supported')
|
||
|
||
// 将属性转换为style
|
||
return allowAttr.reduce((res, item) => {
|
||
// 如果属性为空,则返回空字符串
|
||
if (!attributes) return res
|
||
// 如果属性不为undefined,则将属性名和属性值添加到style列表中
|
||
if (attributes[item] !== undefined) {
|
||
// 如果属性适配器中存在该属性,则使用适配器中的属性名
|
||
// 否则,将属性名转换为短横线连接的形式
|
||
res.push(`${adp[item] || item.replace(/([A-Z])/g, (s, m) => `-${m.toLowerCase()}`)}: ${attributes[item]}`)
|
||
}
|
||
return res
|
||
|
||
}, []).join(';')
|
||
}
|
||
function attributes2class(attributes, type) {
|
||
if (!attributes) return ''
|
||
// 定义允许的属性
|
||
const allowAttr = {
|
||
inline: ['bold', 'italic', 'underline', 'strike', 'ins', 'link'],
|
||
block: ['header', 'list']
|
||
}[type]
|
||
|
||
// 如果不支持该类型的属性,则抛出错误
|
||
if (!allowAttr) throw new Error('type not supported')
|
||
|
||
// 将属性转换为class列表
|
||
const classList = allowAttr.reduce((res, item) => {
|
||
// 如果属性为空,则返回空字符串
|
||
if (!attributes || item === "link") return res
|
||
// 如果属性为true,则将属性名添加到class列表中
|
||
if (attributes[item] === true) {
|
||
res.push(item)
|
||
// 如果属性不为undefined,则将属性名和属性值添加到class列表中
|
||
} else if (attributes[item] !== undefined) {
|
||
res.push(`${item}-${attributes[item]}`)
|
||
}
|
||
return res
|
||
|
||
}, [])
|
||
|
||
// 如果属性中包含link,则添加link类
|
||
if ('link' in attributes) {
|
||
classList.push('link')
|
||
}
|
||
|
||
return classList.join(' ')
|
||
}
|
||
class QuillDeltaToJSONConverter {
|
||
constructor(deltaOps) {
|
||
|
||
this.deltaPreHandler(deltaOps)
|
||
|
||
this.deltaOps = deltaOps
|
||
this.converter = new QuillDeltaToHtmlConverterBase(this.deltaOps, {
|
||
multiLineParagraph: false,
|
||
});
|
||
}
|
||
|
||
deltaPreHandler (deltaOps) {
|
||
for (const op of deltaOps) {
|
||
if (typeof op.insert === 'string') continue
|
||
|
||
if (!op.attributes) {
|
||
op.attributes = {}
|
||
}
|
||
|
||
const insertType = Object.keys(op.insert)
|
||
const blockRenderList = ['divider', 'unlockContent', 'mediaVideo']
|
||
|
||
if (insertType && insertType.length > 0 && blockRenderList.includes(insertType[0])) {
|
||
op.attributes.renderAsBlock = true
|
||
}
|
||
}
|
||
}
|
||
|
||
convert () {
|
||
const opsGroups = this.converter.getGroupedOps();
|
||
|
||
return [].concat.apply(
|
||
[],
|
||
opsGroups.map((group) => {
|
||
// console.log(JSON.stringify(group), '--------------------group------------------------------')
|
||
switch (group.constructor) {
|
||
case ListGroup:
|
||
return jsonRules.list(group);
|
||
case BlockGroup:
|
||
return jsonRules.block(group.op, group.ops)
|
||
case BlotBlock:
|
||
return jsonRules.blotBlock(group.op, group.ops)
|
||
default:
|
||
return jsonRules.inlines(group.op, group.ops);
|
||
}
|
||
})
|
||
).filter(op => op.data instanceof Array ? op.data.length : op.data);
|
||
}
|
||
}
|
||
|
||
class QuillDeltaToHtmlConverter extends QuillDeltaToHtmlConverterBase {
|
||
constructor(deltaOps) {
|
||
super(deltaOps, {
|
||
multiLineParagraph: false,
|
||
inlineStyles: true,
|
||
});
|
||
|
||
// this.renderCustomWith(function (customOp, contextOp) {
|
||
// console.log(customOp, contextOp)
|
||
// })
|
||
}
|
||
}
|
||
|
||
exports.QuillDeltaToHtmlConverter = QuillDeltaToHtmlConverter
|
||
exports.QuillDeltaToJSONConverter = QuillDeltaToJSONConverter
|