lib/less/js/lib/less/browser.js in less-2.3.0 vs lib/less/js/lib/less/browser.js in less-2.3.1
- old
+ new
@@ -1,13 +1,10 @@
//
// browser.js - client-side engine
//
-var isFileProtocol = (location.protocol === 'file:' ||
- location.protocol === 'chrome:' ||
- location.protocol === 'chrome-extension:' ||
- location.protocol === 'resource:');
+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 ||
@@ -18,46 +15,68 @@
//
// 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 = false;
+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 () { return this.watchMode = true };
-less.unwatch = function () { return this.watchMode = false };
+less.watch = function () {
+ if (!less.watchMode ){
+ less.env = 'development';
+ initRunningMode();
+ }
+ return this.watchMode = true
+};
-if (less.env === 'development') {
- less.optimization = 0;
+less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; };
- if (/!watch/.test(location.hash)) {
- less.watch();
- }
- 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;
+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;
+ }
}
-var cache;
+if (/!watch/.test(location.hash)) {
+ less.watch();
+}
-try {
- cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
-} catch (_) {
- cache = null;
+var cache = null;
+
+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"
//
@@ -71,10 +90,25 @@
(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);
@@ -98,11 +132,14 @@
function loadStyles() {
var styles = document.getElementsByTagName('style');
for (var i = 0; i < styles.length; i++) {
if (styles[i].type.match(typePattern)) {
- new(less.Parser)().parse(styles[i].innerHTML || '', function (e, tree) {
+ 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;
@@ -118,46 +155,143 @@
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) {
- var url = window.location.href.replace(/[#?].*$/, '');
- var href = sheet.href.replace(/\?.*$/, '');
+ // 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;
- // Stylesheets in IE don't always return the full path
- if (! /^(https?|file):/.test(href)) {
- if (href.charAt(0) == "/") {
- href = window.location.protocol + "//" + window.location.host + href;
+ if (less.relativeUrls) {
+ if (less.rootpath) {
+ if (sheet.entryPath) {
+ rootpath = extractUrlParts(less.rootpath + pathDiff(hrefParts.path, sheet.entryPath)).path;
+ } else {
+ rootpath = less.rootpath;
+ }
} else {
- href = url.slice(0, url.lastIndexOf('/') + 1) + href;
+ rootpath = hrefParts.path;
}
+ } else {
+ if (less.rootpath) {
+ rootpath = less.rootpath;
+ } else {
+ if (sheet.entryPath) {
+ rootpath = sheet.entryPath;
+ } else {
+ rootpath = hrefParts.path;
+ }
+ }
}
- var filename = href.match(/([^\/]+)$/)[1];
- xhr(sheet.href, sheet.type, function (data, lastModified) {
+ 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 });
+ 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: [href.replace(/[\w\.-]+$/, '')],
+ paths: [hrefParts.path],
+ entryPath: sheet.entryPath || hrefParts.path,
mime: sheet.type,
- filename: filename
+ 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 });
+ 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);
}
});
@@ -171,32 +305,32 @@
}
function extractId(href) {
return href.replace(/^[a-z]+:\/\/?[^\/]+/, '' ) // Remove protocol & domain
.replace(/^\//, '' ) // Remove root /
- .replace(/\?.*$/, '' ) // Remove query
- .replace(/\.[^\.\/]+$/, '' ) // Remove file extension
+ .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 ? sheet.href.replace(/\?.*$/, '') : '';
+ 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';
- css.media = sheet.media || 'screen';
+ if( sheet.media ){ css.media = sheet.media; }
css.id = id;
- document.getElementsByTagName('head')[0].appendChild(css);
+ var nextEl = sheet && sheet.nextSibling || null;
+ (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl);
}
if (css.styleSheet) { // IE
try {
css.styleSheet.cssText = styles;
@@ -216,27 +350,32 @@
}
// Don't update the local store if the file wasn't modified
if (lastModified && cache) {
log('saving ' + href + ' to cache.');
- cache.setItem(href, styles);
- cache.setItem(href + ':timestamp', lastModified);
+ 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 ? false : less.async;
+ 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) {
+ if (isFileProtocol && !less.fileAsync) {
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
callback(xhr.responseText);
} else {
errback(xhr.status, url);
}
@@ -284,16 +423,17 @@
function error(e, href) {
var id = 'less-error-message:' + extractId(href);
var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
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 = '<h3>' + (e.message || 'There is an error in your .less file') +
- '</h3>' + '<p>in <a href="' + filename + '">' + filename + "</a> ";
+ '</h3>' + '<p>in <a href="' + filename + '">' + filenameNoPath + "</a> ";
var errorline = function (e, i, classname) {
if (e.extract[i]) {
error.push(template.replace(/\{line\}/, parseInt(e.line) + (i - 1))
.replace(/\{class\}/, classname)
@@ -375,6 +515,5 @@
clearInterval(timer);
}
}, 10);
}
}
-