quill-delta-to-html
Version:
Converts Quill's delta ops to HTML
234 lines (188 loc) • 7.5 kB
text/typescript
import { InsertOpsConverter } from './InsertOpsConverter';
import { OpToHtmlConverter, IOpToHtmlConverterOptions } from './OpToHtmlConverter';
import { DeltaInsertOp } from './DeltaInsertOp';
import { Grouper } from './grouper/Grouper';
import {
VideoItem, InlineGroup, BlockGroup, ListGroup, ListItem, TDataGroup
} from './grouper/group-types';
import { ListNester } from './grouper/ListNester';
import { makeStartTag, makeEndTag, encodeHtml } from './funcs-html';
import './extensions/Object';
import { GroupType } from './value-types';
interface IQuillDeltaToHtmlConverterOptions {
// no more allowing these to be customized; unnecessary
orderedListTag?: string,
bulletListTag?: string,
listItemTag?: string,
paragraphTag?: string,
classPrefix?: string,
encodeHtml?: boolean,
multiLineBlockquote?: boolean,
multiLineHeader?: boolean,
multiLineCodeblock?: boolean,
linkRel?: string,
linkTarget?: string,
allowBackgroundClasses?: boolean
}
const BrTag = '<br/>';
class QuillDeltaToHtmlConverter {
private options: IQuillDeltaToHtmlConverterOptions;
private rawDeltaOps: any[] = [];
private converterOptions: IOpToHtmlConverterOptions;
// render callbacks
private callbacks: any = {};
constructor(
deltaOps: any[],
options?: IQuillDeltaToHtmlConverterOptions) {
this.options = Object._assign({
paragraphTag: 'p',
encodeHtml: true,
classPrefix: 'ql',
multiLineBlockquote: true,
multiLineHeader: true,
multiLineCodeblock: true,
allowBackgroundClasses: false,
linkTarget: '_blank'
}, options, {
orderedListTag: 'ol',
bulletListTag: 'ul',
listItemTag: 'li'
});
this.converterOptions = {
encodeHtml: this.options.encodeHtml,
classPrefix: this.options.classPrefix,
listItemTag: this.options.listItemTag,
paragraphTag: this.options.paragraphTag,
linkRel: this.options.linkRel,
linkTarget: this.options.linkTarget,
allowBackgroundClasses: this.options.allowBackgroundClasses
};
this.rawDeltaOps = deltaOps;
}
_getListTag(op: DeltaInsertOp): string {
return op.isOrderedList() ? this.options.orderedListTag + ''
: op.isBulletList() ? this.options.bulletListTag + ''
: '';
}
getGroupedOps (): TDataGroup[] {
var deltaOps = InsertOpsConverter.convert(this.rawDeltaOps);
var pairedOps = Grouper.pairOpsWithTheirBlock(deltaOps);
var groupedSameStyleBlocks = Grouper.groupConsecutiveSameStyleBlocks(pairedOps, {
blockquotes: !!this.options.multiLineBlockquote,
header: !!this.options.multiLineHeader,
codeBlocks: !!this.options.multiLineCodeblock
});
var groupedOps = Grouper.reduceConsecutiveSameStyleBlocksToOne(groupedSameStyleBlocks);
var listNester = new ListNester();
return listNester.nest(groupedOps);
}
convert() {
return this.getGroupedOps()
.map(group => {
if (group instanceof ListGroup) {
return this._renderWithCallbacks(
GroupType.List, group, () => this._renderList(<ListGroup>group));
} else if (group instanceof BlockGroup) {
var g = <BlockGroup>group;
return this._renderWithCallbacks(
GroupType.Block, group, () => this._renderBlock(g.op, g.ops));
} else if (group instanceof VideoItem) {
return this._renderWithCallbacks(GroupType.Video, group, () => {
var g = <VideoItem>group;
var converter = new OpToHtmlConverter(g.op, this.converterOptions);
return converter.getHtml();
});
} else { // InlineGroup
return this._renderWithCallbacks(GroupType.InlineGroup, group, () => {
return this._renderInlines((<InlineGroup>group).ops);
});
}
})
.join("");
}
_renderWithCallbacks(groupType: GroupType, group: TDataGroup, myRenderFn: () => string) {
var html = '';
var beforeCb = this.callbacks['beforeRender_cb'];
html = typeof beforeCb === 'function' ? beforeCb.apply(null, [groupType, group]) : '';
if (!html) {
html = myRenderFn();
}
var afterCb = this.callbacks['afterRender_cb'];
html = typeof afterCb === 'function' ? afterCb.apply(null, [groupType, html]) : html;
return html;
}
_renderList(list: ListGroup): string {
var firstItem = list.items[0];
return makeStartTag(this._getListTag(firstItem.item.op))
+ list.items.map((li: ListItem) => this._renderListItem(li)).join('')
+ makeEndTag(this._getListTag(firstItem.item.op));
}
_renderListItem(li: ListItem ): string {
//if (!isOuterMost) {
li.item.op.attributes.indent = 0;
//}
var converter = new OpToHtmlConverter(li.item.op, this.converterOptions);
var parts = converter.getHtmlParts();
var liElementsHtml = this._renderInlines(li.item.ops, false);
return parts.openingTag + (liElementsHtml) +
(li.innerList ? this._renderList(li.innerList) : '')
+ parts.closingTag;
}
_renderBlock(bop: DeltaInsertOp, ops: DeltaInsertOp[]) {
var converter = new OpToHtmlConverter(bop, this.converterOptions);
var htmlParts = converter.getHtmlParts();
if (bop.isCodeBlock()) {
return htmlParts.openingTag +
encodeHtml(
ops.map((iop) =>
iop.isCustom() ? this._renderCustom(iop, bop) : iop.insert.value
).join("")
)
+ htmlParts.closingTag;
}
var inlines = ops.map(op => this._renderInline(op, bop)).join('');
return htmlParts.openingTag + (inlines || BrTag) + htmlParts.closingTag;
}
_renderInlines(ops: DeltaInsertOp[], wrapInParagraphTag = true) {
var opsLen = ops.length - 1;
var html = ops.map((op: DeltaInsertOp, i: number) => {
if (i > 0 && i === opsLen && op.isJustNewline()) {
return '';
}
return this._renderInline(op, null);
}).join('');
if (!wrapInParagraphTag) {
return html;
}
return makeStartTag(this.options.paragraphTag) +
html + makeEndTag(this.options.paragraphTag);
}
_renderInline(op: DeltaInsertOp, contextOp: DeltaInsertOp | null) {
if (op.isCustom()) {
return this._renderCustom(op, contextOp);
}
var converter = new OpToHtmlConverter(op, this.converterOptions);
return converter.getHtml().replace(/\n/g, BrTag);
}
_renderCustom(op: DeltaInsertOp, contextOp: DeltaInsertOp | null) {
var renderCb = this.callbacks['renderCustomOp_cb'];
if (typeof renderCb === 'function') {
return renderCb.apply(null, [op, contextOp]);
}
return "";
}
beforeRender(cb: (group: GroupType, data: TDataGroup) => string) {
if (typeof cb === 'function') {
this.callbacks['beforeRender_cb'] = cb;
}
}
afterRender(cb: (group: GroupType, html: string) => string) {
if (typeof cb === 'function') {
this.callbacks['afterRender_cb'] = cb;
}
}
renderCustomWith(cb: (op: DeltaInsertOp, contextOp: DeltaInsertOp) => string) {
this.callbacks['renderCustomOp_cb'] = cb;
}
}
export { QuillDeltaToHtmlConverter };