Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

First three posts and massive style improvements!

  • Loading branch information...
commit 6ef581a0374cea1d7448d09c9e4c545cfe2fe4ad 1 parent b23e69b
Elijah Miller authored February 01, 2009
2  .gitignore
... ...
@@ -0,0 +1,2 @@
  1
+.DS_Store
  2
+_site/
0  README
No changes.
88  _layouts/default.html
... ...
@@ -0,0 +1,88 @@
  1
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  2
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3
+
  4
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
  5
+  <head>
  6
+     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  7
+     <title>
  8
+       {% if page.title %}
  9
+         {{ page.title }} //
  10
+       {% endif %}
  11
+       Elijah Miller
  12
+     </title>
  13
+     <meta name="author" content="Elijah Miller" />
  14
+     <link href="http://feeds.feedburner.com/jqr" rel="alternate" title="Elijah Miller" type="application/atom+xml" />
  15
+
  16
+     <link rel="stylesheet" href="/stylesheets/syntax.css" type="text/css" />
  17
+     <link rel="stylesheet" href="/stylesheets/default.css" type="text/css" media="screen, projection" />
  18
+  </head>
  19
+  <body>
  20
+    <a href="http://github.com/jqr"><img style="position: absolute; top: 0; left: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_left_orange_ff7600.png" alt="Fork me on GitHub" /></a>
  21
+
  22
+    <div id="site">
  23
+      <a id="subscribe" href="http://feeds.feedburner.com/jqr"><img src="/images/rss.png" alt="Subscribe to RSS Feed" /></a>
  24
+      
  25
+      <h1><a href="/">Elijah Miller</a></h1>
  26
+      
  27
+      <div class="clear"></div>
  28
+      
  29
+      <div id="sidebar">
  30
+        <h3>Me</h3>
  31
+        <dl id="contact">
  32
+          <dt>Name</dt>
  33
+          <dd>Elijah Miller / jqr</dd>
  34
+
  35
+          <dt>Email</dt>
  36
+          <dd><a href="mailto:elijah.miller@gmail.com">elijah.miller@gmail.com</a></dd>
  37
+
  38
+        </dl>
  39
+        <h3>And then some</h3>
  40
+        <ul id="profiles">
  41
+          <li><a href="http://twitter.com/jqr">Twitter</a></li>
  42
+
  43
+          <li><a href="http://github.com/jqr">GitHub</a></li>
  44
+
  45
+          <li><a href="http://workingwithrails.com/person/6150-elijah-miller">WorkingWithRails</a></li>
  46
+          
  47
+          <li><a href="http://www.linkedin.com/in/elijahmiller">LinkedIn</a></li>
  48
+        </ul>
  49
+
  50
+        <a href="http://www.workingwithrails.com/recommendation/new/person/6150-elijah-miller"><img src="http://workingwithrails.com/images/tools/compact-small.jpg" alt="Recommend me at Working With Rails"></a>
  51
+      </div>
  52
+      
  53
+      <div id="content">
  54
+        {{ content }}
  55
+      </div>
  56
+      
  57
+      <div class="clear"></div>
  58
+      
  59
+    </div>
  60
+
  61
+    <script type="text/javascript">
  62
+    //<![CDATA[
  63
+    (function() {
  64
+    		var links = document.getElementsByTagName('a');
  65
+    		var query = '?';
  66
+    		for(var i = 0; i < links.length; i++) {
  67
+    			if(links[i].href.indexOf('#disqus_thread') >= 0) {
  68
+    				query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&';
  69
+    			}
  70
+    		}
  71
+    		document.write('<script type="text/javascript" src="http://disqus.com/forums/jqr/get_num_replies.js' + query + '"></' + 'script>');
  72
+    	})();
  73
+    //]]>
  74
+    </script>
  75
+
  76
+    <script type="text/javascript">
  77
+      var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
  78
+      document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
  79
+      </script>
  80
+      <script type="text/javascript">
  81
+      try {
  82
+      var pageTracker = _gat._getTracker("UA-254246-10");
  83
+      pageTracker._trackPageview();
  84
+      } catch(err) {}
  85
+    </script>
  86
+    
  87
+  </body>
  88
+</html>
21  _layouts/post.html
... ...
@@ -0,0 +1,21 @@
  1
+---
  2
+layout: default
  3
+---
  4
+
  5
+<div id="post">
  6
+  <h2>{{ page.title }}</h2>
  7
+  {{ content }}
  8
+</div>
  9
+
  10
