diff --git a/src/components/TopicDetail/TopicDetail.tsx b/src/components/TopicDetail/TopicDetail.tsx index da1f8b0ed..287db0823 100644 --- a/src/components/TopicDetail/TopicDetail.tsx +++ b/src/components/TopicDetail/TopicDetail.tsx @@ -187,9 +187,14 @@ export function TopicDetail() { return null; } - const contributionDir = resourceType === 'roadmap' ? 'roadmaps' : 'best-practices'; + const contributionDir = + resourceType === 'roadmap' ? 'roadmaps' : 'best-practices'; const contributionUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/${contributionDir}/${resourceId}/content`; + const allowMarkingDone = ['pending', 'learning'].includes(progress); + const allowMarkingLearning = ['pending'].includes(progress); + const allowMarkingPending = ['done', 'learning'].includes(progress); + return (
setIsActive(false)} > Check - Done + Mark as Done )} - {!isUpdatingProgress && progress === 'pending' && ( -
- - -
+ {!isUpdatingProgress && allowMarkingDone && ( + )} - {!isUpdatingProgress && progress === 'done' && ( + {!isUpdatingProgress && allowMarkingLearning && ( + + )} + + {!isUpdatingProgress && allowMarkingPending && ( )} - - {!isUpdatingProgress && progress === 'learning' && ( -
- - -
- )} - +
)} -
- -
- - - -
- - - - -
-
- - - -
-
- - - diff --git a/src/components/TopicOverlay/topic.js b/src/components/TopicOverlay/topic.js deleted file mode 100644 index 680b55054..000000000 --- a/src/components/TopicOverlay/topic.js +++ /dev/null @@ -1,331 +0,0 @@ -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.activeResourceType = null; - this.activeResourceId = null; - this.activeTopicId = null; - - this.handleRoadmapTopicClick = this.handleRoadmapTopicClick.bind(this); - this.handleBestPracticeTopicClick = - this.handleBestPracticeTopicClick.bind(this); - this.handleBestPracticeTopicToggle = - this.handleBestPracticeTopicToggle.bind(this); - this.handleBestPracticeTopicPending = - this.handleBestPracticeTopicPending.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.querySvgElementsByTopicId = this.querySvgElementsByTopicId.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); - } - - 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.activeResourceId = null; - this.activeTopicId = null; - } - - isTopicDone(topicId) { - const normalizedGroup = topicId.replace(/^\d+-/, ''); - const el = document.querySelector(`[data-group-id$="-${normalizedGroup}"]`); - return el?.classList.contains('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'); - } - } - - renderTopicFromUrl(url) { - return fetch(url) - .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'); - }) - .then((content) => { - this.populate(content); - }) - .catch((e) => { - console.error(e); - this.populate('Error loading the content!'); - }); - } - - handleBestPracticeTopicToggle(e) { - const { resourceId: bestPracticeId, topicId } = e.detail; - if (!topicId || !bestPracticeId) { - console.log('Missing topic or bestPracticeId: ', e.detail); - return; - } - - const isDone = localStorage.getItem(topicId) === 'done'; - if (isDone) { - this.markAsPending(topicId, bestPracticeId, 'best-practice'); - } else { - this.markAsDone(topicId, bestPracticeId, 'best-practice'); - } - } - - handleBestPracticeTopicPending(e) { - const { resourceId: bestPracticeId, topicId } = e.detail; - if (!topicId || !bestPracticeId) { - console.log('Missing topic or bestPracticeId: ', e.detail); - return; - } - - this.markAsPending(topicId, bestPracticeId, 'best-practice'); - } - - handleBestPracticeTopicClick(e) { - const { resourceId: bestPracticeId, topicId } = e.detail; - if (!topicId || !bestPracticeId) { - console.log('Missing topic or bestPracticeId: ', e.detail); - return; - } - - this.activeResourceType = 'best-practice'; - this.activeResourceId = bestPracticeId; - this.activeTopicId = topicId; - - this.resetDOM(); - - const topicUrl = `/best-practices/${bestPracticeId}/${topicId.replaceAll( - ':', - '/' - )}`; - - this.renderTopicFromUrl(topicUrl).then(() => null); - } - - handleRoadmapTopicClick(e) { - const { resourceId: roadmapId, topicId } = e.detail; - if (!topicId || !roadmapId) { - console.log('Missing topic or roadmap: ', e.detail); - return; - } - - this.activeResourceType = 'roadmap'; - this.activeResourceId = roadmapId; - this.activeTopicId = topicId; - - this.resetDOM(); - const topicUrl = `/${roadmapId}/${topicId.replaceAll(':', '/')}`; - - window.fireEvent({ - category: `RoadmapClick`, - action: `${roadmapId}/load-topic`, - label: topicUrl, - }); - - this.renderTopicFromUrl(topicUrl).then(() => null); - } - - querySvgElementsByTopicId(topicId) { - const matchingElements = []; - - // Elements having sort order in the beginning of the group id - document - .querySelectorAll(`[data-group-id$="-${topicId}"]`) - .forEach((element) => { - const foundGroupId = element?.dataset?.groupId || ''; - const validGroupRegex = new RegExp(`^\\d+-${topicId}$`); - - if (validGroupRegex.test(foundGroupId)) { - matchingElements.push(element); - } - }); - - // Elements with exact match of the topic id - document - .querySelectorAll(`[data-group-id="${topicId}"]`) - .forEach((element) => { - matchingElements.push(element); - }); - - // Matching "check:XXXX" box of the topic - document - .querySelectorAll(`[data-group-id="check:${topicId}"]`) - .forEach((element) => { - matchingElements.push(element); - }); - - return matchingElements; - } - - async markAsDone(topicId, resourceId, resourceType) { - const updatedTopicId = topicId.replace(/^\d+-/, ''); - - const { response, error } = {}; - - if (response) { - this.close(); - this.querySvgElementsByTopicId(updatedTopicId).forEach((item) => { - item?.classList?.add('done'); - }); - } else { - console.error(error); - } - } - - async markAsPending(topicId, resourceId, resourceType) { - const updatedTopicId = topicId.replace(/^\d+-/, ''); - - const { response, error } = {}; - - if (response) { - this.close(); - this.querySvgElementsByTopicId(updatedTopicId).forEach((item) => { - item?.classList?.remove('done'); - }); - } else { - console.error(error); - } - } - - 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.activeResourceId, - this.activeResourceType - ); - // this.close(); - } - - const isClickedPending = - e.target.id === this.markTopicPendingId || - e.target.closest(`#${this.markTopicPendingId}`); - if (isClickedPending) { - this.markAsPending( - this.activeTopicId, - this.activeResourceId, - this.activeResourceType - ); - // this.close(); - } - - const isClickedPopupOpener = - e.target.dataset['popup'] || e.target.closest('button[data-popup]'); - const isClickedClose = - e.target.id === this.closeTopicId || - e.target.closest(`#${this.closeTopicId}`); - if (isClickedClose || isClickedPopupOpener) { - this.close(); - } - } - - init() { - window.addEventListener( - 'best-practice.topic.click', - this.handleBestPracticeTopicClick - ); - window.addEventListener( - 'best-practice.topic.toggle', - this.handleBestPracticeTopicToggle - ); - - window.addEventListener( - 'roadmap.topic.click', - this.handleRoadmapTopicClick - ); - - window.addEventListener('click', this.handleOverlayClick); - window.addEventListener('keydown', (e) => { - if (e.key.toLowerCase() === 'escape') { - this.close(); - } - }); - } -} - -// Initialize the topic loader -const topic = new Topic(); -topic.init();