Skip to content

Instantly share code, notes, and snippets.

@lundeen-bryan
Last active September 18, 2023 05:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lundeen-bryan/73df7c111fc44a8a4c10dcfe8e4ff5d2 to your computer and use it in GitHub Desktop.
Save lundeen-bryan/73df7c111fc44a8a4c10dcfe8e4ff5d2 to your computer and use it in GitHub Desktop.
Show remaining time for Udemy course sections that are open

Udemy Time Remaining

Introduction

A browser extension that calculates and displays the total remaining time of Udemy courses.

Features

  • Automatic time calculation for all videos in a course.
  • Convenient popup interface with total course time.
  • Easy sharing to Twitter with a click.

Installation

  • (TBD) Step-by-step instructions on how to install the extension from the web store.
  • (TBD) Any permissions required by the extension and a brief explanation of why they are needed.

Usage

  • Navigate to a Udemy course page.
  • Click on the extension icon to view total course time.
  • Click on the Twitter icon in the popup to share.
  • (TBD) Screenshots/GIFs can be particularly helpful here to visually guide the user.

Browser Compatibility

  • Chrome, Edge, and Brave.

Contributing

  • Not at this time.

Feedback & Issues

  • Direct users to the GitHub Issues tab for any feedback, bug reports, or feature requests.

Changelog

License

  • [Insert MIT license here]

Credits & Acknowledgements

  • Twitter icon by Freepik from Flaticon.
  • (TBD) Any collaborators or contributors to the project.

Contact Information

  • (TBD)

Donate (Optional)

  • (TBD)
