diff --git a/assets/blog.css b/assets/blog.css index 1a24293..f85005b 100644 --- a/assets/blog.css +++ b/assets/blog.css @@ -131,6 +131,8 @@ time { color: #444 } #prev { float: left } #next { float: right } +#comments { display: none } + footer { text-align: center ; font-size: 1.2em ; margin: 0 diff --git a/assets/request.js b/assets/request.js new file mode 100644 index 0000000..48a880e --- /dev/null +++ b/assets/request.js @@ -0,0 +1,54 @@ +(function() { + if (typeof SJS === 'undefined') SJS = {} + var load, _jsonpCounter = 1 + SJS.request = function(options, cb) { // jsonp request, quacks like mikeal's request module + var jsonpCallbackName = '_jsonpCallback' + _jsonpCounter++ + , url = options.uri + '?callback=SJS.' + jsonpCallbackName + SJS[jsonpCallbackName] = function(obj) { + cb(null, obj) + setTimeout(function() { delete SJS[jsonpCallbackName] }, 0) + } + load(url) + } + + // bootstrap loader from LABjs + load = function(url) { + var oDOC = document + , handler + , head = oDOC.head || oDOC.getElementsByTagName("head") + + // loading code borrowed directly from LABjs itself + // (now removes script elem when done and nullifies its reference --sjs) + setTimeout(function () { + if ("item" in head) { // check if ref is still a live node list + if (!head[0]) { // append_to node not yet ready + setTimeout(arguments.callee, 25) + return + } + head = head[0]; // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists + } + var scriptElem = oDOC.createElement("script"), + scriptdone = false + scriptElem.onload = scriptElem.onreadystatechange = function () { + if ((scriptElem.readyState && scriptElem.readyState !== "complete" && scriptElem.readyState !== "loaded") || scriptdone) { + return false + } + scriptElem.onload = scriptElem.onreadystatechange = null + scriptElem.parentNode.removeChild(scriptElem) + scriptElem = null + scriptdone = true + }; + scriptElem.src = url + head.insertBefore(scriptElem, head.firstChild) + }, 0) + + // required: shim for FF <= 3.5 not having document.readyState + if (oDOC.readyState == null && oDOC.addEventListener) { + oDOC.readyState = "loading" + oDOC.addEventListener("DOMContentLoaded", function handler() { + oDOC.removeEventListener("DOMContentLoaded", handler, false) + oDOC.readyState = "complete" + }, false) + } + } +}()) \ No newline at end of file diff --git a/blog.rb b/blog.rb index a85d5f1..2024482 100755 --- a/blog.rb +++ b/blog.rb @@ -15,7 +15,9 @@ end template = File.read(File.join('templates', 'blog', 'post.html')) -Posts = JSON.parse(File.read(File.join(srcdir, 'posts.json'))) +# read posts +posts_file = File.join(srcdir, 'posts.json') +Posts = JSON.parse(File.read(posts_file)) posts = Posts['published'].map do |filename| lines = File.readlines(File.join(srcdir, filename)) post = { :filename => filename } @@ -33,24 +35,32 @@ posts = Posts['published'].map do |filename| end post[:content] = lines.join post[:body] = RDiscount.new(post[:content]).to_html + # comments on by default + post[:comments] = true if post[:comments].nil? post end +# generate posts posts.each_with_index do |post, i| post[:html] = Mustache.render(template, { :title => post[:title], :post => post, :previous => i < posts.length - 1 && posts[i + 1], - :next => i > 0 && posts[i - 1] + :next => i > 0 && posts[i - 1], + :comments => post[:comments] }) end +# generate landing page index_template = File.read(File.join('templates', 'blog', 'index.html')) index_html = Mustache.render(index_template, { :posts => posts, :post => posts.first, :previous => posts[1] }) +# write landing page File.open(File.join(destdir, 'index.html'), 'w') {|f| f.puts(index_html) } + +# write posts posts.each do |post| File.open(File.join(destdir, post[:filename]), 'w') {|f| f.puts(post[:html]) } end diff --git a/blog/37signals-chalk-dissected.html b/blog/37signals-chalk-dissected.html index c26e0f0..1e19ae4 100644 --- a/blog/37signals-chalk-dissected.html +++ b/blog/37signals-chalk-dissected.html @@ -14,6 +14,28 @@ _gaq.push( ['_setAccount', 'UA-214054-5'] ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); +if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', ready, false) +} else if (window.attachEvent) { + window.attachEvent('onload', ready) +} +function ready() { + function html(id, h) { + document.getElementById(id).innerHTML = h + } + + var discussion = document.getElementById('discussion') + , discussionToggle = document.getElementById('discussion-toggle') + , hidden = true + discussionToggle.onclick = function() { + hidden = !hidden + discussion.style.display = hidden ? 'none' : 'block' + discussionToggle.innerHTML = hidden ? '↓ show discussion ↓' : '↑ hide discussion ↑' + SJS.request({uri: 'http://bohodev.net:8000/comments/'}, function(err, body) { + html('comments', body) + }) + } +}

sjs' blog

@@ -274,6 +296,21 @@ addLineNumbersToAllGists();
+
↓ show discussion ↓
+
+
+
+ +

Name:

+

URL:

+

Thoughts:

+

+
+
+
+ +
+
diff --git a/blog/a-preview-of-mach-o-file-generation.html b/blog/a-preview-of-mach-o-file-generation.html index 6358907..e2f0654 100644 --- a/blog/a-preview-of-mach-o-file-generation.html +++ b/blog/a-preview-of-mach-o-file-generation.html @@ -14,6 +14,28 @@ _gaq.push( ['_setAccount', 'UA-214054-5'] ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); +if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', ready, false) +} else if (window.attachEvent) { + window.attachEvent('onload', ready) +} +function ready() { + function html(id, h) { + document.getElementById(id).innerHTML = h + } + + var discussion = document.getElementById('discussion') + , discussionToggle = document.getElementById('discussion-toggle') + , hidden = true + discussionToggle.onclick = function() { + hidden = !hidden + discussion.style.display = hidden ? 'none' : 'block' + discussionToggle.innerHTML = hidden ? '↓ show discussion ↓' : '↑ hide discussion ↑' + SJS.request({uri: 'http://bohodev.net:8000/comments/'}, function(err, body) { + html('comments', body) + }) + } +}

sjs' blog

@@ -69,6 +91,21 @@ straightforward, an example is in asm/binary.rb, in the #output method.


+ +
+
+
+ +

Name:

+

URL:

+

Thoughts:

+

+
+
+
+ +
+
diff --git a/blog/basics-of-the-mach-o-file-format.html b/blog/basics-of-the-mach-o-file-format.html index f6a81e0..5afc886 100644 --- a/blog/basics-of-the-mach-o-file-format.html +++ b/blog/basics-of-the-mach-o-file-format.html @@ -14,6 +14,28 @@ _gaq.push( ['_setAccount', 'UA-214054-5'] ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); +if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', ready, false) +} else if (window.attachEvent) { + window.attachEvent('onload', ready) +} +function ready() { + function html(id, h) { + document.getElementById(id).innerHTML = h + } + + var discussion = document.getElementById('discussion') + , discussionToggle = document.getElementById('discussion-toggle') + , hidden = true + discussionToggle.onclick = function() { + hidden = !hidden + discussion.style.display = hidden ? 'none' : 'block' + discussionToggle.innerHTML = hidden ? '↓ show discussion ↓' : '↑ hide discussion ↑' + SJS.request({uri: 'http://bohodev.net:8000/comments/'}, function(err, body) { + html('comments', body) + }) + } +}

sjs' blog

@@ -292,6 +314,21 @@ would almost have a useful Mach object file.)


