diff --git a/public/images/devops-ebook.png b/public/images/devops-ebook.png new file mode 100644 index 000000000..b535b186b Binary files /dev/null and b/public/images/devops-ebook.png differ diff --git a/src/components/Analytics/Analytics.astro b/src/components/Analytics/Analytics.astro index cb8d627b9..e0c068612 100644 --- a/src/components/Analytics/Analytics.astro +++ b/src/components/Analytics/Analytics.astro @@ -2,7 +2,8 @@ --- - + diff --git a/src/components/DownloadPopup.astro b/src/components/DownloadPopup.astro index 3c15b2173..82aa6e90e 100644 --- a/src/components/DownloadPopup.astro +++ b/src/components/DownloadPopup.astro @@ -34,9 +34,21 @@ import CaptchaFields from './Captcha/CaptchaFields.astro'; type='submit' name='submit' class='text-white bg-gradient-to-r from-amber-700 to-blue-800 hover:from-amber-800 hover:to-blue-900 font-regular rounded-md text-md px-5 py-2.5 w-full text-center mr-2' - onclick="window.fireEvent({ category: 'Subscription', action: 'Submitted Popup Form', label: 'Download Roadmap Popup' })" + submit-download-form > Send Link + + diff --git a/src/components/FAQs/Answer.astro b/src/components/FAQs/Answer.astro index 5cce8e3ae..d28a283c4 100644 --- a/src/components/FAQs/Answer.astro +++ b/src/components/FAQs/Answer.astro @@ -1,3 +1,3 @@ -
+
\ No newline at end of file diff --git a/src/components/FAQs/FAQs.astro b/src/components/FAQs/FAQs.astro index 0f007afa5..f7d7447f1 100644 --- a/src/components/FAQs/FAQs.astro +++ b/src/components/FAQs/FAQs.astro @@ -1,7 +1,7 @@
-

+

Frequently Asked Questions

diff --git a/src/components/FAQs/Question.astro b/src/components/FAQs/Question.astro index 4b47a28c4..1ba53c113 100644 --- a/src/components/FAQs/Question.astro +++ b/src/components/FAQs/Question.astro @@ -14,9 +14,9 @@ const { question, isActive = false } = Astro.props; >
diff --git a/src/components/Footer.astro b/src/components/Footer.astro index c23b33e97..9e36eb126 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -84,23 +84,29 @@ import Icon from './Icon.astro';

DevOps · Kubernetes · Cloud-Native

diff --git a/src/components/InteractiveRoadmap/InteractiveRoadmap.astro b/src/components/InteractiveRoadmap/InteractiveRoadmap.astro index 3d0746289..2bb16fef8 100644 --- a/src/components/InteractiveRoadmap/InteractiveRoadmap.astro +++ b/src/components/InteractiveRoadmap/InteractiveRoadmap.astro @@ -1,10 +1,10 @@ --- -import DownloadPopup from "../DownloadPopup.astro"; -import Loader from "../Loader.astro"; -import ShareIcons from "../ShareIcons.astro"; -import SubscribePopup from "../SubscribePopup.astro"; -import TopicOverlay from "../TopicOverlay.astro"; -import "./InteractiveRoadmap.css"; +import DownloadPopup from '../DownloadPopup.astro'; +import Loader from '../Loader.astro'; +import ShareIcons from '../ShareIcons.astro'; +import SubscribePopup from '../SubscribePopup.astro'; +import TopicOverlay from '../TopicOverlay.astro'; +import './InteractiveRoadmap.css'; export interface Props { roadmapId: string; @@ -16,31 +16,32 @@ export interface Props { }; } -const { roadmapId, jsonUrl, dimensions = null, description } = - Astro.props; +const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props; --- -
-
+
+
- +
@@ -49,4 +50,4 @@ const { roadmapId, jsonUrl, dimensions = null, description } =
- + diff --git a/src/components/InteractiveRoadmap/topic.js b/src/components/InteractiveRoadmap/topic.js index 8a481f258..ef6b7e767 100644 --- a/src/components/InteractiveRoadmap/topic.js +++ b/src/components/InteractiveRoadmap/topic.js @@ -1,204 +1,219 @@ 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.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.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 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.contentEl.replaceChildren(''); // Remove content - } - - close() { - this.resetDOM(true); - - this.activeRoadmapId = null; - this.activeTopicId = null; - } - - /** - * @param {string | HTMLElement} html - */ - populate(html) { - this.contentEl.replaceChildren(html); - this.loaderEl.classList.add('hidden'); - this.topicActionsEl.classList.remove('hidden'); - - const normalizedGroup = (this.activeTopicId || '').replace(/^\d+-/, ''); - const isDone = localStorage.getItem(normalizedGroup) === 'done'; - - 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; + 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.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.activeRoadmapId = null; + this.activeTopicId = null; + } + + /** + * @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 normalizedGroup = (this.activeTopicId || '').replace(/^\d+-/, ''); + const isDone = localStorage.getItem(normalizedGroup) === 'done'; + + if (isDone) { + this.markTopicDoneEl.classList.add('hidden'); + this.markTopicPendingEl.classList.remove('hidden'); + } else { + this.markTopicDoneEl.classList.remove('hidden'); + this.markTopicPendingEl.classList.add('hidden'); } - - markAsDone(topicId) { - const updatedTopicId = topicId.replace(/^\d+-/, ''); - localStorage.setItem(updatedTopicId, 'done'); - - this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => { - item?.classList?.add('done'); + } + + 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'); }); - } - - markAsPending(topicId) { - const updatedTopicId = topicId.replace(/^\d+-/, ''); - - localStorage.removeItem(updatedTopicId); - this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => { - item?.classList?.remove('done'); + } + + 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!'); }); - } - - 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(); + } + + 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); } - - const isClickedClose = e.target.id === this.closeTopicId || e.target.closest(`#${this.closeTopicId}`); - if (isClickedClose) { + }); + + 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('keydown', (e) => { + if (e.key.toLowerCase() === 'escape') { this.close(); } - } - - init() { - window.addEventListener('topic.click', this.handleTopicClick); - window.addEventListener('click', this.handleOverlayClick); - window.addEventListener('keydown', (e) => { - if (e.key.toLowerCase() === 'escape') { - this.close(); - } - }); - } + }); } - \ No newline at end of file +} diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro index 1851e6a92..196587125 100644 --- a/src/components/Navigation.astro +++ b/src/components/Navigation.astro @@ -32,24 +32,22 @@ import Icon from './Icon.astro';
+ + diff --git a/src/components/OpenSourceBanner.astro b/src/components/OpenSourceBanner.astro index 4264da2f0..6603f6fc4 100644 --- a/src/components/OpenSourceBanner.astro +++ b/src/components/OpenSourceBanner.astro @@ -7,7 +7,7 @@ const starCount = await getFormattedStars('kamranahmedse/developer-roadmap');
-