+<div id="disqus_thread"></div><script type="text/javascript" src="http://disqus.com/forums/jqr/embed.js"></script><noscript><a href="http://jqr.disqus.com/?url=ref">View comments.</a></noscript>
  11
+ 
  12
+<!-- 
  13
+  <div id="related">
  14
+    <h2>Related Posts</h2>
  15
+    <ul class="posts">
  16
+      {% for post in site.related_posts limit:3 %}
  17
+        <li><span>{{ post.date | date_to_string }}</span> &raquo; <a href="{{ post.url }}">{{ post.title }}</a></li>
  18
+      {% endfor %}
  19
+    </ul>
  20
+  </div>
  21
+-->
14  _posts/2009-01-31-all-my-code-are-belong-to-us.textile
Source Rendered
... ...
@@ -0,0 +1,14 @@
  1
+---
  2
+layout: post
  3
+title: All My Code Are Belong to Us
  4
+---
  5
+
  6
+I've really been pushing myself to move as much code as possible into the public space. <a href="http://github.com">GitHub</a> has come at a perfect time to encourage this behavior. It makes the tedious process of sharing code so easy you'd be stupid not to.
  7
+
  8
+I put all of it <a href="http://github.com/jqr">directly on GitHub</a> as a Rails plugin and a Ruby gem. Learning how to do this is a few hours of investment, but it's an investment you only need to make once. I'm using <a href="http://blog.evanweaver.com/files/doc/fauna/echoe/files/README.html">Echoe</a> right now, but I'll probably switch to <a href="http://technicalpickles.com/posts/craft-the-perfect-gem-with-jeweler">Jeweler</a> for even simpler releases.
  9
+
  10
+I am certain that making code reusable is one of the best ways to gain a solid understanding of how a software developer's tools should be used. Polishing up code for public release encourages a ton of good behavior that makes even personal reuse easier and as a side benefit, other people will improve my code for free!
  11
+
  12
+Most of the plugins and gems I write are simple enhancements to patterns and are the result of DRYing up code in a single project. After abstracting the initial idea from one project, I find it's much easier to recognize the same pattern in other projects.
  13
+
  14
+So take a few minutes and look for patterns in your code. Give the pattern a name. Uncover the simplest way to express it. Start using it. Discover it's limitations. Publish it. Talk about it.
68  _posts/2009-02-01-making-rails-serialize-even-better.textile
Source Rendered
... ...
@@ -0,0 +1,68 @@
  1
+---
  2
+layout: post
  3
+title: Making Rails' Serialize Even Better
  4
+---
  5
+
  6
+Rails has this handy method that allows you store almost any object in the database with ease. Most often I end up using it for storing optional attributes in a hash.
  7
+
  8
+Here is the proper syntax for telling Rails that there is an options attribute that should only store Hash values.
  9
+
  10
+{% highlight ruby %}
  11
+class User < ActiveRecord::Base
  12
+  serialize :options, Hash
  13
+end
  14
+{% endhighlight %}
  15
+
  16
+<h3>The problem</h3>
  17
+
  18
+The options attribute will start off as nil, and remain nil until you set it to something else. Setting the class_name to Hash only affects what you can write to this attribute.
  19
+
  20
+{% highlight irb %}
  21
+>> user = User.new
  22
+=> #<User id: nil, name: nil, options: nil>
  23
+>> user.options[:theme]
  24
+NoMethodError: You have a nil object when you didn't expect it!
  25
+You might have expected an instance of ActiveRecord::Base.
  26
+The error occurred while evaluating nil.[]
  27
+	from (irb):2
  28
+=> nil
  29
+{% endhighlight %}
  30
+
  31
+<h3>The solution</h3>
  32
+What we really need is to automatically return an empty Hash on this new object so we can go on our merry way.
  33
+
  34
+Add this to your environment.rb.
  35
+
  36
+{% highlight ruby %}
  37
+config.gem 'jqr-typed_serialize', 
  38
+  :lib => 'typed_serialize', 
  39
+  :source => 'http://gems.github.com'
  40
+{% endhighlight %}
  41
+
  42
+Now run this command to install the gem.
  43
+{% highlight sh %}
  44
+$ rake gems:install
  45
+{% endhighlight %}
  46
+
  47
+A quick change of our model will fix all of our woes.
  48
+{% highlight ruby %}
  49
+class User < ActiveRecord::Base
  50
+  typed_serialize :options, Hash
  51
+end
  52
+{% endhighlight %}
  53
+
  54
