2
// browser.js - client-side engine
5
var isFileProtocol = (location.protocol === 'file:' ||
6
location.protocol === 'chrome:' ||
7
location.protocol === 'chrome-extension:' ||
8
location.protocol === 'resource:');
10
less.env = less.env || (location.hostname == '127.0.0.1' ||
11
location.hostname == '0.0.0.0' ||
12
location.hostname == 'localhost' ||
13
location.port.length > 0 ||
14
isFileProtocol ? 'development'
17
// Load styles asynchronously (default: false)
19
// This is set to `false` by default, so that the body
20
// doesn't start loading before the stylesheets are parsed.
21
// Setting this to `true` can result in flickering.
25
// Interval between watch polls
26
less.poll = less.poll || (isFileProtocol ? 1000 : 1500);
31
less.watch = function () { return this.watchMode = true };
32
less.unwatch = function () { return this.watchMode = false };
34
if (less.env === 'development') {
35
less.optimization = 0;
37
if (/!watch/.test(location.hash)) {
40
less.watchTimer = setInterval(function () {
42
loadStyleSheets(function (e, root, _, sheet, env) {
44
createCSS(root.toCSS(), sheet, env.lastModified);
50
less.optimization = 3;
56
cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
62
// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
64
var links = document.getElementsByTagName('link');
65
var typePattern = /^text\/(x-)?less$/;
69
for (var i = 0; i < links.length; i++) {
70
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
71
(links[i].type.match(typePattern)))) {
72
less.sheets.push(links[i]);
77
less.refresh = function (reload) {
78
var startTime, endTime;
79
startTime = endTime = new(Date);
81
loadStyleSheets(function (e, root, _, sheet, env) {
83
log("loading " + sheet.href + " from cache.");
85
log("parsed " + sheet.href + " successfully.");
86
createCSS(root.toCSS(), sheet, env.lastModified);
88
log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms');
89
(env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms');
95
less.refreshStyles = loadStyles;
97
less.refresh(less.env === 'development');
99
function loadStyles() {
100
var styles = document.getElementsByTagName('style');
101
for (var i = 0; i < styles.length; i++) {
102
if (styles[i].type.match(typePattern)) {
103
new(less.Parser)().parse(styles[i].innerHTML || '', function (e, tree) {
104
var css = tree.toCSS();
105
var style = styles[i];
106
style.type = 'text/css';
107
if (style.styleSheet) {
108
style.styleSheet.cssText = css;
110
style.innerHTML = css;
117
function loadStyleSheets(callback, reload) {
118
for (var i = 0; i < less.sheets.length; i++) {
119
loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1));
123
function loadStyleSheet(sheet, callback, reload, remaining) {
124
var url = window.location.href.replace(/[#?].*$/, '');
125
var href = sheet.href.replace(/\?.*$/, '');
126
var css = cache && cache.getItem(href);
127
var timestamp = cache && cache.getItem(href + ':timestamp');
128
var styles = { css: css, timestamp: timestamp };
130
// Stylesheets in IE don't always return the full path
131
if (! /^(https?|file):/.test(href)) {
132
if (href.charAt(0) == "/") {
133
href = window.location.protocol + "//" + window.location.host + href;
135
href = url.slice(0, url.lastIndexOf('/') + 1) + href;
138
var filename = href.match(/([^\/]+)$/)[1];
140
xhr(sheet.href, sheet.type, function (data, lastModified) {
141
if (!reload && styles && lastModified &&
142
(new(Date)(lastModified).valueOf() ===
143
new(Date)(styles.timestamp).valueOf())) {
145
createCSS(styles.css, sheet);
146
callback(null, null, data, sheet, { local: true, remaining: remaining });
148
// Use remote copy (re-parse)
151
optimization: less.optimization,
152
paths: [href.replace(/[\w\.-]+$/, '')],
155
}).parse(data, function (e, root) {
156
if (e) { return error(e, href) }
158
callback(e, root, data, sheet, { local: false, lastModified: lastModified, remaining: remaining });
159
removeNode(document.getElementById('less-error-message:' + extractId(href)));
168
}, function (status, url) {
169
throw new(Error)("Couldn't load " + url + " (" + status + ")");
173
function extractId(href) {
174
return href.replace(/^[a-z]+:\/\/?[^\/]+/, '' ) // Remove protocol & domain
175
.replace(/^\//, '' ) // Remove root /
176
.replace(/\?.*$/, '' ) // Remove query
177
.replace(/\.[^\.\/]+$/, '' ) // Remove file extension
178
.replace(/[^\.\w-]+/g, '-') // Replace illegal characters
179
.replace(/\./g, ':'); // Replace dots with colons(for valid id)
182
function createCSS(styles, sheet, lastModified) {
185
// Strip the query-string
186
var href = sheet.href ? sheet.href.replace(/\?.*$/, '') : '';
188
// If there is no title set, use the filename, minus the extension
189
var id = 'less:' + (sheet.title || extractId(href));
191
// If the stylesheet doesn't exist, create a new node
192
if ((css = document.getElementById(id)) === null) {
193
css = document.createElement('style');
194
css.type = 'text/css';
195
css.media = sheet.media || 'screen';
197
document.getElementsByTagName('head')[0].appendChild(css);
200
if (css.styleSheet) { // IE
202
css.styleSheet.cssText = styles;
204
throw new(Error)("Couldn't reassign styleSheet.cssText.");
208
if (css.childNodes.length > 0) {
209
if (css.firstChild.nodeValue !== node.nodeValue) {
210
css.replaceChild(node, css.firstChild);
213
css.appendChild(node);
215
})(document.createTextNode(styles));
218
// Don't update the local store if the file wasn't modified
219
if (lastModified && cache) {
220
log('saving ' + href + ' to cache.');
221
cache.setItem(href, styles);
222
cache.setItem(href + ':timestamp', lastModified);
226
function xhr(url, type, callback, errback) {
227
var xhr = getXMLHttpRequest();
228
var async = isFileProtocol ? false : less.async;
230
if (typeof(xhr.overrideMimeType) === 'function') {
231
xhr.overrideMimeType('text/css');
233
xhr.open('GET', url, async);
234
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
237
if (isFileProtocol) {
238
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
239
callback(xhr.responseText);
241
errback(xhr.status, url);
244
xhr.onreadystatechange = function () {
245
if (xhr.readyState == 4) {
246
handleResponse(xhr, callback, errback);
250
handleResponse(xhr, callback, errback);
253
function handleResponse(xhr, callback, errback) {
254
if (xhr.status >= 200 && xhr.status < 300) {
255
callback(xhr.responseText,
256
xhr.getResponseHeader("Last-Modified"));
257
} else if (typeof(errback) === 'function') {
258
errback(xhr.status, url);
263
function getXMLHttpRequest() {
264
if (window.XMLHttpRequest) {
265
return new(XMLHttpRequest);
268
return new(ActiveXObject)("MSXML2.XMLHTTP.3.0");
270
log("browser doesn't support AJAX.");
276
function removeNode(node) {
277
return node && node.parentNode.removeChild(node);
281
if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) }
284
function error(e, href) {
285
var id = 'less-error-message:' + extractId(href);
286
var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
287
var elem = document.createElement('div'), timer, content, error = [];
288
var filename = e.filename || href;
291
elem.className = "less-error-message";
293
content = '<h3>' + (e.message || 'There is an error in your .less file') +
294
'</h3>' + '<p>in <a href="' + filename + '">' + filename + "</a> ";
296
var errorline = function (e, i, classname) {
298
error.push(template.replace(/\{line\}/, parseInt(e.line) + (i - 1))
299
.replace(/\{class\}/, classname)
300
.replace(/\{content\}/, e.extract[i]));
305
content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
306
} else if (e.extract) {
308
errorline(e, 1, 'line');
310
content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
311
'<ul>' + error.join('') + '</ul>';
313
elem.innerHTML = content;
315
// CSS for error messages
317
'.less-error-message ul, .less-error-message li {',
318
'list-style-type: none;',
319
'margin-right: 15px;',
323
'.less-error-message label {',
325
'margin-right: 15px;',
329
'.less-error-message pre {',
333
'display: inline-block;',
335
'.less-error-message pre.line {',
338
'.less-error-message h3 {',
340
'font-weight: bold;',
341
'padding: 15px 0 5px 0;',
344
'.less-error-message a {',
347
'.less-error-message .error {',
349
'font-weight: bold;',
350
'padding-bottom: 2px;',
351
'border-bottom: 1px dashed red;',
353
].join('\n'), { title: 'error-message' });
355
elem.style.cssText = [
356
"font-family: Arial, sans-serif",
357
"border: 1px solid #e00",
358
"background-color: #eee",
359
"border-radius: 5px",
360
"-webkit-border-radius: 5px",
361
"-moz-border-radius: 5px",
364
"margin-bottom: 15px"
367
if (less.env == 'development') {
368
timer = setInterval(function () {
370
if (document.getElementById(id)) {
371
document.body.replaceChild(elem, document.getElementById(id));
373
document.body.insertBefore(elem, document.body.firstChild);
375
clearInterval(timer);