Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Bulldog, first version.

  • Loading branch information...
commit 36d55700d956792149ecdab363ff7411124450c0 0 parents
John Wedgwood authored
Showing with 1,053 additions and 0 deletions.
  1. +3 −0  .gitignore
  2. +6 −0 Gemfile
  3. +20 −0 Gemfile.lock
  4. +139 −0 README.markdown
  5. +85 −0 blog_app.rb
  6. +59 −0 blog_config.rb
  7. +9 −0 config.ru
  8. +14 −0 layout/layout.haml
  9. +9 −0 layout/page.haml
  10. +3 −0  layout/plain.haml
  11. +3 −0  layout/post.haml
  12. +15 −0 layout/post/_comments.haml
  13. +31 −0 layout/post/_post_content.haml
  14. +4 −0 layout/sidebar/_about.haml
  15. +5 −0 layout/sidebar/_bars.haml
  16. +7 −0 layout/sidebar/_nav.haml
  17. +6 −0 layout/sidebar/_recent.haml
  18. +18 −0 layout/sidebar/_social.haml
  19. +5 −0 layout/sidebar/_title.haml
  20. +6 −0 layout/sidebar/sidebar.haml
  21. +61 −0 layout/stylesheets/_article.scss
  22. +16 −0 layout/stylesheets/_fonts_and_colors.scss
  23. +96 −0 layout/stylesheets/_html.scss
  24. +36 −0 layout/stylesheets/_layout.scss
  25. +32 −0 layout/stylesheets/_mixins.scss
  26. +58 −0 layout/stylesheets/_sidebar.scss
  27. +11 −0 layout/stylesheets/styles.scss
  28. +3 −0  lib/blog.rb
  29. +83 −0 lib/blog/page_set.rb
  30. +44 −0 lib/blog/pages_and_posts.rb
  31. +40 −0 lib/blog/template_extensions.rb
  32. +44 −0 pages.rb
  33. +2 −0  pages/404.haml
  34. +2 −0  pages/500.haml
  35. +2 −0  pages/about.haml
  36. +7 −0 pages/archive.haml
  37. +2 −0  pages/contact.haml
  38. +6 −0 pages/index.haml
  39. +8 −0 pages/tag.haml
  40. +30 −0 posts.rb
  41. +8 −0 posts/sample-page-1.haml
  42. +8 −0 posts/sample-page-2.haml
  43. BIN  public/favicon.png
  44. BIN  public/images/paris.jpg
  45. BIN  public/images/portrait_100x100.jpg
  46. +7 −0 redirects.rb
3  .gitignore
@@ -0,0 +1,3 @@
+.bundle
+tmp/
+.sass-cache
6 Gemfile
@@ -0,0 +1,6 @@
+source 'http://rubygems.org'
+
+gem 'sinatra'
+gem 'haml'
+gem 'hashie'
+gem 'builder'
20 Gemfile.lock
@@ -0,0 +1,20 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ builder (3.0.0)
+ haml (3.0.25)
+ hashie (1.0.0)
+ rack (1.2.2)
+ sinatra (1.2.1)
+ rack (~> 1.1)
+ tilt (>= 1.2.2, < 2.0)
+ tilt (1.2.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ builder
+ haml
+ hashie
+ sinatra
139 README.markdown
@@ -0,0 +1,139 @@
+# Bulldog
+
+Bulldog is a simple blogging platform designed to solve a few simple goals:
+
+* Development of layout, pages and new posts happens on my laptop
+* Free to deploy (via Heroku)
+* Dead simple to understand and modify
+* Written in Ruby so it's easy to hack
+* Layout, posts and pages written in Haml, because I like Haml
+* Meta data separated from the page and post content, because it seems cleaner
+* Simple layout scheme that isn't really theme based
+* Comments handled by an outside service (in my case, disqus)
+
+# Quick Setup
+
+Assuming you have a heroku account, you can do this -- fork the repo, and clone it to your local machine, then:
+
+* Edit blog_config.rb to provide your name, profile and other information
+* git commit -a -m "First Blog Commit"
+* heroku create
+* git push heroku master
+* heroku open
+
+At that point you should be staring at your new blog, with sample content.
+
+To create your first post:
+
+* Edit a new post in the file posts/{your-post-template-name}.haml
+* Add a new post to the top of the file posts.rb
+ post '/choose-an-appropriate-slug' do |p|
+ p.title = "Insert Title Here"
+ p.template = "your-post-template-name"
+ p.published_at = DateTime.parse('2011-04-01') # Choose a date
+ end
+* git commit -a -m "First Post"
+* git push heroku master
+* heroku open
+
+You may want to preview things before you actually make them live, but that's entirely up to you. How you do that depends on your own server setup.
+
+## Configuration
+
+The main blog configuration code is in the file blog_config.rb, and contains settings for blog name, description, profile image, favicon, disqus, analytics, etc. There are required elements and optional elements. Of course
+you can just go edit the layouts files directly if you want.
+
+## Defining Pages and Posts
+
+Bulldog is built using Sinatra and Haml. It operates on a few really simple principles. There are two types of objects in the system - pages and posts.
+
+Pages and posts are written in Haml, which means that they can also include ruby code. Pages and posts are added to the blog by declaring them using this syntax:
+
+ #-----------
+ # pages.rb
+ #-----------
+ page '/about' do |p|
+ p.template = 'about'
+ p.title = 'About Me'
+ end
+
+ # ... more like this ...
+
+
+ #-----------
+ # posts.rb
+ #-----------
+ post '/writing-an-instagram-web-client' do |p|
+ p.title = 'Writing an Instagram Web Client'
+ p.subtitle = 'Ahhh, Paris...'
+ p.template = 'writing-an-instagram-web-client'
+ p.published_at = DateTime.parse('2011-03-24')
+ p.tags = %w{instagram}
+ p.summary = %Q{
+ Most of my Instagram consumption happens outside my phone,
+ which makes it hard to follow people. I wrote an Instagram
+ client to make that easier.
+ }
+ end
+
+ # ... more like this ...
+
+In the end this means is that a page is defined by two elements:
+
+* The page content
+* Meta data defined by this simple Hash
+
+Pages and posts are declared in the files pages.rb and posts.rb. The content for a page is in the file pages/{page-template-name}.haml and posts are located in the file posts/{post-template-name}.haml
+
+
+## Rendering Pages and Posts
+
+Pages and posts are rendered inside layouts. All layout related information is stored in the folder layout/. The default layout for a page is layout/page.haml. The default layout for a post is layout/post.haml. An individual page or post can override this default by setting a new layout in the declaration, so:
+
+ page '/' do |p|
+ # ...
+ p.layout = 'blank' # or any other layout name you want
+ end
+
+When a page or post is rendered, the page template, and all the layout code has access to a variable @page. (This name "@page" is used for both pages and posts, to keep layout code simpler). It also has access to all pages and posts via the variables @posts and @pages. This is useful for rendering "recent posts" or navigational elements.
+
+The variable @page is set to the Hash of attributes which were set in the declaration of the page or post. In practice this means that you can go and add attributes any time you want by simply declaring them and then using them in a template or layout.
+
+
+## Nesting Layouts and Partials
+
+Since it is really common in a blog to have nested layouts (e.g. post is rendered in a layout that has a title and that is rendered in a layout that has the sidebar + chrome), there are a few extensions to Sinatra.
+
+"inside" - This helper allows you to define a Haml block that is rendered inside another layout, so:
+
+ = inside :master_layout do
+ %p Content here...
+
+"partial" - This helper allows you to render a partial template without any layout, so:
+
+ = partial :'sidebar/sidebar'
+
+
+## Redirects
+
+If you have specific redirects you want to handle (e.g. you renamed a post), this is done in the file redirects.rb. The syntax is pretty simple:
+
+ module Redirect
+ #::::::::::::::::::::::::::::::::::::::::::::::::::::::
+ # Which URLs to redirect
+ Urls = {
+ '/old/path' => '/new/path'
+ }
+ end
+
+This mapping is used in the code that handles page-not-found (404) errors. If there is no matching page, then the redirects are checked before telling the user that the page could not be found.
+
+
+## Organization
+
+* Code to manage the blog is located in lib/**.rb
+* The main sinatra app is located in blog_app.rb, and manages RSS feeds, CSS, 404 and 500 errors.
+* Declaration of pages and posts is in pages.rb and posts.rb
+* Post content is located in posts/*.haml
+* Page content is located in pages/*.haml
+* Custom redirects are located in redirects.rb
85 blog_app.rb
@@ -0,0 +1,85 @@
+require 'sinatra'
+require 'haml'
+require 'lib/blog'
+
+# Load the blog contents
+require 'blog_config'
+require 'pages'
+require 'posts'
+require 'redirects'
+
+# Haml config
+set :haml, :format => :html5
+
+
+#::::::::::::::::::::::::::::::::::::::::
+# Before every page load
+#::::::::::::::::::::::::::::::::::::::::
+before do
+ # Set up pages before we process them
+ PageSet.config_for_request(request, params)
+end
+
+
+#::::::::::::::::::::::::::::::::::::::::
+# SCSS and other "Static" Content
+#::::::::::::::::::::::::::::::::::::::::
+get '/styles.css' do
+ scss :'stylesheets/styles'
+end
+
+
+#::::::::::::::::::::::::::::::::::::::::
+# RSS feed, hand coded
+#::::::::::::::::::::::::::::::::::::::::
+get '/rss' do
+ # Ensure the response is useful for the caller
+ cache_control :public, :must_revalidate, :max_age => 60
+ content_type 'application/rss+xml'
+
+ # For URL generation
+ url_prefix = "#{request.scheme}://#{request.host_with_port}"
+
+ # Generate the feed
+ builder do |xml|
+ xml.instruct! :xml, :version => '1.0'
+ xml.rss :version => "2.0" do
+ xml.channel do
+ xml.title BlogConfig.title
+ xml.description BlogConfig.description
+ xml.link url_prefix
+
+ PageSet.all_pages_of_type('post').each do |post|
+ xml.item do
+ xml.title post.title
+ xml.link "#{url_prefix}#{post.path}"
+ xml.description render(:haml, post.template.to_sym, {}, {:layout => false})
+ xml.pubDate Time.parse(post.published_at.to_s).rfc822()
+ xml.guid "#{url_prefix}#{post.path}"
+ end
+ end
+ end
+ end
+ end
+end
+
+
+#::::::::::::::::::::::::::::::::::::::::
+# Error Handling
+#::::::::::::::::::::::::::::::::::::::::
+not_found do
+ # Check to see if we have a redirect for this path
+ Redirect::Urls.each do |from_path, to_path|
+ if (from_path == request.path) then
+ redirect(to_path)
+ halt
+ end
+ end
+
+ # Nope...
+ render_page '/404'
+end
+
+error do
+ render_page '/500'
+end
59 blog_config.rb
@@ -0,0 +1,59 @@
+#:::::::::::::::::::::::::::::::::::::::::::::
+# Global blog configuration
+#:::::::::::::::::::::::::::::::::::::::::::::
+BlogConfig = Hashie::Mash.new({
+ #:::::::::::::::::::::::::::::::::::::::::::::
+ # Required
+ #:::::::::::::::::::::::::::::::::::::::::::::
+
+ # Placed in the title tag of every page along with the page title
+ :title => 'Your Blog Name',
+
+ # Used for the RSS feed and the default page meta description
+ :description => 'All the stuff you blog about',
+
+ # Path to the favicon for this blog
+ :favicon => '/favicon.png',
+
+ #::::::::::::::::::::::::::::::::::::::::
+ # See layout/sidebar/_about.haml for where these are used
+
+ # The owner name, which can easily match the title...
+ :owner_name => 'Your Name',
+
+ # Short paragraph about the blog owner
+ :about => %Q{
+ Your great bio goes here.
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
+ ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut ...
+ },
+
+ # Square profile image for the blog owner
+ :profile_image => '/images/portrait_100x100.jpg',
+
+
+ #:::::::::::::::::::::::::::::::::::::::::::::
+ # Optional
+ #:::::::::::::::::::::::::::::::::::::::::::::
+
+ # If you use Feedburner, place the feed URL here. Default path is '/rss'
+ # :rss_feed => 'http://feeds.feedburner.com/feedburner-name',
+
+ # If you use TypeKit, you should place the script from TypeKit here
+ # :typekit => %Q{
+ # <script type='text/javascript' src='http://use.typekit.com/big-code-here.js'></script>
+ # <script type='text/javascript'>
+ # try{Typekit.load();}catch(e){}
+ # </script>
+ # },
+
+ # Replace these with real urls to your pages (see layout/sidebar/_social.haml)
+ :twitter_url => 'http://twitter.com/',
+ :facebook_url => 'http://www.facebook.com/',
+ :linked_in_url => 'http://www.linkedin.com/',
+ :tumblr_url => 'http://www.tumblr.com/',
+
+ # Disqus comment support is enabled by setting this value (see layout/post/_comments.haml)
+ # :disqus_shortname => 'disqus_shortname'
+})
9 config.ru
@@ -0,0 +1,9 @@
+require 'blog_app'
+
+if ENV['RACK_ENV'] == 'development' then
+ log = File.new("tmp/log/sinatra.log", "a")
+ STDOUT.reopen(log)
+ STDERR.reopen(log)
+end
+
+run Sinatra::Application
14 layout/layout.haml
@@ -0,0 +1,14 @@
+!!!
+%html
+ %head
+ %title= "#{BlogConfig.title}: #{@page.title}"
+ %meta{:type => 'Description', :content => (@page.description || BlogConfig.description)}
+ %link{:rel => 'shortcut icon', :href => BlogConfig.favicon}
+ %link{:rel => 'alternate',:type => 'application/rss+xml', :href => (BlogConfig.rss_feed || '/rss'), :title => 'RSS'}
+ %link{:rel => "stylesheet", :type => "text/css", :href => "/styles.css", :media => "screen"}
+ = BlogConfig.typekit unless (BlogConfig.typekit.to_s.strip == '')
+ %body
+ #sidebar
+ = partial :'sidebar/sidebar'
+ = yield
+ .clear
9 layout/page.haml
@@ -0,0 +1,9 @@
+= inside :layout do
+ %article
+ %header
+ %h1
+ %a{:href => @page.path}= @page.title
+ %h2= @page.subtitle
+ .content
+ = partial @page.template.to_sym
+ .clear
3  layout/plain.haml
@@ -0,0 +1,3 @@
+= inside :layout do
+ = partial @page.template.to_sym
+ .clear
3  layout/post.haml
@@ -0,0 +1,3 @@
+= inside :layout do
+ = partial :'post/_post_content', {:page => @page, :hide_comments => false}
+
15 layout/post/_comments.haml
@@ -0,0 +1,15 @@
+- if BlogConfig.disqus_shortname then
+ .comments
+ #disqus_thread
+
+ :javascript
+ var disqus_shortname = '#{BlogConfig.disqus_shortname}';
+ (function() {
+ var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
+ dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
+ })();
+
+ :preserve
+ <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+ <a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
31 layout/post/_post_content.haml
@@ -0,0 +1,31 @@
+- page ||= @page
+- hide_comments ||= false
+
+%article
+ %header
+ %h1
+ %a{:href => page.path}= page.title
+ %h2= page.subtitle
+
+ .content
+ = partial page.template.to_sym
+ .clear
+
+ %footer
+ %section
+ %span= page.published_at.strftime('%Y')
+ = page.published_at.strftime('%b %d')
+
+ - if hide_comments
+ %section
+ %span Comment
+ %a{:href => page.path} Leave a Comment
+
+ %section.tag-list.last
+ %span Tags
+ - page.tags.sort.each do |tag|
+ %a{:href => "/tag/#{tag}"}= tag
+ .clear
+
+ - unless hide_comments
+ = partial :'post/_comments'
4 layout/sidebar/_about.haml
@@ -0,0 +1,4 @@
+%section#about
+ %img{:src => BlogConfig.profile_image}
+ %p
+ = BlogConfig.about
5 layout/sidebar/_bars.haml
@@ -0,0 +1,5 @@
+%section#bars
+ %div
+ %div
+ %div
+ %div
7 layout/sidebar/_nav.haml
@@ -0,0 +1,7 @@
+%section
+ %h4 Links
+ %nav
+ %ul
+ - @pages.reject{|p| p.hide_in_nav}.each do |page|
+ %li
+ %a{:href => page.path}= page.title
6 layout/sidebar/_recent.haml
@@ -0,0 +1,6 @@
+%section
+ %h4 Recent
+ %ul
+ - @posts[0..5].each do |page|
+ %li
+ %a{:href => page.path}= page.title
18 layout/sidebar/_social.haml
@@ -0,0 +1,18 @@
+%section
+ %h4 Find Me
+ %ul
+ - if BlogConfig.twitter_url
+ %li
+ %a{:href => BlogConfig.twitter_url} Twitter
+
+ - if BlogConfig.facebook_url
+ %li
+ %a{:href => BlogConfig.facebook_url} Facebook
+
+ - if BlogConfig.linked_in_url
+ %li
+ %a{:href => BlogConfig.linked_in_url} LinkedIn
+
+ - if BlogConfig.tumblr_url
+ %li
+ %a{:href => BlogConfig.tumblr_url} Tumblr
5 layout/sidebar/_title.haml
@@ -0,0 +1,5 @@
+%section#top
+ %h2
+ %span A Blog By
+ %br
+ = BlogConfig.owner_name
6 layout/sidebar/sidebar.haml
@@ -0,0 +1,6 @@
+= partial :'sidebar/_bars'
+= partial :'sidebar/_title'
+= partial :'sidebar/_about'
+= partial :'sidebar/_nav'
+= partial :'sidebar/_recent'
+= partial :'sidebar/_social'
61 layout/stylesheets/_article.scss
@@ -0,0 +1,61 @@
+header {
+ position: relative;
+ border-bottom: dotted #ccc 2px;
+
+ h1 { margin-bottom: 10px; }
+
+ h2 {
+ font-size: 24px;
+ color: #888;
+ font-weight: normal;
+ margin-top: 10px;
+ }
+}
+
+
+footer {
+ font-family: $head_font_stack;
+ padding: 0 0 30px 0;
+ border-bottom: dotted #ccc 2px;
+ margin-bottom: 50px;
+
+ section {
+ display: block;
+ float: left;
+ border-right: solid 1px #ccc;
+ padding: 3px 20px;
+ color: $headline_color;
+
+ &.last { border: none; }
+
+ span {
+ font-size: 16px;
+ color: #888;
+ display: block;
+ margin-bottom: 3px;
+ }
+ }
+}
+
+
+article {
+ h4 { margin-top: 30px; }
+
+ .content img {
+ max-width: 90%;
+ display: block;
+ padding: 5px;
+ border: solid 1px #ccc;
+ @include box_shadow(rgba(0,0,0,0.5), 0 0 15px);
+ }
+
+
+ .comments {
+ margin: 30px 20px;
+ padding-top: 30px;
+ border-top: dotted 2px #ccc;
+ }
+}
+
+
+.tag-list a { margin-right: 5px; }
16 layout/stylesheets/_fonts_and_colors.scss
@@ -0,0 +1,16 @@
+/* Two Fonts */
+$head_font_stack: "p22-underground-1","p22-underground-2",sans-serif;
+$body_font_stack: "ff-tisa-web-pro-1","ff-tisa-web-pro-2", Georgia, serif;
+
+/* Four Colors - only first two are used */
+$color_one: #da3121; /* Dark - Headline */
+$color_two: #F9F0E0; /* Border - Light */
+$color_three: #3253be; /* Dark */
+$color_four: #f36931; /* Light */
+
+/* Adjust Colors for Use on the Page */
+$border_color: $color_two;
+$headline_color: $color_one;
+$text_color: #444;
+$link_color: #44a;
+$link_color: #3253be;
96 layout/stylesheets/_html.scss
@@ -0,0 +1,96 @@
+html {
+ background-color: $border_color;
+ padding: 20px;
+}
+
+body {
+ margin: 0px;
+ font-family: $body_font_stack;
+ font-size: 15px;
+ line-height: 21px;
+ color: $text_color;
+ background-color: white;
+ padding: 30px 70px;
+ -webkit-font-smoothing: subpixel-antialiased;
+ text-rendering: optimizeLegibility;
+ min-height: 1000px;
+}
+
+a {
+ font-weight: normal;
+ color: $link_color;
+ text-decoration: none;
+ border: none;
+
+ &:visited { color: $link_color; }
+ &:hover { text-decoration: underline; }
+ & img { border: none; }
+}
+
+p, article ul li {
+ font-family: $body_font_stack;
+ color: $text_color;
+ line-height: 1.5em;
+}
+
+ol, ul { margin: 0 0 0 20px; padding: 0; }
+ol li, ul li { margin: 5px 0; padding: 0; }
+
+blockquote {
+ padding: 5px 10px 10px 10px;
+ margin-left: 10px;
+ border-left: solid 3px #ccc;
+ font-style: normal;
+}
+
+code {
+ font-family: Consolata, monospace;
+ background: #f8f8f8;
+ white-space: pre;
+ display: block;
+ padding: 10px 20px;
+ border: solid 1px #ccc;
+ font-size: 13px;
+}
+
+q {
+ display: block;
+ margin: 3px 0 10px 5px;
+ font-style: italic;
+ color: #666;
+ font-size: 12px;
+}
+
+
+/* Headings */
+h1, h2, h3, h4, h5, h6 {
+ color: $headline_color;
+ line-height: 1.25em;
+ font-family: $head_font_stack;
+ font-weight: normal;
+
+ a {
+ color: inherit !important;
+ font-weight: inherit;
+ }
+}
+
+h1 {
+ font-size: 38px;
+ letter-spacing: -1px;
+}
+
+h2 {
+ margin: 1.5em 0 0.5em 0;
+}
+
+h4 {
+ text-transform: uppercase;
+ color: #555;
+ font-size: 13px;
+ font-weight: 500;
+}
+
+
+/* Useful! */
+.clear { clear: both; }
36 layout/stylesheets/_layout.scss
@@ -0,0 +1,36 @@
+#sidebar {
+ width: 220px;
+ padding-left: 30px;
+ float: right;
+ min-height: 500px;
+}
+
+/* Must match the sidebar */
+header, footer, .content { margin-right: 280px; }
+
+.content { padding: 10px 20px 20px 20px; }
+
+/* Purely decorative...*/
+#bars {
+ float: right;
+ width: 95px;
+ height: 30px;
+ overflow: hidden;
+ margin-top: 0px;
+
+ div {
+ display: block;
+ float: right;
+ width: 20px;
+ height: 20px;
+ @include border_radius(6px);
+ margin-right: 3px;
+ }
+
+ div {
+ &:nth-child(1) { background-color: $color_one; }
+ &:nth-child(2) { background-color: $color_two; }
+ &:nth-child(3) { background-color: $color_three; }
+ &:nth-child(4) { background-color: $color_four; }
+ }
+}
32 layout/stylesheets/_mixins.scss
@@ -0,0 +1,32 @@
+@mixin background_gradient($from_color, $to_color) {
+ background: mix($from_color, $to_color);
+ background: -webkit-gradient(linear, left top, left bottom, from($from_color), to($to_color));
+ background: -moz-linear-gradient(top, $from_color, $to_color);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='$from_color', endColorstr='$to_color');
+}
+
+@mixin border_radius($radius) {
+ -webkit-border-radius: $radius;
+ -moz-border-radius: $radius;
+ border-radius: $radius;
+}
+
+@mixin rounded($side, $radius: 10px) {
+ border-#{$side}-radius: $radius;
+ -moz-border-radius-#{$side}: $radius;
+ -webkit-border-#{$side}-radius: $radius;
+}
+
+@mixin opacity($value) {
+ opacity: #{"." + $value};
+ filter: alpha(opacity=$value);
+ -ms-filter: "alpha(opacity=$value)";
+ -khtml-opacity: #{"." + $value};
+ -moz-opacity: #{"." + $value};
+}
+
+@mixin box_shadow($color, $values) {
+ -webkit-box-shadow: $color $values;
+ -moz-box-shadow: $color $values;
+ box-shadow: $color $values;
+}
58 layout/stylesheets/_sidebar.scss
@@ -0,0 +1,58 @@
+#sidebar {
+ font-size: 14px;
+
+ #top {
+ margin: 20px 0 0 0px;
+ padding: 0px;
+
+ h2 {
+ font-size: 18px;
+ line-height: 26px;
+ margin: 0px;
+ color: #666;
+ font-weight: normal;
+ padding-top: 40px;
+ text-align: left;
+
+ span {
+ color: #888;
+ font-size: 16px;
+ text-transform: lowercase;
+ }
+ }
+ }
+
+ #about {
+ img {
+ width: 60px;
+ height: 60px;
+ float: left;
+ clear: none;
+ margin: 0 10px 5px 0;
+ border: solid 1px #ccc;
+ padding: 2px;
+ background-color: #fafafa;
+ }
+ }
+
+ h4 {
+ color: #666;
+ font-weight: 500;
+ font-size: 14px;
+ margin: 30px 0 10px 0;
+ letter-spacing: 1px;
+ }
+
+ ul {
+ list-style-type: none;
+ margin-left: 0px;
+ padding-left: 0px;
+ li {
+ padding-left: 0px;
+ margin-left: 0px;
+ a {
+ font-family: $head_font_stack;
+ }
+ }
+ }
+}
11 layout/stylesheets/styles.scss
@@ -0,0 +1,11 @@
+@import '_fonts_and_colors';
+@import '_mixins';
+@import '_html';
+@import '_layout';
+@import '_sidebar';
+@import '_article';
+
+.index-footer {
+ padding-top: 0px;
+ text-align: center;
+}
3  lib/blog.rb
@@ -0,0 +1,3 @@
+require File.expand_path("../blog/pages_and_posts", __FILE__)
+require File.expand_path("../blog/template_extensions", __FILE__)
+require File.expand_path("../blog/page_set", __FILE__)
83 lib/blog/page_set.rb
@@ -0,0 +1,83 @@
+require 'hashie'
+require 'date'
+
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+# PageSet --
+# Captures the idea of a set of pages of varying types, with
+# tools for extracting subsets of the entire collection.
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+class PageSet
+ @@all_pages = []
+ @@pages_by_type = Hashie::Mash.new
+
+
+ #::::::::::::::::::::::::::::
+ # Add a page to the page list
+ def self.add_page(page)
+ # Apply defaults
+ page = Hashie::Mash.new({
+ :published_at => DateTime.now,
+ :tags => []
+ }).merge(page)
+
+ # Add to the global list
+ @@all_pages << page
+
+ # Add to the "by type" list
+ @@pages_by_type[page.page_type] ||= []
+ @@pages_by_type[page.page_type] << page
+
+ # Allow for tag filtering on the array
+ self.add_tag_filtering_to(@@pages_by_type[page.page_type])
+
+ # Return the added page
+ page
+ end
+
+
+ #::::::::::::::::::::::::::::
+ # Return the set of all pages
+ def self.all_pages
+ @@all_pages
+ end
+
+
+ #::::::::::::::::::::::::::::::::::::::::::::
+ # Return the set of all pages of a given type
+ def self.all_pages_of_type(page_type)
+ @@pages_by_type[page_type]
+ end
+
+
+ #::::::::::::::::::::::::::::::::::::::::::::
+ # Given a path, find the page
+ def self.find_by_path(path)
+ @@all_pages.detect{|p| p.path == path}
+ end
+
+
+ #:::::::::::::::::::::::::::::::::::::::::::
+ # Set up all pages at the start of a request
+ def self.config_for_request(request, params)
+ @@all_pages.each do |page|
+ # Allow for varying levels of details in callback
+ case page.config_block.arity
+ when 1
+ page.config_block.call(page)
+ when 2
+ page.config_block.call(page, params)
+ when 3
+ page.config_block.call(page, params, request)
+ end
+ end
+ end
+
+
+ #::::::::::::::::::::::::::::::::::::::::::
+ # Add tag-filtering to a given array object
+ def self.add_tag_filtering_to(array)
+ def array.tagged_with(tag)
+ self.select{|p| p.tags.include?(tag) }
+ end
+ end
+end
44 lib/blog/pages_and_posts.rb
@@ -0,0 +1,44 @@
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+# Utilities for Defining Pages and Posts
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+# Create a page, and add it to our page-set
+def page(path, page_type = 'page', &block)
+ # Add the new page to our set
+ PageSet.add_page({
+ :path => path,
+ :page_type => page_type,
+ :config_block => block.to_proc
+ })
+
+ # Define the action for the page
+ get path do
+ render_page(path)
+ end
+end
+
+
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+# Create a post and add it to our set of pages
+def post(path, &block)
+ page(path, 'post', &block)
+end
+
+
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+# Given a path, find the page for it and render that page
+def render_page(path)
+ # The page to show
+ new_page = PageSet.find_by_path(path)
+
+ # Our pages and posts
+ @pages = PageSet.all_pages_of_type('page')
+ @posts = PageSet.all_pages_of_type('post')
+
+ # So the templates can access the data
+ @page = new_page
+
+ # Render page type to embed the content
+ render :haml, @page.page_type.to_sym, :layout => false
+end
40 lib/blog/template_extensions.rb
@@ -0,0 +1,40 @@
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+# Support for finding templates in all our different locations
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+set :views, ['posts', 'pages', 'layout']
+
+helpers do
+ def find_template(views, name, engine, &block)
+ Array(views).each { |v| super(v, name, engine, &block) }
+ end
+end
+
+
+
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+# Sinatra Extentions - HAML support only
+#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+module Sinatra
+ module Templates
+ #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+ # Render a haml block inside a layout, allowing for recursion
+ def inside(layout, options = {}, locals = {}, &block)
+ # Capture the content
+ output = if respond_to?(:block_is_haml?) && block_is_haml?(block)
+ capture_haml(&block)
+ else
+ block.call
+ end
+
+ # Render the layout with the contents inside it
+ render(:haml, layout, options.merge({:layout => false}), locals) { output }
+ end
+
+
+ #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+ # Render a haml partial
+ def partial(partial, locals = {})
+ render(:haml, partial, {:layout => false}, locals)
+ end
+ end
+end
44 pages.rb
@@ -0,0 +1,44 @@
+page '/' do |p|
+ p.template = 'index'
+ p.title = 'Home'
+ p.page_type = 'plain'
+end
+
+
+page '/about' do |p|
+ p.template = 'about'
+ p.title = 'About Me'
+end
+
+
+page '/contact' do |p|
+ p.template = 'contact'
+ p.title = 'Contact Me'
+end
+
+
+page '/archive' do |p|
+ p.template = 'archive'
+ p.title = 'The Archives'
+end
+
+
+page '/tag/:tag' do |p, params|
+ p.template = 'tag'
+ p.title = "Tagged: #{params[:tag]}"
+ p.hide_in_nav = true
+end
+
+
+page '/404' do |p|
+ p.template = '404'
+ p.title = "Page Not Found"
+ p.hide_in_nav = true
+end
+
+
+page '/500' do |p|
+ p.template = '500'
+ p.title = "Oops"
+ p.hide_in_nav = true
+end
2  pages/404.haml
@@ -0,0 +1,2 @@
+%p We can't find that page.
+%a{:href => '/archive'} Search the Archives &rarr;
2  pages/500.haml
@@ -0,0 +1,2 @@
+%p There was some sort of problem
+%a{:href => '/'} Back to the Home Page &rarr;
2  pages/about.haml
@@ -0,0 +1,2 @@
+%p
+ Coming soon ...
7 pages/archive.haml
@@ -0,0 +1,7 @@
+- last_group = nil
+- @posts.each do |page|
+ - if [page.published_at.year, page.published_at.month] != last_group then
+ - last_group = [page.published_at.year, page.published_at.month]
+ %h2= page.published_at.strftime('%B %Y')
+ %p
+ %a{:href => page.path}= page.title
2  pages/contact.haml
@@ -0,0 +1,2 @@
+%p
+ Coming soon ...
6 pages/index.haml
@@ -0,0 +1,6 @@
+- @posts[0..3].each do |page|
+ = partial :'post/_post_content', {:page => page, :hide_comments => true}
+
+.content.index-footer
+ %h2 Didn't find what you were looking for?
+ %a{:href => '/archive'} Search the Archives &rarr;
8 pages/tag.haml
@@ -0,0 +1,8 @@
+- @posts.tagged_with(params[:tag]).each do |page|
+ %h2
+ %a{:href => page.path}= page.title
+ %p= page.summary
+ %p.tag-list
+ %span Tagged:
+ - page.tags.sort.each do |tag|
+ %a{:href => "/tag/#{tag}"}= tag
30 posts.rb
@@ -0,0 +1,30 @@
+post '/second-sample-page' do |p|
+ p.title = "Second Sample Page"
+ p.subtitle = "Really really, it's just a sample page"
+ p.template = "sample-page-2"
+ p.published_at = DateTime.parse('2011-04-02')
+ p.tags = %w{samples}
+ p.summary = %Q{
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
+ enim ad minim veniam, quis nostrud exercitation ullamco laboris
+ nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
+ in reprehenderit in voluptate velit esse cillum dolore eu fugiat
+ }
+end
+
+
+post '/first-sample-page' do |p|
+ p.title = "First Sample Page"
+ p.subtitle = "Really, it's just a sample page"
+ p.template = "sample-page-1"
+ p.published_at = DateTime.parse('2011-04-01')
+ p.tags = %w{samples}
+ p.summary = %Q{
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
+ enim ad minim veniam, quis nostrud exercitation ullamco laboris
+ nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
+ in reprehenderit in voluptate velit esse cillum dolore eu fugiat
+ }
+end
8 posts/sample-page-1.haml
@@ -0,0 +1,8 @@
+%h2 What an awesome sample page.
+%p
+ How about a picture?
+%h3 Paris
+%p
+ %img{:src => '/images/paris.jpg'}
+%p
+ That's all folks!
8 posts/sample-page-2.haml
@@ -0,0 +1,8 @@
+%h2 What an awesome second sample page.
+%p
+ How about another picture of Paris?
+%h3 Paris
+%p
+ %img{:src => '/images/paris.jpg'}
+%p
+ Ahhh ... Paris.
BIN  public/favicon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  public/images/paris.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  public/images/portrait_100x100.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 redirects.rb
@@ -0,0 +1,7 @@
+module Redirect
+ #::::::::::::::::::::::::::::::::::::::::::::::::::::::
+ # Which URLs to redirect
+ Urls = {
+ '/post/4065537804/writing-an-instagram-web-client' => '/writing-an-instagram-web-client'
+ }
+end
Please sign in to comment.
Something went wrong with that request. Please try again.