/* # Styles * --audio-src: url(...), ..., ... * --audio-duration: ms|s|m|h * --audio-offset: ms|s|m|h * --audio-loop: infinite|number * --audio-state: playing|stopped|paused * --audio-playback: playthrough * playthrough: continues playing once triggered * stop: stops the playback once state changes * pause: pauses the playback once state changes * --audio-ontrigger: continue(def) | reset | multi * continue: continues playing if the audio is still playing * reset: resets the audio to the beginning * multi: creates a new audio playback source P.S., none of this triggers for childrens TODO: add 'processChildren' bool to mConfig */ var KTK = KTK || {}; KTK.CSSA = (function() { // CSSA configuration options. Merged with user-passed configuration. var mConfig = { observeNodeConfig: { attributeFilter: ['style', 'class', 'id'] }, observeDOMConfig: { childList: true }, processDOM: true, observeDOM: true, selectors: ['active', 'hover', 'focus', 'checked'] }; // MutatonObservers for the DOM and Nodes var observerDOM = new MutationObserver(observeDOMCallback); var observerNode = new MutationObserver(observeNodeCallback); // Default properties acquired for managing audio states var defaultProperties = ['--audio-src','--audio-state','--audio-playback','--audio-offset','--audio-duration','--audio-ontrigger','--audio-loop','--audio-volume']; /* observeDOMCallback(mutatonRecords) * This function is the callback for MutationRecord updates to the DOM. For * each added Node `processNode` and `obseveNode` is called. */ function observeDOMCallback(mutationRecords) { for (var mutation of mutationRecords) { for (var i = 0; i < mutation.addedNodes.length; i++) { processNode(mutation.addedNodes[i]); observeNode(mutation.addedNodes[i]); } } } /* observeDOM() * This function begins observing document.body with observerDOM. It also * uses the `observeDOMConfig` property as contained in `mConfig`. */ function observeDOM() { observerDOM.observe(document.body, mConfig.observeDOMConfig); } /* processDOM() * This function runs through all Nodes in the document body and calls * `processNode` and `observeNode` on each. */ function processDOM() { var nodes = document.body.querySelectorAll('*'); for (var i = 0; i < nodes.length; i++) { processNode(nodes[i]); observeNode(nodes[i]); } } /* observeNodeCallback(mutationRecords) * This function is the callback for MutationRecord updates to an individual * Node. This calls `processNode` for each mutation records. */ function observeNodeCallback(mutationRecords) { for (var mutation of mutationRecords) { processNode(mutation.target); } } /* observeNode(Node which) * This function begins observing the passed node with observerNode. It also * uses the `observeNodeConfig` property as contained in `mConfig`. */ function observeNode(which) { observerNode.observe(which, mConfig.observeNodeConfig); } /* processNode() * This function runs all StyleProcessors stored in styleProcessors for a * given node, running the node through the associated styleProcessor if that * style value is found in the Node. */ function processNode(which) { var cs = window.getComputedStyle(which, null); if (cs.getPropertyValue('--audio-src')) { setupNode(which); } } /* getSelector(Element elem) * This function gets a string value that represents the selector of the given * element. It is formatted as `tagname#id.class1.class2`. */ function getSelector(elem) { return elem.tagName.toLowerCase() + (elem.id ? '#'+elem.id : '') + (elem.className ? '.'+elem.className.replace(' ', '.') : ''); } /* hasSelectorRule(String selector) * This function checks the document's styleSheets for a rule that matches the * provided `selector`. It is worth noting that matching is effectively done * from the end of the selectorText to the beginning, providing matches for * `.my_div .selector:hover` matching if `.selector:hover` is provided. */ function hasSelectorRule(selector) { for (var i = 0; i < document.styleSheets.length; i++) { var rules = document.styleSheets[i].cssRules; for (var j = 0; j < rules.length; j++) { // Okay, this is bogus, but we just check for rules that end with our desired selector if (rules[j].selectorText.indexOf(selector, rules[j].selectorText.length - selector.length) !== -1) return true; } } return false; } /* getPropertyValues(Element elem, Array properties) * This function gets the computed value properties for the given `elem` based * upon the provided `properties` array. * * Returns: Associative array of properties and their values. */ function getPropertyValues(elem, search) { var values = {}; var cs = window.getComputedStyle(elem, null); for (var i = 0; i < search.length; i++) { values[search[i]] = cs.getPropertyValue(search[i]).split(','); for (var j = 0; j < values[search[i]].length; j++) { values[search[i]][j] = values[search[i]][j].trim() } } return values; } function handleAudioStateIndex(elem, state, index) { var url_reg = /(?:\(['"]?)(.*?)(?:['"]?\))/; var url_parse = url_reg.exec(state['--audio-src'][index]); var cSrc = url_parse ? url_parse[1] : ''; var cPlaystate = state['--audio-state'][index] || 'stopped'; var cPlayback = state['--audio-playback'][index] || 'playthrough'; var cOnTrigger = state['--audio-ontrigger'][index]; var cLoop = state['--audio-loop'][index] || 1; var cOffset = state['--audio-offset'][index] || '0s'; var cVolume = parseInt(state['--audio-volume'][index] || 100) / 100; // Check/convert cOffset as if the