Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 5 commits
  • 4 files changed
  • 0 comments
  • 1 contributor
1  app.js
@@ -6,6 +6,7 @@ app.use(jojo);
6 6 app.listen(8080);
7 7
8 8 app.set('view engine', 'ejs');
  9 +app.set('jojo index view', 'pages/index');
9 10 app.set('jojo article view', 'pages/article');
10 11
11 12 app.get('/abba', function (req, res) {
8 node_modules/jojo/package.json
@@ -13,8 +13,12 @@
13 13 "node": ">= 0.4.0"
14 14 },
15 15 "dependencies": {
16   - "express": ">= 2.0.0"
  16 + "express": ">= 2.0.0",
  17 + "async": "*"
  18 + },
  19 + "devDependencies": {
  20 + "ejs": "*",
  21 + "showdown": "*"
17 22 },
18   - "devDependencies": {},
19 23 "optionalDependencies": {}
20 24 }
238 node_modules/jojo/src/jojo.js
... ... @@ -1,14 +1,13 @@
1 1 var fs = require('fs'),
2 2 path = require('path'),
3   - url = require('url');
  3 + async = require('async');
4 4
5   -// TODO: What the fuck does this mean???
6   -// TODO: Restore config.callouts (config should not be a local variable?)
7   -
8   -// TODO: Switch over to a static file creator and file watching
  5 +// TODO: Switch over to a static file creator and file watching (don't forget about creation of new files -- directory watching? or async setTimeout that watches dir)
9 6
10 7 // TODO: Implement RSS
11 8
  9 +// TODO: Make separate methods for base page, article page, and rss which are tied back to jojo
  10 +