+{% highlight irb %}
  55
+>> user = User.new
  56
+=> #<User id: nil, name: nil, options: nil>
  57
+>> user.options[:theme]
  58
+=> nil
  59
+>> user.options
  60
+=> {}
  61
+{% endhighlight %}
  62
+
  63
+Voila!
  64
+
  65
+<h3>The how and why</h3>
  66
+
  67
+If you're curious about how this works, I've written a simple post describing the <a href="/2009/02/01/the-making-of-typed-serialize.html">the making of typed_serialize</a>, or you can <a href="http://github.com/jqr/typed_serialize">browse the code</a>.
  68
+
122  _posts/2009-02-01-the-making-of-typed-serialize.textile
Source Rendered
... ...
@@ -0,0 +1,122 @@
  1
+---
  2
+layout: post
  3
+title: The Making of typed_serialize
  4
+---
  5
+
  6
+My <a href="http://github.com/jqr/typed_serialize">typed_serialize plugin</a> came from the repetition of code just like this.
  7
+
  8
+{% highlight ruby %}
  9
+def options
  10
+  value = super 
  11
+  if value.is_a?(Hash)
  12
+    value
  13
+  else
  14
+    self.options = {}
  15
+  end
  16
+end
  17
+{% endhighlight %}
  18
+
  19
+It calls super to peek at what ActiveRecord would return for the serialized column. If it's a Hash, we just return it right away. If it's anything else we set it to a new Hash and return that.
  20
+
  21
+
  22
+<h3>Distilling the interface</h3>
  23
+
  24
+After thinking about the pattern for a bit, I decided that simplest shorthand would be this.
  25
+
  26
+{% highlight ruby %}
  27
+class User < ActiveRecord::Base
  28
+  typed_serialize :options, Hash
  29
+end
  30
+{% endhighlight %}
  31
+
  32
+This code says "there is a typed and serialized attribute named options, that will always be a Hash." Notice that it is nearly the same usage as the original serialize method.
  33
+
  34
+
  35
+<h3>Get to it</h3>
  36
+
  37
+First off, we define a method that is accessible at the time of class definition. Since the usage is the same as serialize, we can use the original serialize method definition as a starting point.
  38
+
  39
+{% highlight ruby %}
  40
+class ActiveRecord::Base 
  41
+  def self.typed_serialize(attr_name, class_name = Object)
  42
+  end
  43
+end
  44
+{% endhighlight %}
  45
+
  46
+On second thought, what's the point of class_name being optional? It made sense for the original serialize method, but not typed_serialize. Let's make class_name mandatory.
  47
+
  48
+{% highlight ruby %}
  49
+class ActiveRecord::Base 
  50
+  def self.typed_serialize(attr_name, class_name)
  51
+  end
  52
+end
  53
+{% endhighlight %}
  54
+
  55
+
  56
+OK, now our User model can properly execute, but it does absolutely nothing. So let's at least call Rails' serialize method to get the standard behavior.
  57
+
  58
+{% highlight ruby %}
  59
+class ActiveRecord::Base 
  60
+  def self.typed_serialize(attr_name, class_name = Object)
  61
+    serialize(attr_name, class_name)
  62
+  end
  63
+end
  64
+{% endhighlight %}
  65
+
  66
+<h3>Adding the meat</h3>
  67
+
  68
+Our repeated code revolved around a custom reader for a serialized attribute. So let's add a custom reader for attr_name using define_method and our original repeated code.
  69
+
  70
+{% highlight ruby %}
  71
+class ActiveRecord::Base 
  72
+  def self.typed_serialize(attr_name, class_name = Object)
  73
+    serialize(attr_name, class_name)
  74
+
  75
+    define_method(attr_name) do
  76
+      value = super 
  77
+      if value.is_a?(Hash)
  78
+        value
  79
+      else
  80
+        self.options = {}
  81
+      end
  82
+    end
  83
+  end
  84
+end
  85
+{% endhighlight %}
  86
+
  87
+The original code has a couple of small problems. It assumes the value should always be a Hash and written attribute is always named options.
  88
+
  89
+A quick look at serialize's implementation tells us it stores its data a hash with the key as the attribute name in string form, and the value is the class_name.
  90
+
  91
+{% highlight ruby %}
  92
+expected_class = self.class.serialized_attributes[attr_name.to_s]
  93
+{% endhighlight %}
  94
+
  95
+We'll use Ruby's send method to call a method with a name we won't know until runtime.
  96
+
  97
+{% highlight ruby %}
  98
