Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
10ebf8a
fixing src checking when inline scripts exist on the page
asuth Feb 21, 2012
3cd5a88
doing everything the ultra-dumb, low-performance way. Now, any time a…
asuth Feb 21, 2012
b15e194
use ajax to get new page
asuth Feb 21, 2012
d1ced8f
supporting ie8 dynamic stylesheets
asuth Feb 22, 2012
b5eb282
removing about page
asuth Mar 6, 2012
47d2376
upgrading socketio to 0.9
asuth Mar 6, 2012
51085c6
supporting SSL pages (supply a cert via command line), making interva…
asuth Mar 6, 2012
35d1ea1
add new stylesheet before removing old one
asuth Mar 8, 2012
b3e32e2
using replaceChild so that its a single dom operation
asuth Mar 8, 2012
f81da6a
open broken stylesheets (ones with ParseError:...) in a popup window …
asuth Mar 8, 2012
10754d3
remove port
asuth Mar 13, 2012
9890dbe
fixing package.jsoN
asuth Mar 28, 2012
e502288
fixing version number
asuth Mar 28, 2012
689d659
remove textmate popup
asuth Apr 1, 2012
c538324
just whitespace fixes
asuth Apr 1, 2012
5335434
make vogue show a dot when its loading and finished
asuth Apr 2, 2012
5ce9ae3
bumping version
asuth Apr 2, 2012
94e7b7c
make dot bigger and top-left instead of top-right
asuth Apr 3, 2012
7ae2e77
fixing runaway timer issue
asuth Apr 4, 2012
3076d3d
dont error out when you have a popup blocker
asuth Apr 5, 2012
2d74836
close popups of parse errors before opening new ones for the same url
asuth Apr 5, 2012
334228c
fix trailing whitespace
asuth Feb 24, 2013
06e15d7
making loading dot position: fixed instead of absolute so it shows wh…
asuth Feb 24, 2013
9c3f465
allow multiple paths to be used
asuth Mar 5, 2013
467901b
vogue puppet local changes
Jun 3, 2014
cce942d
move to newer socketio and use namespaces properly
Jun 3, 2014
460662b
BREAKS BACKWARDS COMPAT. Taking fs-watching capabilities out of vogue…
asuth Jun 24, 2014
3e0288d
adding console.log on SIGUSR and removing missed deletion
asuth Jun 24, 2014
8fdfe6d
upgrading socket.io, with main goal of supporting etags so that socke…
asuth Apr 4, 2016
fdec5bb
far-futures cache vogue-client.js .. a quick perf improvement, not id…
asuth Apr 4, 2016
31a4e62
moving code to top level
asuth Apr 4, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions client/vogue-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
// Vogue - Client
// Original Copyright (c) 2011 Andrew Davey (andrew@equin.co.uk)
// Adapted and rewritten by Quizlet (github.com/quizlet/vogue)

