336 lines
11 KiB
JavaScript
336 lines
11 KiB
JavaScript
//
|
|
// browser.js - client-side engine
|
|
//
|
|
|
|
var isFileProtocol = (location.protocol === 'file:' ||
|
|
location.protocol === 'chrome:' ||
|
|
location.protocol === 'resource:');
|
|
|
|
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 = false;
|
|
|
|
// Interval between watch polls
|
|
less.poll = isFileProtocol ? 1000 : 1500;
|
|
|
|
//
|
|
// Watch mode
|
|
//
|
|
less.watch = function () { return this.watchMode = true };
|
|
less.unwatch = function () { return this.watchMode = false };
|
|
|
|
if (less.env === 'development') {
|
|
less.optimization = 0;
|
|
|
|
if (/!watch/.test(location.hash)) {
|
|
less.watch();
|
|
}
|
|
less.watchTimer = setInterval(function () {
|
|
if (less.watchMode) {
|
|
loadStyleSheets(function (root, sheet, env) {
|
|
if (root) {
|
|
createCSS(root.toCSS(), sheet, env.lastModified);
|
|
}
|
|
});
|
|
}
|
|
}, less.poll);
|
|
} else {
|
|
less.optimization = 3;
|
|
}
|
|
|
|
var cache;
|
|
|
|
try {
|
|
cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
|
|
} catch (_) {
|
|
cache = null;
|
|
}
|
|
|
|
//
|
|
// Get all <link> 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]);
|
|
}
|
|
}
|
|
|
|
|
|
less.refresh = function (reload) {
|
|
var startTime = endTime = new(Date);
|
|
|
|
loadStyleSheets(function (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)().parse(styles[i].innerHTML || '', function (e, tree) {
|
|
styles[i].type = 'text/css';
|
|
styles[i].innerHTML = tree.toCSS();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
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 loadStyleSheet(sheet, callback, reload, remaining) {
|
|
var href = sheet.href.replace(/\?.*$/, '');
|
|
var css = cache && cache.getItem(href);
|
|
var timestamp = cache && cache.getItem(href + ':timestamp');
|
|
var styles = { css: css, timestamp: timestamp };
|
|
|
|
xhr(sheet.href, function (data, lastModified) {
|
|
if (!reload && styles &&
|
|
(new(Date)(lastModified).valueOf() ===
|
|
new(Date)(styles.timestamp).valueOf())) {
|
|
// Use local copy
|
|
createCSS(styles.css, sheet);
|
|
callback(null, sheet, { local: true, remaining: remaining });
|
|
} else {
|
|
// Use remote copy (re-parse)
|
|
new(less.Parser)({
|
|
optimization: less.optimization,
|
|
paths: [href.replace(/[\w\.-]+$/, '')]
|
|
}).parse(data, function (e, root) {
|
|
if (e) { return error(e, href) }
|
|
try {
|
|
callback(root, sheet, { local: false, lastModified: lastModified, remaining: remaining });
|
|
removeNode(document.getElementById('less-error-message:' + extractId(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(/\?.*$/, '') // Remove query
|
|
.replace(/\.[^\/]+$/, '') // Remove file extension
|
|
.replace(/[^\w-]+/g, '-'); // Replace illegal characters
|
|
}
|
|
|
|
function createCSS(styles, sheet, lastModified) {
|
|
var css;
|
|
|
|
// Strip the query-string
|
|
var href = sheet.href ? sheet.href.replace(/\?.*$/, '') : '';
|
|
|
|
// 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;
|
|
css.id = id;
|
|
document.getElementsByTagName('head')[0].appendChild(css);
|
|
}
|
|
|
|
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.');
|
|
cache.setItem(href, styles);
|
|
cache.setItem(href + ':timestamp', lastModified);
|
|
}
|
|
}
|
|
|
|
function xhr(url, callback, errback) {
|
|
var xhr = getXMLHttpRequest();
|
|
var async = isFileProtocol ? false : less.async;
|
|
|
|
if (typeof(xhr.overrideMimeType) === 'function') {
|
|
xhr.overrideMimeType('text/css');
|
|
}
|
|
xhr.open('GET', url, async);
|
|
xhr.send(null);
|
|
|
|
if (isFileProtocol) {
|
|
if (xhr.status === 0) {
|
|
callback(xhr.responseText);
|
|
} else {
|
|
errback(xhr.status);
|
|
}
|
|
} 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);
|
|
|
|
if (! e.extract) { throw e }
|
|
|
|
var template = ['<div>',
|
|
'<pre class="ctx"><span>[-1]</span>{0}</pre>',
|
|
'<pre><span>[0]</span>{current}</pre>',
|
|
'<pre class="ctx"><span>[1]</span>{2}</pre>',
|
|
'</div>'].join('\n');
|
|
|
|
var elem = document.createElement('div'), timer;
|
|
elem.id = id;
|
|
elem.className = "less-error-message";
|
|
elem.innerHTML = '<h3>' + (e.message || 'There is an error in your .less file') + '</h3>' +
|
|
'<p><a href="' + href + '">' + href + "</a> " +
|
|
'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
|
|
template.replace(/\[(-?\d)\]/g, function (_, i) {
|
|
return (parseInt(e.line) + parseInt(i)) || '';
|
|
}).replace(/\{(\d)\}/g, function (_, i) {
|
|
return e.extract[parseInt(i)] || '';
|
|
}).replace(/\{current\}/, e.extract[1].slice(0, e.column) +
|
|
'<span class="error">' +
|
|
e.extract[1].slice(e.column) +
|
|
'</span>');
|
|
// CSS for error messages
|
|
createCSS([
|
|
'.less-error-message span {',
|
|
'margin-right: 15px;',
|
|
'}',
|
|
'.less-error-message pre {',
|
|
'color: #ee4444;',
|
|
'padding: 4px 0;',
|
|
'margin: 0;',
|
|
'}',
|
|
'.less-error-message pre.ctx {',
|
|
'color: #dd7777;',
|
|
'}',
|
|
'.less-error-message h3 {',
|
|
'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);
|
|
}
|
|
}
|
|
|