// // browser.js - client-side engine // var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol); less.env = less.env || (location.hostname == '127.0.0.1' || location.hostname == '0.0.0.0' || location.hostname == 'localhost' || location.port.length > 0 || isFileProtocol ? 'development' : 'production'); // Load styles asynchronously (default: false) // // This is set to `false` by default, so that the body // doesn't start loading before the stylesheets are parsed. // Setting this to `true` can result in flickering. // less.async = less.async || false; less.fileAsync = less.fileAsync || false; // Interval between watch polls less.poll = less.poll || (isFileProtocol ? 1000 : 1500); //Setup user functions if (less.functions) { for(var func in less.functions) { less.tree.functions[func] = less.functions[func]; } } var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash); if (dumpLineNumbers) { less.dumpLineNumbers = dumpLineNumbers[1]; } // // Watch mode // less.watch = function () { if (!less.watchMode ){ less.env = 'development'; initRunningMode(); } return this.watchMode = true }; less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; }; function initRunningMode(){ if (less.env === 'development') { less.optimization = 0; less.watchTimer = setInterval(function () { if (less.watchMode) { loadStyleSheets(function (e, root, _, sheet, env) { if (root) { createCSS(root.toCSS(), sheet, env.lastModified); } }); } }, less.poll); } else { less.optimization = 3; } } if (/!watch/.test(location.hash)) { less.watch(); } var cache = null; if (less.env != 'development') { try { cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; } catch (_) {} } // // Get all tags with the 'rel' attribute set to "stylesheet/less" // var links = document.getElementsByTagName('link'); var typePattern = /^text\/(x-)?less$/; less.sheets = []; for (var i = 0; i < links.length; i++) { if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && (links[i].type.match(typePattern)))) { less.sheets.push(links[i]); } } // // With this function, it's possible to alter variables and re-render // CSS without reloading less-files // var session_cache = ''; less.modifyVars = function(record) { var str = session_cache; for (name in record) { str += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ ((record[name].slice(-1) === ';')? record[name] : record[name] +';'); } new(less.Parser)().parse(str, function (e, root) { createCSS(root.toCSS(), less.sheets[less.sheets.length - 1]); }); }; less.refresh = function (reload) { var startTime, endTime; startTime = endTime = new(Date); loadStyleSheets(function (e, root, _, sheet, env) { if (env.local) { log("loading " + sheet.href + " from cache."); } else { log("parsed " + sheet.href + " successfully."); createCSS(root.toCSS(), sheet, env.lastModified); } log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms'); (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms'); endTime = new(Date); }, reload); loadStyles(); }; less.refreshStyles = loadStyles; less.refresh(less.env === 'development'); function loadStyles() { var styles = document.getElementsByTagName('style'); for (var i = 0; i < styles.length; i++) { if (styles[i].type.match(typePattern)) { new(less.Parser)({ filename: document.location.href.replace(/#.*$/, ''), dumpLineNumbers: less.dumpLineNumbers }).parse(styles[i].innerHTML || '', function (e, tree) { var css = tree.toCSS(); var style = styles[i]; style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.innerHTML = css; } }); } } } function loadStyleSheets(callback, reload) { for (var i = 0; i < less.sheets.length; i++) { loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1)); } } function pathDiff(url, baseUrl) { // diff between two paths to create a relative path var urlParts = extractUrlParts(url), baseUrlParts = extractUrlParts(baseUrl), i, max, urlDirectories, baseUrlDirectories, diff = ""; if (urlParts.hostPart !== baseUrlParts.hostPart) { return ""; } max = Math.max(baseUrlParts.directories.length, urlParts.directories.length); for(i = 0; i < max; i++) { if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; } } baseUrlDirectories = baseUrlParts.directories.slice(i); urlDirectories = urlParts.directories.slice(i); for(i = 0; i < baseUrlDirectories.length-1; i++) { diff += "../"; } for(i = 0; i < urlDirectories.length-1; i++) { diff += urlDirectories[i] + "/"; } return diff; } function extractUrlParts(url, baseUrl) { // urlParts[1] = protocol&hostname || / // urlParts[2] = / if path relative to host base // urlParts[3] = directories // urlParts[4] = filename // urlParts[5] = parameters var urlPartsRegex = /^((?:[a-z-]+:)?\/\/(?:[^\/\?#]+\/)|([\/\\]))?((?:[^\/\\\?#]+[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/, urlParts = url.match(urlPartsRegex), returner = {}, directories = [], i, baseUrlParts; if (!urlParts) { throw new Error("Could not parse sheet href - '"+url+"'"); } // Stylesheets in IE don't always return the full path if (!urlParts[1] || urlParts[2]) { baseUrlParts = baseUrl.match(urlPartsRegex); if (!baseUrlParts) { throw new Error("Could not parse page url - '"+baseUrl+"'"); } urlParts[1] = baseUrlParts[1]; if (!urlParts[2]) { urlParts[3] = baseUrlParts[3] + urlParts[3]; } } if (urlParts[3]) { directories = urlParts[3].replace("\\", "/").split("/"); for(i = 0; i < directories.length; i++) { if (directories[i] === ".." && i > 0) { directories.splice(i-1, 2); i -= 2; } } } returner.hostPart = urlParts[1]; returner.directories = directories; returner.path = urlParts[1] + directories.join("/"); returner.fileUrl = returner.path + (urlParts[4] || ""); returner.url = returner.fileUrl + (urlParts[5] || ""); return returner; } function loadStyleSheet(sheet, callback, reload, remaining) { // sheet may be set to the stylesheet for the initial load or a collection of properties including // some env variables for imports var contents = sheet.contents || {}; var files = sheet.files || {}; var hrefParts = extractUrlParts(sheet.href, window.location.href); var href = hrefParts.url; var css = cache && cache.getItem(href); var timestamp = cache && cache.getItem(href + ':timestamp'); var styles = { css: css, timestamp: timestamp }; var rootpath; if (less.relativeUrls) { if (less.rootpath) { if (sheet.entryPath) { rootpath = extractUrlParts(less.rootpath + pathDiff(hrefParts.path, sheet.entryPath)).path; } else { rootpath = less.rootpath; } } else { rootpath = hrefParts.path; } } else { if (less.rootpath) { rootpath = less.rootpath; } else { if (sheet.entryPath) { rootpath = sheet.entryPath; } else { rootpath = hrefParts.path; } } } xhr(href, sheet.type, function (data, lastModified) { // Store data this session session_cache += data.replace(/@import .+?;/ig, ''); if (!reload && styles && lastModified && (new(Date)(lastModified).valueOf() === new(Date)(styles.timestamp).valueOf())) { // Use local copy createCSS(styles.css, sheet); callback(null, null, data, sheet, { local: true, remaining: remaining }, href); } else { // Use remote copy (re-parse) try { contents[href] = data; // Updating top importing parser content cache new(less.Parser)({ optimization: less.optimization, paths: [hrefParts.path], entryPath: sheet.entryPath || hrefParts.path, mime: sheet.type, filename: href, rootpath: rootpath, relativeUrls: sheet.relativeUrls, contents: contents, // Passing top importing parser content cache ref down. files: files, dumpLineNumbers: less.dumpLineNumbers }).parse(data, function (e, root) { if (e) { return error(e, href) } try { callback(e, root, data, sheet, { local: false, lastModified: lastModified, remaining: remaining }, href); removeNode(document.getElementById('less-error-message:' + extractId(href))); } catch (e) { error(e, href); } }); } catch (e) { error(e, href); } } }, function (status, url) { throw new(Error)("Couldn't load " + url + " (" + status + ")"); }); } function extractId(href) { return href.replace(/^[a-z]+:\/\/?[^\/]+/, '' ) // Remove protocol & domain .replace(/^\//, '' ) // Remove root / .replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension .replace(/[^\.\w-]+/g, '-') // Replace illegal characters .replace(/\./g, ':'); // Replace dots with colons(for valid id) } function createCSS(styles, sheet, lastModified) { var css; // Strip the query-string var href = sheet.href || ''; // If there is no title set, use the filename, minus the extension var id = 'less:' + (sheet.title || extractId(href)); // If the stylesheet doesn't exist, create a new node if ((css = document.getElementById(id)) === null) { css = document.createElement('style'); css.type = 'text/css'; if( sheet.media ){ css.media = sheet.media; } css.id = id; var nextEl = sheet && sheet.nextSibling || null; (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl); } if (css.styleSheet) { // IE try { css.styleSheet.cssText = styles; } catch (e) { throw new(Error)("Couldn't reassign styleSheet.cssText."); } } else { (function (node) { if (css.childNodes.length > 0) { if (css.firstChild.nodeValue !== node.nodeValue) { css.replaceChild(node, css.firstChild); } } else { css.appendChild(node); } })(document.createTextNode(styles)); } // Don't update the local store if the file wasn't modified if (lastModified && cache) { log('saving ' + href + ' to cache.'); try { cache.setItem(href, styles); cache.setItem(href + ':timestamp', lastModified); } catch(e) { //TODO - could do with adding more robust error handling log('failed to save'); } } } function xhr(url, type, callback, errback) { var xhr = getXMLHttpRequest(); var async = isFileProtocol ? less.fileAsync : less.async; if (typeof(xhr.overrideMimeType) === 'function') { xhr.overrideMimeType('text/css'); } xhr.open('GET', url, async); xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); xhr.send(null); if (isFileProtocol && !less.fileAsync) { if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) { callback(xhr.responseText); } else { errback(xhr.status, url); } } else if (async) { xhr.onreadystatechange = function () { if (xhr.readyState == 4) { handleResponse(xhr, callback, errback); } }; } else { handleResponse(xhr, callback, errback); } function handleResponse(xhr, callback, errback) { if (xhr.status >= 200 && xhr.status < 300) { callback(xhr.responseText, xhr.getResponseHeader("Last-Modified")); } else if (typeof(errback) === 'function') { errback(xhr.status, url); } } } function getXMLHttpRequest() { if (window.XMLHttpRequest) { return new(XMLHttpRequest); } else { try { return new(ActiveXObject)("MSXML2.XMLHTTP.3.0"); } catch (e) { log("browser doesn't support AJAX."); return null; } } } function removeNode(node) { return node && node.parentNode.removeChild(node); } function log(str) { if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) } } function error(e, href) { var id = 'less-error-message:' + extractId(href); var template = '
  • {content}
  • '; var elem = document.createElement('div'), timer, content, error = []; var filename = e.filename || href; var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; elem.id = id; elem.className = "less-error-message"; content = '

    ' + (e.message || 'There is an error in your .less file') + '

    ' + '

    in ' + filenameNoPath + " "; var errorline = function (e, i, classname) { if (e.extract[i]) { error.push(template.replace(/\{line\}/, parseInt(e.line) + (i - 1)) .replace(/\{class\}/, classname) .replace(/\{content\}/, e.extract[i])); } }; if (e.stack) { content += '
    ' + e.stack.split('\n').slice(1).join('
    '); } else if (e.extract) { errorline(e, 0, ''); errorline(e, 1, 'line'); errorline(e, 2, ''); content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':

    ' + ''; } elem.innerHTML = content; // CSS for error messages createCSS([ '.less-error-message ul, .less-error-message li {', 'list-style-type: none;', 'margin-right: 15px;', 'padding: 4px 0;', 'margin: 0;', '}', '.less-error-message label {', 'font-size: 12px;', 'margin-right: 15px;', 'padding: 4px 0;', 'color: #cc7777;', '}', '.less-error-message pre {', 'color: #dd6666;', 'padding: 4px 0;', 'margin: 0;', 'display: inline-block;', '}', '.less-error-message pre.line {', 'color: #ff0000;', '}', '.less-error-message h3 {', 'font-size: 20px;', 'font-weight: bold;', 'padding: 15px 0 5px 0;', 'margin: 0;', '}', '.less-error-message a {', 'color: #10a', '}', '.less-error-message .error {', 'color: red;', 'font-weight: bold;', 'padding-bottom: 2px;', 'border-bottom: 1px dashed red;', '}' ].join('\n'), { title: 'error-message' }); elem.style.cssText = [ "font-family: Arial, sans-serif", "border: 1px solid #e00", "background-color: #eee", "border-radius: 5px", "-webkit-border-radius: 5px", "-moz-border-radius: 5px", "color: #e00", "padding: 15px", "margin-bottom: 15px" ].join(';'); if (less.env == 'development') { timer = setInterval(function () { if (document.body) { if (document.getElementById(id)) { document.body.replaceChild(elem, document.getElementById(id)); } else { document.body.insertBefore(elem, document.body.firstChild); } clearInterval(timer); } }, 10); } }