/**
* Author: Vera Konigin
* Site: https://groundedwren.neocities.org
* Contact: vera@groundedwren.com
*
* File Description: Comments Control
*/
/**
* By default, any JavaScript code written is defined in the global namespace, which means it's accessible directly under the "window" element.
* If you have a lot of scripts, this can lead to clutter and naming collisions (what if two different scripts use a variable called "i"? They can inadvertently mess each other up).
* To get around this, we define the registerNamespace function in the global namespace, which just confines all the code in the function passed to it to a property under window.
* That property is represented as the "path" parameter. It is passed to the function for ease of access.
*/
function registerNamespace(path, nsFunc)
{
var ancestors = path.split(".");
var ns = window;
for(var i = 0; i < ancestors.length; i++)
{
ns[ancestors[i]] = ns[ancestors[i]] || {};
ns = ns[ancestors[i]];
}
nsFunc(ns);
}
registerNamespace("GW.Controls", function (ns)
{
ns.CommentForm = class CommentForm extends HTMLElement
{
//#region staticProperties
static observedAttributes = [];
static instanceCount = 0;
static instanceMap = {};
//#endregion
//#region instance properties
instanceId;
isInitialized;
titleText;
discordURL;
//#region element properties
formEl;
titleEl;
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}`;
}
//#region HTMLElement implementation
connectedCallback()
{
if (this.isInitialized) { return; }
this.titleText = this.getAttribute("titleText") || "Add a Comment";
this.discordURL = this.getAttribute("discordURL")
this.renderContent();
this.registerHandlers();
this.isInitialized = true;
}
//#endregion
renderContent()
{
//Markup
this.innerHTML = `
`;
//element properties
this.formEl = document.getElementById(`${this.idKey}-form`);
this.titleEl = document.getElementById(`${this.idKey}-title`);
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,
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,
true
);
request.setRequestHeader("Content-Type", "application/json");
request.onreadystatechange = function ()
{
if (request.readyState == 4)
{
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({ content: contentAry.join("; ") }));
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 observedAttributes = [];
static instanceCount = 0;
static instanceMap = {};
//#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;
}
get idKey()
{
return `gw-comment-list-${this.instanceId}`;
}
//#region HTMLElement implementation
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;
}
//#endregion
async loadAndRender()
{
this.innerHTML = `
Comments loading....
`
const sheetReader = new GW.Gizmos.GoogleSheetsReader(this.gSpreadsheetId, this.gSheetId);
const sheetData = 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.childrenIdxs = respondeeComment.childrenIdxs || [];
respondeeComment.childrenIdxs.push(childIdx);
});
let commentsToBuild = [];
topLevelCommentIdxs.forEach(
topCommentIdx => commentsToBuild.push(
{
parent: this.containerEl,
parentId: null,
comment: allComments[topCommentIdx]
}
)
);
while (commentsToBuild.length > 0)
{
let { parent, parentId, comment } = commentsToBuild.shift();
if (!comment.Timestamp)
{
continue;
}
parent.insertAdjacentHTML("beforeend", `
`);
const commentEl = document.getElementById(`${this.idKey}-cmt-${comment.ID}`);
(comment.childrenIdxs || []).forEach(
childIdx => commentsToBuild.push({
parent: commentEl.articleEl,
parentId: comment.ID,
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 observedAttributes = [];
static instanceCount = 0;
static instanceMap = {};
//#endregion
//#region instance properties
instanceId;
isInitialized;
commentId;
replyToId;
numChildren;
commenterName;
isoTimestamp;
datetime;
websiteURL;
commentText;
gwCommentFormId;
//#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.replyToId = this.getAttribute("replyToId");
this.numChildren = this.getAttribute("numChildren");
this.commenterName = this.getAttribute("commenterName");
this.isoTimestamp = this.getAttribute("isoTimestamp");
this.datetime = new Date(this.isoTimestamp);
this.websiteURL = this.getAttribute("websiteURL");
this.commentText = this.getAttribute("commentText");
this.gwCommentFormId = this.getAttribute("gwCommentFormId");
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
? ``
: ``;
//Markup
this.innerHTML = `
${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
};
customElements.define("gw-comment-card", ns.CommentCard);
});