(function() {

var script,
hop = Object.prototype.hasOwnProperty,
head = document.getElementsByTagName("head")[0],
broken_sheet_popups = {};

function vogue() {
var stylesheets,
socket = io(script.rootUrl, {path: '/vogue/socket.io'}),
finishedLoadingCount,
sheetCount = 0,
loaderTimer;

// when stylesheets are being loaded, add a dot on the screen
// to indicate vogue progress
function animateLoader() {
if (++finishedLoadingCount !== sheetCount)
return;

loader.style.background = '#0965d6';
loader.style.border = '2px solid #cfe4ff';
loaderTimer = setTimeout(function() {
var opacity = 1;
// old school tweening to work in all browsers
loaderTimer = setInterval(function() {
opacity -= .05;
loader.style.filter = 'alpha(opacity=' + opacity*100 + ')';
loader.style.opacity = opacity;
// javascript rounding errors make it possible to get <= 0
if (opacity <= 0) {
resetLoader();
}
}, 30);
}, 500);
}

function resetLoader() {
// could be either a timeout or an interval, so clear both
clearInterval(loaderTimer);
clearTimeout(loaderTimer);
loader.setAttribute('style', 'display: none; opacity: 1; filter: alpha(opacity=100); background: #e6df27; position: fixed; top: 60px; left: 60px; z-index: 9999; height: 20px; width: 20px; border-radius: 20px; border: 2px solid #aba623;');
}

var loader = document.createElement('div');
loader.setAttribute('id', 'vogueLoader');
resetLoader();
document.getElementsByTagName('body')[0].appendChild(loader);

function updateAllStylesheets() {
console.log('Updating stylesheets');

finishedLoadingCount = 0;
resetLoader();
loader.style.display = 'block';

for (href in stylesheets) {
if (hop.call(stylesheets, href)) {
// use ajax to download contents of CSS
// and create a <style> tag with it. This prevents a flash of unstyled content
var new_url = stylesheets[href].href + (stylesheets[href].href.indexOf('?') === -1 ? "?": "&") + "_vogue_rand=" + Math.random();
var ajax = new XMLHttpRequest();
ajax.base_href = href;

ajax.onreadystatechange = function() {
if (this.readyState == 4) {

// popup windows can exist but be null
// close the old popup before opening a new one
if (hop.call(broken_sheet_popups, this.base_href) && broken_sheet_popups[this.base_href]) {
broken_sheet_popups[this.base_href].close();
delete broken_sheet_popups[this.base_href];
}

// look for Stylus parse errors
// (this isn't a stylus dependency, just an added feature for stylus)
var match = this.responseText.match(/ParseError: ([^:]+):(\d+)/);
// if we get a parse error, pop up it up and don't return the error
if (match) {
broken_sheet_popups[this.base_href] = window.open(this.base_href, "_blank", "height=650,width=800,toolbar=0");
return;
}

// http://www.phpied.com/dynamic-script-and-style-elements-in-ie/
var sheet = document.createElement('style');

sheet.setAttribute("type", "text/css");
if (sheet.styleSheet) {
// IE
sheet.styleSheet.cssText = this.responseText;
} else {
// the world
sheet.appendChild(document.createTextNode(this.responseText));
}
// replace the old stylesheet with the new
stylesheets[this.base_href].parentNode.replaceChild(sheet, stylesheets[this.base_href]);

stylesheets[this.base_href] = sheet;
// make it look like a <link> tag
sheet.href = this.base_href;

animateLoader();
}
}
ajax.open("GET", new_url, true);
ajax.send(null);
}
}
}

/**
* Fetch all the local stylesheets from the page.
*
* @returns {Object} The list of local stylesheets keyed by their base URL.
*/
function getLocalStylesheets() {

/**
* Checks if the stylesheet is local.
*
* @param {Object} link The link to check for.
* @returns {Boolean}
*/
function isLocalStylesheet(link) {
var href,
i,
isExternal = true;

if (link.getAttribute("rel") !== "stylesheet") {
return false;
}
href = link.href;

for (i = 0; i < script.bases.length; i += 1) {
if (href.indexOf(script.bases[i]) > -1) {
isExternal = false;
break;
}
}

return ! isExternal;
}

/**
* Checks if the stylesheet's media attribute is 'print'
*
* @param (Object) link The stylesheet element to check.
* @returns (Boolean)
*/
function isPrintStylesheet(link) {
return link.getAttribute("media") === "print";
}

/**
* Get the link's base URL.
*
* @param {String} href The URL to check.
* @returns {String|Boolean} The base URL, or false if no matches found.
*/
function getBase(href) {
var base, j;
for (j = 0; j < script.bases.length; j += 1) {
base = script.bases[j];
if (href.indexOf(base) > -1) {
return href.substr(base.length);
}
}
return false;
}

function getProperty(property) {
return this[property];
}

var stylesheets = {},
reImport = /@import\s+url\(["']?([^"'\)]+)["']?\)/g,
links = document.getElementsByTagName("link"),
link,
href,
matches,
content,
i,
m;

// Go through all the links in the page, looking for stylesheets.
for (i = 0, m = links.length; i < m; i += 1) {
link = links[i];
if (isPrintStylesheet(link)) continue;
if (!isLocalStylesheet(link)) continue;
// Link is local, get the base URL.
href = getBase(link.href);
if (href !== false) {
stylesheets[href] = link;
sheetCount++;
}
}

// Go through all the style tags, looking for @import tags.
links = document.getElementsByTagName("style");
for (i = 0, m = links.length; i < m; i += 1) {
if (isPrintStylesheet(links[i])) continue;
content = links[i].text || links[i].textContent;
while ((matches = reImport.exec(content))) {
link = {
rel: "stylesheet",
href: matches[1],
getAttribute: getProperty
};
if (isLocalStylesheet(link)) {
// Link is local, get the base URL.
href = getBase(link.href);
if (href !== false) {
stylesheets[href] = link;
sheetCount++;
}
}
}
}
return stylesheets;
}

stylesheets = getLocalStylesheets();
socket.on("update", updateAllStylesheets);
}

/**
* Load a script into the page, and call a callback when it is loaded.
*
* @param {String} src The URL of the script to be loaded.
* @param {Function} loadedCallback The function to be called when the script is loaded.
*/
function loadScript(src, loadedCallback) {
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", src);

// Call the callback when the script is loaded.
script.onload = loadedCallback;
script.onreadystatechange = function() {
if (this.readyState === "complete" || this.readyState === "loaded") {
loadedCallback();
}
};

head.appendChild(script);
}

/**
* Load scripts into the page, and call a callback when they are loaded.
*
* @param {Array} scripts The scripts to be loaded.
* @param {Function} loadedCallback The function to be called when all the scripts have loaded.
*/
function loadScripts(scripts, loadedCallback) {
var srcs = [],
property,
count,
i,
src,
countDown = function() {
count -= 1;
if (!count) {
loadedCallback();
}
};

for (property in scripts) {
if (!(property in window)) {
srcs.push(scripts[property]);
}
}

count = srcs.length;
if (!count) {
loadedCallback();
}

for (i = 0; i < srcs.length; i += 1) {
src = srcs[i];
loadScript(src, countDown);
}
}

/**
* Fetches the info for the vogue client.
*/
function getScriptInfo() {
var bases = [document.location.protocol + "//" + document.location.host],
scripts,
src,
rootUrl,
baseMatch;
if (typeof window.__vogue__ === "undefined") {
scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
src = scripts[i].getAttribute("src");
if (src && src.slice( - 15) === 'vogue-client.js') break;
}
rootUrl = src.match(/^(?:https?\:)?\/\/(.*?)\//)[1];
// There is an optional base argument, that can be used.
baseMatch = src.match(/\bbase=(.*)(&|$)/);

if (baseMatch) {
bases = bases.concat(baseMatch[1].split(","));
}
return {
rootUrl: rootUrl,
bases: bases
};
} else {
window.__vogue__.bases = bases;
return window.__vogue__;
}
}

script = getScriptInfo();
loadScripts({
io: window.location.protocol + '//' + script.rootUrl + "/vogue/socket.io/socket.io.js"
},
vogue);
} ());
11 changes: 7 additions & 4 deletions src/package.json → package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"name": "vogue",
"description": "Auto-reload stylesheets in web browser whenever the CSS files are saved.",
"version": "0.4.3",
"homepage": "http://github.com/andrewdavey/vogue",
"repository": "http://github.com/andrewdavey/vogue.git",
"version": "0.6.0",
"homepage": "http://github.com/quizlet/vogue",
"repository": "http://github.com/quizlet/vogue.git",
"author": "Andrew Davey <andrew@equin.co.uk> (http://aboutcode.net/)",
"contributors": [
{ "name": "Andrew Sutherland", "email": "andrew@quizlet.com"}
],
"directories": {
"lib": ""
},
Expand All @@ -15,7 +18,7 @@
"vogue": "./vogue.js"
},
"dependencies": {
"socket.io": "0.7.x",
"socket.io": "1.4.5",
"parseopt": ">=1.0.0"
}
}
Loading