Created
February 10, 2023 09:10
-
-
Save egyjs/7b3c7814f93ad82ee0f099f1106b928e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String.prototype.replaceTemplateVars = function (data){ | |
let str = this; | |
// data is object | |
for (let key in data) { | |
str = str.replace(new RegExp(`{${key}}`, 'g'), data[key]); | |
} | |
return str; | |
} | |
document.addEventListener("DOMContentLoaded", function() { | |
// Global Variables | |
let chapterList = document.querySelector(".chapter-list"), | |
editorSection = document.querySelector(".editor-section"), | |
chapterAccordion = document.querySelector(".accordion"), | |
addBtn = document.querySelector(".add-btn"), | |
chapterContainerElement = document.querySelector(".chapters"), | |
mobileView = window.innerWidth > 500 ? false : true, | |
chaptersArr = [], | |
toolbarOptions = [ | |
[ | |
{ | |
header: 1, | |
}, | |
{ | |
header: 2, | |
}, | |
"bold", | |
"italic", | |
"underline", | |
"blockquote", | |
"code-block", | |
{ | |
list: "bullet", | |
}, | |
{ | |
list: "ordered", | |
}, | |
{ | |
align: [], | |
}, | |
"clean" | |
], | |
], | |
quillConfig = { | |
modules: { | |
toolbar: toolbarOptions, | |
}, | |
placeholder: !mobileView ? "It was a bright cold day in April, and the clocks were striking thirteen" : undefined, | |
theme: "snow", | |
}; | |
// quillSelector = mobileView ? "#chapter-accordion-1 .editor" : ".lg-chapter-1 .editor"; | |
// let quill = new Quill(quillSelector, quillConfig); | |
// Event Listeners | |
addBtn.addEventListener("click", addChapter); | |
chapterList.addEventListener("click", deleteChapter); | |
chapterList.addEventListener("click", editChapter); | |
chapterList.addEventListener("click", selectChapter); | |
// Functions | |
function addChapter() { | |
let chapterTitlesCount = document.querySelectorAll(".chapter-title").length, | |
chapterNumber = chapterTitlesCount + 1, | |
chapterTitle = `Chapter ${chapterNumber}`, | |
sidebarChapterLinkTemplate = mobileView ? "#..." : "#temp-chapter-list-item", // todo: work on mobile view | |
chapterEditorTemplate = mobileView ? "#temp-sm-chapter-editor" : "#temp-lg-chapter-editor"; // todo: work on mobile view | |
// if (mobileView) { | |
// chapterTitleCount = document.querySelectorAll(".accordion-item").length; | |
// } | |
let chapterData = {ID: chapterNumber, TITLE: chapterTitle} | |
// clone the template and String.replace "{ID}" with the {chapterID} | |
let sidebarChapterLink = document.querySelector(sidebarChapterLinkTemplate).innerHTML | |
.replaceTemplateVars(chapterData); | |
// clone the template and String.replace "{ID}" with the {chapterID} | |
let chapterEditor = document.querySelector(chapterEditorTemplate).innerHTML | |
.replaceTemplateVars(chapterData); | |
// append the sidebar chapter link to the sidebar | |
chapterList.insertAdjacentHTML("beforeend", sidebarChapterLink); | |
// append the chapter editor to the editor section | |
editorSection.insertAdjacentHTML("beforeend", chapterEditor); | |
// create a new quill editor in window | |
if(window[`QuillEditors`] === undefined) { | |
window[`QuillEditors`] = {}; | |
} | |
window[`QuillEditors`][chapterNumber] = new Quill(`#chapter-editor-${chapterNumber} .editor`, quillConfig); | |
// if first chapter, select it | |
if (chapterNumber === 1) { | |
selectChapter({target: document.querySelector(".chapter-title")}); | |
} | |
} | |
function deleteChapter(e) { | |
if (e.target.matches(".del-btn")) { | |
let id = e.target.dataset.id, | |
chapterSidebarLink = document.querySelector(`#chapter-title-${id}`); | |
chapterSidebarLink.remove(); | |
} | |
} | |
function editChapter(e) { | |
if (e.target.matches(".edit-btn")) { | |
let id = e.target.dataset.id, | |
input = document.querySelector(`#chapter-title-${id} input`); | |
input.removeAttribute("disabled"); | |
input.focus(); | |
// on blur, save the chapter title | |
input.addEventListener("blur", saveChapterTitle); | |
} | |
} | |
function saveChapterTitle(e) { | |
e.target.setAttribute("disabled", "disabled"); | |
} | |
function selectChapter(e) { | |
// if (isNaN()) | |
if (e.target.matches(".chapter-title, .chapter-title *:not(.del-btn)")) { | |
// console.log(e.target. | |
// closest element has data-id | |
let sidebarItem = e.target.closest(".chapter-title"); | |
let id = sidebarItem.dataset.id; | |
// if the chapter is already selected, return | |
if (sidebarItem.classList.contains("active")) { | |
return; | |
} | |
// remove the active class from all the chapterList.children | |
chapterList.querySelectorAll(".chapter-title").forEach((el) => { | |
el.classList.remove("active"); | |
}); | |
// add the active class to the clicked chapter | |
sidebarItem.classList.add("active"); | |
// hide all the chapter editors | |
editorSection.querySelectorAll(".editor-container").forEach((el) => { | |
el.classList.remove("active"); | |
}); | |
// show the clicked chapter editor | |
editorSection.querySelector(`#chapter-editor-${id}`).classList.add("active"); | |
} | |
} | |
// trigger the addChapter function | |
addChapter() | |
// on quill editor change, preview the contents | |
for (let chapterId in window[`QuillEditors`]) { | |
let quillEditor = window[`QuillEditors`][chapterId]; | |
quillEditor.on("text-change", (delta, oldDelta, source) => { QuillEditorChange(quillEditor, delta, oldDelta, source) }); | |
} | |
function QuillEditorChange(editor, delta, oldDelta, source) { | |
if (source === "user") { | |
// loop through all the chapters and regenerate the pages array | |
for (let chapterId in window[`QuillEditors`]) { | |
let id = editor.container.closest(".editor-container").dataset.id; | |
// 1. get the chapter title | |
let chapterTitle = document.querySelector(`#chapter-title-${id} input`).value; | |
// 2. get the chapter content | |
let chapterContent = editor.root.innerHTML; | |
// 3. clone chapter preview template | |
let chapterPreview = document.querySelector("#temp-chapter-preview").innerHTML.replaceTemplateVars({ID: id}); | |
// 4. append the chapter div container | |
chapterContainerElement.insertAdjacentHTML("beforeend", chapterPreview); | |
// 5. generate / append pages array | |
let pages = generateChapterPagesArray(id, chapterTitle, chapterContent); | |
chaptersArr.push({id,pages}); | |
} | |
// 6. generate the preview | |
// generatePreview(); | |
} | |
} | |
// generate the pages array | |
function generateChapterPagesArray(id, chapterTitle = null, chapterContent = null, foundOverflow = false) { | |
document.querySelectorAll('.chapterPreviewContainer *').forEach(el => el.remove()); | |
// todo: use generatePreview() to control the pages dom | |
let pagesArray = []; | |
// 1. add the content to virtual DOM to build the pages array by checking if content is overflowing | |
// js_offscreenPreview | |
let offscreenPreview = document.querySelector("#js_offscreenPreview"); | |
let virtualPage = offscreenPreview.querySelector(".preview"); | |
// 3. get the height of the content js_offscreenPreview page and check if content is overflowing | |
let newContent =`<h1 class="cht">${chapterTitle}</h1>${chapterContent}` | |
virtualPage.innerHTML = newContent; | |
// 4. get height of all the elements in the content | |
let elementsHeight = Array.from(virtualPage.children) | |
.map(el => el.getBoundingClientRect().height) | |
.reduce((a,b) => a + b); | |
// 5. get the height of the page | |
let pageHeight = virtualPage.getBoundingClientRect().height; | |
// 6. count the number of pages | |
let pagesCount = Math.ceil(elementsHeight / pageHeight); | |
// 8. create elements stack | |
let elementsStack = Array.from(virtualPage.children); | |
console.log(elementsStack) | |
// 7. create the pages dom | |
for (let i = 0; i < pagesCount; i++) { | |
let pageID = `${id}_${i + 1}`; | |
let page = document.querySelector("#temp-chapter-page").innerHTML.replaceTemplateVars({ID: pageID, PAGE: i + 1}); | |
// pagesArray.push(page); | |
// add the page to the chapter preview | |
let chapterPreview = document.querySelector(`#chapter-preview-${id} .chapterPreviewContainer`); | |
chapterPreview.insertAdjacentHTML("beforeend", page); | |
let currentPage = document.querySelector(`.preview_${pageID}`); | |
let pageBottom = virtualPage.getBoundingClientRect().bottom; | |
let pageFinished = false; | |
let consumedPageHeight = 0; | |
while (!pageFinished && elementsStack.length > 0) { | |
console.log('looping') | |
let el = elementsStack[0]; | |
let elBounding = el.getBoundingClientRect(); | |
let elBottom = elBounding.bottom; | |
let elHeight = elBounding.height; | |
let newConsumedPageHeight = consumedPageHeight + elHeight; | |
// 10. if the element is overflowing, split it and add the overflowing part to the next page | |
if (newConsumedPageHeight > pageHeight) { | |
console.log('overflowing') | |
// 1. calculate the first overflowing space | |
let characterWidth = getCharacterWidth(el); | |
let firstOverflowingSpaceCalculation = Math.floor((pageHeight - consumedPageHeight) / characterWidth); | |
let firstOverflowingSpace = el.textContent.lastIndexOf(" ", firstOverflowingSpaceCalculation); | |
let firstPart = el.textContent.substring(0, firstOverflowingSpace); | |
let secondPart = el.textContent.substring(firstOverflowingSpace + 1); | |
// split the overflowing element | |
let firstPartEl = document.createElement(el.tagName); | |
firstPartEl.innerHTML = firstPart; | |
let secondPartEl = document.createElement(el.tagName); | |
secondPartEl.innerHTML = secondPart; | |
// secondPartEl.classList.add("overflowing"); | |
// secondPartEl.classList.add(`oHidden`); | |
// add the first part to the page | |
currentPage.appendChild(firstPartEl); | |
// add the second part to the dom to measure it | |
// document.querySelectorAll(".overflowing").forEach(el => el.remove()); | |
// currentPage.appendChild(secondPartEl); | |
// secondPartEl = document.querySelector(`.preview_${pageID} .overflowing`) | |
// add the second part to the stack to be added to the next page | |
// currentPage.appendChild(secondPartEl); | |
// secondPartEl = document.querySelector(`.preview_${pageID} .overflowing`) | |
elementsStack.shift(); | |
elementsStack.unshift(secondPartEl); | |
// stop the loop | |
pageFinished = true; | |
}else{ | |
console.log(el) | |
// add the element to the page | |
currentPage.appendChild(el); | |
// remove the element from the stack | |
elementsStack.shift(); | |
console.log("no overflow") | |
consumedPageHeight += elHeight; | |
} | |
} | |
} | |
// secondPartEl.remove(); | |
document.querySelectorAll(".overflowing").forEach(el => el.remove()); | |
return pagesArray; | |
} | |
function createHiddenSpan(el) { | |
let textContent = el.textContent; | |
let span = document.createElement("span"); | |
span.style.visibility = "hidden"; | |
span.style.whiteSpace = "pre"; | |
span.style.fontSize = window.getComputedStyle(el).fontSize; | |
span.style.fontFamily = window.getComputedStyle(el).fontFamily; | |
document.body.appendChild(span); | |
span.textContent = textContent; | |
document.body.removeChild(span); | |
return span; | |
} | |
function getCharacterWidth(el) { | |
let textContent = el.textContent; | |
let span = createHiddenSpan(el); | |
let spanWidth = span.offsetWidth; | |
return spanWidth / textContent.length; | |
} | |
function generatePreview() { | |
// 1. clear the preview | |
chapterContainerElement.innerHTML = ""; | |
// 2. loop through the chapters array | |
chaptersArr.forEach((chapter) => { | |
// temp-chapter-preview | |
// 3. clone the chapter template | |
let chapterTemplate = document.querySelector("#temp-chapter-preview").innerHTML; | |
let chapterPreview = chapterTemplate.replaceTemplateVars({ ID: chapter.id }); | |
// 4. append the chapter div container | |
chapterContainerElement.insertAdjacentHTML("beforeend", chapterPreview); | |
// 5. append the chapter content | |
let chapterContent = document.querySelector(`#chapter-preview-${chapter.id} .chapterPreviewContainer`); | |
chapter.pages.forEach((page) => { | |
let pageId = chapter.id + "_" + chapter.pages.indexOf(page); | |
// 6. clone the page template | |
let pageTemplate = document.querySelector("#temp-chapter-page").innerHTML; | |
let pagePreview = pageTemplate.replaceTemplateVars({ ID: pageId }); | |
// 7. append the page div container | |
chapterContent.insertAdjacentHTML("beforeend", pagePreview); | |
// 8. append the page content | |
let pageContent = document.querySelector(`.preview_${pageId}`); | |
console.log(page); | |
// pageContent.append(...page); | |
}); | |
}); | |
chaptersArr = []; | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment