We're growing into something new. Read More
kyle

Kyle Bragger  Ranger


# encoding: utf-8

require 'bundler'
Bundler.require

PART_SEP = '__PART__'

puts "======================================="
puts "          Let's make magic...          "
puts "======================================="

#----------------------------------------------------
# setup
#----------------------------------------------------
output_file = './output/OnBuildingCommunityProducts.pdf'

book_input = {}

KDocMargin = 55

KTextColor = '101008'
KTextColorLight = '606058'
KHeadingColor = '58C3B3'
KQuestionColor = '58C3B3'

#----------------------------------------------------
# build pdf
#----------------------------------------------------
Prawn::Document.generate(output_file, {
  margin: KDocMargin,
  page_size: 'A4'
}) do
  font_families.update('NotoSans' => {
    normal: './fonts/Noto_Sans/NotoSans-Regular.ttf',
      bold: './fonts/Noto_Sans/NotoSans-Bold.ttf',
    italic: './fonts/Noto_Sans/NotoSans-Italic.ttf'
  }, 'Merriweather' => {
    normal: './fonts/Merriweather/Merriweather-Regular.ttf',
    italic: './fonts/Merriweather/Merriweather-Italic.ttf'
  })
  
  
  #----------------------------------------------------
  # cover
  #----------------------------------------------------
  puts 'processing cover'
  
  image './images/cover.jpg',
        at: [-KDocMargin, Prawn::Document::PageGeometry::SIZES["A4"][1] - KDocMargin],
        fit: Prawn::Document::PageGeometry::SIZES["A4"]
  
  start_new_page
  
  
  #----------------------------------------------------
  # acknowledgements
  #----------------------------------------------------
  puts 'processing acknowledgements'
  
  font "NotoSans", style: :normal

  fill_color(KHeadingColor)
  font_size 24
  text "Acknowledgements, &c.", leading: 8, style: :bold

  fill_color(KTextColor)
  font_size 12
  text "Thanks to the many folks who helped with this along the way, including my wife, Brooke, John Capecelatro, Simon Goetz, and everyone else who I’ve forgotten to mention here.", leading: 2
  text "\nThe fonts used in this book are NotoSans and Merriweather, both available through Google. NotoSans is available under an Apache License, version 2.0. Merriweather is available under the SIL Open Font License, version 1.1.", leading: 2
  text "\nAll content is (c) Kyle Bragger.", leading: 2

  start_new_page
  
  
  #----------------------------------------------------
  # intro
  #----------------------------------------------------
  puts 'processing intro'
  
  font "NotoSans", style: :normal

  fill_color(KHeadingColor)
  font_size 24
  text "About Kyle & This Book", leading: 8, style: :bold

  fill_color(KTextColor)
  font_size 12
  text "This tiny book is part one of an experiment I’ve wanted to do for a long time now. It’s written entirely in a Q&A format. \
Its questions were solicited from people across the Interwebs over the course of a few weeks, and range from questions about Forrst, a community of \
developers and designers I started in 2009, community building in general, product design, and more.", leading: 2
  text "\nAs for me, I'm a self-taught developer and product designer. Over the years, I've built, grown, and sold products related to communities, design, development, human behavior, and more. \
My most successful experiment so far has been Forrst, which I ran for three years before we were acquired in 2012. It now lives on at Zurb. \
I also started a job board for short-term development and design jobs called Tinyproj which I grew to 10,000 members. Currently, I'm working on Exposure at Elepath. I also made an iPhone app called Thinglist.", leading: 2
  text "\nEnjoy!", leading: 2
  text "\n— Kyle", leading: 2

  start_new_page
  
  
  #----------------------------------------------------
  # questions/answers
  #----------------------------------------------------
  part_num = 0
  question_count = 0
  
  files = Dir['./questions/*.md']
  files.collect{|f| f.sub(/^page_/, '')}.sort{|a,b|
    a = a.match(/(\d+)\.md/)[1].to_i
    b = b.match(/(\d+)\.md/)[1].to_i
    a <=> b
  }.each do |file|
    part_num += 1
    last = part_num == files.length
    
    puts "-> processing page (#{part_num}/#{files.length})"
    
    File.open(file) do |f|
      
      parts = f.read.split(PART_SEP).collect(&:strip)

      if parts[3] =~ /^TODO/
        puts "! skipping #{part_num}"
        next
      end

      question_count += 1
      
      # question
      fill_color(KQuestionColor)
      font "Merriweather", style: :italic
      font_size 14
      text parts[0], leading: 4
      
      # credit
      fill_color(KTextColorLight)
      font "Merriweather", style: :normal
      font_size 11

      twitter_text = ''
      if parts[2].length > 0 && parts[2] != '@'
        twitter_text = ", @#{parts[2].sub(/@/, '')}"
      end
      text "— #{parts[1]}#{twitter_text}", leading: 2, align: :right
      
      # answer
      fill_color(KTextColor)
      font "NotoSans", style: :normal
      font_size 13
      text "\n" + parts[3], leading: 2.5, inline_format: true
      
      start_new_page
      
    end
  end

  puts "#{question_count} questions published"

  #----------------------------------------------------
  # thanks
  #----------------------------------------------------
  puts 'processing thanks'
  
  font "NotoSans", style: :normal

  fill_color(KHeadingColor)
  font_size 24
  text "Thank you.", leading: 8, style: :bold

  fill_color(KTextColor)
  font_size 12
  text "Thanks so much for your purchase. I sincerely hope you came away from this with some new insight into building community products." \
     + " If you have any feedback, questions, complaints, or otherwise, I’m on Twitter" \
     + " at <strong>@kylebragger</strong>, and reachable via email at" \
     + " <strong>[email protected]</strong>.\n\nCheers,\nKyle", leading: 2, inline_format: true
  
  # page #s
  number_pages "<page> of <total>",
               at: [0, 50],
               align: :center,
               page_filter: lambda {|p| p > 1},
               start_count_at: 2,
               width: bounds.width,
               color: KTextColorLight