+send("#{attr_name}=", expected_class.new)
  99
+{% endhighlight %}
  100
+
  101
+<h3>All together now.</h3>
  102
+
  103
+{% highlight ruby %}
  104
+class ActiveRecord::Base 
  105
+  def self.typed_serialize(attr_name, class_name = Object)
  106
+    serialize(attr_name, class_name)
  107
+
  108
+    define_method(attr_name) do
  109
+      expected_class = self.class.serialized_attributes[attr_name.to_s]
  110
+    
  111
+      value = super
  112
+      if value.is_a?(expected_class) 
  113
+        value
  114
+      else
  115
+        send("#{attr_name}=", expected_class.new)
  116
+      end
  117
+    end
  118
+  end
  119
+end
  120
+{% endhighlight %}
  121
+
  122
+This is my first post detailing an implementation. Interestingly enough, it alerted me to a few unnecessarily complex portions of even this tiny amount of code.
27  atom.xml
... ...
@@ -0,0 +1,27 @@
  1
+---
  2
+layout: nil
  3
+---
  4
+<?xml version="1.0" encoding="utf-8"?>
  5
+<feed xmlns="http://www.w3.org/2005/Atom">
  6
+ 
  7
+ <title>Elijah Miller</title>
  8
+ <link href="http://test.com/atom.xml" rel="self"/>
  9
+ <link href="http://test.com/"/>
  10
+ <updated>{{ site.time | date_to_xmlschema }}</updated>
  11
+ <id>http://test.com/</id>
  12
+ <author>
  13
+   <name>Elijah hMiller</name>
  14
+   <email>elijah.miller@gmail.com</email>
  15
+ </author>
  16
+ 
  17
+ {% for post in site.posts %}
  18
+   <entry>
  19
+     <title>{{ post.title }}</title>
  20
+     <link href="http://test.com{{ post.url }}"/>
  21
+     <updated>{{ post.date | date_to_xmlschema }}</updated>
  22
+     <id>http://test.com{{ post.id }}</id>
  23
+     <content type="html">{{ post.content | xml_escape }}</content>
  24
+   </entry>
  25
+ {% endfor %}
  26
+ 
  27
+</feed>
BIN  images/rss.png
30  index.html
... ...
@@ -1 +1,29 @@
1  
-testing
  1
+---
  2
+layout: default
  3
+---
  4
+ 
  5
+<div id="home">
  6
+  <h2>Posts</h2>
  7
+  <ul class="posts">
  8
+    {% for post in site.posts %}
  9
+      <li><span>{{ post.date | date_to_string }}</span>: <a href="{{ post.url }}">{{ post.title }}</a></li>
  10
+    {% endfor %}
  11
+  </ul>
  12
+
  13
+  <h2>Tweets</h2>
  14
+  <div id="tweets">
  15
+    Loading...
  16
+  </ul>
  17
+  
  18
+  <script type="text/javascript" src="/javascripts/twitter-1.11.2.js"></script>
  19
+  
  20
+  <script type="text/javascript">
  21
+    getTwitters('tweets', { 
  22
+      id: 'jqr', 
  23
+      count: 5, 
  24
+      enableLinks: true, 
  25
+      clearContents: true,
  26
+      template: '%text%'
  27
+    });
  28
+  </script>
  29
+</div>
307  javascripts/twitter-1.11.2.js
... ...
@@ -0,0 +1,307 @@
  1
+/**
  2
+ * remy sharp / http://remysharp.com
  3
+ * http://remysharp.com/2007/05/18/add-twitter-to-your-blog-step-by-step/
  4
+ *
  5
+ * @params
  6
+ *   cssIdOfContainer: e.g. twitters
  7
+ *   options: 
  8
+ *       {
  9
+ *           id: {String} username,
  10
+ *           count: {Int} 1-20, defaults to 1 - max limit 20
  11
+ *           prefix: {String} '%name% said', defaults to blank
  12
+ *           clearContents: {Boolean} true, removes contents of element specified in cssIdOfContainer, defaults to true
  13
+ *           ignoreReplies: {Boolean}, skips over tweets starting with '@', defaults to false
  14
+ *           template: {String} HTML template to use for LI element (see URL above for examples), defaults to predefined template
  15
+ *           enableLinks: {Boolean} linkifies text, defaults to true,
  16
+ *           timeout: {Int} How long before triggering onTimeout, defaults to 10 seconds if onTimeout is set
  17
+ *           onTimeoutCancel: {Boolean} Completely cancel twitter call if timedout, defaults to false
  18
+ *           onTimeout: {Function} Function to run when the timeout occurs. Function is bound to element specified with 
  19
+ *              cssIdOfContainer (i.e. 'this' keyword)
  20
+ *
  21
+ *      CURRENTLY DISABLED DUE TO CHANGE IN TWITTER API:
  22
+ *           withFriends: {Boolean} includes friend's status
  23
+ *
  24
+ *       }
  25
+ *
  26
+ * @license MIT (MIT-LICENSE.txt)
  27
+ * @version 1.11 - Added timeout functionality, and removed withFriends while Twitter works out API changes
  28
+ * @date $Date: 2008-10-16 18:49:40 +0100 (Thu, 16 Oct 2008) $
  29
+ */
  30
