parent
426fe44dc8
commit
f4635d794f
3 changed files with 36 additions and 458 deletions
@ -1,82 +0,0 @@ |
|||||||
--- |
|
||||||
import Icon from '../AstroIcon.astro'; |
|
||||||
import Loader from '../Loader.astro'; |
|
||||||
|
|
||||||
export interface Props { |
|
||||||
contentContributionLink: string; |
|
||||||
} |
|
||||||
|
|
||||||
const { contentContributionLink } = Astro.props; |
|
||||||
--- |
|
||||||
|
|
||||||
<div id='topic-overlay' class='hidden'> |
|
||||||
<div |
|
||||||
class='fixed right-0 top-0 z-40 h-screen w-full overflow-y-auto bg-white p-4 sm:max-w-[600px] sm:p-6' |
|
||||||
tabindex='-1' |
|
||||||
id='topic-body' |
|
||||||
> |
|
||||||
<div id='topic-loader' class='hidden'> |
|
||||||
<Loader /> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div id='topic-actions' class='mb-2 hidden'> |
|
||||||
<div data-guest-required class='hidden'> |
|
||||||
<button |
|
||||||
data-popup='login-popup' |
|
||||||
class='inline-flex items-center rounded-md bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700' |
|
||||||
> |
|
||||||
<Icon icon='check' /> |
|
||||||
<span class='ml-2'>Done</span> |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div data-auth-required> |
|
||||||
<button |
|
||||||
id='mark-topic-done' |
|
||||||
class='inline-flex hidden items-center rounded-md bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700' |
|
||||||
> |
|
||||||
<Icon icon='check' /> |
|
||||||
<span class='ml-2'>Done</span> |
|
||||||
</button> |
|
||||||
|
|
||||||
<button |
|
||||||
id='mark-topic-pending' |
|
||||||
class='inline-flex hidden items-center rounded-md bg-red-600 p-1 px-2 text-sm text-white hover:bg-red-700' |
|
||||||
> |
|
||||||
<Icon icon='reset' /> |
|
||||||
<span class='ml-2'>Pending</span> |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
|
|
||||||
<button |
|
||||||
type='button' |
|
||||||
id='close-topic' |
|
||||||
class='absolute right-2.5 top-2.5 inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900' |
|
||||||
> |
|
||||||
<Icon icon='close' /> |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div |
|
||||||
id='topic-content' |
|
||||||
class='prose prose-quoteless prose-h1:mb-2.5 prose-h1:mt-7 prose-h2:mb-3 prose-h2:mt-0 prose-h3:mb-[5px] prose-h3:mt-[10px] prose-p:mb-2 prose-p:mt-0 prose-blockquote:font-normal prose-blockquote:not-italic prose-blockquote:text-gray-700 prose-li:m-0 prose-li:mb-0.5' |
|
||||||
> |
|
||||||
</div> |
|
||||||
|
|
||||||
<p |
|
||||||
id='contrib-meta' |
|
||||||
class='mt-10 hidden border-t pt-3 text-sm text-gray-400' |
|
||||||
> |
|
||||||
We are still working on this page. You can contribute by submitting a |
|
||||||
brief description and a few links to learn more about this topic <a |
|
||||||
target='_blank' |
|
||||||
class='text-blue-700 underline' |
|
||||||
href={contentContributionLink}>on GitHub repository.</a |
|
||||||
>. |
|
||||||
</p> |
|
||||||
</div> |
|
||||||
<div class='fixed inset-0 z-30 bg-gray-900 bg-opacity-50 dark:bg-opacity-80'> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<script src='./topic.js'></script> |
|
@ -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(); |
|
Loading…
Reference in new issue