/** * @file Comments control * @author Vera Konigin vera@groundedwren.com * https://groundedwren.neocities.org */ window.GW = window.GW || {}; (function Controls(ns) { ns.CommentForm = class CommentForm extends HTMLElement { //#region staticProperties static instanceCount = 0; static instanceMap = {}; //#endregion //#region instance properties instanceId; isInitialized; titleText; discordURL; encodedPath; fallbackEmail; //#region element properties formEl; titleEl; bannerEl; dispNameInpt; emailInpt; websiteInpt; respToInpt; commentInpt; resetBtn; submitBtn; //#endregion //#endregion constructor() { super(); this.instanceId = CommentForm.instanceCount++; CommentForm.instanceMap[this.instanceId] = this; } get idKey() { return `gw-comment-form-${this.instanceId}`; } connectedCallback() { if (this.isInitialized) { return; } this.titleText = this.getAttribute("titleText") || "Add a Comment"; this.discordURL = this.getAttribute("discordURL"); this.encodedPath = this.getAttribute("encodedPath"); this.fallbackEmail = this.getAttribute("fallbackEmail"); this.renderContent(); this.registerHandlers(); this.isInitialized = true; } renderContent() { //Markup this.innerHTML = `

${this.titleText}

Comments are manually approved
`; //element properties this.formEl = document.getElementById(`${this.idKey}-form`); this.titleEl = document.getElementById(`${this.idKey}-title`); this.bannerEl = document.getElementById(`${this.idKey}-banner`); this.dispNameInpt = document.getElementById(`${this.idKey}-dispName`); this.emailInpt = document.getElementById(`${this.idKey}-email`); this.websiteInpt = document.getElementById(`${this.idKey}-website`); this.respToInpt = document.getElementById(`${this.idKey}-respTo`); this.commentInpt = document.getElementById(`${this.idKey}-comment`); this.resetBtn = document.getElementById(`${this.idKey}-reset`); this.submitBtn = document.getElementById(`${this.idKey}-submit`); //default values this.dispNameInpt.value = localStorage.getItem("comment-name") || ""; this.emailInpt.value = localStorage.getItem("comment-email") || ""; this.websiteInpt.value = localStorage.getItem("comment-website") || ""; } //#region Handlers registerHandlers() { this.formEl.onsubmit = this.onSubmit; } onSubmit = (event) => { event.preventDefault(); const contentObj = { name: this.dispNameInpt.value, email: this.emailInpt.value, website: this.websiteInpt.value, responseTo: this.respToInpt.value, comment: ( this.commentInpt.value || "" ).replaceAll("\n", "
").replaceAll("(", "\\("), timestamp: new Date().toUTCString(), }; const contentAry = []; for (let contentKey in contentObj) { contentAry.push(`${contentKey}=${contentObj[contentKey]}`); } const request = new XMLHttpRequest(); request.open( "POST", this.discordURL || atob("aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3Mv" + this.encodedPath), true ); request.setRequestHeader("Content-Type", "application/json"); request.onreadystatechange = () => { if (request.readyState !== XMLHttpRequest.DONE) { return; } if (Math.floor(request.status / 100) !== 2) { console.log(request.responseText); this.bannerEl.classList.add("warning"); this.bannerEl.innerHTML = ` That didn't work. ${this.fallbackEmail ? `Click here to send as an email instead.` : "" } `; } else { alert("Your comment has been submitted!"); } }; request.send(JSON.stringify({ embeds: [{ fields: Object.keys(contentObj).map(key => { return { name: key, value: contentObj[key] }}) }] })); localStorage.setItem("comment-name", contentObj.name); localStorage.setItem("comment-email", contentObj.email); localStorage.setItem("comment-website", contentObj.website); this.formEl.reset(); this.dispNameInpt.value = contentObj.name; this.emailInpt.value = contentObj.email; this.websiteInpt.value = contentObj.website; }; //#endregion }; customElements.define("gw-comment-form", ns.CommentForm); ns.CommentList = class CommentList extends HTMLElement { //#region staticProperties static instanceCount = 0; static instanceMap = {}; static Data = []; //#endregion //#region instance properties instanceId; isInitialized; gSpreadsheetId; gSheetId; isNewestFirst; gwCommentFormId; //#region element properties //#endregion //#endregion constructor() { super(); this.instanceId = CommentList.instanceCount++; CommentList.instanceMap[this.instanceId] = this; CommentList.Data[this.instanceId] = {}; } get idKey() { return `gw-comment-list-${this.instanceId}`; } connectedCallback() { if (this.isInitialized) { return; } this.gSpreadsheetId = this.getAttribute("gSpreadsheetId"); this.gSheetId = this.getAttribute("gSheetId"); this.isNewestFirst = this.getAttribute("isNewestFirst"); this.gwCommentFormId = this.getAttribute("gwCommentFormId"); this.loadAndRender(); this.isInitialized = true; } async loadAndRender() { this.innerHTML = `
Comments loading....
` const sheetReader = new GW.Gizmos.GoogleSheetsReader(this.gSpreadsheetId, this.gSheetId); await sheetReader.loadData(); this.innerHTML = ""; const allComments = sheetReader.rowData; if (this.isNewestFirst) { allComments.reverse(); } this.renderContent(); this.registerHandlers(); const allCommentsIndex = {}; const topLevelCommentIdxs = []; const childCommentIdxs = []; for (let i = 0; i < allComments.length; i++) { const comment = allComments[i]; allCommentsIndex[comment.ID] = i; if (!comment.ResponseTo) { topLevelCommentIdxs.push(i); } else { childCommentIdxs.push(i); } } childCommentIdxs.forEach(childIdx => { const replyId = allComments[childIdx].ResponseTo; const respondeeComment = allComments[allCommentsIndex[replyId]]; respondeeComment.ChildIdxs = respondeeComment.ChildIdxs || []; respondeeComment.ChildIdxs.push(childIdx); }); let commentsToBuild = []; topLevelCommentIdxs.forEach( topCommentIdx => commentsToBuild.push({ parent: this.containerEl, comment: allComments[topCommentIdx] }) ); while (commentsToBuild.length > 0) { let { parent, comment } = commentsToBuild.shift(); if (!comment.Timestamp) { continue; } CommentList.Data[this.instanceId][comment.ID] = comment; parent.insertAdjacentHTML("beforeend", ` `); const commentEl = document.getElementById(`${this.idKey}-cmt-${comment.ID}`); (comment.ChildIdxs || []).forEach( childIdx => commentsToBuild.push({ parent: commentEl.articleEl, comment: allComments[childIdx] }) ); } } renderContent() { //Markup this.innerHTML = `
`; //element properties this.containerEl = document.getElementById(`${this.idKey}-container`); } //#region Handlers registerHandlers() { } //#endregion }; customElements.define("gw-comment-list", ns.CommentList); ns.CommentCard = class CommentCard extends HTMLElement { //#region staticProperties static instanceCount = 0; static instanceMap = {}; //#endregion //#region instance properties instanceId; isInitialized; commentId; gwCommentFormId; replyToId; numChildren; commenterName; datetime; websiteURL; commentText; //#region element properties articleEl; replyBtn; //#endregion //#endregion constructor() { super(); this.instanceId = CommentCard.instanceCount++; CommentCard.instanceMap[this.instanceId] = this; } get idKey() { return `gw-comment-card-${this.instanceId}`; } //#region HTMLElement implementation connectedCallback() { if (this.isInitialized) { return; } this.commentId = this.getAttribute("commentId"); this.gwCommentFormId = this.getAttribute("gwCommentFormId"); const commentData = ns.CommentList.Data[this.getAttribute("listInstance")][this.commentId]; this.replyToId = commentData.ResponseTo; this.numChildren = (commentData.ChildIdxs || []).length; this.commenterName = commentData["Display Name"]; this.datetime = commentData.Timestamp; this.websiteURL = commentData.Website; this.commentText = this.parseCommentText(commentData.Comment); this.renderContent(); this.registerHandlers(); this.isInitialized = true; } //#endregion renderContent() { let headerText = this.replyToId ? `Comment #${this.commentId} replying to #${this.replyToId}` : `Top level comment #${this.commentId}`; headerText += ` with ${this.numChildren} direct ${this.numChildren == 1 ? "reply" : "replies"}`; const displayTimestamp = this.datetime.toLocaleString( undefined, { dateStyle: "short", timeStyle: "short" } ); const commenterNameEl = this.websiteURL ? `${this.commenterName}` : `${this.commenterName}`; //Markup this.innerHTML = `
${commenterNameEl}
${this.commentText}
`; //element properties this.articleEl = document.getElementById(`${this.idKey}-article`); this.timestamp = document.getElementById(`${this.idKey}-timestamp`); this.replyBtn = document.getElementById(`${this.idKey}-reply`); this.hideBtn = document.getElementById(`${this.idKey}-hide`); this.showBtn = document.getElementById(`${this.idKey}-show`); } //#region Handlers registerHandlers() { this.replyBtn.onclick = this.onReply; this.hideBtn.onclick = this.onHide; this.showBtn.onclick = this.onShow; } onReply = () => { const gwCommentForm = document.getElementById(this.gwCommentFormId); const respToInpt = gwCommentForm.respToInpt; if (!respToInpt) { alert("Comment form not found"); return; } respToInpt.value = this.commentId; respToInpt.focus(); }; onHide = () => { this.classList.add("collapsed"); this.showBtn.focus(); }; onShow = () => { this.classList.remove("collapsed"); this.timestamp.focus(); }; //#endregion parseCommentText(commentString) { let commentText = ""; let linkObj = {}; for(let i = 0; i < commentString.length; i++){ let char = commentString.charAt(i); switch (char) { case '[': linkObj = {tStart: i}; break; case ']': if(linkObj.tStart !== undefined && linkObj.tStart !== i-1) { linkObj.tEnd = i; } else { linkObj = {}; } break; case '(': if(linkObj.tEnd !== undefined && linkObj.tEnd === i-1) { linkObj.lStart = i; } else { linkObj = {}; } break; case ')': if(linkObj.lStart !== undefined && linkObj.lStart !== i-1) { linkObj.lEnd = i; } else { linkObj = {}; } break; } if(linkObj.lEnd !== undefined) { const linkText = commentString.substring(linkObj.tStart + 1, linkObj.tEnd); const linkURL = commentString.substring(linkObj.lStart + 1, linkObj.lEnd); commentText = commentText.substring(0, commentText.length - (i - linkObj.tStart)); commentText += `${linkText}`; linkObj = {}; } else { commentText += char; } } return commentText; } }; customElements.define("gw-comment-card", ns.CommentCard); }) (window.GW.Controls = window.GW.Controls || {});