+
  31
+// to protect variables from resetting if included more than once
  32
+if (typeof renderTwitters != 'function') (function () {
  33
+    /** Private variables */
  34
+    var browser = (function() {
  35
+    	var b = navigator.userAgent.toLowerCase();
  36
+
  37
+    	// Figure out what browser is being used
  38
+    	return {
  39
+    		safari: /webkit/.test(b),
  40
+    		opera: /opera/.test(b),
  41
+    		msie: /msie/.test(b) && !(/opera/).test(b),
  42
+    		mozilla: /mozilla/.test(b) && !(/(compatible|webkit)/).test(b)
  43
+    	};
  44
+    })();
  45
+
  46
+    var guid = 0;
  47
+    var readyList = [];
  48
+    var isReady = false;
  49
+    
  50
+    /** Global functions */
  51
+    
  52
+    // to create a public function within our private scope, we attach the 
  53
+    // the function to the window object
  54
+    window.renderTwitters = function (obj, options) {
  55
+        // private shortcuts
  56
+        function node(e) {
  57
+            return document.createElement(e);
  58
+        }
  59
+        
  60
+        function text(t) {
  61
+            return document.createTextNode(t);
  62
+        }
  63
+
  64
+        var target = document.getElementById(options.twitterTarget);
  65
+        var data = null;
  66
+        var ul = node('ul'), li, statusSpan, timeSpan, i, max = obj.length > options.count ? options.count : obj.length;
  67
+        
  68
+        for (i = 0; i < max && obj[i]; i++) {
  69
+            data = getTwitterData(obj[i]);
  70
+                        
  71
+            if (options.ignoreReplies && obj[i].text.substr(0, 1) == '@') {
  72
+                max++;
  73
+                continue; // skip
  74
+            }
  75
+            
  76
+            li = node('li');
  77
+            
  78
+            if (options.template) {
  79
+                li.innerHTML = options.template.replace(/%([a-z_\-\.]*)%/ig, function (m, l) {
  80
+                    var r = data[l] + "" || "";
  81
+                    if (l == 'text' && options.enableLinks) r = linkify(r);
  82
+                    return r;
  83
+                });
  84
+            } else {
  85
+                statusSpan = node('span');
  86
+                statusSpan.className = 'twitterStatus';
  87
+                timeSpan = node('span');
  88
+                timeSpan.className = 'twitterTime';
  89
+                statusSpan.innerHTML = obj[i].text; // forces the entities to be converted correctly
  90
+
  91
+                if (options.enableLinks == true) {
  92
+                    statusSpan.innerHTML = linkify(statusSpan.innerHTML);
  93
+                }
  94
+
  95
+                timeSpan.innerHTML = relative_time(obj[i].created_at);
  96
+
  97
+                if (options.prefix) {
  98
+                    var s = node('span');
  99
+                    s.className = 'twitterPrefix';
  100
+                    s.innerHTML = options.prefix.replace(/%(.*?)%/g, function (m, l) {
  101
+                        return obj[i].user[l];
  102
+                    });
  103
+                    li.appendChild(s);
  104
+                    li.appendChild(text(' ')); // spacer :-(
  105
+                }
  106
+
  107
+                li.appendChild(statusSpan);
  108
+                li.appendChild(text(' '));
  109
+                li.appendChild(timeSpan);
  110
+            }
  111
+            
  112
+            ul.appendChild(li);
  113
+        }
  114
+
  115
+        if (options.clearContents) {
  116
+            while (target.firstChild) {
  117
+                target.removeChild(target.firstChild);
  118
+            }
  119
+        }
  120
+
  121
+        target.appendChild(ul);
  122
+    };
  123
+    
  124
+    window.getTwitters = function (target, id, count, options) {
  125
+        guid++;
  126
+        
  127
+
  128
+        if (typeof id == 'object') {
  129
+            options = id;
  130
+            id = options.id;
  131
+            count = options.count;
  132
+        } 
  133
+
  134
+        // defaulting options
  135
+        if (!count) count = 1;
  136
+        
  137
+        if (options) {
  138
+            options.count = count;
  139
+        } else {
  140
+            options = {};
  141
+        }
  142
+        
  143
+        if (!options.timeout && typeof options.onTimeout == 'function') {
  144
+            options.timeout = 10;
  145
+        }
  146
+        
  147
+        if (typeof options.clearContents == 'undefined') {
  148
+            options.clearContents = true;
  149
+        }
  150
+        
  151
+        // Hack to disable withFriends, twitter changed their API so this requires auth
  152
+        // http://getsatisfaction.com/twitter/topics/friends_timeline_api_call_suddenly_requires_auth
  153
+        if (options.withFriends) options.withFriends = false;
  154
+
  155
+        // need to make these global since we can't pass in to the twitter callback
  156
+        options['twitterTarget'] = target;
  157
+        
  158
+        // default enable links
  159
+        if (typeof options.enableLinks == 'undefined') options.enableLinks = true;
  160
+
  161
+        // this looks scary, but it actually allows us to have more than one twitter
  162
+        // status on the page, which in the case of my example blog - I do!
  163
+        window['twitterCallback' + guid] = function (obj) {
  164
+            if (options.timeout) {
  165
+                clearTimeout(window['twitterTimeout' + guid]);
  166
+            }
  167
+            renderTwitters(obj, options);
  168
+        };
  169
+
  170
+        // check out the mad currying!
  171
+        ready((function(options, guid) {
  172
+            return function () {
  173
+                // if the element isn't on the DOM, don't bother
  174
+                if (!document.getElementById(options.twitterTarget)) {
  175
+                    return;
  176
+                }
  177
+                
  178
+                var url = 'http://www.twitter.com/statuses/' + (options.withFriends ? 'friends_timeline' : 'user_timeline') + '/' + id + '.json?callback=twitterCallback' + guid + '&count=20';
  179
+
  180
+                if (options.timeout) {
  181
+                    window['twitterTimeout' + guid] = setTimeout(function () {
  182
+                        // cancel callback
  183
+                        if (options.onTimeoutCancel) window['twitterCallback' + guid] = function () {};
  184
+                        options.onTimeout.call(document.getElementById(options.twitterTarget));
  185
+                    }, options.timeout * 1000);
  186
+                }
  187
+                
  188
+                var script = document.createElement('script');
  189
+                script.setAttribute('src', url);
  190
+                document.getElementsByTagName('head')[0].appendChild(script);
  191
+            };
  192
+        })(options, guid));
  193
+    };
  194
+    
  195
+    // GO!
  196
+    DOMReady();
  197
+    
  198
+
  199
+    /** Private functions */
  200
+    
  201
+    function getTwitterData(orig) {
  202
+        var data = orig, i;
  203
+        for (i in orig.user) {
  204
+            data['user_' + i] = orig.user[i];
  205
+        }
  206
+        
  207
+        data.time = relative_time(orig.created_at);
  208
+        
  209
+        return data;
  210
+    }
  211
+    
  212
+    function ready(callback) {
  213
+        if (!isReady) {
  214
+            readyList.push(callback);
  215
+        } else {
  216
+            callback.call();
  217
+        }
  218
+    }
  219
+    
  220
+    function fireReady() {
  221
+        isReady = true;
  222
+        var fn;
  223
+        while (fn = readyList.shift()) {
  224
+            fn.call();
  225
+        }
  226
+    }
  227
+
  228
+    // ready and browser adapted from John Resig's jQuery library (http://jquery.com)
  229
+    function DOMReady() {
  230
+        if ( browser.mozilla || browser.opera ) {
  231
+            document.addEventListener( "DOMContentLoaded", fireReady, false );
  232
+        } else if ( browser.msie ) {
  233
+            // If IE is used, use the excellent hack by Matthias Miller
  234
+            // http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited
  235
+
  236
+            // Only works if you document.write() it
  237
+            document.write("<scr" + "ipt id=__ie_init defer=true src=//:><\/script>");
  238
+
  239
+            // Use the defer script hack
  240
+            var script = document.getElementById("__ie_init");
  241
+
  242
+            // script does not exist if jQuery is loaded dynamically
  243
+            if (script) {
  244
+                script.onreadystatechange = function() {
  245
+                    if ( this.readyState != "complete" ) return;
  246
+                    this.parentNode.removeChild( this );
  247
+                    fireReady.call();
  248
+                };
  249
+            }
  250
+
  251
+            // Clear from memory
  252
+            script = null;
  253
+
  254
+            // If Safari  is used
  255
+        } else if ( browser.safari ) {
  256
+            // Continually check to see if the document.readyState is valid
  257
+            var safariTimer = setInterval(function () {
  258
+                // loaded and complete are both valid states
  259
+                if ( document.readyState == "loaded" || 
  260
+                document.readyState == "complete" ) {
  261
+
  262
+                    // If either one are found, remove the timer
  263
+                    clearInterval( safariTimer );
  264
+                    safariTimer = null;
  265
+                    // and execute any waiting functions
  266
+                    fireReady.call();
  267
+                }
  268
+            }, 10);
  269
+        }
  270
+    }
  271
+    
  272
+    function relative_time(time_value) {
  273
+        var values = time_value.split(" ");
  274
+        time_value = values[1] + " " + values[2] + ", " + values[5] + " " + values[3];
  275
+        var parsed_date = Date.parse(time_value);
  276
+        var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
  277
+        var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
  278
+        delta = delta + (relative_to.getTimezoneOffset() * 60);
  279
+
  280
+        var r = '';
  281
+        if (delta < 60) {
  282
+            r = 'less than a minute ago';
  283
+        } else if(delta < 120) {
  284
+            r = 'about a minute ago';
  285
+        } else if(delta < (45*60)) {
  286
+            r = (parseInt(delta / 60)).toString() + ' minutes ago';
  287
+        } else if(delta < (2*90*60)) { // 2* because sometimes read 1 hours ago
  288
+            r = 'about an hour ago';
  289
+        } else if(delta < (24*60*60)) {
  290
+            r = 'about ' + (parseInt(delta / 3600)).toString() + ' hours ago';
  291
+        } else if(delta < (48*60*60)) {
  292
+            r = '1 day ago';
  293
+        } else {
  294
+            r = (parseInt(delta / 86400)).toString() + ' days ago';
  295
+        }
  296
+
  297
+        return r;
  298
+    }
  299
+
  300
+    function linkify(s) {
  301
+        return s.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/g, function(m) {
  302
+            return m.link(m);
  303
+        }).replace(/@[\S]+/g, function(m) {
  304
+            return '<a href="http://twitter.com/' + m.substr(1) + '">' + m + '</a>';
  305
+        });
  306
+    }
  307
+})();
94  stylesheets/default.css
... ...
@@ -0,0 +1,94 @@
  1