12 11 /**
13 12 * jojo - 10 second blog engine for hackers (in javascript)
14 13 * @param {Object<ExpressServer>} [app] App to write to
@@ -29,7 +28,7 @@ function jojo(req, res, next) {
29 28 basepath = settings['jojo basepath'] || '/',
30 29 cwd = process.cwd(),
31 30 articleDir = settings['jojo articles'] || path.join(cwd, 'articles'),
32   - formatEngine = settings['jojo formatter'] || 'showdown',
  31 + yoyo = new Yoyo(app),
33 32 indexView = settings['jojo index view'],
34 33 articleView = settings['jojo article view'],
35 34 baseIndex;
@@ -38,102 +37,59 @@ function jojo(req, res, next) {
38 37 if (url === basepath) {
39 38 // and there is an index view
40 39 if (indexView) {
41   - // TODO: Index page with articles
42   - return res.render(indexView, 'Post summary');
  40 + // Generate an index page with articles
  41 + return yoyo.readArticles(articleDir, function (err, articles) {
  42 + // If there is an error, log it and move to next fn
  43 + if (err) {
  44 + console.error(err);
  45 + return next();
  46 + }
  47 +
  48 + // Otherwise, render
  49 + // TODO: Figure out how to include view data that we don't know about
  50 + res.render(indexView, {'title': 'TESTING', 'articles': articles});
  51 + });
43 52 }
44 53 } else {
45 54 baseIndex = url.indexOf(basepath);
46 55 if (baseIndex !== -1) {
47   - // TODO: Abstract this into a jojo.readArticles (and readArticlesSync?) function
48 56 // Otherwise, attempt to find a matching article
49   - return fs.readdir(articleDir, function (err, fileNames) {
50   - var articleUrl = url.slice(baseIndex + 1);
51   -
52   - // If there is an error, log it and continue to the next method
  57 + return yoyo.readArticles(articleDir, function (err, articles) {
  58 + // If there is an error, log it and run the next function
53 59 if (err) {
54   - console.error('Article directory could not be read: ', articleDir);
55 60 console.error(err);
56 61 return next();
57 62 }
58 63
59 64 // Iterate the files
60   - var i = 0,
61   - len = fileNames.length,
62   - fileName,
63   - fileParts,
64   - articleName,
65   - fileFound = false;
  65 + var articleUrl = url.slice(baseIndex + 1),
  66 + i = 0,
  67 + len = articles.length,
  68 + article,
  69 + articleFound = false;
66 70 for (; i < len; i++) {
67   - fileName = fileNames[i];
68   -
69   - // Remove the extension from the file
70   - fileParts = fileName.split('.');
71   - if (fileParts.length > 1) {
72   - fileParts.pop();
73   - }
  71 + article = articles[i];
74 72
75 73 // If it matches our article url, save it
76   - articleName = fileParts.join('.');
77   - if (articleUrl.indexOf(articleName) !== -1) {
78   - fileFound = true;
  74 + if (articleUrl.indexOf(article.url) !== -1) {
  75 + articleFound = true;
79 76 break;
80 77 }
81 78 }
82 79
83   - // If the file was found, interpret it
84   - if (fileFound) {
85   - fs.readFile(path.join(articleDir, fileName), 'utf8', function (err, file) {
86   - // If there was an error, log it and call the next method
87   - if (err) {
88   - console.error('Article could not be read: ', fileName);
89   - console.error(err);
90   - return next();
91   - }
92   -
93   - var formatter = require(formatEngine);
94   -
95   - // If the engine is showdown, get the proper formatter
96   - if (formatEngine === 'showdown') {
97   - // This is so wrong... on so many levels... =_=
98   - var tempThis = {};
99   - formatter.Showdown.converter.call(tempThis);
100   - formatter = tempThis.makeHtml;
101   - }
102   -
103   - // TODO: This is what cloudhead uses but I am not too fond of it
104   - // Find where the JSON ends (denoted by a double line break)
105   - var dblLineBreakIndex = file.search(/\n\r?\n/g);
106   -
107   - // Fallback the dblLineBreakIndex
108   - if (dblLineBreakIndex === -1) {
109   - dblLineBreakIndex = file.length;
110   - }
111   -
112   - // Break up the properties and content
113   - var propsStr = file.slice(0, dblLineBreakIndex),
114   - props = new Function('return ' + propsStr + ';')(),
115   - rawContent = file.slice(dblLineBreakIndex);
116   -
117   - // Render the content via the formatter
118   - var content = formatter(rawContent),
119   - // TODO: Allow for non-json parser (e.g. yaml)
120   - renderObj = JSON.parse(propsStr);
121   -
122   - // Save the content to the renderObj
123   - renderObj.content = content;
124   -
125   - // TODO: Proper config item
126   - renderObj.config = app.settings;
127   -
128   - // If there is an articleView, render through it
129   - if (articleView !== undefined) {
130   - // TODO: Allow for alternative jojo render engine?
131   - res.render(articleView, renderObj);
132   - } else {
133   - // Otherwise, use res.send
134   - res.send(content);
135   - }
136   - });
  80 + // If the article was found, use it
  81 + if (articleFound) {
  82 + // TODO: Proper config item
  83 + article.config = app.settings;
  84 +
  85 + // If there is an articleView, render through it
  86 + if (articleView !== undefined) {
  87 + // TODO: Allow for alternative jojo render engine?
  88 + res.render(articleView, article);
  89 + } else {
  90 + // Otherwise, send the content
  91 + res.send(article.content);
  92 + }
137 93 } else {
138 94 // Otherwise, call the next method
139 95 next();
@@ -141,6 +97,7 @@ function jojo(req, res, next) {
141 97 });
142 98 }
143 99 }
  100 +
144 101 // Otherwise, call the next method
145 102 next();
146 103 }
@@ -160,5 +117,118 @@ jojo.createServer = function () {
160 117
161 118 // TODO: jojo.static which preloads views and does not dynamically fetch during each request
162 119
  120 +// State (and sanity) preserver for jojo
  121 +function Yoyo(app) {
  122 + // Fallback app
  123 + app = app || {'settings': {}};
  124 +
  125 + // Save app to this
  126 + this.app = app;
  127 +}
  128 +Yoyo.prototype = {
  129 + 'readArticles': function (articleDir, callback) {
  130 + var that = this;
  131 + fs.readdir(articleDir, function (err, articles) {
  132 + // If there is an error, log and callback with it
  133 + if (err) {
  134 + console.error('Article directory could not be read: ', articleDir);
  135 + return callback(err);
  136 + }
  137 +
  138 + // Otherwise, read in all of the articles
  139 + async.map(articles, function (article, callback) {
  140 + var articlePath = path.join(articleDir, article);
  141 + that.readArticle(articlePath, callback);
  142 + }, callback);
  143 + });
  144 + },
  145 + 'readArticle': function (articlePath, callback) {
  146 + var that = this;
  147 + fs.readFile(articlePath, 'utf8', function (err, article) {
  148 + // If there was an error, log it
  149 + if (err) {
  150 + console.error('An article could not be read: ', articlePath);
  151 + return callback(err);
  152 + }
  153 +
  154 + // Otherwise, parse the article
  155 + var parsedArticle = that.parseArticle(article);
  156 +
  157 + // Callback with the article
  158 + callback(null, parsedArticle);
  159 + });
  160 + },
  161 + 'parseArticle': function (article) {
  162 + var app = this.app,
  163 + settings = app.settings,
  164 + dataEngine = settings['jojo data parser'] || 'json',
  165 + formatEngine = settings['jojo formatter'] || 'showdown',
  166 + formatter = require(formatEngine),
  167 + dataParser = dataEngine === 'json' ? JSON.parse : require(dataEngine);
  168 +
  169 + // If the engine is showdown, get the proper formatter
  170 + if (formatEngine === 'showdown') {
  171 + // This is so wrong... on so many levels... =_=
  172 + var tempThis = {};
  173 + formatter.Showdown.converter.call(tempThis);
  174 + formatter = tempThis.makeHtml;
  175 + }
  176 +
  177 + // TODO: This is what cloudhead uses but I am not too fond of it
  178 + // Find where the JSON ends (denoted by a double line break)
  179 + var dblLineBreakIndex = article.search(/\n\r?\n/g);
  180 +
  181 + // Fallback the dblLineBreakIndex
  182 + if (dblLineBreakIndex === -1) {
  183 + dblLineBreakIndex = article.length;
  184 + }
  185 +
  186 + // Break up the properties and content
  187 + var propsStr = article.slice(0, dblLineBreakIndex),
  188 + props = new Function('return ' + propsStr + ';')(),
  189 + rawContent = article.slice(dblLineBreakIndex);
  190 +
  191 + // Render the content via the formatter
  192 + var content = formatter(rawContent),
  193 + retObj = dataParser(propsStr);
  194 +
  195 + // Save the content to the renderObj
  196 + retObj.content = content;
  197 +
  198 + // Fallback the url
  199 + retObj.url = retObj.url || jojo.getUrl(retObj);
  200 +
  201 + // Return the parsed object
  202 + return retObj;
  203 + }
  204 +};
  205 +
  206 +// Expose Yoyo via jojo
  207 +jojo.Yoyo = Yoyo;
  208 +
  209 +// Create a overwritable helper function for generating article URLs
  210 +jojo.getUrl = function (article) {
  211 + var urlParts = [],
  212 + date = article.date;
  213 + if (date) {
  214 + urlParts.push(date.replace(/\//g, '-'));
  215 + }
  216 + urlParts.push(article.title.replace(/\s+/g, '-'));
  217 +
  218 + return urlParts.join('-').toLowerCase();
  219 +}
  220 +
  221 +// Create helper methods for reading and parsing articles
  222 +var emptyYoyo = new Yoyo();
  223 +jojo.readArticles = function (articleDir, callback) {
  224 + emptyYoyo.readArticles(articleDir, callback);
  225 +};
  226 +jojo.readArticle = function (articlePath, callback) {
  227 + emptyYoyo.readArticle(articlePath, callback);
  228 +};
  229 +jojo.parseArticle = function (article) {
  230 + emptyYoyo.parseArticle(article);
  231 +};
  232 +
163 233 // Export jojo
164 234 module.exports = jojo;
4 views/layout.ejs
... ... @@ -1,8 +1,8 @@
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4   - <link rel="stylesheet" type="text/css" href="/css/main.css">
5   - <link rel="alternate" type="application/atom+xml" title="<%= title %> - feed" href="/index.xml" />
  4 + <!-- <link rel="stylesheet" type="text/css" href="/css/main.css"> -->
  5 + <!-- <link rel="alternate" type="application/atom+xml" title="<%= title %> - feed" href="/index.xml" /> -->
6 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
7 7 <title><%= title %></title>
8 8 </head>

No commit comments for this range

Something went wrong with that request. Please try again.