|
// meant for the CJS2 Chrome extension. Usually added for udemy.com and youtube.com |
|
|
|
// minify before pasting to extension |
|
// uglifyjs myPastedFile.js -o myMinifiedFile.js |
|
|
|
const CSJ_UTILS = { |
|
// keyboard handler |
|
controlPlusSomeKeyOnSubmit(givenKey, actionCallback) { |
|
// ctrl + some key |
|
document.addEventListener("keydown", function (event) { |
|
if ((event.ctrlKey || event.metaKey) && event.key === givenKey) { |
|
actionCallback(event); |
|
} |
|
}); |
|
}, |
|
controlPlusShiftPlusSomeKeyOnSubmit(givenKey, actionCallback) { |
|
// ctrl + shift + some key |
|
document.addEventListener("keydown", function (event) { |
|
if ( |
|
(event.ctrlKey || event.metaKey) && |
|
event.shiftKey && |
|
event.key === givenKey |
|
) { |
|
actionCallback(event); |
|
} |
|
}); |
|
}, |
|
/** |
|
* Show notification (non-blocking) in the browser |
|
* |
|
* @param {String} message |
|
* @param {Number} timeout notification hides after this, if negative the popup stays |
|
* |
|
*/ |
|
showNotification(message = "", timeout = 1500) { |
|
// create the nofication UI node |
|
const notification = document.createElement("div"); |
|
notification.id = "sanjar"; |
|
notification.style.position = "fixed"; |
|
notification.style.top = "10px"; |
|
notification.style.right = "10px"; |
|
notification.style.padding = "10px"; |
|
notification.style.background = "rgba(0, 0, 0, 0.8)"; |
|
notification.style.color = "white"; |
|
notification.style.borderRadius = "5px"; |
|
notification.style.zIndex = "9999"; |
|
notification.style.fontSize = "18px"; // Increase font size |
|
notification.textContent = message; |
|
notification.style.border = "3px solid skyblue"; |
|
|
|
// attach notification node to page |
|
document.body.appendChild(notification); |
|
console.log("Notification attached successfully", { message, timeout }); |
|
|
|
// remove after timeout |
|
// but let it be if timeout is negative |
|
if (timeout < 0) return; |
|
|
|
setTimeout(() => { |
|
try { |
|
document.body.removeChild(notification); |
|
console.log("Notification removed successfully", { message, timeout }); |
|
} catch (error) { |
|
console.error("Error removing notification:", { |
|
error, |
|
message, |
|
timeout, |
|
}); |
|
} |
|
}, timeout); |
|
}, |
|
}; |
|
|
|
const allFeatures = { |
|
toggleUdemySideBar() { |
|
const toggleUdemySideBarAction = () => { |
|
try { |
|
theatreModeButton = document.querySelector( |
|
'*[data-purpose="theatre-mode-toggle-button"]' |
|
); |
|
theatreModeButton?.click(); |
|
console.log("Udemy sidebar toggle successful"); |
|
} catch (error) { |
|
console.log("Sidebar toggle shortcut error"); |
|
console.log(error); |
|
} |
|
}; |
|
|
|
CSJ_UTILS.controlPlusSomeKeyOnSubmit("b", toggleUdemySideBarAction); |
|
}, |
|
addTitleToTOTLExtension() { |
|
// Why - need to watch Udemy in "full-screen-in-window", |
|
// but Chrome doesn't allow hiding the titlebar and omnibox in window mode. |
|
// Arc browser allows hiding them. But full-screen doesn't work in window mode. So used a browser extension for that. |
|
// steps below |
|
|
|
// Setup: |
|
// 1. Install the 'Turn Off the Lights Extension' browser extension |
|
// - Set it up - Open extension options, Advanced -> scroll somewhat --> enable Video toolbar checkbox |
|
// 2. Install the 'Custom JavaScript for Websites 2' browser extension |
|
// - Set it up - create an entry for udemy.com and paste this file. Remember to press 'Save'. |
|
// 4. Install the arc browser - hide the side bar and make the window small. |
|
// 3. Use it - go to udemy, and start watching a video. You should see a video toolbar on top. |
|
// It will now have the full lecture name printed in white |
|
|
|
// Approach - updates the video title every one second (using setTimeout). There's no other way since Udemy |
|
// is an SPA and the video event isn't properly known |
|
|
|
// UI element markers discovered from extension repo |
|
// https://github.com/turnoffthelights/Turn-Off-the-Lights-Chrome-extension/ |
|
|
|
// Performance is OK - not noticeable. |
|
|
|
function addTitleBox(text = "") { |
|
// aria-label one has section + lecture, second has title only |
|
let gottenTitle = |
|
document |
|
.querySelector('[class*="lecture-view--container"]') |
|
?.getAttribute("aria-label") || |
|
document.querySelector('[class*="video-viewer--title-overlay"]') |
|
?.textContent || |
|
"title not found"; |
|
|
|
let existingVideoTitle = document.querySelector("#sanjarTOTLtitle"); |
|
let panelNotFound; |
|
const usingExisting = !!existingVideoTitle; |
|
if (!existingVideoTitle) { |
|
const sanjarPanels = [...document.querySelectorAll(".stefanvdvis")]; |
|
const sanjarLastPanel = sanjarPanels.at(-1); |
|
const sanjarVideoTitle = document.createElement("span"); |
|
sanjarVideoTitle.setAttribute("id", "sanjarTOTLtitle"); |
|
sanjarVideoTitle.style.color = "white"; |
|
sanjarVideoTitle.style.fontWeight = "600"; |
|
sanjarVideoTitle.style.fontSize = "18px"; |
|
sanjarLastPanel?.appendChild(sanjarVideoTitle); |
|
|
|
panelNotFound = !sanjarLastPanel; |
|
existingVideoTitle = sanjarVideoTitle; |
|
} |
|
|
|
// alert('main ran') |
|
existingVideoTitle.textContent = text || gottenTitle; |
|
console.log("addTitleBox Ran", { panelNotFound, existingVideoTitle }); |
|
return { |
|
existingVideoTitle, |
|
id: "sanjarTOTLtitle", |
|
existingText: existingVideoTitle.textContent, |
|
usingExisting, |
|
extensionFound: !!document.querySelector(".stefanvdvis"), |
|
}; |
|
} |
|
|
|
function updater() { |
|
addTitleBox(); |
|
// alert('Updater Ran') |
|
const t = setTimeout(() => { |
|
updater(); |
|
clearTimeout(t); |
|
}, 1000); |
|
} |
|
|
|
updater(); |
|
|
|
// function main() { |
|
// document.addEventListener("DOMContentLoaded", (event) => { |
|
// console.log("DOM fully loaded and parsed"); |
|
// updater(); |
|
// // alert('main ran') |
|
// }); |
|
// } |
|
// main(); |
|
}, |
|
toggleStudyPauseMode() { |
|
/* For study-tutorial mode */ |
|
// Task code - paste in Custom JavaScript extension under tutorial site |
|
|
|
// saves the toggle state to localStorage |
|
// changes setting permanently |
|
|
|
const eventListenerRemover = new AbortController(); |
|
|
|
function toggleTutorialModeSetting(localStorageKey) { |
|
// reducer |
|
|
|
const currentValue = JSON.parse( |
|
localStorage.getItem(localStorageKey) || "false" |
|
); |
|
const newValue = !currentValue; |
|
|
|
localStorage.setItem(localStorageKey, JSON.stringify(newValue)); |
|
|
|
if (newValue) { |
|
windowTabSwitchWatcherSetup(localStorageKey); // add event handlers |
|
} else { |
|
eventListenerRemover.abort(); // remove existing event handlers |
|
} |
|
|
|
console.log( |
|
`Tutorial mode is now ${newValue ? "ON" : "OFF"}, localStorage toggled`, |
|
location.host |
|
); |
|
|
|
// show notification |
|
CSJ_UTILS.showNotification( |
|
`Tutorial mode ${newValue ? "ON" : "OFF"}`, |
|
1000 |
|
); |
|
} |
|
|
|
function windowTabSwitchWatcherSetup(localStorageKey) { |
|
// does UI change (does not know about localStorage or state) |
|
function toggleVideo(value = null) { |
|
const videoElements = document.querySelectorAll("video"); // contains false positives too |
|
|
|
if (!videoElements?.length) throw new Error("No video elements found"); |
|
|
|
const mainVideoElement = Array.from(videoElements).find( |
|
(videoElement) => videoElement.src.includes("blob:https") |
|
); // works for Udemy, YouTube |
|
|
|
if (!mainVideoElement) throw new Error("No main video found"); |
|
|
|
switch (value) { |
|
case "play": |
|
mainVideoElement.play(); |
|
break; |
|
case "pause": |
|
mainVideoElement.pause(); |
|
break; |
|
|
|
default: |
|
mainVideoElement.paused |
|
? mainVideoElement.play() |
|
: mainVideoElement.pause(); |
|
break; |
|
} |
|
|
|
console.log("Video state toggled"); |
|
} |
|
|
|
window.addEventListener( |
|
"focus", |
|
function () { |
|
// document.title = "focused"; |
|
console.log("Tab focus toggled: now focused"); |
|
|
|
if (JSON.parse(localStorage.getItem(localStorageKey))) |
|
toggleVideo("play"); |
|
}, |
|
{ signal: eventListenerRemover.signal } |
|
); |
|
|
|
window.addEventListener( |
|
"blur", |
|
function () { |
|
console.log("Tab focus toggled: unfocused"); |
|
|
|
// document.title = "not focused"; |
|
if (JSON.parse(localStorage.getItem(localStorageKey))) |
|
toggleVideo("pause"); |
|
}, |
|
{ signal: eventListenerRemover.signal } |
|
); |
|
|
|
console.log("Tab focus watcher setup done."); |
|
} |
|
|
|
// 1. Decide the localStorage key |
|
const TUTORIAL_MODE_TOGGLE_KEY = "tutorial-mode-sanjar"; |
|
|
|
// 2. watcher setup (for on page load, since I won't enable feature every time I visit the page) |
|
const currentValue = JSON.parse( |
|
localStorage.getItem(TUTORIAL_MODE_TOGGLE_KEY) || "false" |
|
); |
|
if (currentValue) windowTabSwitchWatcherSetup(TUTORIAL_MODE_TOGGLE_KEY); |
|
|
|
// 3. preferences change from keyboard shortcut - done |
|
CSJ_UTILS.controlPlusShiftPlusSomeKeyOnSubmit("u", () => |
|
toggleTutorialModeSetting(TUTORIAL_MODE_TOGGLE_KEY) |
|
); |
|
}, |
|
}; |
|
|
|
// run on page load |
|
// mostly setups |
|
const featuresToRun = [ |
|
allFeatures.toggleUdemySideBar, |
|
allFeatures.addTitleToTOTLExtension, |
|
allFeatures.toggleStudyPauseMode, |
|
]; |
|
featuresToRun.forEach((f) => f()); |
works, but hard testing left. also, move this to some better place (Gist isn't enough), since CJS2 needs minified files (plain file hits the character limit).