+.clear {
  2
+  clear: both;
  3
+}
  4
+
  5
+img {
  6
+  border: 0;
  7
+}
  8
+
  9
+body {
  10
+  line-height: 130%;
  11
+  font-size: 100%;
  12
+  color: #222;
  13
+  background: #fff;
  14
+  font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
  15
+}
  16
+
  17
+a:hover, #sidebar a:hover, #content a {
  18
+  color: #b40;
  19
+}
  20
+#site {
  21
+  width: 850px;
  22
+  margin: auto;
  23
+}
  24
+
  25
+#subscribe {
  26
+  float: right;
  27
+  margin: 20px;
  28
+}
  29
+
  30
+h1 {
  31
+  margin: 0;
  32
+  padding: 20px;
  33
+}
  34
+
  35
+h1 a {
  36
+  text-decoration: none;
  37
+  color: inherit;
  38
+}
  39
+
  40
+#slogan {
  41
+  margin: 15px;
  42
+}
  43
+
  44
+#sidebar {
  45
+  width: 160px;
  46
+  padding: 10px 20px 20px 20px;
  47
+  border-left: 5px solid #eee;
  48
+  float: right;
  49
+}
  50
+#sidebar a {
  51
+  display: block;
  52
+  color: inherit;
  53
+}
  54