end

puts "Done. Book is in #{output_file}"
puts ""

Building a PDF book with Ruby

Hey y'all, it's been a while! I just released a small e-book[1] about building the Forrst community, product design, and such. It was all DIY, and I thought I'd share the build script I wrote in Ruby that generates the PDF. To build the book, I can just run: `$ ruby build.rb` The only dependencies are the **Prawn** Rubygem, which is an incredibly robust library for generating PDFs. There's a powerful DSL that lets you control just about every single aspect of the output. 1. Here's the book, and a handsome discount for Forrsters only: https://gum.co/obcp-book/forrrrrrrst

Sets is now open to all.

I'm happy to report that my new project, Sets, is now open to all. It's a great way to build curated lists of links, either by yourself or with other contributors.

The Toolkit

I'm working on a product that helps folks build curated sets of links. *The Toolkit* is a set myself and some internet folks are contributing to; our goal is to share stuff that we feel is of value when building web/mobile products day to day. Enjoy!

Something coming soon.

Here's a preview of something new we're working on. It's a big rewrite with a lot of moving parts, so not making any promises yet, but it's pretty awesome. Also, the design isn't final, so keep those pitchforks put away. ;)

def owns?(obj)
  !!(obj.respond_to?(:user_id) && obj.user_id == self.id)
end

Ruby ownership pattern

Here's a little Ruby snippet I tend to use when I'm dealing with record ownership in apps, so I can do stuff like @user.owns?(@post) (such as when writing view logic to display post author-only controls).

if (typeof history !== 'undefined')
    history.pushState({
        title: document.title,
        url: el.attr('href')
    }, document.title, el.attr('href'));

Strange behavior with HTML5's History API and AJAX requests

I'm currently refactoring our old endless scroll code for the new Forrst we're rolling out in a few weeks, and I'm using the new History API where supported. I'm noticing some interesting behavior though. Here's what I'm doing: Load the page in browser as usual Scroll, loading data via AJAX and pushing a new state (see above code) I follow a link (e.g. to a post page) I hit back, and the browser uses the proper URL but seems to be serving cached data, and data from the AJAX request; I don't ever see a request in my logs For reference, the URLs are not using any type of hashbang stuff, just pure path + query string. Here's a screencast: screenr.com/… Any ideas?

Discussion

Are context and thoughtfulness really all that bad?

We have a minimum length requirement of 100 characters for Forrst posts. While it's something I've wanted to do for a while, it actually was only rolled out fairly recently. Since then, I've seen a bevy of, shall we say, creative ways to get around the limit. Forrst is increasingly all about providing context and being thoughtful about what you're sharing here. It's why we ask you not post tiny cropped screenshots, photos of your screen, vague questions, code issues without actually providing much/any code to look at, and why we ask that job posts have real details in them, not just "need developer ASAP!!!". It's also why posts need to be at least 100 characters long. Saying "made this today LOL!" or "it's a button" isn't providing enough context for folks to really understand not only what you're sharing, but why you're sharing it, what you're looking for from the community, and perhaps most importantly, what your process is like when writing code or crafting designs. So, my question is this: Why do you think folks are so averse to taking the time to share and explain their work in detail?* I'd much rather see 10 well-thought out posts that are thoughtful and share context and process than 100 quickfire, "did it because I was bored" posts. I should add that this applies equally for comments. Is there really a point in saying "love it!", "great job", "sucks" or similar? Does that add anything to the greater conversation that hitting the Like button can't? As you've probably figured out, this is a very interesting and potentially fantastic time for the growth community we're all part of here; part of that means really finding our voice and purpose as a community, and context will play a very important role there. We've started to get more aggressive about moderating posts that don't meet our expectations in terms of thoughtfulness and context, and I suspect that comments will follow suit. I'll leave you with this closing thought: next time you're interacting here (or for that matter, anywhere), just remember that your future boss, colleague, client, or employee may be here as well. How you present yourself publicly can have a very positive, or negative, impact on your success in our line of work. * Don't get me started on sloppy, poorly formatted stuff thatlook lyke i tiped lik this :))))) !!!11

Discussion

OpenPhoto

Just came across this again, after having checked it out over the summer. I'm a big fan of what they're doing.