lib/less/js/lib/less/browser.js in less-2.4.0 vs lib/less/js/lib/less/browser.js in less-2.5.0
- old
+ new
@@ -1,18 +1,33 @@
//
// browser.js - client-side engine
//
+/*global less, window, document, XMLHttpRequest, location */
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 ||
+ (location.port &&
+ location.port.length > 0) ||
isFileProtocol ? 'development'
: 'production');
+var logLevel = {
+ info: 2,
+ errors: 1,
+ none: 0
+};
+
+// The amount of logging in the javascript console.
+// 2 - Information and errors
+// 1 - Errors
+// 0 - None
+// Defaults to 2
+less.logLevel = typeof(less.logLevel) != 'undefined' ? less.logLevel : logLevel.info;
+
// 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.
@@ -24,175 +39,290 @@
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];
+ if (less.functions.hasOwnProperty(func)) {
+ 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
-};
+var typePattern = /^text\/(x-)?less$/;
+var cache = null;
+var fileCache = {};
-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 (e) {
- error(e, sheet.href);
- } else if (root) {
- createCSS(root.toCSS(less), sheet, env.lastModified);
- }
- });
- }
- }, less.poll);
- } else {
- less.optimization = 3;
+function log(str, level) {
+ if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) {
+ console.log('less: ' + str);
}
}
-if (/!watch/.test(location.hash)) {
- less.watch();
+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)
}
-var cache = null;
+function errorConsole(e, rootHref) {
+ var template = '{line} {content}';
+ var filename = e.filename || rootHref;
+ var errors = [];
+ var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
+ " in " + filename + " ";
-if (less.env != 'development') {
- try {
- cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
- } catch (_) {}
+ var errorline = function (e, i, classname) {
+ if (e.extract[i] !== undefined) {
+ errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
+ .replace(/\{class\}/, classname)
+ .replace(/\{content\}/, e.extract[i]));
+ }
+ };
+
+ if (e.extract) {
+ errorline(e, 0, '');
+ errorline(e, 1, 'line');
+ errorline(e, 2, '');
+ content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' +
+ errors.join('\n');
+ } else if (e.stack) {
+ content += e.stack;
+ }
+ log(content, logLevel.errors);
}
-//
-// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
-//
-var links = document.getElementsByTagName('link');
-var typePattern = /^text\/(x-)?less$/;
+function createCSS(styles, sheet, lastModified) {
+ // Strip the query-string
+ var href = sheet.href || '';
-less.sheets = [];
+ // If there is no title set, use the filename, minus the extension
+ var id = 'less:' + (sheet.title || extractId(href));
-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]);
+ // If this has already been inserted into the DOM, we may need to replace it
+ var oldCss = document.getElementById(id);
+ var keepOldCss = false;
+
+ // Create a new stylesheet node for insertion or (if necessary) replacement
+ var css = document.createElement('style');
+ css.setAttribute('type', 'text/css');
+ if (sheet.media) {
+ css.setAttribute('media', sheet.media);
}
-}
+ css.id = id;
-//
-// 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 (var name in record) {
- str += ((name.slice(0,1) === '@')? '' : '@') + name +': '+
- ((record[name].slice(-1) === ';')? record[name] : record[name] +';');
+ if (css.styleSheet) { // IE
+ try {
+ css.styleSheet.cssText = styles;
+ } catch (e) {
+ throw new(Error)("Couldn't reassign styleSheet.cssText.");
+ }
+ } else {
+ css.appendChild(document.createTextNode(styles));
+
+ // If new contents match contents of oldCss, don't replace oldCss
+ keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
+ oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
}
- new(less.Parser)(new less.tree.parseEnv(less)).parse(str, function (e, root) {
- if (e) {
- error(e, "session_cache");
+
+ var head = document.getElementsByTagName('head')[0];
+
+ // If there is no oldCss, just append; otherwise, only append if we need
+ // to replace oldCss with an updated stylesheet
+ if (oldCss === null || keepOldCss === false) {
+ var nextEl = sheet && sheet.nextSibling || null;
+ if (nextEl) {
+ nextEl.parentNode.insertBefore(css, nextEl);
} else {
- createCSS(root.toCSS(less), less.sheets[less.sheets.length - 1]);
+ head.appendChild(css);
}
- });
-};
+ }
+ if (oldCss && keepOldCss === false) {
+ oldCss.parentNode.removeChild(oldCss);
+ }
-less.refresh = function (reload) {
- var startTime, endTime;
- startTime = endTime = new(Date);
+ // Don't update the local store if the file wasn't modified
+ if (lastModified && cache) {
+ log('saving ' + href + ' to cache.', logLevel.info);
+ 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', logLevel.errors);
+ }
+ }
+}
- loadStyleSheets(function (e, root, _, sheet, env) {
- if (e) {
- return error(e, sheet.href);
+function errorHTML(e, rootHref) {
+ var id = 'less-error-message:' + extractId(rootHref || "");
+ var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
+ var elem = document.createElement('div'), timer, content, errors = [];
+ var filename = e.filename || rootHref;
+ var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1];
+
+ elem.id = id;
+ elem.className = "less-error-message";
+
+ content = '<h3>' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
+ '</h3>' + '<p>in <a href="' + filename + '">' + filenameNoPath + "</a> ";
+
+ var errorline = function (e, i, classname) {
+ if (e.extract[i] !== undefined) {
+ errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
+ .replace(/\{class\}/, classname)
+ .replace(/\{content\}/, e.extract[i]));
}
- if (env.local) {
- log("loading " + sheet.href + " from cache.");
- } else {
- log("parsed " + sheet.href + " successfully.");
- createCSS(root.toCSS(less), 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;
+ if (e.extract) {
+ errorline(e, 0, '');
+ errorline(e, 1, 'line');
+ errorline(e, 2, '');
+ content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
+ '<ul>' + errors.join('') + '</ul>';
+ } else if (e.stack) {
+ content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
+ }
+ elem.innerHTML = content;
-less.refresh(less.env === 'development');
+ // 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' });
-function loadStyles() {
- var styles = document.getElementsByTagName('style');
- for (var i = 0; i < styles.length; i++) {
- if (styles[i].type.match(typePattern)) {
- var env = new less.tree.parseEnv(less);
- env.filename = document.location.href.replace(/#.*$/, '');
+ 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(';');
- new(less.Parser)(env).parse(styles[i].innerHTML || '', function (e, cssAST) {
- if (e) {
- return error(e, "inline");
- }
- var css = cssAST.toCSS(less);
- var style = styles[i];
- style.type = 'text/css';
- if (style.styleSheet) {
- style.styleSheet.cssText = css;
+ if (less.env == 'development') {
+ timer = setInterval(function () {
+ if (document.body) {
+ if (document.getElementById(id)) {
+ document.body.replaceChild(elem, document.getElementById(id));
} else {
- style.innerHTML = css;
+ document.body.insertBefore(elem, document.body.firstChild);
}
- });
- }
+ clearInterval(timer);
+ }
+ }, 10);
}
}
-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 error(e, rootHref) {
+ if (!less.errorReporting || less.errorReporting === "html") {
+ errorHTML(e, rootHref);
+ } else if (less.errorReporting === "console") {
+ errorConsole(e, rootHref);
+ } else if (typeof less.errorReporting === 'function') {
+ less.errorReporting("add", e, rootHref);
}
}
-function pathDiff(url, baseUrl) {
- // diff between two paths to create a relative path
+function removeErrorHTML(path) {
+ var node = document.getElementById('less-error-message:' + extractId(path));
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+}
- var urlParts = extractUrlParts(url),
- baseUrlParts = extractUrlParts(baseUrl),
- i, max, urlDirectories, baseUrlDirectories, diff = "";
- if (urlParts.hostPart !== baseUrlParts.hostPart) {
- return "";
+function removeErrorConsole(path) {
+ //no action
+}
+
+function removeError(path) {
+ if (!less.errorReporting || less.errorReporting === "html") {
+ removeErrorHTML(path);
+ } else if (less.errorReporting === "console") {
+ removeErrorConsole(path);
+ } else if (typeof less.errorReporting === 'function') {
+ less.errorReporting("remove", path);
}
- max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
- for(i = 0; i < max; i++) {
- if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
+}
+
+function loadStyles(modifyVars) {
+ var styles = document.getElementsByTagName('style'),
+ style;
+ for (var i = 0; i < styles.length; i++) {
+ style = styles[i];
+ if (style.type.match(typePattern)) {
+ var env = new less.tree.parseEnv(less),
+ lessText = style.innerHTML || '';
+ env.filename = document.location.href.replace(/#.*$/, '');
+
+ if (modifyVars || less.globalVars) {
+ env.useFileCache = true;
+ }
+
+ /*jshint loopfunc:true */
+ // use closure to store current value of i
+ var callback = (function(style) {
+ return function (e, cssAST) {
+ if (e) {
+ return error(e, "inline");
+ }
+ var css = cssAST.toCSS(less);
+ style.type = 'text/css';
+ if (style.styleSheet) {
+ style.styleSheet.cssText = css;
+ } else {
+ style.innerHTML = css;
+ }
+ };
+ })(style);
+ new(less.Parser)(env).parse(lessText, callback, {globalVars: less.globalVars, modifyVars: modifyVars});
+ }
}
- 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
@@ -206,22 +336,22 @@
if (!urlParts) {
throw new Error("Could not parse sheet href - '"+url+"'");
}
- // Stylesheets in IE don't always return the full path
+ // 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] = urlParts[1] || baseUrlParts[1] || "";
if (!urlParts[2]) {
urlParts[3] = baseUrlParts[3] + urlParts[3];
}
}
-
+
if (urlParts[3]) {
directories = urlParts[3].replace(/\\/g, "/").split("/");
// extract out . before .. so .. doesn't absorb a non-directory
for(i = 0; i < directories.length; i++) {
@@ -245,166 +375,69 @@
returner.fileUrl = returner.path + (urlParts[4] || "");
returner.url = returner.fileUrl + (urlParts[5] || "");
return returner;
}
-function loadStyleSheet(sheet, callback, reload, remaining) {
+function pathDiff(url, baseUrl) {
+ // diff between two paths to create a relative path
- // sheet may be set to the stylesheet for the initial load or a collection of properties including
- // some env variables for imports
- 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 env;
- var newFileInfo = {
- relativeUrls: less.relativeUrls,
- currentDirectory: hrefParts.path,
- filename: href
- };
-
- if (sheet instanceof less.tree.parseEnv) {
- env = new less.tree.parseEnv(sheet);
- newFileInfo.entryPath = env.currentFileInfo.entryPath;
- newFileInfo.rootpath = env.currentFileInfo.rootpath;
- newFileInfo.rootFilename = env.currentFileInfo.rootFilename;
- } else {
- env = new less.tree.parseEnv(less);
- env.mime = sheet.type;
- newFileInfo.entryPath = hrefParts.path;
- newFileInfo.rootpath = less.rootpath || hrefParts.path;
- newFileInfo.rootFilename = href;
+ var urlParts = extractUrlParts(url),
+ baseUrlParts = extractUrlParts(baseUrl),
+ i, max, urlDirectories, baseUrlDirectories, diff = "";
+ if (urlParts.hostPart !== baseUrlParts.hostPart) {
+ return "";
}
-
- if (env.relativeUrls) {
- //todo - this relies on option being set on less object rather than being passed in as an option
- // - need an originalRootpath
- if (less.rootpath) {
- newFileInfo.rootpath = extractUrlParts(less.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path;
- } else {
- newFileInfo.rootpath = hrefParts.path;
- }
+ max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
+ for(i = 0; i < max; i++) {
+ if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
}
-
- 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 {
- env.contents[href] = data; // Updating content cache
- env.paths = [hrefParts.path];
- env.currentFileInfo = newFileInfo;
-
- new(less.Parser)(env).parse(data, function (e, root) {
- if (e) { return callback(e, null, null, sheet); }
- try {
- callback(e, root, data, sheet, { local: false, lastModified: lastModified, remaining: remaining }, href);
- //TODO - there must be a better way? A generic less-to-css function that can both call error
- //and removeNode where appropriate
- //should also add tests
- if (env.currentFileInfo.rootFilename === href) {
- removeNode(document.getElementById('less-error-message:' + extractId(href)));
- }
- } catch (e) {
- callback(e, null, null, sheet);
- }
- });
- } catch (e) {
- callback(e, null, null, sheet);
- }
- }
- }, function (status, url) {
- callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, null, sheet);
- });
+ 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 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) {
- // 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 this has already been inserted into the DOM, we may need to replace it
- var oldCss = document.getElementById(id);
- var keepOldCss = false;
-
- // Create a new stylesheet node for insertion or (if necessary) replacement
- var css = document.createElement('style');
- css.setAttribute('type', 'text/css');
- if (sheet.media) {
- css.setAttribute('media', sheet.media);
- }
- css.id = id;
-
- if (css.styleSheet) { // IE
+function getXMLHttpRequest() {
+ if (window.XMLHttpRequest) {
+ return new XMLHttpRequest();
+ } else {
try {
- css.styleSheet.cssText = styles;
+ /*global ActiveXObject */
+ return new ActiveXObject("MSXML2.XMLHTTP.3.0");
} catch (e) {
- throw new(Error)("Couldn't reassign styleSheet.cssText.");
+ log("browser doesn't support AJAX.", logLevel.errors);
+ return null;
}
- } else {
- css.appendChild(document.createTextNode(styles));
-
- // If new contents match contents of oldCss, don't replace oldCss
- keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
- oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
}
-
- var head = document.getElementsByTagName('head')[0];
-
- // If there is no oldCss, just append; otherwise, only append if we need
- // to replace oldCss with an updated stylesheet
- if (oldCss == null || keepOldCss === false) {
- var nextEl = sheet && sheet.nextSibling || null;
- (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl);
- }
- if (oldCss && keepOldCss === false) {
- head.removeChild(oldCss);
- }
-
- // 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) {
+function doXHR(url, type, callback, errback) {
var xhr = getXMLHttpRequest();
var async = isFileProtocol ? less.fileAsync : less.async;
if (typeof(xhr.overrideMimeType) === 'function') {
xhr.overrideMimeType('text/css');
}
+ log("XHR: Getting '" + url + "'", logLevel.info);
xhr.open('GET', url, async);
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
xhr.send(null);
+ 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);
+ }
+ }
+
if (isFileProtocol && !less.fileAsync) {
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
callback(xhr.responseText);
} else {
errback(xhr.status, url);
@@ -416,134 +449,216 @@
}
};
} 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 loadFile(originalHref, currentFileInfo, callback, env, modifyVars) {
+
+ if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) {
+ originalHref = currentFileInfo.currentDirectory + originalHref;
}
-}
-function getXMLHttpRequest() {
- if (window.XMLHttpRequest) {
- return new(XMLHttpRequest);
+ // sheet may be set to the stylesheet for the initial load or a collection of properties including
+ // some env variables for imports
+ var hrefParts = extractUrlParts(originalHref, window.location.href);
+ var href = hrefParts.url;
+ var newFileInfo = {
+ currentDirectory: hrefParts.path,
+ filename: href
+ };
+
+ if (currentFileInfo) {
+ newFileInfo.entryPath = currentFileInfo.entryPath;
+ newFileInfo.rootpath = currentFileInfo.rootpath;
+ newFileInfo.rootFilename = currentFileInfo.rootFilename;
+ newFileInfo.relativeUrls = currentFileInfo.relativeUrls;
} else {
+ newFileInfo.entryPath = hrefParts.path;
+ newFileInfo.rootpath = less.rootpath || hrefParts.path;
+ newFileInfo.rootFilename = href;
+ newFileInfo.relativeUrls = env.relativeUrls;
+ }
+
+ if (newFileInfo.relativeUrls) {
+ if (env.rootpath) {
+ newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path;
+ } else {
+ newFileInfo.rootpath = hrefParts.path;
+ }
+ }
+
+ if (env.useFileCache && fileCache[href]) {
try {
- return new(ActiveXObject)("MSXML2.XMLHTTP.3.0");
+ var lessText = fileCache[href];
+ callback(null, lessText, href, newFileInfo, { lastModified: new Date() });
} catch (e) {
- log("browser doesn't support AJAX.");
- return null;
+ callback(e, null, href);
}
+ return;
}
-}
-function removeNode(node) {
- return node && node.parentNode.removeChild(node);
-}
+ doXHR(href, env.mime, function (data, lastModified) {
+ // per file cache
+ fileCache[href] = data;
-function log(str) {
- if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) }
+ // Use remote copy (re-parse)
+ try {
+ callback(null, data, href, newFileInfo, { lastModified: lastModified });
+ } catch (e) {
+ callback(e, null, href);
+ }
+ }, function (status, url) {
+ callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href);
+ });
}
-function error(e, rootHref) {
- var id = 'less-error-message:' + extractId(rootHref || "");
- var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
- var elem = document.createElement('div'), timer, content, error = [];
- var filename = e.filename || rootHref;
- var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1];
+function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
- elem.id = id;
- elem.className = "less-error-message";
+ var env = new less.tree.parseEnv(less);
+ env.mime = sheet.type;
- content = '<h3>' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
- '</h3>' + '<p>in <a href="' + filename + '">' + filenameNoPath + "</a> ";
+ if (modifyVars || less.globalVars) {
+ env.useFileCache = true;
+ }
- var errorline = function (e, i, classname) {
- if (e.extract[i] != undefined) {
- error.push(template.replace(/\{line\}/, (parseInt(e.line) || 0) + (i - 1))
- .replace(/\{class\}/, classname)
- .replace(/\{content\}/, e.extract[i]));
- }
- };
+ loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) {
- if (e.extract) {
- errorline(e, 0, '');
- errorline(e, 1, 'line');
- errorline(e, 2, '');
- content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
- '<ul>' + error.join('') + '</ul>';
- } else if (e.stack) {
- content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
- }
- elem.innerHTML = content;
+ if (webInfo) {
+ webInfo.remaining = remaining;
- // 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' });
+ var css = cache && cache.getItem(path),
+ timestamp = cache && cache.getItem(path + ':timestamp');
- 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 (!reload && timestamp && webInfo.lastModified &&
+ (new(Date)(webInfo.lastModified).valueOf() ===
+ new(Date)(timestamp).valueOf())) {
+ // Use local copy
+ createCSS(css, sheet);
+ webInfo.local = true;
+ callback(null, null, data, sheet, webInfo, path);
+ return;
+ }
+ }
- 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);
+ //TODO add tests around how this behaves when reloading
+ removeError(path);
+
+ if (data) {
+ env.currentFileInfo = newFileInfo;
+ new(less.Parser)(env).parse(data, function (e, root) {
+ if (e) { return callback(e, null, null, sheet); }
+ try {
+ callback(e, root, data, sheet, webInfo, path);
+ } catch (e) {
+ callback(e, null, null, sheet);
}
- clearInterval(timer);
+ }, {modifyVars: modifyVars, globalVars: less.globalVars});
+ } else {
+ callback(e, null, null, sheet, webInfo, path);
+ }
+ }, env, modifyVars);
+}
+
+function loadStyleSheets(callback, reload, modifyVars) {
+ for (var i = 0; i < less.sheets.length; i++) {
+ loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars);
+ }
+}
+
+function initRunningMode(){
+ if (less.env === 'development') {
+ less.optimization = 0;
+ less.watchTimer = setInterval(function () {
+ if (less.watchMode) {
+ loadStyleSheets(function (e, root, _, sheet, env) {
+ if (e) {
+ error(e, sheet.href);
+ } else if (root) {
+ createCSS(root.toCSS(less), sheet, env.lastModified);
+ }
+ });
}
- }, 10);
+ }, less.poll);
+ } else {
+ less.optimization = 3;
}
}
+
+
+
+//
+// Watch mode
+//
+less.watch = function () {
+ if (!less.watchMode ){
+ less.env = 'development';
+ initRunningMode();
+ }
+ this.watchMode = true;
+ return true;
+};
+
+less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; };
+
+if (/!watch/.test(location.hash)) {
+ less.watch();
+}
+
+if (less.env != 'development') {
+ try {
+ cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
+ } catch (_) {}
+}
+
+//
+// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
+//
+var links = document.getElementsByTagName('link');
+
+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
+//
+less.modifyVars = function(record) {
+ less.refresh(false, record);
+};
+
+less.refresh = function (reload, modifyVars) {
+ var startTime, endTime;
+ startTime = endTime = new Date();
+
+ loadStyleSheets(function (e, root, _, sheet, env) {
+ if (e) {
+ return error(e, sheet.href);
+ }
+ if (env.local) {
+ log("loading " + sheet.href + " from cache.", logLevel.info);
+ } else {
+ log("parsed " + sheet.href + " successfully.", logLevel.info);
+ createCSS(root.toCSS(less), sheet, env.lastModified);
+ }
+ log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info);
+ if (env.remaining === 0) {
+ log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info);
+ }
+ endTime = new Date();
+ }, reload, modifyVars);
+
+ loadStyles(modifyVars);
+};
+
+less.refreshStyles = loadStyles;
+
+less.Parser.fileLoader = loadFile;
+
+less.refresh(less.env === 'development');