+#sidebar dl, #sidebar ul {
  55
+  margin: 0 0 30px 0;
  56
+  padding: 0;
  57
+}
  58
+#sidebar dl#contact dt {
  59
+  display: none;
  60
+}
  61
+#sidebar dl#contact dd {
  62
+  margin: 0;
  63
+}
  64
+#sidebar ul {
  65
+  list-style-type: none;
  66
+}
  67
+#sidebar a {
  68
+  text-decoration: none;
  69
+}
  70
+#sidebar  {
  71
+}
  72
+
  73
+#content {
  74
+  width: 600px;
  75
+  padding: 0 20px 20px 20px;
  76
+}
  77
+#content ul {
  78
+  list-style-type: none;
  79
+  padding: 0;
  80
+}
  81
+#content li {
  82
+  margin-bottom: 10px;
  83
+}
  84
+
  85
+h3 {
  86
+  margin: 0 0 10px 0;
  87
+  padding: 0;
  88
+}
  89
+
  90
+.highlight pre {
  91
+  background: #eee;
  92
+  border: 1px solid #ccc;
  93
+  padding: 15px;
  94
+}
62  stylesheets/syntax.css
... ...
@@ -0,0 +1,62 @@
  1
+/* From http://github.com/mojombo/tpw/raw/master/css/syntax.css */
  2
+
  3
+.highlight  { background: #ffffff; }
  4
+.highlight .c { color: #999988; font-style: italic } /* Comment */
  5
+.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
  6
+.highlight .k { font-weight: bold } /* Keyword */
  7
+.highlight .o { font-weight: bold } /* Operator */
  8
+.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
  9
+.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
  10
+.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
  11
+.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
  12
+.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
  13
+.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
  14
+.highlight .ge { font-style: italic } /* Generic.Emph */
  15
+.highlight .gr { color: #aa0000 } /* Generic.Error */
  16
+.highlight .gh { color: #999999 } /* Generic.Heading */
  17
+.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
  18
+.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
  19
+.highlight .go { color: #888888 } /* Generic.Output */
  20
+.highlight .gp { color: #555555 } /* Generic.Prompt */
  21
+.highlight .gs { font-weight: bold } /* Generic.Strong */
  22
+.highlight .gu { color: #aaaaaa } /* Generic.Subheading */
  23
+.highlight .gt { color: #aa0000 } /* Generic.Traceback */
  24
+.highlight .kc { font-weight: bold } /* Keyword.Constant */
  25
+.highlight .kd { font-weight: bold } /* Keyword.Declaration */
  26
+.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
  27
+.highlight .kr { font-weight: bold } /* Keyword.Reserved */
  28
+.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
  29
+.highlight .m { color: #009999 } /* Literal.Number */
  30
+.highlight .s { color: #d14 } /* Literal.String */
  31
+.highlight .na { color: #008080 } /* Name.Attribute */
  32
+.highlight .nb { color: #0086B3 } /* Name.Builtin */
  33
+.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
  34
+.highlight .no { color: #008080 } /* Name.Constant */
  35
+.highlight .ni { color: #800080 } /* Name.Entity */
  36
+.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
  37
+.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
  38
+.highlight .nn { color: #555555 } /* Name.Namespace */
  39
+.highlight .nt { color: #000080 } /* Name.Tag */
  40
+.highlight .nv { color: #008080 } /* Name.Variable */
  41
+.highlight .ow { font-weight: bold } /* Operator.Word */
  42
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
  43
+.highlight .mf { color: #009999 } /* Literal.Number.Float */
  44
+.highlight .mh { color: #009999 } /* Literal.Number.Hex */
  45
+.highlight .mi { color: #009999 } /* Literal.Number.Integer */
  46
+.highlight .mo { color: #009999 } /* Literal.Number.Oct */
  47
+.highlight .sb { color: #d14 } /* Literal.String.Backtick */
  48
+.highlight .sc { color: #d14 } /* Literal.String.Char */
  49
+.highlight .sd { color: #d14 } /* Literal.String.Doc */
  50
+.highlight .s2 { color: #d14 } /* Literal.String.Double */
  51
+.highlight .se { color: #d14 } /* Literal.String.Escape */
  52
+.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
  53
+.highlight .si { color: #d14 } /* Literal.String.Interpol */
  54
+.highlight .sx { color: #d14 } /* Literal.String.Other */
  55
+.highlight .sr { color: #009926 } /* Literal.String.Regex */
  56
+.highlight .s1 { color: #d14 } /* Literal.String.Single */
  57
+.highlight .ss { color: #990073 } /* Literal.String.Symbol */
  58
+.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
  59
+.highlight .vc { color: #008080 } /* Name.Variable.Class */
  60
+.highlight .vg { color: #008080 } /* Name.Variable.Global */
  61
+.highlight .vi { color: #008080 } /* Name.Variable.Instance */
  62
+.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */

0 notes on commit 6ef581a

Please sign in to comment.
Something went wrong with that request. Please try again.