mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-03-25 09:05:47 +00:00
commenting system is almost ready for primetime
* switched to CORS from JSONP * improved style * separated almost all JavaScript from the HTML * minify & combine JavaScript using closure & cat * fleshed out Makefile
This commit is contained in:
parent
ab85efcf6b
commit
94bf683fb1
22 changed files with 2216 additions and 357 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
*.tmproj
|
||||
.DS_Store
|
||||
_blog
|
||||
assets/*.min.js
|
||||
discussd/discuss.dirty
|
||||
|
|
|
|||
45
Makefile
45
Makefile
|
|
@ -1,2 +1,45 @@
|
|||
build:
|
||||
JAVASCRIPTS=assets/blog.js assets/gitter.js assets/jquery-serializeObject.js assets/proj.js \
|
||||
assets/request.js assets/showdown.js assets/storage-polyfill.js assets/store.js \
|
||||
assets/strftime.js assets/tmpl.js
|
||||
|
||||
MIN_JAVASCRIPTS=assets/blog.min.js assets/gitter.min.js assets/jquery-serializeObject.min.js assets/proj.min.js \
|
||||
assets/request.min.js assets/showdown.min.js assets/storage-polyfill.min.js assets/store.min.js \
|
||||
assets/strftime.min.js assets/tmpl.min.js
|
||||
|
||||
POSTS=$(shell echo _blog/*.html)
|
||||
|
||||
all: proj blog combine
|
||||
|
||||
proj: projects.json templates/proj/index.html templates/proj/proj/index.html
|
||||
./build.js
|
||||
|
||||
blog: _blog/posts.json templates/blog/index.html templates/blog/post.html $(POSTS)
|
||||
@echo
|
||||
./blog.rb _blog blog
|
||||
|
||||
minify: $(JAVASCRIPTS)
|
||||
@echo
|
||||
./minify.sh
|
||||
|
||||
combine: minify $(MIN_JAVASCRIPTS)
|
||||
@echo
|
||||
./combine.sh
|
||||
|
||||
publish_blog: blog combine
|
||||
publish blog
|
||||
|
||||
publish_proj: proj combine
|
||||
publish proj
|
||||
|
||||
publish: publish_blog publish_proj index.html
|
||||
publish index.html
|
||||
publish assets
|
||||
publish blog
|
||||
publish proj
|
||||
|
||||
clean:
|
||||
rm -rf proj/*
|
||||
rm -rf blog/*
|
||||
rm assets/*.min.js
|
||||
|
||||
.PHONY: blog
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ a.img { border: none }
|
|||
}
|
||||
|
||||
#index { display: none
|
||||
; width: 60%
|
||||
; width: 80%
|
||||
; min-width: 200px
|
||||
; max-width: 600px
|
||||
; max-width: 800px
|
||||
; border: solid 1px #999
|
||||
; -moz-border-radius: 10px
|
||||
; -webkit-border-radius: 10px
|
||||
|
|
@ -68,22 +68,22 @@ a.img { border: none }
|
|||
|
||||
.date { float: right }
|
||||
|
||||
#posts { border-left: solid 0.2em #ddd
|
||||
#posts { border-left: solid 0.15em #999
|
||||
; width: 80%
|
||||
; min-width: 400px
|
||||
; max-width: 800px
|
||||
; margin: 4em auto
|
||||
; margin: 4em auto 2em
|
||||
; padding: 0 3em
|
||||
; font-size: 1.2em
|
||||
}
|
||||
|
||||
article { color: #222
|
||||
; padding-bottom: 5em
|
||||
; padding-bottom: 1em
|
||||
; line-height: 1.2em
|
||||
}
|
||||
|
||||
article:last-child { padding-bottom: 0
|
||||
; margin-bottom: 2em
|
||||
; margin-bottom: 1em
|
||||
}
|
||||
|
||||
article h1 { text-align: left
|
||||
|
|
@ -116,7 +116,9 @@ article h3 { font-size: 1.6em
|
|||
; font-weight: normal
|
||||
}
|
||||
|
||||
time { color: #444 }
|
||||
time { color: #444
|
||||
; font-size: 1.2em
|
||||
}
|
||||
|
||||
.gist { font-size: 0.8em }
|
||||
|
||||
|
|
@ -124,21 +126,93 @@ time { color: #444 }
|
|||
; min-width: 400px
|
||||
; max-width: 800px
|
||||
; margin: 1em auto
|
||||
; padding: 0 3em
|
||||
; padding: 0 3em 1em
|
||||
; font-size: 1.2em
|
||||
}
|
||||
|
||||
#prev { float: left }
|
||||
#next { float: right }
|
||||
|
||||
#comments { display: none }
|
||||
.sep { text-align: center
|
||||
; font-size: 2em
|
||||
; color: #666
|
||||
}
|
||||
|
||||
/* show discussion */
|
||||
#sd-container { margin: 3em 0 }
|
||||
|
||||
input[type=submit],
|
||||
#sd { border: solid 1px #999
|
||||
; border-right-color: #333
|
||||
; border-bottom-color: #333
|
||||
; padding: 0.4em 1em
|
||||
; color: #444
|
||||
; background-color: #ececec
|
||||
; -moz-border-radius: 5px
|
||||
; -webkit-border-radius: 5px
|
||||
; border-radius: 5px
|
||||
; text-decoration: none
|
||||
; margin: 0 2px 2px 0
|
||||
}
|
||||
|
||||
input[type=submit]:active,
|
||||
#sd:active { margin: 2px 0 0 2px
|
||||
; color: #000
|
||||
; background-color: #ffc
|
||||
}
|
||||
|
||||
#comment-stuff { display: none
|
||||
; color: #efefef
|
||||
; margin: 0
|
||||
; padding: 2em 0
|
||||
}
|
||||
|
||||
#comments { width: 70%
|
||||
; max-width: 400px
|
||||
; margin: 0 auto
|
||||
}
|
||||
|
||||
.comment { color: #555
|
||||
; border-top: solid 2px #ccc
|
||||
; padding-bottom: 2em
|
||||
; margin-bottom: 2em
|
||||
}
|
||||
|
||||
.comment big { font-size: 2em
|
||||
; font-family: Georgia, serif
|
||||
}
|
||||
|
||||
#comment-form { width: 400px
|
||||
; margin: 2em auto 0
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
textarea { font-size: 1.4em
|
||||
; color: #333
|
||||
; width: 100%
|
||||
; padding: 0.2em
|
||||
; border: solid 1px #999
|
||||
; -moz-border-radius: 5px
|
||||
; -webkit-border-radius: 5px
|
||||
; border-radius: 5px
|
||||
; font-family: verdana, sans-serif
|
||||
}
|
||||
|
||||
input:focus[type=text],
|
||||
textarea:focus { border: solid 1px #333 }
|
||||
|
||||
textarea { height: 100px }
|
||||
|
||||
input[type=submit] { font-size: 1.1em
|
||||
; cursor: pointer
|
||||
}
|
||||
|
||||
footer { text-align: center
|
||||
; font-size: 1.2em
|
||||
; margin: 0
|
||||
; padding: 1em
|
||||
; background-color: #a6bcdf
|
||||
; border-top: solid 1px #666
|
||||
; padding: 1em 0
|
||||
; background-color: #cdf
|
||||
; border-top: solid 1px #bbb
|
||||
; clear: both
|
||||
}
|
||||
|
||||
|
|
|
|||
78
assets/blog.js
Normal file
78
assets/blog.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
;(function() {
|
||||
var server = 'http://bohodev.net:8000/'
|
||||
, getCommentsURL = function(post) { return server + 'comments/' + post }
|
||||
, postCommentURL = function() { return server + 'comment' }
|
||||
, countCommentsURL = function(post) { return server + 'count/' + post }
|
||||
|
||||
function getComments() {
|
||||
console.log('*** getComments()')
|
||||
SJS.request({uri: getCommentsURL(SJS.filename)}, function(err, request, body) {
|
||||
if (err) {
|
||||
$('#comments').text('derp')
|
||||
return
|
||||
}
|
||||
var data
|
||||
, comments
|
||||
, h = ''
|
||||
try {
|
||||
data = JSON.parse(body)
|
||||
} catch (e) {
|
||||
console && console.log && console.log('not json -> ' + body)
|
||||
}
|
||||
comments = data.comments
|
||||
if (comments.length) {
|
||||
h = data.comments.map(function(c) {
|
||||
return tmpl('comment_tmpl', c)
|
||||
}).join('')
|
||||
}
|
||||
$('#comments').html(h)
|
||||
})
|
||||
}
|
||||
jQuery(function($) {
|
||||
$('#need-js').remove()
|
||||
|
||||
SJS.request({uri: countCommentsURL(SJS.filename)}, function(err, request, body) {
|
||||
if (err) return
|
||||
var data
|
||||
, n
|
||||
try {
|
||||
data = JSON.parse(body)
|
||||
} catch (e) {
|
||||
console && console.log && console.log('not json -> ' + body)
|
||||
}
|
||||
n = data.count
|
||||
$('#sd').text(n > 0 ? 'show the discussion (' + n + ')' : 'start the discussion')
|
||||
})
|
||||
|
||||
$('#sd').click(function() {
|
||||
$('#sd-container').remove()
|
||||
$('#comment-stuff').slideDown(1.5, function() { this.scrollIntoView(true) })
|
||||
getComments()
|
||||
return false
|
||||
})
|
||||
|
||||
var showdown = new Showdown.converter()
|
||||
|
||||
$('#comment-form').submit(function() {
|
||||
var comment = $(this).serializeObject()
|
||||
var options = { method: 'POST'
|
||||
, uri: postCommentURL()
|
||||
, body: JSON.stringify(comment)
|
||||
}
|
||||
SJS.request(options, function(err, request, body) {
|
||||
if (err) {
|
||||
console.dir(err)
|
||||
alert('derp')
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME check for error, how do we get the returned status code?
|
||||
|
||||
comment.timestamp = +new Date()
|
||||
comment.html = showdown.makeHtml(comment.body)
|
||||
$('#comments').append(tmpl('comment_tmpl', comment))
|
||||
})
|
||||
return false
|
||||
})
|
||||
})
|
||||
}());
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
while (
|
||||
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
|
||||
all[0]
|
||||
);
|
||||
){/* braces here to satifsy closure-compiler */};
|
||||
|
||||
return v > 4 ? v : undef
|
||||
}())
|
||||
|
|
@ -594,7 +594,7 @@
|
|||
, url = options.uri + '?callback=GITR.' + jsonpCallbackName
|
||||
GITR[jsonpCallbackName] = function(obj) {
|
||||
cb(null, null, obj)
|
||||
setTimeout(function() { delete GITR[jsonpCallbackName] }, 0)
|
||||
delete GITR[jsonpCallbackName]
|
||||
}
|
||||
load(url)
|
||||
}
|
||||
|
|
|
|||
31
assets/jquery-serializeObject.js
vendored
Normal file
31
assets/jquery-serializeObject.js
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*!
|
||||
* jQuery serializeObject - v0.2 - 1/20/2010
|
||||
* http://benalman.com/projects/jquery-misc-plugins/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
|
||||
// Whereas .serializeArray() serializes a form into an array, .serializeObject()
|
||||
// serializes a form into an (arguably more useful) object.
|
||||
|
||||
;(function($,undefined){
|
||||
'$:nomunge'; // Used by YUI compressor.
|
||||
|
||||
$.fn.serializeObject = function(){
|
||||
var obj = {};
|
||||
|
||||
$.each( this.serializeArray(), function(i,o){
|
||||
var n = o.name,
|
||||
v = o.value;
|
||||
|
||||
obj[n] = obj[n] === undefined ? v
|
||||
: $.isArray( obj[n] ) ? obj[n].concat( v )
|
||||
: [ obj[n], v ];
|
||||
});
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -1,54 +1,31 @@
|
|||
(function() {
|
||||
;(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")
|
||||
// cors xhr request, quacks like mikeal's request module
|
||||
SJS.request = function(options, cb) {
|
||||
var url = options.uri
|
||||
, method = options.method || 'GET'
|
||||
, headers = options.headers || {}
|
||||
, body = typeof options.body === 'undefined' ? null : String(options.body)
|
||||
, xhr = new XMLHttpRequest()
|
||||
|
||||
// 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)
|
||||
// withCredentials => cors
|
||||
if ('withCredentials' in xhr) {
|
||||
xhr.open(method, url, true)
|
||||
} else if (typeof XDomainRequest === 'functon') {
|
||||
xhr = new XDomainRequest()
|
||||
xhr.open(method, url)
|
||||
} else {
|
||||
cb(new Error('cross domain requests not supported'))
|
||||
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
|
||||
for (var k in headers) if (headers.hasOwnProperty(k)) {
|
||||
xhr.setRequestHeader(k, headers[k])
|
||||
}
|
||||
var scriptElem = oDOC.createElement("script"),
|
||||
scriptdone = false
|
||||
scriptElem.onload = scriptElem.onreadystatechange = function () {
|
||||
if ((scriptElem.readyState && scriptElem.readyState !== "complete" && scriptElem.readyState !== "loaded") || scriptdone) {
|
||||
return false
|
||||
xhr.onload = function() {
|
||||
var response = xhr.responseText
|
||||
cb(null, xhr, response)
|
||||
}
|
||||
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)
|
||||
xhr.send(body)
|
||||
}
|
||||
}
|
||||
}())
|
||||
}());
|
||||
|
|
|
|||
1296
assets/showdown.js
Normal file
1296
assets/showdown.js
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
83
assets/strftime.js
Normal file
83
assets/strftime.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/// strftime
|
||||
/// http://github.com/samsonjs/strftime
|
||||
/// @_sjs
|
||||
///
|
||||
/// Copyright 2010 Sami Samhuri <sami.samhuri@gmail.com>
|
||||
/// MIT License
|
||||
|
||||
var strftime = (function() {
|
||||
var Weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
|
||||
'Friday', 'Saturday'];
|
||||
|
||||
var WeekdaysShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
var Months = ['January', 'February', 'March', 'April', 'May', 'June', 'July',
|
||||
'August', 'September', 'October', 'November', 'December'];
|
||||
|
||||
var MonthsShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
|
||||
'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
function pad(n, padding) {
|
||||
padding = padding || '0';
|
||||
return n < 10 ? (padding + n) : n;
|
||||
}
|
||||
|
||||
function hours12(d) {
|
||||
var hour = d.getHours();
|
||||
if (hour == 0) hour = 12;
|
||||
else if (hour > 12) hour -= 12;
|
||||
return hour;
|
||||
}
|
||||
|
||||
// Most of the specifiers supported by C's strftime
|
||||
function strftime(fmt, d) {
|
||||
d || (d = new Date());
|
||||
return fmt.replace(/%(.)/g, function(_, c) {
|
||||
switch (c) {
|
||||
case 'A': return Weekdays[d.getDay()];
|
||||
case 'a': return WeekdaysShort[d.getDay()];
|
||||
case 'B': return Months[d.getMonth()];
|
||||
case 'b': // fall through
|
||||
case 'h': return MonthsShort[d.getMonth()];
|
||||
case 'D': return strftime('%m/%d/%y', d);
|
||||
case 'd': return pad(d.getDate());
|
||||
case 'e': return d.getDate();
|
||||
case 'F': return strftime('%Y-%m-%d', d);
|
||||
case 'H': return pad(d.getHours());
|
||||
case 'I': return pad(hours12(d));
|
||||
case 'k': return pad(d.getHours(), ' ');
|
||||
case 'l': return pad(hours12(d), ' ');
|
||||
case 'M': return pad(d.getMinutes());
|
||||
case 'm': return pad(d.getMonth() + 1);
|
||||
case 'n': return '\n';
|
||||
case 'p': return d.getHours() < 12 ? 'AM' : 'PM';
|
||||
case 'R': return strftime('%H:%M', d);
|
||||
case 'r': return strftime('%I:%M:%S %p', d);
|
||||
case 'S': return pad(d.getSeconds());
|
||||
case 's': return d.getTime();
|
||||
case 'T': return strftime('%H:%M:%S', d);
|
||||
case 't': return '\t';
|
||||
case 'u':
|
||||
var day = d.getDay();
|
||||
return day == 0 ? 7 : day; // 1 - 7, Monday is first day of the week
|
||||
case 'v': return strftime('%e-%b-%Y', d);
|
||||
case 'w': return d.getDay(); // 0 - 6, Sunday is first day of the week
|
||||
case 'Y': return d.getFullYear();
|
||||
case 'y':
|
||||
var year = d.getYear();
|
||||
return year < 100 ? year : year - 100;
|
||||
case 'Z':
|
||||
var tz = d.toString().match(/\((\w+)\)/);
|
||||
return tz && tz[1] || '';
|
||||
case 'z':
|
||||
var off = d.getTimezoneOffset();
|
||||
return (off < 0 ? '-' : '+') + pad(off / 60) + pad(off % 60);
|
||||
default: return c;
|
||||
}
|
||||
});
|
||||
}
|
||||
return strftime;
|
||||
}());
|
||||
|
||||
if (typeof exports !== 'undefined') exports.strftime = strftime;
|
||||
else (function(global) { global.strftime = strftime }(this));
|
||||
35
assets/tmpl.js
Normal file
35
assets/tmpl.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Simple JavaScript Templating
|
||||
// John Resig - http://ejohn.org/ - MIT Licensed
|
||||
;(function(){
|
||||
var cache = {};
|
||||
|
||||
this.tmpl = function tmpl(str, data){
|
||||
// Figure out if we're getting a template, or if we need to
|
||||
// load the template - and be sure to cache the result.
|
||||
var fn = !/\W/.test(str) ?
|
||||
cache[str] = cache[str] ||
|
||||
tmpl(document.getElementById(str).innerHTML) :
|
||||
|
||||
// Generate a reusable function that will serve as a template
|
||||
// generator (and which will be cached).
|
||||
new Function("obj",
|
||||
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
||||
|
||||
// Introduce the data as local variables using with(){}
|
||||
"with(obj){p.push('" +
|
||||
|
||||
// Convert the template into pure JavaScript
|
||||
str
|
||||
.replace(/[\r\t\n]/g, " ")
|
||||
.split("<%").join("\t")
|
||||
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
||||
.replace(/\t=(.*?)%>/g, "',$1,'")
|
||||
.split("\t").join("');")
|
||||
.split("%>").join("p.push('")
|
||||
.split("\r").join("\\'")
|
||||
+ "');}return p.join('');");
|
||||
|
||||
// Provide some basic currying to the user
|
||||
return data ? fn( data ) : fn;
|
||||
};
|
||||
})();
|
||||
5
blog.rb
5
blog.rb
|
|
@ -46,6 +46,7 @@ posts.each_with_index do |post, i|
|
|||
:post => post,
|
||||
:previous => i < posts.length - 1 && posts[i + 1],
|
||||
:next => i > 0 && posts[i - 1],
|
||||
:filename => post[:filename],
|
||||
:comments => post[:comments]
|
||||
})
|
||||
end
|
||||
|
|
@ -54,7 +55,9 @@ end
|
|||
index_template = File.read(File.join('templates', 'blog', 'index.html'))
|
||||
index_html = Mustache.render(index_template, { :posts => posts,
|
||||
:post => posts.first,
|
||||
:previous => posts[1]
|
||||
:previous => posts[1],
|
||||
:filename => posts.first[:filename],
|
||||
:comments => posts.first[:comments]
|
||||
})
|
||||
|
||||
# write landing page
|
||||
|
|
|
|||
|
|
@ -7,36 +7,20 @@
|
|||
var _gaq = _gaq || [];
|
||||
_gaq.push( ['_setAccount', 'UA-214054-5']
|
||||
, ['_trackPageview']
|
||||
);
|
||||
)
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
;(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true
|
||||
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)
|
||||
})()
|
||||
</script>
|
||||
<script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js></script>
|
||||
<script>
|
||||
window.SJS = window.SJS || {}
|
||||
SJS.filename = "37signals-chalk-dissected.html"
|
||||
</script>
|
||||
<script src=../assets/blog-all.min.js></script>
|
||||
<header>
|
||||
<h1><a href=index.html>sjs' blog</a></h1>
|
||||
</header>
|
||||
|
|
@ -292,25 +276,39 @@ addLineNumbersToAllGists();
|
|||
|
||||
</article>
|
||||
</div>
|
||||
<p class=sep>༄</p>
|
||||
<div id=around>
|
||||
<a id=prev href=a-preview-of-mach-o-file-generation.html>← A preview of Mach-O file generation</a>
|
||||
<br style=clear:both>
|
||||
</div>
|
||||
<div class=center><a id=discussion-toggle href=#>↓ show discussion ↓</a></div>
|
||||
<div id=discussion>
|
||||
<div id=comment-form>
|
||||
<form method=post action=http://bohodev.net:8000/comment>
|
||||
<input name=from type=hidden value=37signals-chalk-dissected.html>
|
||||
<p>Name: <input name=name size=30></p>
|
||||
<p>URL: <input name=url size=30></p>
|
||||
<p>Thoughts: <textarea name=body cols=40 rows=5></textarea></p>
|
||||
<p><input type=submit value=Add to discussion></p>
|
||||
</form>
|
||||
</div>
|
||||
<p id=need-js align=center><strong>(discussion requires JavaScript)</strong></p>
|
||||
<div class=center id=sd-container><a id=sd href=#comment-stuff>show the discussion</a></div>
|
||||
<div id=comment-stuff>
|
||||
<div id=comments>
|
||||
<img id=discussion-spinner src=../assets/spinner.gif>
|
||||
</div>
|
||||
<form id=comment-form>
|
||||
<input name=post type=hidden value=37signals-chalk-dissected.html>
|
||||
<p><input name=name type=text placeholder=name></p>
|
||||
<p><input name=email type=text placeholder=email></p>
|
||||
<p><input name=url type=text placeholder=url></p>
|
||||
<p><textarea name=body placeholder=thoughts></textarea></p>
|
||||
<p align=center><input type=submit value='so there'></p>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/html" id="comment_tmpl">
|
||||
<div class=comment>
|
||||
<p>
|
||||
<% if (url) { %>
|
||||
<a href="<%= url %>"><%= name %></a>
|
||||
<% } else { %>
|
||||
<%= name %>
|
||||
<% } %>
|
||||
@ <%= strftime('%F %r', new Date(timestamp)) %>
|
||||
</p>
|
||||
<blockquote><%= html %></blockquote>
|
||||
</div>
|
||||
</script>
|
||||
<footer>
|
||||
<a href=https://twitter.com/_sjs>@_sjs</a>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -7,36 +7,20 @@
|
|||
var _gaq = _gaq || [];
|
||||
_gaq.push( ['_setAccount', 'UA-214054-5']
|
||||
, ['_trackPageview']
|
||||
);
|
||||
)
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
;(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true
|
||||
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)
|
||||
})()
|
||||
</script>
|
||||
<script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js></script>
|
||||
<script>
|
||||
window.SJS = window.SJS || {}
|
||||
SJS.filename = "a-preview-of-mach-o-file-generation.html"
|
||||
</script>
|
||||
<script src=../assets/blog-all.min.js></script>
|
||||
<header>
|
||||
<h1><a href=index.html>sjs' blog</a></h1>
|
||||
</header>
|
||||
|
|
@ -86,26 +70,40 @@ straightforward, an example is in asm/binary.rb, in the #output method.</p>
|
|||
|
||||
</article>
|
||||
</div>
|
||||
<p class=sep>༄</p>
|
||||
<div id=around>
|
||||
<a id=prev href=basics-of-the-mach-o-file-format.html>← Basics of the Mach-O file format</a>
|
||||
<a id=next href=37signals-chalk-dissected.html>37signals' Chalk Dissected →</a>
|
||||
<br style=clear:both>
|
||||
</div>
|
||||
<div class=center><a id=discussion-toggle href=#>↓ show discussion ↓</a></div>
|
||||
<div id=discussion>
|
||||
<div id=comment-form>
|
||||
<form method=post action=http://bohodev.net:8000/comment>
|
||||
<input name=from type=hidden value=a-preview-of-mach-o-file-generation.html>
|
||||
<p>Name: <input name=name size=30></p>
|
||||
<p>URL: <input name=url size=30></p>
|
||||
<p>Thoughts: <textarea name=body cols=40 rows=5></textarea></p>
|
||||
<p><input type=submit value=Add to discussion></p>
|
||||
</form>
|
||||
</div>
|
||||
<p id=need-js align=center><strong>(discussion requires JavaScript)</strong></p>
|
||||
<div class=center id=sd-container><a id=sd href=#comment-stuff>show the discussion</a></div>
|
||||
<div id=comment-stuff>
|
||||
<div id=comments>
|
||||
<img id=discussion-spinner src=../assets/spinner.gif>
|
||||
</div>
|
||||
<form id=comment-form>
|
||||
<input name=post type=hidden value=a-preview-of-mach-o-file-generation.html>
|
||||
<p><input name=name type=text placeholder=name></p>
|
||||
<p><input name=email type=text placeholder=email></p>
|
||||
<p><input name=url type=text placeholder=url></p>
|
||||
<p><textarea name=body placeholder=thoughts></textarea></p>
|
||||
<p align=center><input type=submit value='so there'></p>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/html" id="comment_tmpl">
|
||||
<div class=comment>
|
||||
<p>
|
||||
<% if (url) { %>
|
||||
<a href="<%= url %>"><%= name %></a>
|
||||
<% } else { %>
|
||||
<%= name %>
|
||||
<% } %>
|
||||
@ <%= strftime('%F %r', new Date(timestamp)) %>
|
||||
</p>
|
||||
<blockquote><%= html %></blockquote>
|
||||
</div>
|
||||
</script>
|
||||
<footer>
|
||||
<a href=https://twitter.com/_sjs>@_sjs</a>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -7,36 +7,20 @@
|
|||
var _gaq = _gaq || [];
|
||||
_gaq.push( ['_setAccount', 'UA-214054-5']
|
||||
, ['_trackPageview']
|
||||
);
|
||||
)
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
;(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true
|
||||
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)
|
||||
})()
|
||||
</script>
|
||||
<script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js></script>
|
||||
<script>
|
||||
window.SJS = window.SJS || {}
|
||||
SJS.filename = "basics-of-the-mach-o-file-format.html"
|
||||
</script>
|
||||
<script src=../assets/blog-all.min.js></script>
|
||||
<header>
|
||||
<h1><a href=index.html>sjs' blog</a></h1>
|
||||
</header>
|
||||
|
|
@ -309,26 +293,40 @@ would almost have a useful Mach object file.)</i></p>
|
|||
|
||||
</article>
|
||||
</div>
|
||||
<p class=sep>༄</p>
|
||||
<div id=around>
|
||||
<a id=prev href=working-with-c-style-structs-in-ruby.html>← Working with C-style structs in Ruby</a>
|
||||
<a id=next href=a-preview-of-mach-o-file-generation.html>A preview of Mach-O file generation →</a>
|
||||
<br style=clear:both>
|
||||
</div>
|
||||
<div class=center><a id=discussion-toggle href=#>↓ show discussion ↓</a></div>
|
||||
<div id=discussion>
|
||||
<div id=comment-form>
|
||||
<form method=post action=http://bohodev.net:8000/comment>
|
||||
<input name=from type=hidden value=basics-of-the-mach-o-file-format.html>
|
||||
<p>Name: <input name=name size=30></p>
|
||||
<p>URL: <input name=url size=30></p>
|
||||
<p>Thoughts: <textarea name=body cols=40 rows=5></textarea></p>
|
||||
<p><input type=submit value=Add to discussion></p>
|
||||
</form>
|
||||
</div>
|
||||
<p id=need-js align=center><strong>(discussion requires JavaScript)</strong></p>
|
||||
<div class=center id=sd-container><a id=sd href=#comment-stuff>show the discussion</a></div>
|
||||
<div id=comment-stuff>
|
||||
<div id=comments>
|
||||
<img id=discussion-spinner src=../assets/spinner.gif>
|
||||
</div>
|
||||
<form id=comment-form>
|
||||
<input name=post type=hidden value=basics-of-the-mach-o-file-format.html>
|
||||
<p><input name=name type=text placeholder=name></p>
|
||||
<p><input name=email type=text placeholder=email></p>
|
||||
<p><input name=url type=text placeholder=url></p>
|
||||
<p><textarea name=body placeholder=thoughts></textarea></p>
|
||||
<p align=center><input type=submit value='so there'></p>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/html" id="comment_tmpl">
|
||||
<div class=comment>
|
||||
<p>
|
||||
<% if (url) { %>
|
||||
<a href="<%= url %>"><%= name %></a>
|
||||
<% } else { %>
|
||||
<%= name %>
|
||||
<% } %>
|
||||
@ <%= strftime('%F %r', new Date(timestamp)) %>
|
||||
</p>
|
||||
<blockquote><%= html %></blockquote>
|
||||
</div>
|
||||
</script>
|
||||
<footer>
|
||||
<a href=https://twitter.com/_sjs>@_sjs</a>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -3,34 +3,34 @@
|
|||
<meta name=viewport content=width=device-width>
|
||||
<title>blog :: samhuri.net</title>
|
||||
<link rel=stylesheet href=../assets/blog.css>
|
||||
<script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js></script>
|
||||
<script>
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push( ['_setAccount', 'UA-214054-5']
|
||||
, ['_trackPageview']
|
||||
);
|
||||
|
||||
(function() {
|
||||
;(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
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() {
|
||||
var index = document.getElementById('index')
|
||||
, indexToggle = document.getElementById('index-toggle')
|
||||
, hidden = true
|
||||
indexToggle.onclick = function() {
|
||||
jQuery(function($) {
|
||||
var hidden = true
|
||||
, index = $('#index')
|
||||
$('#index-toggle').click(function() {
|
||||
index.toggle()
|
||||
hidden = !hidden
|
||||
index.style.display = hidden ? 'none' : 'block'
|
||||
indexToggle.innerHTML = hidden ? '↓ show posts ↓' : '↑ hide posts ↑'
|
||||
}
|
||||
}
|
||||
$(this).html(hidden ? '↓ show posts ↓' : '↑ hide posts ↑')
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
window.SJS = window.SJS || {}
|
||||
SJS.filename = "37signals-chalk-dissected.html"
|
||||
</script>
|
||||
<script src=../assets/blog-all.min.js></script>
|
||||
<header>
|
||||
<h1><a href=index.html>sjs' blog</a></h1>
|
||||
</header>
|
||||
|
|
@ -294,11 +294,40 @@ addLineNumbersToAllGists();
|
|||
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<p class=sep>༄</p>
|
||||
<div id=around>
|
||||
<a id=prev href=a-preview-of-mach-o-file-generation.html>← A preview of Mach-O file generation</a>
|
||||
<br style=clear:both>
|
||||
</div>
|
||||
<p id=need-js align=center><strong>(discussion requires JavaScript)</strong></p>
|
||||
<div class=center id=sd-container><a id=sd href=#comment-stuff>show the discussion</a></div>
|
||||
<div id=comment-stuff>
|
||||
<div id=comments>
|
||||
<img id=discussion-spinner src=../assets/spinner.gif>
|
||||
</div>
|
||||
<form id=comment-form>
|
||||
<input name=post type=hidden value=37signals-chalk-dissected.html>
|
||||
<p><input name=name type=text placeholder=name></p>
|
||||
<p><input name=email type=text placeholder=email></p>
|
||||
<p><input name=url type=text placeholder=url></p>
|
||||
<p><textarea name=body placeholder=thoughts></textarea></p>
|
||||
<p align=center><input type=submit value='so there'></p>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/html" id="comment_tmpl">
|
||||
<div class=comment>
|
||||
<p>
|
||||
<% if (url) { %>
|
||||
<a href="<%= url %>"><%= name %></a>
|
||||
<% } else { %>
|
||||
<%= name %>
|
||||
<% } %>
|
||||
@ <%= strftime('%F %r', new Date(timestamp)) %>
|
||||
</p>
|
||||
<blockquote><%= html %></blockquote>
|
||||
</div>
|
||||
</script>
|
||||
<footer>
|
||||
<a href=https://twitter.com/_sjs>@_sjs</a>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -7,36 +7,20 @@
|
|||
var _gaq = _gaq || [];
|
||||
_gaq.push( ['_setAccount', 'UA-214054-5']
|
||||
, ['_trackPageview']
|
||||
);
|
||||
)
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
;(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true
|
||||
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)
|
||||
})()
|
||||
</script>
|
||||
<script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js></script>
|
||||
<script>
|
||||
window.SJS = window.SJS || {}
|
||||
SJS.filename = "working-with-c-style-structs-in-ruby.html"
|
||||
</script>
|
||||
<script src=../assets/blog-all.min.js></script>
|
||||
<header>
|
||||
<h1><a href=index.html>sjs' blog</a></h1>
|
||||
</header>
|
||||
|
|
@ -158,25 +142,39 @@ of the Mach-O file format</a></i><p>
|
|||
|
||||
</article>
|
||||
</div>
|
||||
<p class=sep>༄</p>
|
||||
<div id=around>
|
||||
<a id=next href=basics-of-the-mach-o-file-format.html>Basics of the Mach-O file format →</a>
|
||||
<br style=clear:both>
|
||||
</div>
|
||||
<div class=center><a id=discussion-toggle href=#>↓ show discussion ↓</a></div>
|
||||
<div id=discussion>
|
||||
<div id=comment-form>
|
||||
<form method=post action=http://bohodev.net:8000/comment>
|
||||
<input name=from type=hidden value=working-with-c-style-structs-in-ruby.html>
|
||||
<p>Name: <input name=name size=30></p>
|
||||
<p>URL: <input name=url size=30></p>
|
||||
<p>Thoughts: <textarea name=body cols=40 rows=5></textarea></p>
|
||||
<p><input type=submit value=Add to discussion></p>
|
||||
</form>
|
||||
</div>
|
||||
<p id=need-js align=center><strong>(discussion requires JavaScript)</strong></p>
|
||||
<div class=center id=sd-container><a id=sd href=#comment-stuff>show the discussion</a></div>
|
||||
<div id=comment-stuff>
|
||||
<div id=comments>
|
||||
<img id=discussion-spinner src=../assets/spinner.gif>
|
||||
</div>
|
||||
<form id=comment-form>
|
||||
<input name=post type=hidden value=working-with-c-style-structs-in-ruby.html>
|
||||
<p><input name=name type=text placeholder=name></p>
|
||||
<p><input name=email type=text placeholder=email></p>
|
||||
<p><input name=url type=text placeholder=url></p>
|
||||
<p><textarea name=body placeholder=thoughts></textarea></p>
|
||||
<p align=center><input type=submit value='so there'></p>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/html" id="comment_tmpl">
|
||||
<div class=comment>
|
||||
<p>
|
||||
<% if (url) { %>
|
||||
<a href="<%= url %>"><%= name %></a>
|
||||
<% } else { %>
|
||||
<%= name %>
|
||||
<% } %>
|
||||
@ <%= strftime('%F %r', new Date(timestamp)) %>
|
||||
</p>
|
||||
<blockquote><%= html %></blockquote>
|
||||
</div>
|
||||
</script>
|
||||
<footer>
|
||||
<a href=https://twitter.com/_sjs>@_sjs</a>
|
||||
</footer>
|
||||
|
|
|
|||
7
combine.sh
Executable file
7
combine.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env zsh
|
||||
|
||||
echo "request,showdown,strftime,tmpl,jquery-serializeObject,blog -> assets/blog-all.min.js"
|
||||
cat assets/{request,showdown,strftime,tmpl,jquery-serializeObject,blog}.min.js >|assets/blog-all.min.js
|
||||
|
||||
echo "gitter.storage-polyfill,store,proj -> assets/proj-all.min.js"
|
||||
cat assets/{gitter,storage-polyfill,store,proj}.min.js >|assets/proj-all.min.js
|
||||
|
|
@ -2,10 +2,14 @@
|
|||
|
||||
var fs = require('fs')
|
||||
, http = require('http')
|
||||
, path = require('path')
|
||||
, parseURL = require('url').parse
|
||||
, keys = require('keys')
|
||||
, markdown = require('markdown')
|
||||
, strftime = require('strftime').strftime
|
||||
, DefaultOptions = { host: 'localhost'
|
||||
, port: 2020
|
||||
, postsFile: 'posts.json'
|
||||
, postsFile: path.join(__dirname, 'posts.json')
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
|
@ -19,12 +23,15 @@ function main() {
|
|||
readJSON(options.postsFile, function(err, posts) {
|
||||
if (err) {
|
||||
console.error('failed to parse posts file, is it valid JSON?')
|
||||
console.dir(e)
|
||||
console.dir(err)
|
||||
process.exit(1)
|
||||
}
|
||||
if (context.posts === null) {
|
||||
var n = posts.published.length
|
||||
, t = strftime('%Y-%m-%d %I:%M:%S %p')
|
||||
console.log('(' + t + ') ' + 'loaded discussions for ' + n + ' posts...')
|
||||
}
|
||||
context.posts = posts.published
|
||||
var n = context.posts.length
|
||||
console.log((context.posts === null ? '' : 're') + 'loaded ' + n + ' posts...')
|
||||
if (typeof cb == 'function') cb()
|
||||
})
|
||||
}
|
||||
|
|
@ -56,63 +63,232 @@ function readJSON(f, cb) {
|
|||
})
|
||||
}
|
||||
|
||||
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
|
||||
// returns a request handler that returns a string
|
||||
function createTextHandler(options) {
|
||||
if (typeof options === 'string') {
|
||||
options = { body: options }
|
||||
} else {
|
||||
options = options || {}
|
||||
}
|
||||
var comments = context.db.get(data.post) || []
|
||||
comments.push({ name: data.name
|
||||
, email: data.email
|
||||
, body: data.body
|
||||
var body = options.body || ''
|
||||
, code = options.cody || 200
|
||||
, type = options.type || 'text/plain'
|
||||
, n = body.length
|
||||
return function(req, res) {
|
||||
var headers = res.headers || {}
|
||||
headers['content-type'] = type
|
||||
headers['content-length'] = n
|
||||
|
||||
// console.log('code: ', code)
|
||||
// console.log('headers: ', JSON.stringify(headers, null, 2))
|
||||
// console.log('body: ', body)
|
||||
|
||||
res.writeHead(code, headers)
|
||||
res.end(body)
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-Origin Resource Sharing
|
||||
var createCorsHandler = (function() {
|
||||
var AllowedOrigins = [ 'http://samhuri.net'
|
||||
, 'http://localhost:8888'
|
||||
]
|
||||
|
||||
return function(handler) {
|
||||
handler = handler || createTextHandler('ok')
|
||||
return function(req, res) {
|
||||
var origin = req.headers.origin
|
||||
console.log('origin: ', origin)
|
||||
console.log('index: ', AllowedOrigins.indexOf(origin))
|
||||
if (AllowedOrigins.indexOf(origin) !== -1) {
|
||||
res.headers = { 'Access-Control-Allow-Origin': origin
|
||||
, 'Access-Control-Request-Method': 'POST, GET'
|
||||
, 'Access-Control-Allow-Headers': 'content-type'
|
||||
}
|
||||
handler(req, res)
|
||||
} else {
|
||||
BadRequest(req, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}())
|
||||
|
||||
var DefaultHandler = createTextHandler({ code: 404, body: 'not found' })
|
||||
, BadRequest = createTextHandler({ code: 400, body: 'bad request' })
|
||||
, ServerError = createTextHandler({ code: 500, body: 'server error' })
|
||||
, _routes = {}
|
||||
|
||||
function route(method, pattern, handler) {
|
||||
if (typeof pattern === 'function' && !handler) {
|
||||
handler = pattern
|
||||
pattern = ''
|
||||
}
|
||||
if (!pattern || typeof pattern.exec !== 'function') {
|
||||
pattern = new RegExp('^/' + pattern)
|
||||
}
|
||||
var route = { pattern: pattern, handler: handler }
|
||||
console.log('routing ' + method, pattern)
|
||||
if (!(method in _routes)) _routes[method] = []
|
||||
_routes[method].push(route)
|
||||
}
|
||||
|
||||
function resolve(method, path) {
|
||||
var rs = _routes[method]
|
||||
, i = rs.length
|
||||
, m
|
||||
, r
|
||||
while (i--) {
|
||||
r = rs[i]
|
||||
m = r.pattern.exec ? r.pattern.exec(path) : path.match(r.pattern)
|
||||
if (m) return r.handler
|
||||
}
|
||||
console.warn('*** using default handler, this is probably not what you want')
|
||||
return DefaultHandler
|
||||
}
|
||||
|
||||
function get(pattern, handler) {
|
||||
route('GET', pattern, handler)
|
||||
}
|
||||
|
||||
function post(pattern, handler) {
|
||||
route('POST', pattern, handler)
|
||||
}
|
||||
|
||||
function options(pattern, handler) {
|
||||
route('OPTIONS', pattern, handler)
|
||||
}
|
||||
|
||||
function handleRequest(req, res) {
|
||||
var handler = resolve(req.method, req.url)
|
||||
try {
|
||||
handler(req, res)
|
||||
} catch (e) {
|
||||
console.error('!!! error handling ' + req.method, req.url)
|
||||
console.dir(e)
|
||||
}
|
||||
}
|
||||
|
||||
function commentServer(context) {
|
||||
function addComment(post, name, email, url, body) {
|
||||
var comments = context.db.get(post) || []
|
||||
comments.push({ name: name
|
||||
, email: email
|
||||
, url: url
|
||||
, body: body
|
||||
, timestamp: Date.now()
|
||||
})
|
||||
context.db.set(data.post, comments)
|
||||
console.log('[' + new Date() + '] add comment ' + JSON.stringify(data, null, 2))
|
||||
return true
|
||||
context.db.set(post, comments)
|
||||
console.log('[' + new Date() + '] comment on ' + post)
|
||||
console.log('name:', name)
|
||||
console.log('email:', email)
|
||||
console.log('url:', url)
|
||||
console.log('body:', body)
|
||||
}
|
||||
|
||||
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]
|
||||
function getComments(req, res) {
|
||||
var post = parseURL(req.url).pathname.replace(/^\/comments\//, '')
|
||||
, comments
|
||||
, s
|
||||
if (context.posts.indexOf(post) === -1) {
|
||||
badRequest(res)
|
||||
console.warn('post not found: ' + post)
|
||||
BadRequest(req, 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)
|
||||
res.respond({comments: comments.map(function(c) {
|
||||
delete c.email
|
||||
c.html = markdown.parse(c.body)
|
||||
// FIXME discount has a race condition, sometimes gives a string
|
||||
// with trailing garbage.
|
||||
while (c.html.charAt(c.html.length - 1) !== '>') {
|
||||
console.log("!!! removing trailing garbage from discount's html")
|
||||
c.html = c.html.slice(0, c.html.length - 1)
|
||||
}
|
||||
return c
|
||||
})})
|
||||
}
|
||||
|
||||
function postComment(req, res) {
|
||||
var body = ''
|
||||
req.on('data', function(chunk) { body += chunk })
|
||||
req.on('end', function() {
|
||||
var data, post, name, email, url
|
||||
try {
|
||||
data = JSON.parse(body)
|
||||
} catch (e) {
|
||||
console.log('not json -> ' + body)
|
||||
BadRequest(req, res)
|
||||
return
|
||||
}
|
||||
post = (data.post || '').trim()
|
||||
name = (data.name || 'anonymous').trim()
|
||||
email = (data.email || '').trim()
|
||||
url = (data.url || '').trim()
|
||||
if (!url.match(/^https?:\/\//)) url = 'http://' + url
|
||||
body = data.body || ''
|
||||
if (!post || !body || context.posts.indexOf(post) === -1) {
|
||||
console.warn('mising post, body, or post not found: ' + post)
|
||||
console.warn('body: ', body)
|
||||
BadRequest(req, res)
|
||||
return
|
||||
}
|
||||
addComment(post, name, email, url, body)
|
||||
res.respond()
|
||||
// TODO mail watchers about the comment
|
||||
})
|
||||
}
|
||||
|
||||
function countComments(req, res) {
|
||||
var post = parseURL(req.url).pathname.replace(/^\/count\//, '')
|
||||
, comments
|
||||
if (context.posts.indexOf(post) === -1) {
|
||||
console.warn('post not found: ' + post)
|
||||
BadRequest(req, res)
|
||||
return
|
||||
}
|
||||
comments = context.db.get(post) || []
|
||||
res.respond({count: comments.length})
|
||||
}
|
||||
|
||||
return { get: getComments
|
||||
, count: countComments
|
||||
, post: postComment
|
||||
}
|
||||
}
|
||||
|
||||
function requestHandler(context) {
|
||||
var comments = commentServer(context)
|
||||
get(/comments\//, createCorsHandler(comments.get))
|
||||
get(/count\//, createCorsHandler(comments.count))
|
||||
post(/comment\/?/, createCorsHandler(comments.post))
|
||||
options(createCorsHandler())
|
||||
|
||||
return function(req, res) {
|
||||
console.log(req.method + ' ' + req.url)
|
||||
res.respond = function(obj) {
|
||||
var s = ''
|
||||
var headers = res.headers || {}
|
||||
if (obj) {
|
||||
try {
|
||||
s = JSON.stringify(obj)
|
||||
} catch (e) {
|
||||
ServerError(req, res)
|
||||
return
|
||||
}
|
||||
headers['content-type'] = 'application/json'
|
||||
}
|
||||
headers['content-length'] = s.length
|
||||
|
||||
/*
|
||||
console.log('code: ', s ? 200 : 204)
|
||||
process.stdout.write('headers: ')
|
||||
console.dir(headers)
|
||||
console.log('body: ', s)
|
||||
*/
|
||||
|
||||
res.writeHead(s ? 200 : 204, headers)
|
||||
res.end(s)
|
||||
}
|
||||
handleRequest(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,14 +327,6 @@ function parseArgs(defaults) {
|
|||
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) {
|
||||
|
|
|
|||
11
minify.sh
Executable file
11
minify.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env zsh
|
||||
|
||||
setopt extendedglob
|
||||
|
||||
for js (assets/*.js~*.min.js) {
|
||||
target=${js%.js}.min.js
|
||||
if [ ! -f $target ] || [ $js -nt $target ]; then
|
||||
echo "$js -> $target"
|
||||
closure-compiler < $js >| $target
|
||||
fi
|
||||
}
|
||||
|
|
@ -3,34 +3,36 @@
|
|||
<meta name=viewport content=width=device-width>
|
||||
<title>blog :: samhuri.net</title>
|
||||
<link rel=stylesheet href=../assets/blog.css>
|
||||
<script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js></script>
|
||||
<script>
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push( ['_setAccount', 'UA-214054-5']
|
||||
, ['_trackPageview']
|
||||
);
|
||||
|
||||
(function() {
|
||||
;(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
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() {
|
||||
var index = document.getElementById('index')
|
||||
, indexToggle = document.getElementById('index-toggle')
|
||||
, hidden = true
|
||||
indexToggle.onclick = function() {
|
||||
jQuery(function($) {
|
||||
var hidden = true
|
||||
, index = $('#index')
|
||||
$('#index-toggle').click(function() {
|
||||
index.toggle()
|
||||
hidden = !hidden
|
||||
index.style.display = hidden ? 'none' : 'block'
|
||||
indexToggle.innerHTML = hidden ? '↓ show posts ↓' : '↑ hide posts ↑'
|
||||
}
|
||||
}
|
||||
$(this).html(hidden ? '↓ show posts ↓' : '↑ hide posts ↑')
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{{#comments}}
|
||||
<script>
|
||||
window.SJS = window.SJS || {}
|
||||
SJS.filename = "{{filename}}"
|
||||
</script>
|
||||
<script src=../assets/blog-all.min.js></script>
|
||||
{{/comments}}
|
||||
<header>
|
||||
<h1><a href=index.html>sjs' blog</a></h1>
|
||||
</header>
|
||||
|
|
@ -45,16 +47,17 @@ function ready() {
|
|||
</nav>
|
||||
<div id=posts>
|
||||
{{! TODO extract a post partial used here and in post.html }}
|
||||
{{#post}}
|
||||
<article>
|
||||
{{#post}}
|
||||
<header>
|
||||
<h1><a href={{filename}}>{{title}}</a></h1>
|
||||
<time>{{date}}</time>
|
||||
</header>
|
||||
{{{body}}}
|
||||
</article>
|
||||
{{/post}}
|
||||
</article>
|
||||
</div>
|
||||
<p class=sep>༄</p>
|
||||
<div id=around>
|
||||
{{#previous}}
|
||||
<a id=prev href={{filename}}>← {{title}}</a>
|
||||
|
|
@ -64,6 +67,36 @@ function ready() {
|
|||
{{/next}}
|
||||
<br style=clear:both>
|
||||
</div>
|
||||
{{#comments}}
|
||||
<p id=need-js align=center><strong>(discussion requires JavaScript)</strong></p>
|
||||
<div class=center id=sd-container><a id=sd href=#comment-stuff>show the discussion</a></div>
|
||||
<div id=comment-stuff>
|
||||
<div id=comments>
|
||||
<img id=discussion-spinner src=../assets/spinner.gif>
|
||||
</div>
|
||||
<form id=comment-form>
|
||||
<input name=post type=hidden value={{filename}}>
|
||||
<p><input name=name type=text placeholder=name></p>
|
||||
<p><input name=email type=text placeholder=email></p>
|
||||
<p><input name=url type=text placeholder=url></p>
|
||||
<p><textarea name=body placeholder=thoughts></textarea></p>
|
||||
<p align=center><input type=submit value='so there'></p>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/html" id="comment_tmpl">
|
||||
<div class=comment>
|
||||
<p>
|
||||
<% if (url) { %>
|
||||
<a href="<%= url %>"><%= name %></a>
|
||||
<% } else { %>
|
||||
<%= name %>
|
||||
<% } %>
|
||||
@ <%= strftime('%F %r', new Date(timestamp)) %>
|
||||
</p>
|
||||
<blockquote><%= html %></blockquote>
|
||||
</div>
|
||||
</script>
|
||||
{{/comments}}
|
||||
<footer>
|
||||
<a href=https://twitter.com/_sjs>@_sjs</a>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -7,38 +7,22 @@
|
|||
var _gaq = _gaq || [];
|
||||
_gaq.push( ['_setAccount', 'UA-214054-5']
|
||||
, ['_trackPageview']
|
||||
);
|
||||
)
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
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}}
|
||||
;(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true
|
||||
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)
|
||||
})()
|
||||
</script>
|
||||
{{#comments}}
|
||||
<script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js></script>
|
||||
<script>
|
||||
window.SJS = window.SJS || {}
|
||||
SJS.filename = "{{filename}}"
|
||||
</script>
|
||||
<script src=../assets/blog-all.min.js></script>
|
||||
{{/comments}}
|
||||
<header>
|
||||
<h1><a href=index.html>sjs' blog</a></h1>
|
||||
</header>
|
||||
|
|
@ -55,6 +39,7 @@ function ready() {
|
|||
{{/post}}
|
||||
</article>
|
||||
</div>
|
||||
<p class=sep>༄</p>
|
||||
<div id=around>
|
||||
{{#previous}}
|
||||
<a id=prev href={{filename}}>← {{title}}</a>
|
||||
|
|
@ -64,22 +49,35 @@ function ready() {
|
|||
{{/next}}
|
||||
<br style=clear:both>
|
||||
</div>
|
||||
<div class=center><a id=discussion-toggle href=#>↓ show discussion ↓</a></div>
|
||||
{{#comments}}
|
||||
<div id=discussion>
|
||||
<div id=comment-form>
|
||||
<form method=post action=http://bohodev.net:8000/comment>
|
||||
<input name=from type=hidden value={{#post}}{{filename}}{{/post}}>
|
||||
<p>Name: <input name=name size=30></p>
|
||||
<p>URL: <input name=url size=30></p>
|
||||
<p>Thoughts: <textarea name=body cols=40 rows=5></textarea></p>
|
||||
<p><input type=submit value=Add to discussion></p>
|
||||
</form>
|
||||
</div>
|
||||
<p id=need-js align=center><strong>(discussion requires JavaScript)</strong></p>
|
||||
<div class=center id=sd-container><a id=sd href=#comment-stuff>show the discussion</a></div>
|
||||
<div id=comment-stuff>
|
||||
<div id=comments>
|
||||
<img id=discussion-spinner src=../assets/spinner.gif>
|
||||
</div>
|
||||
<form id=comment-form>
|
||||
<input name=post type=hidden value={{filename}}>
|
||||
<p><input name=name type=text placeholder=name></p>
|
||||
<p><input name=email type=text placeholder=email></p>
|
||||
<p><input name=url type=text placeholder=url></p>
|
||||
<p><textarea name=body placeholder=thoughts></textarea></p>
|
||||
<p align=center><input type=submit value='so there'></p>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/html" id="comment_tmpl">
|
||||
<div class=comment>
|
||||
<p>
|
||||
<% if (url) { %>
|
||||
<a href="<%= url %>"><%= name %></a>
|
||||
<% } else { %>
|
||||
<%= name %>
|
||||
<% } %>
|
||||
@ <%= strftime('%F %r', new Date(timestamp)) %>
|
||||
</p>
|
||||
<blockquote><%= html %></blockquote>
|
||||
</div>
|
||||
</script>
|
||||
{{/comments}}
|
||||
<footer>
|
||||
<a href=https://twitter.com/_sjs>@_sjs</a>
|
||||
|
|
|
|||
Loading…
Reference in a new issue