-
-
Save jpinnix/1d22bcad9a2a1ee802aeadd449bb07cf to your computer and use it in GitHub Desktop.
Responsive YouTube Player and YouTube Timestamp for Roamresearch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Responsive YouTube with Timestamp Control for Roamresearch | |
// @author Connected Cognition Crumbs <[email protected]> | |
// @require Roam42: Wait until Roam42 loads completely | |
// @version 0.3 | |
// @description Add timestamp controls to YouTube videos embedded in Roam and makes the player responsive. | |
// Parameters: | |
// Shortcuts: for grabbing title and current time as a timestamp. | |
// grabTitleKey: if in a DIRECT child block of the YT video, | |
// grabs the title and paste it to the beginning of the current block. | |
// grabTimeKey: if in ANY child blocks of the YT video, | |
// grabs the current time of the player and paste it to the beginning. | |
// Player Size: Video height and width when the right sidebar is closed. | |
// @match https://*.roamresearch.com | |
const ytParams = { | |
//Player Size | |
vidHeight : 480, | |
vidWidth : 720, | |
//Shortcuts | |
grabTitleKey : 'alt+a t', | |
grabTimeKey : 'alt+a n' | |
}; | |
const players = new Map(); | |
var ytReady = setInterval(() => { | |
if(typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') { | |
const tag = document.createElement('script'); | |
tag.src = 'https://www.youtube.com/iframe_api'; | |
const firstScriptTag = document.getElementsByTagName('script')[0]; | |
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); | |
clearInterval(ytReady); | |
} | |
}, 1000); | |
//Fill out the current block with the given text | |
function fillTheBlock(givenTxt){ | |
var setValue = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; | |
let newTextArea = document.querySelector("textarea.rm-block-input"); | |
setValue.call(newTextArea, givenTxt); | |
var e = new Event('input', { bubbles: true }); | |
newTextArea.dispatchEvent(e); | |
} | |
var mouseTrapReady = setInterval(() => { | |
if(Mousetrap === undefined) return; | |
Mousetrap.bind(ytParams.grabTitleKey, async function(e) { | |
e.preventDefault() | |
if (e.srcElement.localName == "textarea") { | |
var container = e.srcElement.closest('.roam-block-container'); | |
var parContainer = container.parentElement.closest('.roam-block-container'); | |
var myIframe = parContainer.querySelector("iframe"); | |
if(myIframe === null) return false; | |
var oldTxt = document.querySelector("textarea.rm-block-input").value; | |
var newValue = players[myIframe.id].getVideoData().title + " " + oldTxt; | |
fillTheBlock(newValue); | |
} | |
return false; | |
}); | |
Mousetrap.bind(ytParams.grabTimeKey, async function(e) { | |
e.preventDefault() | |
for (var plyId in players) { | |
if(players[plyId].getPlayerState() == 1){ | |
var timeStr = new Date(players[plyId].getCurrentTime() * 1000).toISOString().substr(11, 8) | |
var oldTxt = document.querySelector("textarea.rm-block-input").value; | |
fillTheBlock(timeStr + " " + oldTxt); | |
return false; | |
} | |
} | |
return false; | |
}); | |
clearInterval(mouseTrapReady); | |
}, 1000); | |
const activateYtVideos = () => { | |
if(typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') return; | |
Array.from(document.getElementsByTagName('IFRAME')) | |
.filter(iframe => iframe.src.includes('youtube.com')) | |
.forEach(ytEl => { | |
if(ytEl.closest('.rm-zoom-item') !== null) { | |
return; //ignore breadcrumbs | |
} | |
const block = ytEl.closest('.roam-block-container'); | |
if(ytEl.src.indexOf("enablejsapi") === -1){ | |
var ytId = extractVideoID(ytEl.src); | |
var frameId = "yt-" + ytEl.closest('.roam-block').id; | |
ytEl.parentElement.id = frameId; | |
ytEl.remove(); | |
players[frameId] = new window.YT.Player(frameId, { | |
height: ytParams.vidHeight, | |
width: ytParams.vidWidth, | |
videoId: ytId | |
}); | |
wrapIframe(frameId); | |
} else { | |
var frameId = ytEl.id | |
} | |
addTimestampControls(block, players[frameId]); | |
var sideBarOpen = document.getElementById("right-sidebar").childElementCount - 1; | |
//Make iframes flexible | |
adjustIframe(frameId, sideBarOpen); | |
}); | |
}; | |
const addTimestampControls = (block, player) => { | |
if (block.children.length < 2) return null; | |
const childBlocks = Array.from(block.children[1].querySelectorAll('.roam-block-container')); | |
childBlocks.forEach(child => { | |
const timestamp = getTimestamp(child); | |
const buttonIfPresent = child.querySelectorAll('.timestamp-control')[0] | |
if (buttonIfPresent) { | |
buttonIfPresent.remove(); | |
} | |
if (timestamp) { | |
addControlButton(child, () => player.seekTo(timestamp, true)); | |
} | |
}); | |
}; | |
const adjustIframe = (frameId, sideBarOpen) => { | |
var child = document.getElementById(frameId); //Iframe | |
var par = child.parentElement; | |
if(sideBarOpen){ | |
child.style.position = 'absolute'; | |
child.style.margin = '0px'; | |
child.style.border = '0px'; | |
child.style.width = '100%'; | |
child.style.height = '100%'; | |
child.style.borderStyle = 'inset'; | |
child.style.borderRadius = '25px'; | |
par.style.position = 'relative'; | |
par.style.paddingBottom = '56.25%'; | |
par.style.height = '0px'; | |
} else { | |
child.style.position = null; | |
child.style.margin = '0px'; | |
child.style.border = '0px'; | |
child.style.width = ytParams.vidWidth + 'px'; | |
child.style.height = ytParams.vidHeight + 'px'; | |
child.style.borderStyle = 'inset'; | |
child.style.borderRadius = '25px'; | |
par.style.position = null; | |
par.style.paddingBottom = '0px'; | |
par.style.height = ytParams.vidHeight + 20 + 'px'; | |
} | |
} | |
const wrapIframe = (frameId) => { | |
var child = document.getElementById(frameId); //Iframe | |
var par = document.createElement('div'); | |
child.parentNode.insertBefore(par, child); | |
par.appendChild(child); | |
child.style.position = 'absolute'; | |
child.style.margin = '0px'; | |
child.style.border = '0px'; | |
child.style.width = '100%'; | |
child.style.height = '100%'; | |
par.style.position = 'relative'; | |
par.style.paddingBottom = '56.25%'; | |
par.style.height = '0px'; | |
}; | |
const getControlButton = (block) => block.querySelectorAll('.timestamp-control')[0]; | |
const addControlButton = (block, fn) => { | |
const button = document.createElement('button'); | |
button.innerText = '►'; | |
button.classList.add('timestamp-control'); | |
button.style.borderRadius = '50%'; | |
button.addEventListener('click', fn); | |
const parentEl = block.children[0]; | |
parentEl.insertBefore(button, parentEl.querySelectorAll('.roam-block')[0]); | |
}; | |
const getTimestamp = (block) => { | |
const innerBlockSelector = block.querySelectorAll('.roam-block'); | |
const blockText = innerBlockSelector.length ? innerBlockSelector[0].textContent : ''; | |
const matches = blockText.match(/^((?:\d+:)?\d+:\d\d)\D/); // start w/ m:ss or h:mm:ss | |
if (!matches || matches.length < 2) return null; | |
const timeParts = matches[1].split(':').map(part => parseInt(part)); | |
if (timeParts.length == 3) return timeParts[0]*3600 + timeParts[1]*60 + timeParts[2]; | |
else if (timeParts.length == 2) return timeParts[0]*60 + timeParts[1]; | |
else return null; | |
}; | |
const extractVideoID = (url) => { | |
var regExp = /^(https?:\/\/)?((www\.)?(youtube(-nocookie)?|youtube.googleapis)\.com.*(v\/|v=|vi=|vi\/|e\/|embed\/\/?|user\/.*\/u\/\d+\/)|youtu\.be\/)([_0-9a-z-]+)/i; | |
var match = url.match(regExp); | |
if ( match && match[7].length == 11 ){ | |
return match[7]; | |
}else{ | |
return null; | |
} | |
}; | |
setInterval(activateYtVideos, 1000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment