Skip to content

Instantly share code, notes, and snippets.

@meekg33k
Created March 2, 2021 01:40
Show Gist options
  • Save meekg33k/023697d1f9d73597aedf6459d72c1657 to your computer and use it in GitHub Desktop.
Save meekg33k/023697d1f9d73597aedf6459d72c1657 to your computer and use it in GitHub Desktop.
Code snippet for Medium's algorithm problem on how to apply deltas to a post structure.
/** Ideally this will be declared in a constants module */
const ADD_PARAGRAPH_DELTA = "addParagraph";
const DELETE_PARAGRAPH_DELTA = "deleteParagraph";
const UPDATE_PARAGRAPH_DELTA = "updateParagraph";
let sectionParagraphIndices = [];
/**
* @param {string} delta
* @param {Array} paragraphs
*
* This method applies changes to a single paragraph
*/
const applyDeltaToSingleParagraph = (delta, paragraphs) => {
const { type, paragraphIndex, paragraph } = delta;
let modifiedParagraphs = [...paragraphs];
let text;
if (paragraph) { text = paragraph.text; } //DELETE has no text
switch (type) {
case ADD_PARAGRAPH_DELTA:
modifiedParagraphs.splice(
paragraphIndex,
0,
{ id: text, text }
);
updateSectionParagraphIndices(paragraphIndex, type);
break;
case DELETE_PARAGRAPH_DELTA:
modifiedParagraphs = [
...modifiedParagraphs.slice(0, paragraphIndex),
...modifiedParagraphs.slice(paragraphIndex + 1)
];
updateSectionParagraphIndices(paragraphIndex, type);
break;
case UPDATE_PARAGRAPH_DELTA:
modifiedParagraphs[paragraphIndex] = { id: text, text };
break;
default:
break;
};
return modifiedParagraphs;
};
const applyDeltaToParagraphs = (paragraphs, deltas) => {
if (deltas && deltas.length > 0 && paragraphs) {
deltas.forEach((delta) => {
const { paragraphIndex } = delta;
if (paragraphIndex >= 0) {
paragraphs = applyDeltaToSingleParagraph(delta, paragraphs);
}
});
};
return paragraphs;
}
/**
* @param {Number} paragraphIndex
* @param {String} type
*
* This function updates a paragraph with the section indices
*/
const updateSectionParagraphIndices = (paragraphIndex, type) => {
sectionParagraphIndices.forEach((sectionParagraphIndex) => {
const [start, end] = sectionParagraphIndex;
if (paragraphIndex <= end) {
if (paragraphIndex >= start) {
//This is the changed section
if (type === ADD_PARAGRAPH_DELTA) { sectionParagraphIndex[1] = end + 1; }
if (type === DELETE_PARAGRAPH_DELTA) { sectionParagraphIndex[1] = end - 1; }
}
else {
//Update affected sections because of preceding change
if (type === ADD_PARAGRAPH_DELTA) {
sectionParagraphIndex[0] = start + 1;
sectionParagraphIndex[1] = end + 1;
}
if (type === DELETE_PARAGRAPH_DELTA) {
sectionParagraphIndex[0] = start - 1;
sectionParagraphIndex[1] = end - 1;
}
}
}
});
}
/**
*
* @param {Array} sections
* @param {String} paragraphLength
*
* This function returns an array of start and end
* positions for each section in the post content.e.g [[0, 2], [4,5]]
*/
const constructSectionParagraphIndices = (sections, paragraphLength) => {
let sectionStartEndPositions = [];
if (sections && sections.length > 0) {
sections.forEach((section, index) => {
const currSectionStartIndex = section.startIndex;
if (index + 1 < sections.length) {
const currSectionEndIndex = sections[index + 1].startIndex - 1;
sectionStartEndPositions.push([currSectionStartIndex, currSectionEndIndex])
}
else {
sectionStartEndPositions.push([currSectionStartIndex, paragraphLength - 1]);
}
});
};
return sectionStartEndPositions;
};
const constructParagraphBySectionData = (paragraphs) => {
const SECTION_DELIMITER = '-';
const sectionParagraphLength = sectionParagraphIndices.length;
let paragraphsBySection = [];
if (sectionParagraphIndices && sectionParagraphLength > 0) {
sectionParagraphIndices.forEach((sectionParagraphIndex, index) => {
const [start, end] = sectionParagraphIndex;
paragraphsBySection.push(...paragraphs.slice(start, end + 1));
if (index < sectionParagraphLength - 1 && start <= end) {
paragraphsBySection.push({ text: SECTION_DELIMITER });
}
});
};
//Edge case: Check if last character is section delimiter
if (paragraphsBySection[paragraphsBySection.length - 1].text === SECTION_DELIMITER) {
paragraphsBySection = paragraphsBySection.slice(0, paragraphsBySection.length - 1);
}
return paragraphsBySection;
};
/**
* @param {Array} paragraphs
*
* This function constructs the final text to
* be rendered on the page
*/
const constructFinalPageMarkup = (paragraphs) => {
const PARAGRAPH_DELIMITER = '\n';
if (paragraphs && paragraphs.length > 0) {
return paragraphs.reduce((acc, paragraph) => {
if (paragraph.text) {
const markup = acc ? `${acc}${PARAGRAPH_DELIMITER}${paragraph.text}` : `${paragraph.text}`;
return markup;
}
}, '');
}
else { return '' };
};
/**
* @param {Object} postContent
*
* The main function the parses the post content structure
*/
const parsePostContent = (postContent) => {
const clonedPostContent = { ...postContent };
const { paragraphs, sections } = clonedPostContent;
let paragraphsBySectionData = paragraphs;
let pageMarkup = '';
if (paragraphs && paragraphs.length > 0) {
sectionParagraphIndices = constructSectionParagraphIndices(sections, paragraphs.length);
}
if (deltas && deltas.length > 0) {
paragraphsBySectionData = applyDeltaToParagraphs(
paragraphs,
deltas,
sectionParagraphIndices
);
}
paragraphsBySectionData = constructParagraphBySectionData(paragraphsBySectionData, sectionParagraphIndices);
pageMarkup = constructFinalPageMarkup(paragraphsBySectionData);
return pageMarkup;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment