/* JWConfZoomHand 2021.08.28 (c) 2021 - JB Some Parts from JWConf Signal 2018.04.29 (c) 2018 - Simon Lorenz - werbung@lorenzweb.net */ (function() { // used locale var requestedLocale = "de"; // switch enabled var signalEnabled = true; // remember testActive var testActive = false; // remember if zoom hand raised var ZoomHandRaised = false; var JWConfRaisings = 0; // loop through table of attendees and call for observer subscription /* function subscribeNewAttendees() { if (signalEnabled === false) { return; } var tableAttendees = document.getElementById("content").getElementsByTagName("table")[0]; if (typeof(tableAttendees) !== "undefined" && tableAttendees !== null) { for (var i = 0, row; row = tableAttendees.rows[i]; i++) { if (row.parentNode.localName !== "thead") addObserver(row); } } } */ function subscribeNewAttendees() { if (signalEnabled === false) { return; } var tablesAttendees = document.evaluate('//table[starts-with(@class,"listener-table")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); var tableAttendees = null; for ( var i=0; i < tablesAttendees.snapshotLength; i++ ) { tableAttendees = tablesAttendees.snapshotItem(i) for (var i = 0, row; row = tableAttendees.rows[i]; i++) { if (row.parentNode.localName !== "thead") { addObserver(row); } } } } /* wegen unten XPathResult.ORDERED_NODE_ITERATOR_TYPE durch XPathResult.ORDERED_NODE_SNAPSHOT_TYPE ersetzt ( siehe https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate Results of NODE_SNAPSHOT types are snapshots, which are essentially lists of matched nodes. You can make changes to the document by altering snapshot nodes. Modifying the document doesn't invalidate the snapshot; however, if the document is changed, the snapshot may not correspond to the current state of the document, since nodes may have moved, been changed, added, or removed. führt leider manchmal zu jwconfsignal.js:46 Uncaught DOMException: Failed to execute 'iterateNext' on 'XPathResult': The document has mutated since the result was returned. at subscribeNewAttendees (chrome-extension://hhcadpapjmemfpokloljohmahipbjhmb/jwconfsignal.js:46:42) at chrome-extension://hhcadpapjmemfpokloljohmahipbjhmb/jwconfsignal.js:250:4 */ function updateJWConfRaisings() { //JWConfRaisings = document.evaluate('count(//table[starts-with(@class,"listener-table")]//tr[@class="speechrequest detected"])', document, null, XPathResult.ANY_TYPE, null ).numberValue; JWConfRaisings = document.evaluate('count(//table[starts-with(@class,"listener-table")]//tr[@class="detected speechrequest" or @class="speechrequest detected" or @style="background-color: rgb(23, 121, 186); color: rgb(254, 254, 254);" or @style="background-color: rgb(161, 184, 105); color: rgb(254, 254, 254);"])', document, null, XPathResult.ANY_TYPE, null ).numberValue; } function subscribeAttList() { var x = new MutationObserver(function (e) { if (e[0].removedNodes) { updateJWConfRaisings(); decideZoomHand(false); } }); x.observe(document.getElementById("content"), { childList: true, subtree: true}); } // add observer for any new attendee function addObserver(addToElement) { if (!addToElement.classList.contains('detected')) { addToElement.classList.add('detected'); var observer = new MutationObserver(function(mutations) { if (signalEnabled === false) { return; } mutations.forEach(function(mutation) { if (mutation.attributeName === "class") { var attributeValue = $(mutation.target).prop(mutation.attributeName); console.log("Class attribute", addToElement.children[0].innerText, "changed to:", attributeValue); if (attributeValue === "detected speechrequest") { decideZoomHand(true); } else if (attributeValue === "detected") { updateJWConfRaisings(); decideZoomHand(false); } } }); }); observer.observe(addToElement, { attributes: true }); } } // send message to background script to raise hand in zoom if raised hand is active, else send message to lower hand function decideZoomHand(actionRaise) { contentElement = document.getElementById("content"); if (typeof(contentElement) !== "undefined" && contentElement !== null) { if (actionRaise === true) { if (ZoomHandRaised === false) { //send message to background script which opens the ZoomRaiseHand Script chrome.runtime.sendMessage({greeting: "raise"}); ZoomHandRaised = true; } } else { // check if any other is still raising else all good, lower hand if (JWConfRaisings > 0) { console.log("There are some other raisings") return; } if (ZoomHandRaised === true ) { chrome.runtime.sendMessage({greeting: "raise"}); ZoomHandRaised = false; } } } } // add on/off selector to "Live Monitor" headline function addSelectorToMonitorHeadline() { var headlineToFind = " Zuhörer"; var h4Elements = document.getElementsByTagName("h4"); if (typeof(h4Elements) !== "undefined" && h4Elements !== null) { for (var i=0, h4Element; h4Element = h4Elements[i]; i++) { if (typeof(h4Element.childNodes) !== "undefined" && h4Element.childNodes !== null) { for (var ii=0, childNode; childNode = h4Element.childNodes[ii]; ii++) { if (typeof(childNode.nodeValue) !== "undefined" && childNode.nodeValue !== null) { if (childNode.nodeValue.startsWith(headlineToFind)) { // add selector var selectorElem = document.createElement("div"); selectorElem.style = "display: inline-block; margin-left: 10px;height: 22px;background-color: #c60f13;width: 49px;"; selectorElem.innerHTML = '
' selectorElem.id = 'signalSelector'; selectorElem.addEventListener("click", function( e ) { toggleSelectorSignal(); }); selectorElem.addEventListener("contextmenu", function( e ) { openTestMenu(); e.preventDefault(); }, false); h4Element.insertBefore(selectorElem, h4Element.childNodes[ii+1]); // add test menu (originally color picker) var testMenuElem = document.createElement("div"); testMenuElem.style = "position: absolute; top: 100px; left: 50px; display: none; background-color: #0a0a0a; color: #dadada; padding: 10px; font-size: 1rem; min-width:250px; min-height:170px; border: 2px solid #cacaca; -webkit-box-shadow: 10px 10px 20px 0px rgba(0,0,0,0.75); -moz-box-shadow: 10px 10px 20px 0px rgba(0,0,0,0.75); box-shadow: 10px 10px 20px 0px rgba(0,0,0,0.75); z-index: 1;"; testMenuElem.innerHTML = /*getMessage("configMenu_Language") + '  
' + */ '' testMenuElem.id = 'testMenuDiv'; h4Element.insertBefore(testMenuElem, h4Element.childNodes[ii+2]); // handle test $('#btnTestJWConfSignal').click(function() { if (testActive !== true) { startTest(); } }); // handle close $('#btnCloseJWConfSignal').click(function() { stopTest(); closeTestMenu(); }); return; } } } } } } } //open test menu function openTestMenu() { var testMenuDivElem = document.getElementById("testMenuDiv"); if (typeof(testMenuDivElem) !== "undefined" && testMenuDivElem !== null) { testMenuDivElem.style.display = "block"; } } //close test menu function closeTestMenu() { var testMenuDivElem = document.getElementById("testMenuDiv"); if (typeof(testMenuDivElem) !== "undefined" && testMenuDivElem !== null) { testMenuDivElem.style.display = "none"; } } function startTest() { testActive = true; decideZoomHand(true); } function stopTest() { testActive = false; decideZoomHand(false); } function setSubscriptions() { subscribeNewAttendees(); } function toggleSelectorSignal() { if (signalEnabled === true) { var signalElem = document.getElementById("signalSelector"); if (typeof(signalElem) !== "undefined" && signalElem !== null) { signalElem.style.background = "#cacaca"; } signalElem = document.getElementById("switchSignalSelector"); if (typeof(signalElem) !== "undefined" && signalElem !== null) { signalElem.style.marginLeft = "5px"; } signalEnabled = false; } else { var signalElem = document.getElementById("signalSelector"); if (typeof(signalElem) !== "undefined" && signalElem !== null) { signalElem.style.background = "#c60f13"; } signalElem = document.getElementById("switchSignalSelector"); if (typeof(signalElem) !== "undefined" && signalElem !== null) { signalElem.style.marginLeft = "27px"; } signalEnabled = true; setSubscriptions(); } } function getMessage(msgName) { var localMsg = requestedLocale+"_"+msgName; return chrome.i18n.getMessage(localMsg); } function startNewAttendeesDetection() { // check for new attendees every 15 sec window.setInterval(function() { subscribeNewAttendees(); }, 15000); } function detectedLiveMonitorHeadlineElement() { var h4Elements = document.getElementsByTagName("h4"); if (typeof(h4Elements) !== "undefined" && h4Elements !== null) { for (var i=0, h4Element; h4Element = h4Elements[i]; i++) { if (typeof(h4Element.childNodes) !== "undefined" && h4Element.childNodes !== null) { for (var ii=0, childNode; childNode = h4Element.childNodes[ii]; ii++) { if (typeof(childNode.nodeValue) !== "undefined" && childNode.nodeValue !== null) { if (childNode.nodeValue.startsWith(" Zuhörer")) { return true } } } } } } return false; } function waitForTelcoElements() { if (detectedLiveMonitorHeadlineElement() === false) { setTimeout(function () { waitForTelcoElements(); }, 1000); } else { addSelectorToMonitorHeadline(); startNewAttendeesDetection(); subscribeAttList(); //die beiden // in der folgenden Zeile entfernen, wenn sich die Konferenz automatisch verbinden soll bzw. // davor setzen wenn sie manuell verbunden werden soll //setTimeout(function(){ document.evaluate('//*[@id="left-column"]/div/div[2]/div[2]/button', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue.click(); }, 1000); } } function startup() { // runs ones when page is loaded, but telco attendees are added dynamically waitForTelcoElements(); } startup(); })();