summaryrefslogtreecommitdiff
path: root/assets/js/live.js
diff options
context:
space:
mode:
authordelta <darkussdelta@gmail.com>2025-02-03 22:58:46 +0100
committerdelta <darkussdelta@gmail.com>2025-02-03 22:58:46 +0100
commit41a6d16ef6a356bc286b0eafe267d04aeed174f3 (patch)
tree86fe71416dc5f802bacec930f9afadd453824423 /assets/js/live.js
initial commit
Diffstat (limited to 'assets/js/live.js')
-rw-r--r--assets/js/live.js225
1 files changed, 225 insertions, 0 deletions
diff --git a/assets/js/live.js b/assets/js/live.js
new file mode 100644
index 0000000..a015d05
--- /dev/null
+++ b/assets/js/live.js
@@ -0,0 +1,225 @@
+/*
+ Live.js - One script closer to Designing in the Browser
+ Written for Handcraft.com by Martin Kool (@mrtnkl).
+
+ Version 4.
+ Recent change: Made stylesheet and mimetype checks case insensitive.
+
+ http://livejs.com
+ http://livejs.com/license (MIT)
+ @livejs
+
+ Include live.js#css to monitor css changes only.
+ Include live.js#js to monitor js changes only.
+ Include live.js#html to monitor html changes only.
+ Mix and match to monitor a preferred combination such as live.js#html,css
+
+ By default, just include live.js to monitor all css, js and html changes.
+
+ Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
+ as a page reload due to a change in html or css would not re-include the bookmarklet.
+ To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
+*/
+(function () {
+ var headers = { Etag: 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
+ resources = {},
+ pendingRequests = {},
+ currentLinkElements = {},
+ oldLinkElements = {},
+ interval = 1000,
+ loaded = false,
+ active = { html: 1, css: 1, js: 1 };
+
+ var Live = {
+ // performs a cycle per interval
+ heartbeat: function () {
+ if (document.body) {
+ // make sure all resources are loaded on first activation
+ if (!loaded) Live.loadresources();
+ Live.checkForChanges();
+ }
+ setTimeout(Live.heartbeat, interval);
+ },
+
+ // loads all local css and js resources upon first activation
+ loadresources: function () {
+ // helper method to assert if a given url is local
+ function isLocal(url) {
+ var loc = document.location,
+ reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
+ return url.match(reg);
+ }
+
+ // gather all resources
+ var scripts = document.getElementsByTagName("script"),
+ links = document.getElementsByTagName("link"),
+ uris = [];
+
+ // track local js urls
+ for (var i = 0; i < scripts.length; i++) {
+ var script = scripts[i],
+ src = script.getAttribute("src");
+ if (src && isLocal(src)) uris.push(src);
+ if (src && src.match(/\blive.js#/)) {
+ for (var type in active) active[type] = src.match("[#,|]" + type) != null;
+ if (src.match("notify")) alert("Live.js is loaded.");
+ }
+ }
+ if (!active.js) uris = [];
+ if (active.html) uris.push(document.location.href);
+
+ // track local css urls
+ for (var i = 0; i < links.length && active.css; i++) {
+ var link = links[i],
+ rel = link.getAttribute("rel"),
+ href = link.getAttribute("href", 2);
+ if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
+ uris.push(href);
+ currentLinkElements[href] = link;
+ }
+ }
+
+ // initialize the resources info
+ for (var i = 0; i < uris.length; i++) {
+ var url = uris[i];
+ Live.getHead(url, function (url, info) {
+ resources[url] = info;
+ });
+ }
+
+ // add rule for morphing between old and new css files
+ var head = document.getElementsByTagName("head")[0],
+ style = document.createElement("style"),
+ rule = "transition: all .3s ease-out;";
+ css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join("");
+ style.setAttribute("type", "text/css");
+ head.appendChild(style);
+ style.styleSheet ? (style.styleSheet.cssText = css) : style.appendChild(document.createTextNode(css));
+
+ // yep
+ loaded = true;
+ },
+
+ // check all tracking resources for changes
+ checkForChanges: function () {
+ for (var url in resources) {
+ if (pendingRequests[url]) continue;
+
+ Live.getHead(url, function (url, newInfo) {
+ var oldInfo = resources[url],
+ hasChanged = false;
+ resources[url] = newInfo;
+ for (var header in oldInfo) {
+ // do verification based on the header type
+ var oldValue = oldInfo[header],
+ newValue = newInfo[header],
+ contentType = newInfo["Content-Type"];
+ switch (header.toLowerCase()) {
+ case "etag":
+ if (!newValue) break;
+ // fall through to default
+ default:
+ hasChanged = oldValue != newValue;
+ break;
+ }
+ // if changed, act
+ if (hasChanged) {
+ Live.refreshResource(url, contentType);
+ break;
+ }
+ }
+ });
+ }
+ },
+
+ // act upon a changed url of certain content type
+ refreshResource: function (url, type) {
+ switch (type.toLowerCase()) {
+ // css files can be reloaded dynamically by replacing the link element
+ case "text/css":
+ var link = currentLinkElements[url],
+ html = document.body.parentNode,
+ head = link.parentNode,
+ next = link.nextSibling,
+ newLink = document.createElement("link");
+
+ html.className = html.className.replace(/\s*livejs\-loading/gi, "") + " livejs-loading";
+ newLink.setAttribute("type", "text/css");
+ newLink.setAttribute("rel", "stylesheet");
+ newLink.setAttribute("href", url + "?now=" + new Date() * 1);
+ next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
+ currentLinkElements[url] = newLink;
+ oldLinkElements[url] = link;
+
+ // schedule removal of the old link
+ Live.removeoldLinkElements();
+ break;
+
+ // check if an html resource is our current url, then reload
+ case "text/html":
+ if (url != document.location.href) return;
+
+ // local javascript changes cause a reload as well
+ case "text/javascript":
+ case "application/javascript":
+ case "application/x-javascript":
+ document.location.reload();
+ }
+ },
+
+ // removes the old stylesheet rules only once the new one has finished loading
+ removeoldLinkElements: function () {
+ var pending = 0;
+ for (var url in oldLinkElements) {
+ // if this sheet has any cssRules, delete the old link
+ try {
+ var link = currentLinkElements[url],
+ oldLink = oldLinkElements[url],
+ html = document.body.parentNode,
+ sheet = link.sheet || link.styleSheet,
+ rules = sheet.rules || sheet.cssRules;
+ if (rules.length >= 0) {
+ oldLink.parentNode.removeChild(oldLink);
+ delete oldLinkElements[url];
+ setTimeout(function () {
+ html.className = html.className.replace(/\s*livejs\-loading/gi, "");
+ }, 100);
+ }
+ } catch (e) {
+ pending++;
+ }
+ if (pending) setTimeout(Live.removeoldLinkElements, 50);
+ }
+ },
+
+ // performs a HEAD request and passes the header info to the given callback
+ getHead: function (url, callback) {
+ pendingRequests[url] = true;
+ var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
+ xhr.open("HEAD", url, true);
+ xhr.onreadystatechange = function () {
+ delete pendingRequests[url];
+ if (xhr.readyState == 4 && xhr.status != 304) {
+ xhr.getAllResponseHeaders();
+ var info = {};
+ for (var h in headers) {
+ var value = xhr.getResponseHeader(h);
+ // adjust the simple Etag variant to match on its significant part
+ if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, "");
+ if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
+ info[h] = value;
+ }
+ callback(url, info);
+ }
+ };
+ xhr.send();
+ },
+ };
+
+ // start listening
+ if (document.location.protocol != "file:") {
+ if (!window.liveJsLoaded) Live.heartbeat();
+
+ window.liveJsLoaded = true;
+ } else if (window.console) console.log("Live.js doesn't support the file protocol. It needs http.");
+})();