Skip to content

Instantly share code, notes, and snippets.

@ninetyone
Last active December 5, 2018 07:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ninetyone/73b927dc0fff012a1f4892460aa46bde to your computer and use it in GitHub Desktop.
Save ninetyone/73b927dc0fff012a1f4892460aa46bde to your computer and use it in GitHub Desktop.
Quill.js: Convert Delta to HTML
var editorProcesing = (function() {
function getEditorHTML(deltaData) {
var intermediateHTMLData = getIntermediateHTMLFromDelta(deltaData);
var properHTMLData = getProperHTMLFromIntermediateHTML(intermediateHTMLData);
sendErrorsIfProperHTMLDataHasIntermediateHTMLTags(properHTMLData, deltaData);
return properHTMLData;
}
function getIntermediateHTMLFromDelta(deltaData) {
if (!deltaData) deltaData = quill.getContents();
var intermediateHTMLData = '';
if (!deltaData.ops) return intermediateHTMLData;
for (var i = 0; i < deltaData.ops.length; i += 1) {
var thisDelta = deltaData.ops[i];
if (!!thisDelta.insert) {
var partOfIntermediateHTMLData = getIntermediateHTMLByProcessingDeltaObject(thisDelta);
intermediateHTMLData += partOfIntermediateHTMLData;
}
}
return intermediateHTMLData;
}
function getAllAttributeKeysForThisDeltaObject(thisDelta, isInsertElementAnObject) {
var attributeKeys = [];
if (!!thisDelta.attributes) {
Array.prototype.push.apply(attributeKeys, Object.keys(thisDelta.attributes));
}
if (isInsertElementAnObject) {
Array.prototype.push.apply(attributeKeys, Object.keys(thisDelta.insert));
}
attributeKeys.sort(); //sort attribute keys alphabetically
return attributeKeys;
}
function isThisDeltasInsertElementAnObject(thisDelta) {
return typeof thisDelta.insert === 'object';
}
function getInsertDataForThisDeltaObject(thisDelta, isInsertElementAnObject) {
if (isInsertElementAnObject) {
return '';
} else {
return sanitizeHTML(thisDelta.insert);
}
}
function getAttributesMappedObjectForThisDeltaObject(thisDelta, isInsertElementAnObject) {
var obj1 = thisDelta.attributes;
var obj2 = {};
if (isInsertElementAnObject) {
obj2 = thisDelta.insert;
}
var obj3 = {};
for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
return obj3;
}
function getIntermediateHTMLByProcessingDeltaObject(thisDelta) {
var isInsertElementAnObject = isThisDeltasInsertElementAnObject(thisDelta);
var attributeKeys = getAllAttributeKeysForThisDeltaObject(thisDelta, isInsertElementAnObject);
var attributeObject = getAttributesMappedObjectForThisDeltaObject(thisDelta, isInsertElementAnObject);
var data = getInsertDataForThisDeltaObject(thisDelta, isInsertElementAnObject);
for (var j = attributeKeys.length - 1; j > -1; j -= 1) {
data = getIntermediateHTMLByProcessingEachDeltaAttribute(attributeKeys[j], attributeObject, data);
}
return data;
}
function getIntermediateHTMLByProcessingEachDeltaAttribute(thisAttribute, attributeObject, data) {
switch (thisAttribute) {
case 'bold':
case 'italic':
case 'underline':
case 'strike':
var tag = getTagMappingForThisBlot(thisAttribute);
if (!!tag) {
data = '<' + tag +'>' + data + '</' + tag +'>';
}
break;
case 'header':
var thisAttributeVal = attributeObject[thisAttribute];
data = '<placeholder h-' + thisAttributeVal + '>' + data + '<placeholder newline>';
break;
case 'align':
var thisAttributeVal = attributeObject[thisAttribute];
data = '<placeholder align-' + thisAttributeVal + '>' + data;
break;
case 'blockquote':
data = '<placeholder blockquote>' + data + '<placeholder newline>';
break;
case 'list':
var thisAttributeVal = attributeObject[thisAttribute];
if (data === '\n') {
if (thisAttributeVal === 'ordered') {
data = '<placeholder list-ol>';
} else {
data = '<placeholder list-ul>';
}
} else {
var listArray = data.split('\n');
var listData = '';
listData += listArray[0];
for (var z = 1; z < listArray.length; z += 1) {
if (thisAttributeVal === 'ordered') {
listData += '<placeholder list-ol>' + listArray[z];
} else {
listData += '<placeholder list-ul>' + listArray[z];
}
}
data = listData;
}
break;
case 'divider':
data = '<placeholder newline><hr></hr><placeholder newline>';
break;
case 'link':
var thisAttributeVal = attributeObject[thisAttribute];
data = '<a href="' + thisAttributeVal + '" target="_blank">' + data + '</a>';
break;
case 'image':
var thisAttributeVal = attributeObject[thisAttribute];
data = '<placeholder newline><img src="' + thisAttributeVal + '" alt="'+ (attributeObject.alt || "") +'" /><placeholder newline>';
break;
case 'caption':
data = '<placeholder figcaption>' + data;
break;
case 'video':
var thisAttributeVal = attributeObject[thisAttribute];
data = '<placeholder newline><iframe style="max-width:100%" width="500" height="281" src="' + thisAttributeVal + '" frameborder="0" allowfullscreen></iframe><placeholder newline>';
break;
case 'embed':
var thisAttributeVal = attributeObject[thisAttribute];
data = '<placeholder newline><div class="sportskeeda-embed" data-src="' + thisAttributeVal.src + '">' + thisAttributeVal.html + '</div><placeholder newline>';
break;
case 'color':
case 'width':
case 'height':
case 'background':
case 'alt':
case 'indent':
case 'size':
case 'code-block':
case 'font':
case 'script'://super or subscript
case 'grammarly-inline':
break;
default:
console.error('EDIOTR HTML PARSING ERROR: attribute type: ' + thisAttribute + ' AND data: ' + JSON.stringify(attributeObject));
Rollbar.error('EDIOTR HTML PARSING ERROR: attribute type: ' + thisAttribute + ' AND data: ' + JSON.stringify(attributeObject));
}
return data;
}
function getProperHTMLFromIntermediateHTML(intermediateHTMLData) {
if (!!!intermediateHTMLData) {
return '';
}
intermediateHTMLData = preProcessData(intermediateHTMLData);
var contentBlocksArray = getContentBlocksArray(intermediateHTMLData);
var processedData = '';
for (let i = 0; i < contentBlocksArray.length; i++) {
if (checkIfLineAnyEnclosingTag(contentBlocksArray[i].trim())) {
if (contentBlocksArray[i].indexOf('<placeholder ') > -1) {
var oneBlockElementData = contentBlocksArray[i];
oneBlockElementData = handleHeaderElements(oneBlockElementData);
oneBlockElementData = handleAlignElements(oneBlockElementData);
oneBlockElementData = handleListElements(oneBlockElementData);
oneBlockElementData = handleBlockquoteElements(oneBlockElementData);
oneBlockElementData = handleFigCaptionElements(oneBlockElementData);
//Append oneBlockElementData to processedData
processedData += oneBlockElementData;
} else {
if (contentsAreJustTags(contentBlocksArray[i])) {
processedData += contentBlocksArray[i].trim();
} else {
processedData += '<p>' + contentBlocksArray[i] + '</p>';
}
}
} else {
processedData += contentBlocksArray[i].trim();
}
}
processedData = postProcessHTML(processedData.trim());
return processedData;
}
function contentsAreJustTags(content) {
return content == "<strong>" || content == "<em>" || content == "<s>" ||
content == "<u>" || content == "</strong>" || content == "</em>" ||
content == "</s>" || content == "</u>";
}
function sanitizeHTML(input) {
if(!input) {
return "";
}
return input.replace(/\</g,"&lt;").replace(/\>/g,"&gt;");
}
function checkIfLineAnyEnclosingTag(line) {
var pattern = /^(<(\/|)div)|(<(\/|)hr>)|(<(\/|)img)/i; //['<div', '</div', '<hr>', '</hr>'];
return !pattern.test(line.trim());
}
function postProcessHTML(processedData) {
processedData = processedData.replace(/\u200b/g, '');
if (processedData.trim() === "<p></p><p></p>") {
return "";
}
processedData = processedData.replace(/<p><\/p>/g, '<p>&#8203;</p>');
processedData = processedData.replace(/<p>&#8203;<\/p><p>&#8203;<\/p>$/, '<p>&#8203;<\/p>');
//Encapsulate img and figcaption tags inside figure tag
processedData = processedData.replace(/((<img.*?\/>)(<figcaption>.*?<\/figcaption>)*)/g, '<figure class= "image">$1</figure>');
return processedData
}
function preProcessData(intermediateHTMLData) {
intermediateHTMLData = intermediateHTMLData.replace(/(\n<placeholder newline>)|(<placeholder newline>\n)/g, '<placeholder newline>');
intermediateHTMLData = intermediateHTMLData.replace(/(<placeholder newline>)+/g, '<placeholder newline>');
return intermediateHTMLData;
}
function getContentBlocksArray(intermediateHTMLData) {
var contentBlocksArray = intermediateHTMLData.split(/\n|<placeholder newline>/);
var newContentBlocksArray = [];
for (var z = 0; z < contentBlocksArray.length; z++) {
var oneBlockElementData = contentBlocksArray[z];
if (oneBlockElementData.indexOf('<placeholder list-') > -1) {
var lastElementIndex = oneBlockElementData.lastIndexOf("<placeholder list-") +
"<placeholder list-".length + 3;
var list = oneBlockElementData.substring(0, lastElementIndex);
var afterList = oneBlockElementData.substring(lastElementIndex);
newContentBlocksArray.push(list);
if (afterList.length > 0) {
newContentBlocksArray.push(afterList);
}
} else {
newContentBlocksArray.push(oneBlockElementData);
}
}
return newContentBlocksArray;
}
function handleHeaderElements(oneBlockElementData) {
while (oneBlockElementData.indexOf('<placeholder h-') > -1) {
var index = oneBlockElementData.indexOf('<placeholder h-') + "<placeholder h-".length;
var headerVal = oneBlockElementData.substring(index, index + 1);
oneBlockElementData = oneBlockElementData.replace('<placeholder h-' + headerVal + '>', '');
oneBlockElementData = '<h' + headerVal + '>' + oneBlockElementData + '</h' + headerVal + '>';
}
return oneBlockElementData;
}
function handleBlockquoteElements(oneBlockElementData) {
while (oneBlockElementData.indexOf('<placeholder blockquote>') > -1) {
oneBlockElementData = oneBlockElementData.replace('<placeholder blockquote>', '');
oneBlockElementData = '<blockquote>' + oneBlockElementData + '</blockquote>';
}
return oneBlockElementData;
}
function handleFigCaptionElements(oneBlockElementData) {
while (oneBlockElementData.indexOf('<placeholder figcaption>') > -1) {
oneBlockElementData = oneBlockElementData.replace('<placeholder figcaption>', '');
oneBlockElementData = '<figcaption>' + oneBlockElementData + '</figcaption>';
}
return oneBlockElementData;
}
function handleAlignElements(oneBlockElementData) {
while (oneBlockElementData.indexOf('<placeholder align-') > -1) {
var indexStart = oneBlockElementData.indexOf('<placeholder align-') + "<placeholder align-".length;
var indexEnd = indexStart + oneBlockElementData.substring(indexStart).indexOf('>');
var alignVal = oneBlockElementData.substring(indexStart, indexEnd);
oneBlockElementData = oneBlockElementData.replace('<placeholder align-' + alignVal + '>', '');
oneBlockElementData = '<p align="' + alignVal + '">' + oneBlockElementData + '</p>';
}
return oneBlockElementData;
}
function handleListElements(oneBlockElementData) {
if (oneBlockElementData.indexOf('<placeholder list-') > -1) {
var elementAfterListData = oneBlockElementData;
var myRegexp = /(.*?)<placeholder list-(.*?)>/g;
var listElemArr = [];
var match;
while ((match = myRegexp.exec(oneBlockElementData)) !== null) {
var elem = [];
elem['type'] = match[2];
elem['data'] = match[1]
listElemArr.push(elem);
elementAfterListData = elementAfterListData.replace(match[0], '');
}
var currentListType = listElemArr[0].type;
var listData = '<' + currentListType + '>';
for (let i = 0; i < listElemArr.length; i++) {
listElem = listElemArr[i];
if (listElem.type === currentListType) {
listData += '<li>' + listElem.data + '</li>';
} else {
listData += '</' + currentListType + '>' + '<' + listElem.type + '>';
listData += '<li>' + listElem.data + '</li>';
currentListType = listElem.type;
}
}
listData += '</' + currentListType + '>';
if (elementAfterListData.trim().length > 0) {
if (!checkIfLineAnyEnclosingTag(elementAfterListData.trim())) {
elementAfterListData = elementAfterListData;
} else {
elementAfterListData = '<p>' + elementAfterListData + '</p>';
}
}
oneBlockElementData = listData + elementAfterListData;
}
return oneBlockElementData;
}
function sendErrorsIfProperHTMLDataHasIntermediateHTMLTags(properHTMLData, deltaData) {
if (properHTMLData.indexOf('<placeholder') > -1) {
if (!deltaData) deltaData = quill.getContents();
console.error('EDIOTR HTML PARSING ERROR: HTML has intermediate tags. HTML: ' + properHTMLData + ' AND delta: ' + JSON.stringify(deltaData));
Rollbar.error('EDIOTR HTML PARSING ERROR: HTML has intermediate tags. HTML: ' + properHTMLData + ' AND delta: ' + JSON.stringify(deltaData));
}
}
function getTagMappingForThisBlot(blot) {
var blotToTagMapping = {
bold: 'strong',
italic: 'em',
underline: 'u',
strike: 's',
};
return blotToTagMapping[blot];
}
return {
getEditorHTML: getEditorHTML,
sanitizeHTML: sanitizeHTML
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment