Skip to content

Instantly share code, notes, and snippets.

@egyjs
Created February 10, 2023 09:10
Show Gist options
  • Save egyjs/7b3c7814f93ad82ee0f099f1106b928e to your computer and use it in GitHub Desktop.
Save egyjs/7b3c7814f93ad82ee0f099f1106b928e to your computer and use it in GitHub Desktop.
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