livecss 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +10 -0
- data/Rakefile +1 -0
- data/lib/livecss/engine.rb +4 -0
- data/lib/livecss/version.rb +3 -0
- data/lib/livecss.rb +2 -0
- data/livecss.gemspec +22 -0
- data/vendor/assets/javascripts/livecss.js +174 -0
- metadata +68 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/livecss.rb
ADDED
data/livecss.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "livecss/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "livecss"
|
7
|
+
s.version = Livecss::VERSION
|
8
|
+
s.authors = ["Alex K"]
|
9
|
+
s.email = ["flskif@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Live CSS - instantly see your CSS changes in browser}
|
12
|
+
s.description = %q{}
|
13
|
+
|
14
|
+
s.rubyforge_project = "livecss"
|
15
|
+
s.required_rubygems_version = ">= 1.3.6"
|
16
|
+
s.add_dependency "railties", ">= 3.2.0", "< 5.0"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
$(document).ready(function() { livecss.watchAll(); })
|
2
|
+
|
3
|
+
/*
|
4
|
+
* Live CSS will monitor <link> tags on the page and poll the server for changes to the CSS. This enables you
|
5
|
+
* to refresh styles without disrupting the state of the view, and the page updates itself without you
|
6
|
+
* having to switch from your editor to the browser and hit refresh.
|
7
|
+
*
|
8
|
+
* Usage:
|
9
|
+
* livecss.watchAll() - starts polling all <link> tags in the current page for changes.
|
10
|
+
*
|
11
|
+
* If you want more fine grained control over which CSS is being autoreloaded:
|
12
|
+
* livecss.watch(linkElement) - start watching a single <link> element for changes.
|
13
|
+
* livecss.unwatchAll()
|
14
|
+
* livecss.unwatch(linkElement)
|
15
|
+
*
|
16
|
+
* For convenience, livecss will call watchAll() right away if the page has "startlivecss=true" in the URL's
|
17
|
+
* query string.
|
18
|
+
*/
|
19
|
+
var livecss = {
|
20
|
+
// How often to poll for changes to the CSS.
|
21
|
+
pollFrequency: 1000,
|
22
|
+
outstandingRequests: {}, // stylesheet url => boolean
|
23
|
+
filesLastModified: {}, // stylesheet url => last modified timestamp
|
24
|
+
watchTimers: {}, // stylesheet url => timer ID
|
25
|
+
|
26
|
+
/*
|
27
|
+
* Begins polling all link elements on the current page for changes.
|
28
|
+
*/
|
29
|
+
watchAll: function() {
|
30
|
+
this.unwatchAll();
|
31
|
+
var timerId = setInterval(this.proxy(function() {
|
32
|
+
var linkElements = document.getElementsByTagName("link");
|
33
|
+
var validMediaTypes = ["screen", "handheld", "all", ""];
|
34
|
+
for (var i = 0; i < linkElements.length; i++) {
|
35
|
+
var media = (linkElements[i].getAttribute("media") || "").toLowerCase();
|
36
|
+
if (linkElements[i].getAttribute("rel") == "stylesheet"
|
37
|
+
&& livecss.indexOf(validMediaTypes, media) >= 0
|
38
|
+
&& this.isLocalLink(linkElements[i])) {
|
39
|
+
this.refreshLinkElement(linkElements[i]);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}), this.pollFrequency);
|
43
|
+
this.watchTimers["all"] = timerId;
|
44
|
+
},
|
45
|
+
|
46
|
+
watch: function(linkElement) {
|
47
|
+
var url = linkElement.getAttribute("href");
|
48
|
+
this.unwatch(url);
|
49
|
+
this.watchTimers[url] = setInterval(this.proxy(function() {
|
50
|
+
var linkElement = this.linkElementWithHref(url);
|
51
|
+
this.refreshLinkElement(linkElement);
|
52
|
+
}), this.pollFrequency);
|
53
|
+
},
|
54
|
+
|
55
|
+
unwatchAll: function() {
|
56
|
+
for (var url in this.watchTimers)
|
57
|
+
this.unwatch(url);
|
58
|
+
},
|
59
|
+
|
60
|
+
unwatch: function(url) {
|
61
|
+
if (this.watchTimers[url] != null) {
|
62
|
+
clearInterval(this.watchTimers[url]);
|
63
|
+
delete this.watchTimers[url];
|
64
|
+
delete this.outstandingRequests[url];
|
65
|
+
}
|
66
|
+
},
|
67
|
+
|
68
|
+
linkElementWithHref: function(url) {
|
69
|
+
var linkElements = document.getElementsByTagName("link");
|
70
|
+
for (var i = 0; i < linkElements.length; i++)
|
71
|
+
if (linkElements[i].href == url)
|
72
|
+
return linkElements[i]
|
73
|
+
},
|
74
|
+
|
75
|
+
/*
|
76
|
+
* Replaces a link element with a new one for the given URL. This has to wait for the new <link> to fully
|
77
|
+
* load, because simply changing the href on an existing <link> causes the page to flicker.
|
78
|
+
*/
|
79
|
+
replaceLinkElement: function(linkElement, stylesheetUrl) {
|
80
|
+
var parent = linkElement.parentNode;
|
81
|
+
var sibling = linkElement.nextSibling;
|
82
|
+
var url = this.addCacheBust(linkElement.href);
|
83
|
+
|
84
|
+
var newLinkElement = document.createElement("link");
|
85
|
+
newLinkElement.href = url;
|
86
|
+
newLinkElement.setAttribute("rel", "stylesheet");
|
87
|
+
|
88
|
+
if (sibling)
|
89
|
+
parent.insertBefore(newLinkElement, sibling);
|
90
|
+
else
|
91
|
+
parent.appendChild(newLinkElement);
|
92
|
+
|
93
|
+
// We're polling to check whether the CSS is loaded, because firefox doesn't support an onload event
|
94
|
+
// for <link> elements.
|
95
|
+
var loadingTimer = setInterval(this.proxy(function() {
|
96
|
+
if (!this.isCssElementLoaded(newLinkElement)) return;
|
97
|
+
if (typeof(console) != "undefined")
|
98
|
+
console.log("CSS refreshed:", this.removeCacheBust(url));
|
99
|
+
clearInterval(loadingTimer);
|
100
|
+
delete this.outstandingRequests[this.removeCacheBust(url)];
|
101
|
+
parent.removeChild(linkElement);
|
102
|
+
}), 100);
|
103
|
+
},
|
104
|
+
|
105
|
+
/*
|
106
|
+
* Refreshes the provided linkElement if it's changed. We issue a HEAD request for the CSS. If its
|
107
|
+
* last-modified header is changed, we remove and re-add the <link> element to the DOM which trigger a
|
108
|
+
* re-render from the browser. This uses a cache-bust querystring parameter to ensure we always bust through
|
109
|
+
* the browser's cache.
|
110
|
+
*/
|
111
|
+
refreshLinkElement: function(linkElement) {
|
112
|
+
var url = this.removeCacheBust(linkElement.getAttribute("href"));
|
113
|
+
if (this.outstandingRequests[url]) return;
|
114
|
+
var request = new XMLHttpRequest();
|
115
|
+
this.outstandingRequests[url] = request;
|
116
|
+
var cacheBustUrl = this.addCacheBust(url);
|
117
|
+
|
118
|
+
request.onreadystatechange = this.proxy(function(event) {
|
119
|
+
if (request.readyState != 4) return;
|
120
|
+
delete this.outstandingRequests[url];
|
121
|
+
if (request.status != 200 && request.status != 304) return;
|
122
|
+
var lastModified = Date.parse(request.getResponseHeader("Last-Modified"));
|
123
|
+
if (!this.filesLastModified[url] || this.filesLastModified[url] < lastModified) {
|
124
|
+
this.filesLastModified[url] = lastModified;
|
125
|
+
this.replaceLinkElement(linkElement, cacheBustUrl);
|
126
|
+
}
|
127
|
+
});
|
128
|
+
request.open("HEAD", cacheBustUrl);
|
129
|
+
request.send(null);
|
130
|
+
},
|
131
|
+
|
132
|
+
isCssElementLoaded: function(cssElement) {
|
133
|
+
// cssElement.sheet.cssRules will throw an error in firefox when the css file is not yet loaded.
|
134
|
+
try { return (cssElement.sheet && cssElement.sheet.cssRules.length > 0); } catch(error) { }
|
135
|
+
return false;
|
136
|
+
},
|
137
|
+
|
138
|
+
/* returns true for local urls such as: '/screen.css', 'http://mydomain.com/screen.css', 'css/screen.css'
|
139
|
+
*/
|
140
|
+
isLocalLink: function(linkElement) {
|
141
|
+
//On all tested browsers, this javascript property returns a normalized URL
|
142
|
+
var url = linkElement.href;
|
143
|
+
var regexp = new RegExp("^\/|^" +
|
144
|
+
document.location.protocol + "//" + document.location.host);
|
145
|
+
return (url.search(regexp) == 0);
|
146
|
+
},
|
147
|
+
|
148
|
+
/*
|
149
|
+
* Adds and removes a "cache_bust" querystring parameter to the given URLs. This is so we always bust
|
150
|
+
* through the browser's cache when checking for updated CSS.
|
151
|
+
*/
|
152
|
+
addCacheBust: function(url) { return this.removeCacheBust(url) + "?cache_bust=" + (new Date()).getTime(); },
|
153
|
+
removeCacheBust: function(url) { return url.replace(/\?cache_bust=[^&]+/, ""); },
|
154
|
+
|
155
|
+
/* A utility method to bind the value of "this". Equivalent to jQuery's proxy() function. */
|
156
|
+
proxy: function(fn) {
|
157
|
+
var self = this;
|
158
|
+
return function() { return fn.apply(self, []); };
|
159
|
+
},
|
160
|
+
|
161
|
+
/* Unfortunately IE7 doesn't have this built-in. */
|
162
|
+
indexOf: function(array, item) {
|
163
|
+
for (var i = 0; i < array.length; i++) { if (array[i] == item) return i; }
|
164
|
+
return -1;
|
165
|
+
},
|
166
|
+
|
167
|
+
/* A utility function for abstracting the difference between event listening in IE and other browsers. */
|
168
|
+
addEventListener: function(object, event, fn) {
|
169
|
+
object.attachEvent ? object.attachEvent("on" + event, fn) : object.addEventListener(event, fn, false);
|
170
|
+
}
|
171
|
+
};
|
172
|
+
|
173
|
+
if (window.location.search.toString().indexOf("startlivecss=true") >= 0)
|
174
|
+
livecss.addEventListener(window, "load", function() { livecss.watchAll(); });
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: livecss
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alex K
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: railties
|
16
|
+
requirement: &70205702935160 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.0
|
22
|
+
- - <
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '5.0'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: *70205702935160
|
28
|
+
description: ''
|
29
|
+
email:
|
30
|
+
- flskif@gmail.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- .gitignore
|
36
|
+
- Gemfile
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- lib/livecss.rb
|
40
|
+
- lib/livecss/engine.rb
|
41
|
+
- lib/livecss/version.rb
|
42
|
+
- livecss.gemspec
|
43
|
+
- vendor/assets/javascripts/livecss.js
|
44
|
+
homepage: ''
|
45
|
+
licenses: []
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.6
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project: livecss
|
64
|
+
rubygems_version: 1.8.17
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Live CSS - instantly see your CSS changes in browser
|
68
|
+
test_files: []
|