233 lines
6.4 KiB
JavaScript
233 lines
6.4 KiB
JavaScript
export class Topic {
|
|
constructor() {
|
|
this.overlayId = 'topic-overlay';
|
|
this.contentId = 'topic-content';
|
|
this.loaderId = 'topic-loader';
|
|
this.topicBodyId = 'topic-body';
|
|
this.topicActionsId = 'topic-actions';
|
|
this.markTopicDoneId = 'mark-topic-done';
|
|
this.markTopicPendingId = 'mark-topic-pending';
|
|
this.closeTopicId = 'close-topic';
|
|
this.contributionTextId = 'contrib-meta';
|
|
|
|
this.activeRoadmapId = null;
|
|
this.activeTopicId = null;
|
|
|
|
this.handleTopicClick = this.handleTopicClick.bind(this);
|
|
|
|
this.close = this.close.bind(this);
|
|
this.resetDOM = this.resetDOM.bind(this);
|
|
this.populate = this.populate.bind(this);
|
|
this.handleOverlayClick = this.handleOverlayClick.bind(this);
|
|
this.markAsDone = this.markAsDone.bind(this);
|
|
this.markAsPending = this.markAsPending.bind(this);
|
|
this.queryRoadmapElementsByTopicId = this.queryRoadmapElementsByTopicId.bind(this);
|
|
this.rightClickListener = this.rightClickListener.bind(this);
|
|
this.isTopicDone = this.isTopicDone.bind(this);
|
|
|
|
this.init = this.init.bind(this);
|
|
}
|
|
|
|
get loaderEl() {
|
|
return document.getElementById(this.loaderId);
|
|
}
|
|
|
|
get markTopicDoneEl() {
|
|
return document.getElementById(this.markTopicDoneId);
|
|
}
|
|
|
|
get markTopicPendingEl() {
|
|
return document.getElementById(this.markTopicPendingId);
|
|
}
|
|
|
|
get topicActionsEl() {
|
|
return document.getElementById(this.topicActionsId);
|
|
}
|
|
|
|
get contributionTextEl() {
|
|
return document.getElementById(this.contributionTextId);
|
|
}
|
|
|
|
get contentEl() {
|
|
return document.getElementById(this.contentId);
|
|
}
|
|
|
|
get overlayEl() {
|
|
return document.getElementById(this.overlayId);
|
|
}
|
|
|
|
rightClickListener(e) {
|
|
const groupId = e.target?.closest('g')?.dataset?.groupId;
|
|
if (!groupId) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
if (this.isTopicDone(groupId)) {
|
|
this.markAsPending(groupId);
|
|
} else {
|
|
this.markAsDone(groupId);
|
|
}
|
|
}
|
|
|
|
resetDOM(hideOverlay = false) {
|
|
if (hideOverlay) {
|
|
this.overlayEl.classList.add('hidden');
|
|
} else {
|
|
this.overlayEl.classList.remove('hidden');
|
|
}
|
|
|
|
this.loaderEl.classList.remove('hidden'); // Show loader
|
|
this.topicActionsEl.classList.add('hidden'); // Hide Actions
|
|
this.contributionTextEl.classList.add('hidden'); // Hide contribution text
|
|
this.contentEl.replaceChildren(''); // Remove content
|
|
}
|
|
|
|
close() {
|
|
this.resetDOM(true);
|
|
|
|
this.activeRoadmapId = null;
|
|
this.activeTopicId = null;
|
|
}
|
|
|
|
isTopicDone(topicId) {
|
|
const normalizedGroup = topicId.replace(/^\d+-/, '');
|
|
return localStorage.getItem(normalizedGroup) === 'done';
|
|
}
|
|
|
|
/**
|
|
* @param {string | HTMLElement} html
|
|
*/
|
|
populate(html) {
|
|
this.contentEl.replaceChildren(html);
|
|
this.loaderEl.classList.add('hidden');
|
|
this.topicActionsEl.classList.remove('hidden');
|
|
this.contributionTextEl.classList.remove('hidden');
|
|
|
|
const isDone = this.isTopicDone(this.activeTopicId);
|
|
|
|
if (isDone) {
|
|
this.markTopicDoneEl.classList.add('hidden');
|
|
this.markTopicPendingEl.classList.remove('hidden');
|
|
} else {
|
|
this.markTopicDoneEl.classList.remove('hidden');
|
|
this.markTopicPendingEl.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
fetchTopicHtml(roadmapId, topicId) {
|
|
const topicPartial = topicId.replace(/^\d+-/, '').replaceAll(/:/g, '/');
|
|
const fullUrl = `/${roadmapId}/${topicPartial}`;
|
|
|
|
return fetch(fullUrl)
|
|
.then((res) => {
|
|
return res.text();
|
|
})
|
|
.then((topicHtml) => {
|
|
// It's full HTML with page body, head etc.
|
|
// We only need the inner HTML of the #main-content
|
|
const node = new DOMParser().parseFromString(topicHtml, 'text/html');
|
|
|
|
return node.getElementById('main-content');
|
|
});
|
|
}
|
|
|
|
handleTopicClick(e) {
|
|
const { roadmapId, topicId } = e.detail;
|
|
if (!topicId || !roadmapId) {
|
|
console.log('Missing topic or roadmap: ', e.detail);
|
|
return;
|
|
}
|
|
|
|
this.activeRoadmapId = roadmapId;
|
|
this.activeTopicId = topicId;
|
|
|
|
if (/^ext_link/.test(topicId)) {
|
|
window.open(`https://${topicId.replace('ext_link:', '')}`);
|
|
return;
|
|
}
|
|
|
|
this.resetDOM();
|
|
this.fetchTopicHtml(roadmapId, topicId)
|
|
.then((content) => {
|
|
this.populate(content);
|
|
})
|
|
.catch((e) => {
|
|
console.error(e);
|
|
this.populate('Error loading the content!');
|
|
});
|
|
}
|
|
|
|
queryRoadmapElementsByTopicId(topicId) {
|
|
const elements = document.querySelectorAll(`[data-group-id$="-${topicId}"]`);
|
|
const matchingElements = [];
|
|
|
|
elements.forEach((element) => {
|
|
const foundGroupId = element?.dataset?.groupId || '';
|
|
const validGroupRegex = new RegExp(`^\\d+-${topicId}$`);
|
|
|
|
if (validGroupRegex.test(foundGroupId)) {
|
|
matchingElements.push(element);
|
|
}
|
|
});
|
|
|
|
return matchingElements;
|
|
}
|
|
|
|
markAsDone(topicId) {
|
|
const updatedTopicId = topicId.replace(/^\d+-/, '');
|
|
localStorage.setItem(updatedTopicId, 'done');
|
|
|
|
this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => {
|
|
item?.classList?.add('done');
|
|
});
|
|
}
|
|
|
|
markAsPending(topicId) {
|
|
const updatedTopicId = topicId.replace(/^\d+-/, '');
|
|
|
|
localStorage.removeItem(updatedTopicId);
|
|
this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => {
|
|
item?.classList?.remove('done');
|
|
});
|
|
}
|
|
|
|
handleOverlayClick(e) {
|
|
const isClickedInsideTopic = e.target.closest(`#${this.topicBodyId}`);
|
|
|
|
if (!isClickedInsideTopic) {
|
|
this.close();
|
|
return;
|
|
}
|
|
|
|
const isClickedDone = e.target.id === this.markTopicDoneId || e.target.closest(`#${this.markTopicDoneId}`);
|
|
if (isClickedDone) {
|
|
this.markAsDone(this.activeTopicId);
|
|
this.close();
|
|
}
|
|
|
|
const isClickedPending = e.target.id === this.markTopicPendingId || e.target.closest(`#${this.markTopicPendingId}`);
|
|
if (isClickedPending) {
|
|
this.markAsPending(this.activeTopicId);
|
|
this.close();
|
|
}
|
|
|
|
const isClickedClose = e.target.id === this.closeTopicId || e.target.closest(`#${this.closeTopicId}`);
|
|
if (isClickedClose) {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
init() {
|
|
window.addEventListener('topic.click', this.handleTopicClick);
|
|
window.addEventListener('click', this.handleOverlayClick);
|
|
window.addEventListener('contextmenu', this.rightClickListener);
|
|
|
|
window.addEventListener('keydown', (e) => {
|
|
if (e.key.toLowerCase() === 'escape') {
|
|
this.close();
|
|
}
|
|
});
|
|
}
|
|
}
|