Roadmap to becoming a developer in 2022
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

232 lines
6.4 KiB

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();
}
});
}
}