// put the following script into a bookmarklet, the GIST_URL is the 'raw' URL for the 'udemy_time.js' code
javascript:/*v1.0.1*/(function(){const GIST_URL='https://gist.githubusercontent.com/lundeen-bryan/73df7c111fc44a8a4c10dcfe8e4ff5d2/raw/b2402e8a83bba92e3ab75c47f1997555bb3a73ee/udemy_time.js';fetch(GIST_URL).then(response=>response.text()).then(script=>{console.log('Fetched script from Gist:',script);eval(script);}).catch(error=>{console.error('Error fetching script:',error);});})();
/* Make main container flexible and stack children vertically */
[data-testid="notification-tab-pane-student"] {
display: flex;
flex-direction: column;
}
/* Move notification list to end. Make text wide enough to fill the width of the screen. */
.activity-notifications-module--notification-list--3I_hF {
order: 3;
width: 220%;
margin: 0 auto;
max-width: 1500px;
}
/* Move 'load more' and 'mark all as read' to start */
.activity-notifications-module--load-more-row--3_jVO, .activity-notifications-module--footer--3bQw7 {
order: 1;
}
/* Align 'load more' contents to left */
.activity-notifications-module--load-more-row--3_jVO {
justify-content: left;
}
.activity-notification-module--notification-info--3Yr2y {
height: 170px;
}
/* create CRLF after title then, normal font for the body text */
span[data-purpose="safely-set-inner-html:activity-notification:notification-template"] span.subject::before {
content: "\A";
white-space: pre;
}
.subject {
font-weight: normal;
}
/* Text area for message body */
.item-card-module--item-card-title--S729p{
height: 250px;
}
/* move the msg body down a little to reveal the time posted */
.item-card-module--item-card-title--S729p{
padding-top: 5px;
}
/* increase msg vertical scroll area */
.activity-notifications-module--notification-list--3I_hF{
max-height:42rem
}
.activity-notification-module--card-container--3C8QZ {
height: 300px
}
(function() {
'use strict';
function getMetaContentByName(name) {
const element = document.querySelector(`meta[name="${name}"]`);
return element && element.getAttribute("content");
}
function storeCourseMetadata(data) {
localStorage.setItem('courseMetadata', JSON.stringify(data));
}
function expandUnopenedSections() {
const unopenedSections = [];
document.querySelectorAll(".section--section--BukKG > span").forEach((section) => {
const isExpanded = section.getAttribute("data-checked") === "checked";
if (!isExpanded) {
const accordionTitle = section.parentNode.querySelector(".ud-accordion-panel-heading");
if (accordionTitle) {
accordionTitle.click();
unopenedSections.push(section);
} else {
console.warn("Could not find the accordion panel heading for one of the sections. The website structure might have changed.");
}
}
});
return unopenedSections;
}
function calculateTotalMinutes() {
let totalMinutes = 0;
document.querySelectorAll(".item-link.ud-custom-focus-visible").forEach((item) => {
const isChecked = item.querySelector(".ud-real-toggle-input").checked;
if (!isChecked) {
const timer = item.querySelector(".ud-text-xs span");
if (timer) {
let time = parseInt(timer.innerText.replace("min", "").trim(), 10);
totalMinutes += isNaN(time) ? 0 : time;
}
}
});
return totalMinutes;
}
function convertDuration(totalMinutes) {
const hours = String(Math.trunc(totalMinutes / 60)).padStart(2, "0");
const minutes = String(totalMinutes % 60).padStart(2, "0");
return { hours, minutes };
}
function collapseSections(sections) {
sections.forEach((section) => {
const accordionTitle = section.parentNode.querySelector(".ud-accordion-panel-heading");
if (accordionTitle) {
accordionTitle.click();
} else {
console.warn("Could not find the accordion panel heading for one of the sections while trying to collapse. The website structure might have changed.");
}
});
}
function insertRemainingDuration(totalMinutes) {
const { hours, minutes } = convertDuration(totalMinutes);
const displayArea = document.querySelector('dd[data-purpose="course-additional-stats"]');
if (displayArea) {
const existingVideoDuration = displayArea.querySelector('div:nth-child(2)');
const existingRemainingTimeElement = displayArea.querySelector('.remaining-time');
if (existingRemainingTimeElement) {
existingRemainingTimeElement.textContent = `Remaining Time: ${hours}:${minutes}`;
} else {
const remainingTimeElement = document.createElement("div");
remainingTimeElement.textContent = `Remaining Time: ${hours}:${minutes}`;
remainingTimeElement.className = 'remaining-time';
remainingTimeElement.style.backgroundColor = 'yellow'; // Set the background color to yellow
existingVideoDuration.insertAdjacentElement('afterend', remainingTimeElement);
}
}
}
function addScriptButton() {
const statsSection = document.querySelector('dd[data-purpose="course-additional-stats"]');
if (!statsSection) return;
const buttonContainer = document.createElement('div');
buttonContainer.style.marginTop = '10px';
// Button for Remaining Time
const remainingTimeButton = document.createElement('button');
remainingTimeButton.id = 'courseMetadataButton';
remainingTimeButton.textContent = 'Remaining Time';
remainingTimeButton.style.backgroundColor = '#007bff';
remainingTimeButton.style.color = '#fff';
remainingTimeButton.style.border = 'none';
remainingTimeButton.style.padding = '10px';
remainingTimeButton.style.cursor = 'pointer';
remainingTimeButton.style.display = 'block'; // ensures each button takes up the full width of the container
remainingTimeButton.style.marginBottom = '5px'; // adds a little space between buttons
remainingTimeButton.addEventListener('click', () => {
fetchAndDisplayCourseData();
});
buttonContainer.appendChild(remainingTimeButton);
// Button for Expand All
const expandAllButton = document.createElement('button');
expandAllButton.textContent = 'Expand All';
expandAllButton.style.backgroundColor = '#008000';
expandAllButton.style.color = '#fff';
expandAllButton.style.border = 'none';
expandAllButton.style.padding = '10px';
expandAllButton.style.cursor = 'pointer';
expandAllButton.style.display = 'block'; // ensures each button takes up the full width of the container
expandAllButton.style.marginBottom = '5px'; // adds a little space between buttons
expandAllButton.addEventListener('click', () => {
expandAllSections();
});
buttonContainer.appendChild(expandAllButton);
// Button for Collapse All
const collapseAllButton = document.createElement('button');
collapseAllButton.textContent = 'Collapse All';
collapseAllButton.style.backgroundColor = '#ff0000';
collapseAllButton.style.color = '#fff';
collapseAllButton.style.border = 'none';
collapseAllButton.style.padding = '10px';
collapseAllButton.style.cursor = 'pointer';
collapseAllButton.style.display = 'block'; // ensures each button takes up the full width of the container
collapseAllButton.addEventListener('click', () => {
collapseAllSections();
});
buttonContainer.appendChild(collapseAllButton);
statsSection.parentNode.insertBefore(buttonContainer, statsSection.nextSibling);
}
function expandAllSections() {
document.querySelectorAll(".section--section--BukKG > span").forEach((section) => {
const isExpanded = section.getAttribute("data-checked") === "checked";
if (!isExpanded) {
const accordionTitle = section.parentNode.querySelector(".ud-accordion-panel-heading");
if (accordionTitle) {
accordionTitle.click();
} else {
console.warn("Could not find the accordion panel heading for one of the sections. The website structure might have changed.");
}
}
});
}
function collapseAllSections() {
document.querySelectorAll(".section--section--BukKG > span").forEach((section) => {
const isExpanded = section.getAttribute("data-checked") === "checked";
if (isExpanded) {
const accordionTitle = section.parentNode.querySelector(".ud-accordion-panel-heading");
if (accordionTitle) {
accordionTitle.click();
} else {
console.warn("Could not find the accordion panel heading for one of the sections while trying to collapse. The website structure might have changed.");
}
}
});
}
function fetchAndDisplayCourseData() {
const title = getMetaContentByName("twitter:title");
const url = getMetaContentByName("twitter:url");
const description = getMetaContentByName("twitter:description");
const totalMinutes = calculateTotalMinutes();
const courseMetadata = {
'courseTitle': title,
'courseURL': url,
'courseDescription': description,
'remainingDuration': totalMinutes
};
storeCourseMetadata(courseMetadata);
insertRemainingDuration(totalMinutes);
const sectionsToCollapse = expandUnopenedSections();
collapseSections(sectionsToCollapse);
}
addScriptButton();
fetchAndDisplayCourseData();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment