message, countdown, GetElementByText, etc.
Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greatest.deepsurf.us/scripts/578557/1827723/utils_MERGEDjs.js
// ────────────────────── utils SYSTEM ──────────────────────
// May 17, 7:17 PM 2026
// 💡 What I learned from tampermonkey:
// - The local host script updates instantly, so there is no issue in that regard.
// - The issue is with Tampermonkey; it does not always update.
// - need to wait 10 seconds before it updates to the latest version
// - Need to manually click "update" in the external tab to immediately get the latest version
// - cache buster also works
// 💡 GM_xmlhttpRequest:
// - unstable. does not always work
let StayLoop = true
let HasExecuted = false
let OriginalTitle = false
let sleep = (ms) => {return new Promise(resolve => setTimeout(resolve, ms))}
// function TestSnappy(){
// // message("☑️ first - test snappy", "GUI_v1", "blue", 0, "y80", 17, 3000)
// // message("⚠️ second - test snappy", "GUI_v1", "blue", 0, "y80", 17, 3000)
// // alert('first - test snappy')
// alert('second - test snappy')
// }
function ConsoleLog(text){
console.log(text)
}
function sys_StayLoopOffOn() {
message("stop loop", "GUI_v1", "red", 0, "y80", 16, 3000)
StayLoop = false
setTimeout(() => {
StayLoop = true
message("STAY LOOP: true", "GUI_v1", "blue", 0, "y80", 16, 2000)
}, 1000);
}
function sys_GetOriginalTitle(){
if (HasExecuted) return
OriginalTitle = document.title
HasExecuted = true // don't get title ever again
}
async function sys_SetWintitle(signal, data = '', ms = 2000) {
// OriginalTitle = document.title; issue: it gets wrong title, coz "get original title" invokes immediately even before changing back to original title
sys_GetOriginalTitle()
document.title = signal + " " + data
ConsoleLog("success: wintitle set: " + signal)
await sleep(ms) // 2 seconds
document.title = OriginalTitle
}
// pinVSCODE
// sys_AddSVG(OpenHistory, "fixed", '2.7%', '82.9%', '<svg width="24px" height="24px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <rect width="48" height="48" fill="white" fill-opacity="0.01"></rect> <path d="M5.81824 6.72729V14H13.091" stroke="#0080ff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M4 24C4 35.0457 12.9543 44 24 44V44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C16.598 4 10.1351 8.02111 6.67677 13.9981" stroke="#0080ff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M24.005 12L24.0038 24.0088L32.4832 32.4882" stroke="#0080ff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>')
function sys_AddSVG(callback, pos, top, left, SVGstring){
let SVGbutton = document.createElement('button')
SVGbutton.style.position = pos // 🅿️ Position (variable): fixed / absolute
SVGbutton.innerHTML = SVGstring
SVGbutton.style.border = "none"
SVGbutton.style.top = top // '80%'
SVGbutton.style.left = left // '80%'
SVGbutton.style.padding = '0px'
SVGbutton.style.background = "none"
SVGbutton.style.zIndex = '999'
SVGbutton.addEventListener('click', () => {
callback()
})
document.body.appendChild(SVGbutton)
}
// ─── GET POSTS ─────────────
function sys_GetTopChildrenDoAction(PlaylistContainerID, callback) {
let PostsContainer = document.querySelector(PlaylistContainerID)
if (PostsContainer) {
// message("success: found PostsContainer", "GUI_v1", "blue", 0, "y80", 17, 3000)
// ─── YOU SHOULD USE FOREACH, LOOKS CLEANER. ─────────────
let TopChildrenArr = Array.from(PostsContainer.children)
TopChildrenArr.forEach(callback)
// ─── FOR LOOP ─────────────
// let TopChildrenArr = PostsContainer.children // get top level/direct/immediate children/descendant
// for (let index = 0; index < TopChildrenArr.length; index++) {
// callback(TopChildrenArr, index)
// }
}
else {
// message("error: posts container not found", "GUI_v1", "red", 0, "y80", 17, 3000)
ConsoleLog("❌ error: PostsContainer not found (sys_GetTopChildrenDoAction)")
}
}
// ─── FOREEACH ─────────────
// function POST_ACTION(SingleTopChildObj, index, array) {
// ConsoleLog(`───────── Post ${index + 1} ─────────`)
// ConsoleLog(SingleTopChildObj.innerText)
// }
// ─── FOR LOOP ─────────────
// function POST_ACTION(TopChildrenArr, index) {
// ConsoleLog(`───────── Post ${index + 1} ─────────`)
// ConsoleLog(TopChildrenArr[index].innerText)
// }
async function sys_WaitTextToExist(text, MessageStatus="hide"){
while (true) {
if (document.body.innerText.includes(text)){
if (MessageStatus == "showGUI")
message('☑️ success: found ' + text + ' (sys_WaitTextToExist)', "GUI_v1", "blue", 0, "y80", 17, 3000)
ConsoleLog('☑️ success: found "' + text + '" (sys_WaitTextToExist)')
break
}
if (MessageStatus == "showGUI")
message('⏳ waiting for "' + text + '" (sys_WaitTextToExist)', "GUI_v1", "green", 0, "y80", 17, 3000)
ConsoleLog('⏳ waiting for "' + text + '" (sys_WaitTextToExist)')
await sleep(100)
}
}
async function sys_WaitElementToExist(ElementID, label=null, MessageStatus="hide"){
while (true) {
let ElementObj = document.querySelector(ElementID)
let DisplayName = label || ElementID // use label if provided, else fallback to raw selector
if (ElementObj){
if (MessageStatus == "showGUI")
message('☑️ success: found "' + DisplayName + '" (sys_WaitElementToExist)', "GUI_v1", "blue", 0, "y80", 17, 3000)
// message('☑️ success: found "(' + DisplayName + ' - ' + ElementObj + ')" (sys_WaitElementToExist)', "GUI_v1", "blue", 0, "y80", 17, 3000)
ConsoleLog('☑️ success: found "' + DisplayName + '" (sys_WaitElementToExist)')
// ConsoleLog('☑️ success: found "(' + DisplayName + ' - ' + ElementObj + ')" (sys_WaitElementToExist)')
return ElementObj
}
if (MessageStatus == "showGUI")
message('⏳ waiting for "' + DisplayName + '" (sys_WaitElementToExist)', "GUI_v1", "black", 0, "y80", 17, 3000)
ConsoleLog('⏳ waiting for "' + DisplayName + '" (sys_WaitElementToExist)')
await sleep(100)
}
}
async function sys_WaitElementToDisappear(ElementID, MessageStatus="HideMessage"){
while (true) {
let ElementObj = document.querySelector(ElementID)
// ─── ☑️ ELEMENT DISAPPEARED ─────────────
if (!ElementObj){
if (MessageStatus == "ShowMessage") {
message('☑️ success: element gone', "GUI_v1", "blue", 0, "y80", 16, 3000)
}
ConsoleLog('☑️ success: element gone (sys_WaitElementToDisappear)')
return // <---
}
// ─── ⏳ WAITING ELEMENT TO DISAPPEAR ─────────────
if (MessageStatus == "ShowMessage") {
message('⏳ waiting element to disappear', "GUI_v1", "black", 0, "y80", 16, 3000)
}
ConsoleLog('⏳ waiting element to disappear (sys_WaitElementToExist)')
await sleep(100)
}
}
// use case:
// let ParentElementOfText = sys_GetElementByText("Songs")
// ParentElementOfText.click()
function sys_GetElementByText(text, MessageStatus="hide"){
// ⚠️ CASE SENSITIVE: GetElementByText("Private") and GetElementByText("private") are not the same
let AllElements_arr = Array.from(document.querySelectorAll("*"))
let ParentElementOfText = AllElements_arr.find(GetElementByText)
function GetElementByText(CurrentElement){
let CurrentElementChildNodes_arr = Array.from(CurrentElement.childNodes)
if (CurrentElementChildNodes_arr.some(FindTargetTextNode)){
return true
}
function FindTargetTextNode(CurrentChildNode) {
// if current element child node is a text node, and that text node is the 'text' variable (eg. Songs),
// return that element (return true)
if (CurrentChildNode.nodeType === Node.TEXT_NODE && CurrentChildNode.textContent == text)
return true
}
}
if (ParentElementOfText) {
if (MessageStatus == "showGUI")
message('☑️ success: found "' + text + '" (sys_GetElementByText)', "GUI_v1", "blue", 0, "y80", 17, 3000)
ConsoleLog('☑️ success: found "' + text + '" (sys_GetElementByText)')
// ConsoleLog('☑️ ParentElementOfText: ' + ParentElementOfText + ' (sys_GetElementByText)')
return ParentElementOfText
}
else if (!ParentElementOfText) {
if (MessageStatus == "showGUI")
message('❌ error: not found "' + text + '" (sys_GetElementByText)', "GUI_v1", "red", 0, "y80", 17, 3000)
ConsoleLog('❌ error: not found "' + text + '" (sys_GetElementByText)')
return false
}
}
function sys_CreateTextButton(text, xpos, ypos, FontSize, BgColor, FontColor) {
// ─── Parse prefixed args ─────────────
xpos = parseFloat(xpos.slice(1)) // "x50" → 50
ypos = parseFloat(ypos.slice(1)) // "y20" → 20
FontSize = parseFloat(FontSize.slice(1)) // "s14" → 14
// ─── Derived sizing (auto-adjust padding & margin based on font size) ─────────────
let PaddingVert = Math.round(FontSize * 0.3) // vertical padding
let PaddingHoriz = Math.round(FontSize * 0.7) // horizontal padding
// ─── Create element ─────────────
let TextButton = document.createElement("button");
TextButton.textContent = text;
// ─── Apply styles ─────────────
Object.assign(TextButton.style, {
position : "absolute",
left : `${xpos}%`,
top : `${ypos}%`,
transform : "translate(-50%, -50%)", // center on the x/y point
fontSize : `${FontSize}px`,
padding : `${PaddingVert}px ${PaddingHoriz}px`,
// Sensible defaults (override as needed)
cursor : "pointer",
border : "none",
borderRadius: "3px",
background : BgColor,
color : FontColor,
fontFamily : "consolas",
lineHeight : "1",
whiteSpace : "nowrap", // prevent wrapping that would break the size math
boxSizing : "border-box",
});
document.body.appendChild(TextButton)
return TextButton;
}
// pinVSCODE
function sys_CreateToggleButton(ConfigObj) {
// ConfigObj = { initialState, storageKey, label, position }
let ToggleVar = ConfigObj.initialState;
let container = document.createElement('div')
container.style.position = 'fixed' // 🅿️ Position (single)
container.style.top = ConfigObj.location?.top || '1.8%'
container.style.left = ConfigObj.location?.left || '80%'
// container.style.zIndex = '999' // ⚠️ does not show for youtube
container.style.zIndex = '9999'
container.style.display = 'flex'
container.style.flexDirection = 'column'
container.style.alignItems = 'center'
container.style.gap = '4px'
// container.style.cssText = `
// position: fixed;
// top: ${ConfigObj.position?.top || '1.8%'};
// left: ${ConfigObj.position?.left || '80%'};
// z-index: 9999;
// display: flex;
// flex-direction: column;
// align-items: center;
// gap: 4px;
// `
let labelText = document.createElement('span')
labelText.textContent = ConfigObj.label || 'toggle'
labelText.style.cssText = `
font-size: 10px;
font-weight: 500;
color: white;
user-select: none;
font-family: 'Segoe UI', Arial, sans-serif;
text-shadow:
-1px -1px 0 black,
1px -1px 0 black,
-1px 1px 0 black,
1px 1px 0 black;
`
let switchLabel = document.createElement('label')
switchLabel.id = ConfigObj.id
switchLabel.style.cssText = `
position: relative;
display: inline-block;
width: 25px;
height: 13px;
cursor: pointer;
`
let checkbox = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.checked = ToggleVar
checkbox.style.cssText = `
opacity: 0;
width: 0;
height: 0;
`
let slider = document.createElement('span')
slider.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: ${ToggleVar ? '#2196F3' : '#ccc'};
border-radius: 26px;
transition: 0.3s;
outline: 1px solid black;
`
let SliderButton = document.createElement('span')
SliderButton.style.cssText = `
position: absolute;
content: "";
height: 7px;
width: 7px;
left: 3px;
bottom: 3px;
background-color: black;
border-radius: 50%;
transition: 0.3s;
transform: ${ToggleVar ? 'translateX(11px)' : 'translateX(0)'};
`
slider .appendChild(SliderButton)
switchLabel.appendChild(checkbox)
switchLabel.appendChild(slider)
container .appendChild(labelText)
container .appendChild(switchLabel)
checkbox.addEventListener('click', function(){
ToggleVar = !ToggleVar
if (ToggleVar) {
slider.style.backgroundColor = '#2196F3'
SliderButton.style.transform = 'translateX(11px)'
}
else if (!ToggleVar) {
slider.style.backgroundColor = '#ccc'
SliderButton.style.transform = 'translateX(0)'
}
// ─── Save with unique key ─────────────
localStorage.setItem(ConfigObj.storageKey, ToggleVar)
// stores ToggleVar boolean value into string version. ie, true = "true", false = "false"
// ─── RUN CALLBACK AFTER TOGGLE CLICKED (NO MATTER THE STATE) ─────────────
ConfigObj.callback()
})
document.body.appendChild(container)
return {
container,
slider,
SliderButton,
checkbox,
GetToggleValue: () => ToggleVar,
SetToggleValue: (NewToggleValue) => { // useful. lets you programmatically set the toggle's state aside from clicking the toggle button.
ToggleVar = NewToggleValue
checkbox.checked = NewToggleValue
slider.style.backgroundColor = NewToggleValue ? '#2196F3' : '#ccc'
SliderButton.style.transform = NewToggleValue ? 'translateX(11px)' : 'translateX(0)'
localStorage.setItem(ConfigObj.storageKey, NewToggleValue)
}
}
}
function sys_AddClassToElement(ContainerObj, ClassName){
if (!ContainerObj){
ConsoleLog('❌ error: not found ContainerObj (sys_AddClassToElement)')
return // <---
}
else if (ContainerObj){
ContainerObj.classList.add(ClassName)
}
}
// let TopChildrenArr = sys_AddClassForTopChildren(ContainerElement, "TopChildSongsClass")
function sys_AddClassToTopChildren(ContainerObj, ClassName){
TopChildrenArr = Array.from(ContainerObj.children)
TopChildrenArr.forEach((TopChild) => {
TopChild.classList.add(ClassName)
})
return document.querySelectorAll("." + ClassName)
}
// sys_AddClassToArrayElements(global_TopChildren_arr, ".title-column", "TitleColumn_AddClass")
function sys_AddClassToArrayElements(elements_arr, TargetElementID, ClassNameToUse){
elements_arr.forEach(sys_AddClassToArrayElements)
function sys_AddClassToArrayElements(element){
let FoundTargetElement = element.querySelector(TargetElementID)
if (!FoundTargetElement) {
ConsoleLog('❌ error: not found FoundTargetElement')
return // <---
}
// ─── ADD CLASS ─────────────
FoundTargetElement.classList.add(ClassNameToUse)
}
}
// ────────────────────── utils MESSAGE ──────────────────────
let hour = 3600000
function message(text, GUI, color, extra_xpos, ypos, fontsize, time){
// MessageInstance.message("hello", "GUI_v1", "green", 0, "y80", 16, 3000)
MessageInstance.message(text, GUI, color, extra_xpos, ypos, fontsize, time)
}
function HideMessage(GUI){
message("hide GUI", GUI, "green", 0, "y200", 16, 100) // y200 = vertically hidden
}
class DYNAMIC_MESSAGE {
constructor() {
this.MessageElementsDict = {}; // Store references to active MessageInstance elements
this.FadeTimersDict = {}; // Store references to fade timers
}
message(text, MessageCategory, bgColor = 'green', extra_xpos = 0, ypos = "y10", fontSize = 10, duration = 2000) {
this.hideMessage(MessageCategory);
let MessageElement = document.createElement('div');
MessageElement.innerText = text;
// ─── base styles ─────────────
Object.assign(MessageElement.style, {
position: 'fixed', // 🅿️ Position (single)
zIndex: '999',
paddingTop: '8px',
paddingBottom: '8px',
paddingLeft: '11px',
paddingRight: '11px',
borderRadius: '4px',
color: 'white',
fontFamily: 'Consolas',
fontSize: `${fontSize}px`,
backgroundColor: bgColor,
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
transition: 'opacity 1s ease-in-out',
opacity: '1',
whiteSpace: 'nowrap',
})
// ▬▬▬ Y POSITION ▬▬▬▬▬▬▬▬▬▬▬▬▬
let IntegerYposPercent = parseInt(ypos.replace(/[^0-9]/g, ''), 10)
MessageElement.style.top = `${IntegerYposPercent}%`
// ▬▬▬ PASS 1: show offscreen to measure width ▬▬▬▬▬▬▬▬▬▬▬▬▬
MessageElement.style.left = '0px'
MessageElement.style.visibility = 'hidden'
document.body.appendChild(MessageElement) // append inside body
// document.documentElement.appendChild(MessageElement) // append in HTML ⚠️ error: does not work
// ▬▬▬ PASS 2: calculate centered position then show ▬▬▬▬▬▬▬▬▬▬▬▬▬
// Element.getBoundingClientRect() method returns a DOMRect object that provides information
// about the size of an element and its position relative to the viewport
let RectObjWithDimension = MessageElement.getBoundingClientRect() // ⚠️ key method
let centeredX = (window.innerWidth - RectObjWithDimension.width) / 2
let extra_xpos_px = extra_xpos * (window.innerWidth / 100)
MessageElement.style.left = `${centeredX + extra_xpos_px}px`
MessageElement.style.visibility = 'visible'
this.MessageElementsDict[MessageCategory] = MessageElement;
// ─── fade out ─────────────
let fadeDelay = 1000;
let fadeStartTime = duration - fadeDelay;
if (this.FadeTimersDict[MessageCategory]) clearTimeout(this.FadeTimersDict[MessageCategory])
this.FadeTimersDict[MessageCategory] = setTimeout(() => {
if (this.MessageElementsDict[MessageCategory] === MessageElement) {
MessageElement.style.opacity = '0'
setTimeout(() => {
if (this.MessageElementsDict[MessageCategory] === MessageElement) this.hideMessage(MessageCategory)
}, fadeDelay)
}
}, fadeStartTime)
return MessageElement
}
// Hide/remove a specific MessageInstance
hideMessage(MessageCategory) {
if (this.MessageElementsDict[MessageCategory]) {
document.body.removeChild(this.MessageElementsDict[MessageCategory]);
delete this.MessageElementsDict[MessageCategory];
// Clear any pending fade timers
if (this.FadeTimersDict[MessageCategory]) {
clearTimeout(this.FadeTimersDict[MessageCategory]);
delete this.FadeTimersDict[MessageCategory];
}
}
}
// Hide all active messages
hideAllMessages() {
for (let MessageCategory in this.MessageElementsDict) {
if (this.MessageElementsDict.hasOwnProperty(MessageCategory)) {
this.hideMessage(MessageCategory);
}
}
}
}
// Create a global instance of the MessageInstance system
let MessageInstance = new DYNAMIC_MESSAGE();
// ────────────────────── utils COUNTDOWN ──────────────────────
function countdown(count, text = '', xpos = 0, ypos = "y75", fontSize = 17, color = "black", ms_display = "ms") {
if (ms_display === "ms")
return countdown_ms(count, text, xpos, ypos, fontSize, color)
else if (ms_display === "noms")
return countdown_noms(count, text, xpos, ypos, fontSize, color)
}
function countdown_ms(count, text = '', xpos = 0, ypos = "y75", fontSize = 17, color = "black") {
let SKIP_GUI_THRESHOLD = 0.2
// skip GUI, just sleep
if (count <= SKIP_GUI_THRESHOLD) {
return new Promise(resolve => setTimeout(resolve, count * 1000))
}
// show GUI once — duration slightly longer so it doesn't vanish before reaching 0
message(text + count.toFixed(1), "countdown_GUI", color, xpos, ypos, fontSize, count * 1000 + 200)
let startTime = Date.now()
return new Promise(resolve => {
let interval = setInterval(() => {
let elapsed = (Date.now() - startTime) / 1000
let remaining = count - elapsed
if (remaining <= 0) {
clearInterval(interval)
HideMessage("countdown_GUI")
resolve()
return
}
// update text in-place — no new GUI element
let el = MessageInstance.MessageElementsDict["countdown_GUI"]
if (el) el.innerText = text + remaining.toFixed(1)
}, 100)
})
}
function countdown_noms(count, text = '', xpos = 0, ypos = "y75", fontSize = 17, color = "black") {
message(text + count, "countdown_GUI", color, xpos, ypos, fontSize, count * 1000 + 200)
let remaining = count
return new Promise(resolve => {
let interval = setInterval(() => {
remaining--
let el = MessageInstance.MessageElementsDict["countdown_GUI"]
if (el) el.innerText = text + remaining
if (remaining <= 0) {
clearInterval(interval)
setTimeout(() => {
HideMessage("countdown_GUI")
resolve()
}, 1000) // show "0" for 1s — same as AHK version
}
}, 1000)
})
}
// /▬▬▬▬▬▬▬▬▬\
// ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ NOT USING ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
// ────────────────────── utils YT MUSIC ──────────────────────
async function YTM_SortAnyPlaylist(PlaylistPositionStr, PlaylistContainerID, TitleElementID){
let PlaylistContainerObj = await sys_WaitElementToExist(PlaylistContainerID)
// Get all playlist items (first level children)
let TopChildren_arr = Array.from(PlaylistContainerObj.children) // need this parent elem for DOM change
// Create an array of objects with the DOM element and its title
let PlaylistObjects_arr = [];
TopChildren_arr.forEach(PushElementsToPlaylistObjects)
function PushElementsToPlaylistObjects(SingleTopChildObj){
// let TitleElementObj = item.querySelector('#title') // this works as well
let TitleElementObj = SingleTopChildObj.querySelector(TitleElementID) // this is more explicit
if (TitleElementObj) {
PlaylistObjects_arr.push({
element: SingleTopChildObj,
title: TitleElementObj.textContent.trim()
})
}
}
// ─── check if already sorted ─────────────
let currentOrder = [...PlaylistObjects_arr];
let sortedOrder = [...PlaylistObjects_arr].sort(YTM_SortPlaylists);
let IsAlreadySorted = currentOrder.every((item, index) =>
item.title === sortedOrder[index].title
);
if (IsAlreadySorted) {
ConsoleLog("👍 " + PlaylistPositionStr + " playlist already sorted. Will not sort.")
return // <---
}
// Sort the playlist objects
PlaylistObjects_arr.sort(YTM_SortPlaylists);
// Create a document fragment to hold the sorted items
let fragment = document.createDocumentFragment();
// Move all playlist items to the fragment in sorted order
PlaylistObjects_arr.forEach(obj => {
fragment.appendChild(obj.element);
});
// Clear the container and append the sorted items
while (PlaylistContainerObj.firstChild) {
PlaylistContainerObj.removeChild(PlaylistContainerObj.firstChild);
}
PlaylistContainerObj.appendChild(fragment);
// Create a list of numbered titles for display
let numberedTitles = PlaylistObjects_arr.map((obj, index) => `${index + 1}. ${obj.title}`);
let titlesText = numberedTitles.join('\n');
ConsoleLog("☑️ success: " + PlaylistPositionStr + " playlist sorted")
// message(☑️ PlaylistPositionStr + " playlist sorted", "GUI_v1", "blue", 0, "y93", 16, 3000)
}
// Sort function to handle numerical prefixes
function YTM_SortPlaylists(a, b) {
// Extract any leading number from each title
let numRegex = /^(\d+)\.\s+/;
let aMatch = a.title.match(numRegex);
let bMatch = b.title.match(numRegex);
// If both have numerical prefixes
if (aMatch && bMatch) {
let aNum = parseInt(aMatch[1]);
let bNum = parseInt(bMatch[1]);
if (aNum !== bNum) {
return aNum - bNum;
}
}
// If only one has a numerical prefix
if (aMatch && !bMatch) return -1;
if (!aMatch && bMatch) return 1;
// Alphabetical sorting
return a.title.localeCompare(b.title);
}
// /▬▬▬▬▬▬▬▬▬\
// ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ NOT USING ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