Open Source

+

Open Source

The project is OpenSource, )} ) @@ -75,11 +82,11 @@ const isRoadmapReady = !isUpcoming; hasSearch && ( ← -  Visual Roadmap +  Visual Roadmap ) } @@ -89,13 +96,13 @@ const isRoadmapReady = !isUpcoming; isRoadmapReady && ( - - - Suggest + + + Suggest ) } diff --git a/src/components/Sponsor/Sponsor.astro b/src/components/Sponsor/Sponsor.astro index 9905eadd0..4861cc5fe 100644 --- a/src/components/Sponsor/Sponsor.astro +++ b/src/components/Sponsor/Sponsor.astro @@ -1,5 +1,6 @@ --- import type { GAEventType } from '../Analytics/analytics'; +import Icon from '../Icon.astro'; export type SponsorType = { url: string; @@ -25,10 +26,19 @@ const { id='sponsor-ad' target='_blank' rel='noopener sponsored' - onclick={event ? `window.fireEvent(${JSON.stringify(event)})` : ''} + ga-category={event?.category} + ga-action={event?.action} + ga-label={event?.label} class='fixed bottom-[15px] right-[20px] outline-transparent z-50 bg-white max-w-[330px] shadow-lg outline-0 hidden' > - Sponsor Banner + + Sponsor Banner {title} @@ -37,3 +47,11 @@ const { + + diff --git a/src/components/SubscribePopup.astro b/src/components/SubscribePopup.astro index ecc331e0a..220d86937 100644 --- a/src/components/SubscribePopup.astro +++ b/src/components/SubscribePopup.astro @@ -33,7 +33,9 @@ import CaptchaFields from './Captcha/CaptchaFields.astro'; type='submit' name='submit' class='text-white bg-gradient-to-r from-amber-700 to-blue-800 hover:from-amber-800 hover:to-blue-900 font-regular rounded-md text-md px-5 py-2.5 w-full text-center mr-2' - onclick="window.fireEvent({ category: 'Subscription', action: 'Submitted Popup Form', label: 'Subscribe Roadmap Popup' })" + ga-category='Subscription' + ga-action='Submitted Popup Form' + ga-label='Subscribe Roadmap Popup' > Subscribe diff --git a/src/components/TopicOverlay.astro b/src/components/TopicOverlay.astro index 160fba5a4..b1c43de60 100644 --- a/src/components/TopicOverlay.astro +++ b/src/components/TopicOverlay.astro @@ -1,31 +1,69 @@ --- -import Icon from "./Icon.astro"; -import Loader from "./Loader.astro"; +import Icon from './Icon.astro'; +import Loader from './Loader.astro'; +export interface Props { + roadmapId: string; +} + +const { roadmapId } = Astro.props; +const githubLink = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/roadmaps/${roadmapId}/content`; ---