+ +
+
+
+ +

Name:

+

URL:

+

Thoughts:

+

+
+
+
+ +
+
diff --git a/blog/working-with-c-style-structs-in-ruby.html b/blog/working-with-c-style-structs-in-ruby.html index 2c5fc4e..86502a0 100644 --- a/blog/working-with-c-style-structs-in-ruby.html +++ b/blog/working-with-c-style-structs-in-ruby.html @@ -14,6 +14,28 @@ _gaq.push( ['_setAccount', 'UA-214054-5'] ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); +if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', ready, false) +} else if (window.attachEvent) { + window.attachEvent('onload', ready) +} +function ready() { + function html(id, h) { + document.getElementById(id).innerHTML = h + } + + var discussion = document.getElementById('discussion') + , discussionToggle = document.getElementById('discussion-toggle') + , hidden = true + discussionToggle.onclick = function() { + hidden = !hidden + discussion.style.display = hidden ? 'none' : 'block' + discussionToggle.innerHTML = hidden ? '↓ show discussion ↓' : '↑ hide discussion ↑' + SJS.request({uri: 'http://bohodev.net:8000/comments/'}, function(err, body) { + html('comments', body) + }) + } +}

sjs' blog

@@ -140,6 +162,21 @@ of the Mach-O file format


+

+
+
+
+ +

Name:

+

URL:

+

Thoughts:

+

+
+
+
+ +
+
diff --git a/discussd/discussd.js b/discussd/discussd.js new file mode 100755 index 0000000..f3857f3 --- /dev/null +++ b/discussd/discussd.js @@ -0,0 +1,174 @@ +#!/usr/bin/env node + +var fs = require('fs') + , http = require('http') + , keys = require('keys') + , DefaultOptions = { host: 'localhost' + , port: 2020 + , postsFile: 'posts.json' + } + +function main() { + var options = parseArgs(DefaultOptions) + , db = new keys.Dirty('./discuss.dirty') + , context = { db: db + , posts: null + } + , server = http.createServer(requestHandler(context)) + , loadPosts = function(cb) { + readJSON(options.postsFile, function(err, posts) { + if (err) { + console.error('failed to parse posts file, is it valid JSON?') + console.dir(e) + process.exit(1) + } + context.posts = posts.published + var n = context.posts.length + console.log((context.posts === null ? '' : 're') + 'loaded ' + n + ' posts...') + if (typeof cb == 'function') cb() + }) + } + , listen = function() { + console.log(process.argv[0] + ' listening on ' + options.host + ':' + options.port) + server.listen(options.port, options.host) + } + loadPosts(function() { + fs.watchFile(options.postsFile, loadPosts) + if (db._loaded) { + listen() + } else { + db.db.on('load', listen) + } + }) +} + +function readJSON(f, cb) { + fs.readFile(f, function(err, buf) { + var data + if (!err) { + try { + data = JSON.parse(buf.toString()) + } catch (e) { + err = e + } + } + cb(err, data) + }) +} + +function requestHandler(context) { + function addComment(data) { + if (missingParams(data) || context.posts.indexOf(data.post) === -1) { + console.log('missing params or invalid post title in ' + JSON.stringify(data, null, 2)) + return false + } + var comments = context.db.get(data.post) || [] + comments.push({ name: data.name + , email: data.email + , body: data.body + , timestamp: Date.now() + }) + context.db.set(data.post, comments) + console.log('[' + new Date() + '] add comment ' + JSON.stringify(data, null, 2)) + return true + } + + return function(req, res) { + var body = '' + , m + if (req.method === 'POST' && req.url.match(/^\/comment\/?$/)) { + req.on('data', function(chunk) { body += chunk }) + req.on('end', function() { + var data + try { + data = JSON.parse(body) + } catch (x) { + badRequest(res) + return + } + if (!addComment(data)) { + badRequest(res) + return + } + res.writeHead(204) + res.end() + // TODO mail watchers about the comment + }) + } else if (req.method === 'GET' && (m = req.url.match(/^\/comments\/(.*)$/))) { + var post = m[1] + , comments + , s + if (context.posts.indexOf(post) === -1) { + badRequest(res) + return + } + comments = context.db.get(post) || [] + s = JSON.stringify({comments: comments}) + res.writeHead(200, { 'content-type': 'appliaction/json' + , 'content-length': s.length + }) + res.end(s) + } else { + console.log('unhandled request') + console.dir(req) + badRequest(res) + } + } +} + +function parseArgs(defaults) { + var expectingArg + , options = Object.keys(defaults).reduce(function(os, k) { + os[k] = defaults[k] + return os + }, {}) + process.argv.slice(2).forEach(function(arg) { + if (expectingArg) { + options[expectingArg] = arg + expectingArg = null + } else { + // remove leading dashes + while (arg.charAt(0) === '-') { + arg = arg.slice(1) + } + switch (arg) { + case 'h': + case 'host': + expectingArg = 'host' + break + + case 'p': + case 'port': + expectingArg = 'port' + break + + default: + console.warn('unknown option: ' + arg + ' (setting anyway)') + expectingArg = arg + } + } + }) + return options +} + +function badRequest(res) { + var s = 'bad request' + res.writeHead(400, { 'content-type': 'text/plain' + , 'content-length': s.length + }) + res.end(s) +} + +var missingParams = (function() { + var requiredParams = 'name email body'.split(' ') + return function(d) { + var anyMissing = false + requiredParams.forEach(function(p) { + var v = (d[p] || '').trim() + if (!v) anyMissing = true + }) + return anyMissing + } +}()) + +if (module == require.main) main() diff --git a/templates/blog/post.html b/templates/blog/post.html index a434a92..0ded7b8 100644 --- a/templates/blog/post.html +++ b/templates/blog/post.html @@ -14,6 +14,30 @@ _gaq.push( ['_setAccount', 'UA-214054-5'] ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); +{{#comments}} +if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', ready, false) +} else if (window.attachEvent) { + window.attachEvent('onload', ready) +} +function ready() { + function html(id, h) { + document.getElementById(id).innerHTML = h + } + + var discussion = document.getElementById('discussion') + , discussionToggle = document.getElementById('discussion-toggle') + , hidden = true + discussionToggle.onclick = function() { + hidden = !hidden + discussion.style.display = hidden ? 'none' : 'block' + discussionToggle.innerHTML = hidden ? '↓ show discussion ↓' : '↑ hide discussion ↑' + SJS.request({uri: 'http://bohodev.net:8000/comments/{{filename}}'}, function(err, body) { + html('comments', body) + }) + } +} +{{/comments}}

sjs' blog

@@ -40,6 +64,23 @@ _gaq.push( ['_setAccount', 'UA-214054-5'] {{/next}}
+ +{{#comments}} +
+
+
+ +

Name:

+

URL:

+

Thoughts:

+

+
+
+
+ +
+
+{{/comments}}