From 283cd28180b03d646dd2aad1222ad80188c528ec Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 17 Feb 2013 19:37:41 +0000 Subject: [PATCH 01/28] * Improved style * Blog details are now loaded from a json configuration file --- .gitignore | 5 +- History.md | 4 + README.md | 237 +++++------------------- blog.js | 39 ++-- config/blogConfig.json | 5 + package.json | 14 +- public/stylesheets/footer.css | 45 +++-- public/stylesheets/header.css | 72 ++++---- public/stylesheets/nav.css | 73 ++++---- public/stylesheets/reset.css | 117 ++++++------ public/stylesheets/wrapper.css | 317 +++++++++++++++++---------------- views/about.jade | 28 ++- views/layout.jade | 131 +++++++------- 13 files changed, 521 insertions(+), 566 deletions(-) create mode 100644 History.md create mode 100644 config/blogConfig.json diff --git a/.gitignore b/.gitignore index 688ef23..35b1bbd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ npm-debug.log -node_modules \ No newline at end of file +node_modules +initproject.sh +kenshiro-hackuto-blog.iml +.idea diff --git a/History.md b/History.md new file mode 100644 index 0000000..dc10240 --- /dev/null +++ b/History.md @@ -0,0 +1,4 @@ +0.2.1 / 2013-02-03 +==================== +* Improved style +* Blog details are now loaded from a json configuration file \ No newline at end of file diff --git a/README.md b/README.md index f736594..8f1c3cd 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,50 @@ -

Node2Blog

-

Node2Blog is a simple and easy to use blog template for the casual blogger. For those who wish to setup an operable blog in minutes, this is the project for you. The blog is built with Node.js, Express.js, and Mongodb (with the mongoose driver). The instructions for quickly building a blog with the Node2Blog template is shown below.

- -

Features

- -

Prerequisites

- - -

Tutorial: Getting Started

-

*Note: Forking Node2Blog is the best way to get the repository.

-

In your terminal, 'cd' to the directory where you want to develop the blog and do the following commands

-
$ git clone git@github.com:jawerty/Node2Blog.git blog-folder-name
$ cd blog-folder-name
$ npm install .
-

In order to initiate the blog server on your local machine, do the following command (You need node.js to run the following command)

-
$ node blog
-

The blog should be running on your localhost at the 3000 port; go to http://localhost:3000 to view it. And it should look similar to the screenshot below.

- - - -

If you would like to make a post, go to the the url http://localhost:3000/admin in order to log in and use the admin settings. The password is 'narwhal' by default. To change the password, go to the file 'blog.js' and change the p variable at the top to your desired password.

-

*Note: Change the t variable in 'blog.js' to your blog title (i.e t = 'Jared's Tech Blog').
Also, Change the st variable in 'blog.js' to whatever you would like your subtitle to be (i.e st = 'I am an App developer').

- - - -

When successfully logged in, your navigation bar should have three new options appended to it...

-
    -
  1. Admin-New (create a new post)
  2. -
  3. Admin-Delete (delete a post)
  4. -
  5. Admin-Logout (log out of admin view)
  6. -
- - - -

Creating and deleting posts should be self-explanatory; however, creating a new static page similar to the default 'about' page is detailed below.

-

Adding a static page

-

To create a new page, first you must go to the 'layout.jade' file in the /views folder. Add the following code under the about 'li' tag which is in the 'ol' tag in the #nav div.

-
-
-a(href="/new-page-name")
-	li new_page_name
-
-
-

Now create a new view with whatever name you want (i.e. new_page_name.jade) in the /views folder. Add the following code to your new view

-
-
-extends layout
-
-block wrapper_content
-	.container
-		h1 new_page_name
-		br 
-		p random information
-
-
-

Now modify the get functions in the 'blog.js' file.

- - -

-////////get////////
-app.get('/', home.index);
-app.get('/admin/delete', admin.delete);
-app.get('/admin/new', admin.new);
-app.get('/post/:id', post.post_view);
-app.get('/admin' || '/admin/', admin.admin_check);
-app.get('/admin/logout', function(req,res){
-  delete req.session.admin;
-  console.log('logged-out')
-  res.redirect('/');
-});
-
-app.get('/about', function(req, res) {
-  res.render('about', { title: t, admin:req.session.admin});
-      
-});
-
-//The code you added
-app.get('/new-page-name', function(req, res) {
-  res.render('new_page_name', { title: t, admin:req.session.admin});
-});
-///////////////////
-
- -

You should now be able to go the the '/new-page-name' route and have a view similar to what is below

- - - -

Adding a side widget

-

In order to add a side widget, or simply a box under the "Latest Posts" box, you must go to the file 'layout.jade' and insert this line

-
-
-.widget
-
-
-

Exactly where it is inserted below

-
-
-#box
-	#content
-		#wrapper
-			block wrapper_content
-		if(typeof posts == 'undefined')
-			.widget
-				a(href='/') Back to home
-		else
-			.widget
-				p(style='font-size: 150%; font-weight:bold; border-bottom: 1px solid #b1b1b1;padding-bottom: 5px;margin-bottom: 4px;') Latest Posts
-					br
-					pre
-					for post in posts
-						table(id='post_table', style='padding-top:10px;border-bottom:1px solid #ddd')
-							tr
-								td
-									#left
-										label(style='font-size: 130%;') -  
-								td
-									#right
-										a(href='/post/#{post._id}/#{post.title_sub}', style='font-size: 130%;')=  post.title
-							tr(style='height:10px;')
-		.widget //the inserted code
-
-
-

Now you can input any sort of information you'd like in your new widget box.*Note: Without any posts on your blog, the widget boxes will not be positioned adequately.

-
-
-

Congratulations you now have a working blog suitable to your basic blogger needs.

-
-
-

Optional: Heroku Setup

-

*Note: You must have a heroku account along with the Heroku Toolbelt to follow this part of the tutorial

-

Simply follow the directions on this page to deploy the blog with heroku. However, in order to use MongoDB, you must enter the following command in the directory of your project

-
-
-$ heroku addons:add mongohq:sandbox
-
-
-

This addon is a free starter package for running a server with a MongoDB backend by MongoHQ. This is essentially all you need to setup the basic functions to your new blog.

-

Contact

-

Contact the developer here
Email: jawerty210@gmail.com
Website: http://wrightdev.herokuapp.com

- -

MIT LICENSE

-The MIT License (MIT) Copyright (c) 2012 Jared Wright - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Node Boilerplate Version 2 +========================== +*Requires Node v0.6.6 (or newer)* +node-boilerplate takes html-boilerplate, express, connect, jade and Socket.IO and organizes them into a ready to use website project. It's a fast way to get working on your Node website without having to worry about the setup. It takes care of all the boring parts, like setting up your views, 404 page, 500 page, getting the modules organized, etc... + +Node Boilerplate has 4 goals: + +1. To end the repetition involved with starting a new Node website project +2. To never install anything outside of the project directory (For easier production deployment) +3. To make it easy to install additional modules within the project directory +4. To enable easy upgrade or freezing of project dependencies +(These goals are much easier to meet now that node includes the node_modules convention) + +To start a project: + + git clone git://github.com/robrighter/node-boilerplate.git mynewproject + cd mynewproject + ./initproject.sh +This will copy down all of the boilerplate files, organize them appropriately and init a fresh new git repository within which you can build your next big thing. + + +To run the boilerplate template app: + + node server.js + +Go to http://0.0.0.0:8081 and click on the send message link to see socket.io in action. + + +Additional Features: + +1. Creates a package.json file consistent with associated best practices (http://blog.nodejitsu.com/package-dependencies-done-right) +2. Adds .gitignore for the node_modules directory +3. Includes 404 page and associated route +4. Includes 500 page + +To add additional modules: + +Update the package.json file to include new module dependencies and run 'npm install'. + +**If you have a different set of default modules that you like to use, the structure is setup such that you can fork the project and replace the module dependencies outlined in the ./templates/apps/package.json file to best fit your needs and the initproject.sh script will initialize projects with your new set of modules.** + +Deployment +=============== + +node-boilerplate is setup to be easily deployed on a Joyent Node SmartMachine. This means that: + +1. The version of Node is defined in config.json and in package.json +2. The main script to run is server.js +3. The web server port is pulled from process.env.PORT diff --git a/blog.js b/blog.js index 0052201..9d6f412 100644 --- a/blog.js +++ b/blog.js @@ -1,19 +1,19 @@ - t = 'Node2Blog'; - st = 'A simple blog made in Node.js' - p = 'narwhal'; +var blogConfig = require("./config/blogConfig"); +console.log("Blog config loaded [name=%s, subtitle=%s]", blogConfig.name, blogConfig.st); + +t = blogConfig.name; +st = blogConfig.st; +p = blogConfig.password; + /** * Module dependencies. */ var mongoose = require('mongoose'); -var db = require('./db'); +var db = require('./db'); var post = mongoose.model('post'); - - //change t to whatever you want your blog to be called - //change p to whatever you want your password to be - - admin = null; - var error; +admin = null; +var error; var express = require('express') @@ -27,7 +27,7 @@ var app = express(); var store = new express.session.MemoryStore; //MIDDLEWARE -app.configure(function(){ +app.configure(function () { app.use(express.logger('dev')); app.set('port', process.env.PORT || 3000); app.set('views', __dirname + '/views'); @@ -37,18 +37,17 @@ app.configure(function(){ app.use(express.methodOverride()); app.use(express.cookieParser()); app.use(express.session({ secret: 'ar4452ihbb34y2b3hu4kvk2u34vu23y4yu324k', - store: store - })); + store: store + })); app.use(express.static(path.join(__dirname, 'public'))); app.use(app.router); }); -app.configure('development', function(){ +app.configure('development', function () { app.use(express.errorHandler()); }); - ////////get methods//////// app.get('/', home.index); app.get('/admin/delete', admin.delete); @@ -56,15 +55,15 @@ app.get('/admin/new', admin.new); app.get('/post/:id/:title', post.post_view); app.get('/admin' || '/admin/', admin.admin_check); app.get('/admin/:id/edit', admin.admin_edit); -app.get('/admin/logout', function(req,res){ +app.get('/admin/logout', function (req, res) { delete req.session.admin; console.log('logged-out') res.redirect('/'); }); -app.get('/about', function(req, res) { - res.render('about', { title: t, subTitle:st, admin:req.session.admin}); - +app.get('/about', function (req, res) { + res.render('about', { title: t, subTitle: st, admin: req.session.admin}); + }); /////////////////////////// @@ -82,6 +81,6 @@ app.post('/post/:id/:title', post.post_view_post_handler); //Server start -http.createServer(app).listen(app.get('port'), function(){ +http.createServer(app).listen(app.get('port'), function () { console.log("Your blog is running on port " + app.get('port')); }); diff --git a/config/blogConfig.json b/config/blogConfig.json new file mode 100644 index 0000000..6ba09c2 --- /dev/null +++ b/config/blogConfig.json @@ -0,0 +1,5 @@ +{ + "name": "Kenshiro's Journey", + "st": "Exploring the path of the Hackuto-Shinken", + "password": "kenshiro-o" +} \ No newline at end of file diff --git a/package.json b/package.json index 5856291..3487129 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { "name": "Node2Blog", "description": "Simple and easy to use blog template for your casual blogger.", - "version": "0.2.0", + "version": "0.2.1", "author": { - "name": "Jared Wright", - "email": "jawerty210@gmail.com", - "url": "http://wrightdev.herokuapp.com" + "name": "kenshiro-o", + "email": "kenshiro@kenshiro.me", + "url": "http://kenshiro.me" }, "private": true, "engines": { "node": "0.8.x", - "npm": "1.1.x" + "npm": "1.1.x" }, "scripts": { "start": "node blog" @@ -18,8 +18,8 @@ "dependencies": { "express": "3.0.0rc5", "jade": "*", - "mongoose":"*", - "mongodb":"*" + "mongoose": "*", + "mongodb": "*" } } diff --git a/public/stylesheets/footer.css b/public/stylesheets/footer.css index 893b60b..5bb36f4 100644 --- a/public/stylesheets/footer.css +++ b/public/stylesheets/footer.css @@ -1,12 +1,37 @@ -#footer { - width: 97.5%; - text-align: center; - float: left; - padding: 15px; - font-size: 17px; - font-family: sans-serif; - color: #393939; - z-index: -1; +#footerContainer { + + /* IE10 Consumer Preview */ + background-image: -ms-linear-gradient(bottom, #111 0%, #444 100%); + + /* Mozilla Firefox */ + background-image: -moz-linear-gradient(bottom, #111 0%, #444 100%); + + /* Opera */ + background-image: -o-linear-gradient(bottom, #111 0%, #444 100%); + + /* Webkit (Safari/Chrome 10) */ + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #444)); + + /* Webkit (Chrome 11+) */ + background-image: -webkit-linear-gradient(bottom, #111 0%, #444 100%); + + /* W3C Markup, IE10 Release Preview */ + background-image: linear-gradient(to top, #111 0%, #444 100%); + + clear: both; + width: 100%; + text-align: center; + float: left; + padding: 15px; + font-size: 17px; + font-family: sans-serif; + color: #fff; + z-index: -1; margin: 0 auto; - + + background-color: #000000; +} + +#footerContainer p { + color: #FFF; } \ No newline at end of file diff --git a/public/stylesheets/header.css b/public/stylesheets/header.css index c450020..1c01cd9 100644 --- a/public/stylesheets/header.css +++ b/public/stylesheets/header.css @@ -1,41 +1,53 @@ @font-face { - font-family: telegrafico; - src: url("../images/telegrafico.ttf"); + font-family: telegrafico; + src: url("../images/telegrafico.ttf"); } + #header { - /*background:url('../images/micro_carbon.png');*/ - - /* IE10 Consumer Preview */ - background-image: -ms-linear-gradient(bottom, #111 0%, #444 100%); + background: url('../images/micro_carbon.png'); + + /* IE10 Consumer Preview */ + background-image: -ms-linear-gradient(bottom, #111 0%, #444 100%); + + /* Mozilla Firefox */ + background-image: -moz-linear-gradient(bottom, #111 0%, #444 100%); + + /* Opera */ + background-image: -o-linear-gradient(bottom, #111 0%, #444 100%); - /* Mozilla Firefox */ - background-image: -moz-linear-gradient(bottom, #111 0%, #444 100%); + /* Webkit (Safari/Chrome 10) */ + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #444)); - /* Opera */ - background-image: -o-linear-gradient(bottom, #111 0%, #444 100%); + /* Webkit (Chrome 11+) */ + background-image: -webkit-linear-gradient(bottom, #111 0%, #444 100%); - /* Webkit (Safari/Chrome 10) */ - background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #444)); + /* W3C Markup, IE10 Release Preview */ + background-image: linear-gradient(to top, #111 0%, #444 100%); + height: 25%; + margin: 0; + border-bottom: 1px solid #555; +} - /* Webkit (Chrome 11+) */ - background-image: -webkit-linear-gradient(bottom, #111 0%, #444 100%); +#titleContainer { + max-width: 1000px; + margin: 0 auto; +} - /* W3C Markup, IE10 Release Preview */ - background-image: linear-gradient(to top, #111 0%, #444 100%); - height: 100%; - margin: 0; - border-bottom: 1px solid #555; +.title { + font-size: 1.9em; + font-family: "Lato", "HelveticaNeue-Light", "Helvetica", "sans-serif"; + color: #fff; + font-weight: bold; } -#title { - max-width: 1000px; - font-size: 500%; - color: #fff; - margin: 0 auto; - font-family: telegrafico; + +#subTitleContainer { + max-width: 1000px; + margin: 0 auto; + margin-bottom: 15px; } -#subTitle { - max-width: 1000px; - font-size: 130%; - margin: 0 auto; - margin-bottom: 15px; + +.subTitle { + font-size: 1.5em; + font-style: italic; + color: #cbd9df; } \ No newline at end of file diff --git a/public/stylesheets/nav.css b/public/stylesheets/nav.css index bb6e81a..b2380a7 100644 --- a/public/stylesheets/nav.css +++ b/public/stylesheets/nav.css @@ -1,49 +1,52 @@ #nav { - width: 100%; - height: 45px; - margin: 0px 0px 20px 0px; -/* IE10 Consumer Preview */ -background-image: -ms-linear-gradient(bottom, #444 0%, #858585 100%); + width: 100%; + height: 45px; + margin: 0px 0px 20px 0px; + /* IE10 Consumer Preview */ + background-image: -ms-linear-gradient(bottom, #444 0%, #858585 100%); -/* Mozilla Firefox */ -background-image: -moz-linear-gradient(bottom, #444 0%, #858585 100%); + /* Mozilla Firefox */ + background-image: -moz-linear-gradient(bottom, #444 0%, #858585 100%); -/* Opera */ -background-image: -o-linear-gradient(bottom, #444 0%, #858585 100%); + /* Opera */ + background-image: -o-linear-gradient(bottom, #444 0%, #858585 100%); -/* Webkit (Safari/Chrome 10) */ -background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #444), color-stop(1, #858585)); + /* Webkit (Safari/Chrome 10) */ + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #444), color-stop(1, #858585)); -/* Webkit (Chrome 11+) */ -background-image: -webkit-linear-gradient(bottom, #444 0%, #858585 100%); + /* Webkit (Chrome 11+) */ + background-image: -webkit-linear-gradient(bottom, #444 0%, #858585 100%); -/* W3C Markup, IE10 Release Preview */ -background-image: linear-gradient(to top, #444 0%, #858585 100%); + /* W3C Markup, IE10 Release Preview */ + background-image: linear-gradient(to top, #444 0%, #858585 100%); } -#nav ol{ - - max-width: 1000px; - margin: 0 auto; +#nav ol { + + max-width: 1000px; + margin: 0 auto; } -#nav li{ - vertical-align: middle; - text-align: center; + +#nav li { + vertical-align: middle; + text-align: center; } -#nav a{ - float:left; - display:block; - opacity:.9999; - text-decoration: none; - color: #fff; - margin-right: 2%; - margin-top: 1%; - margin-bottom: 1%; - font-size: 150%; - font-family: verdana; + +#nav a { + float: left; + display: block; + opacity: .9999; + text-decoration: none; + color: #fff; + margin-right: 2%; + margin-top: 1%; + margin-bottom: 1%; + font-size: 1.5em; + font-family: "Helvetica", "HelveticaNeue-Light", "sans-serif"; } -#nav a:hover{ - color: #888; + +#nav a:hover { + color: #888; } \ No newline at end of file diff --git a/public/stylesheets/reset.css b/public/stylesheets/reset.css index ac280c4..b6cd5aa 100644 --- a/public/stylesheets/reset.css +++ b/public/stylesheets/reset.css @@ -1,57 +1,66 @@ -body, html, div, blockquote, img, label, p, h1, h2, h3, h4, h5, h6, pre, ul, ol, -li, dl, dt, dd, form, a, fieldset, input, th, td -{ -margin: 0; padding: 0; border: 0; outline: none; -} -body -{ -line-height: 1; -font-size: 100%; -background-color: #EEE; -margin: 0 auto; -height:100%; -} -h1, h2, h3, h4, h5, h6, p, label -{ -font-size: 100%; -margin: 0px; -} -h1{ - padding-top: 10px; - padding-bottom: 20px; -} -ul, ol -{ -list-style: none; -} -a -{ -font-family: verdana; -color: #505050; -text-decoration: none; -} -a:hover -{ -color: #808080; -cursor: pointer; -} -p -{ -font-size: 120%; -font-family: sans-serif; -color: #505050; -} -label -{ -font-family:sans-serif; -font-style: italic; -color: #505050; -} - -#box{ - max-width: 100%; - min-width:100%; - margin: 0 auto; +html, body { + height: 100%; +} + +body, html, div, blockquote, img, label, p, h1, h2, h3, h4, h5, h6, pre, ul, ol, +li, dl, dt, dd, form, a, fieldset, input, th, td { + margin: 0; + padding: 0; + border: 0; + outline: none; +} + +body { + line-height: 1; + font-size: 100%; + background-color: #FFFFFF; + margin: 0 auto; + height: 100%; +} + +h1, h2, h3, h4, h5, h6, p, label { + font-size: 100%; + margin: 0px; +} + +h1 { + padding-top: 10px; + padding-bottom: 20px; +} + +ul, ol { + list-style: none; +} + +a { + font-family: verdana; + color: #505050; + text-decoration: none; +} + +a:hover { + color: #808080; + cursor: pointer; +} + +p { + font-size: 120%; + font-family: sans-serif; + color: #505050; +} + +label { + font-family: sans-serif; + font-style: italic; + color: #505050; +} + +#box { + max-width: 100%; + min-width: 100%; + min-height: 100%; + height: auto !important; + margin: 0 auto -4em; } diff --git a/public/stylesheets/wrapper.css b/public/stylesheets/wrapper.css index 9287873..d049aa6 100644 --- a/public/stylesheets/wrapper.css +++ b/public/stylesheets/wrapper.css @@ -1,192 +1,211 @@ -#content{ - width:100%; - height: 100%; - margin: 0 auto; - max-width: 1000px; +#content { + width: 100%; + height: 100%; + margin: 0 auto; + max-width: 1000px; } + #wrapper { - float:left; - background-color: #f0f0f0; - border: 1px solid #ddd; - border-radius: 5px; - width: 71.3%; - height: 100%; - padding: 25px; - margin-bottom: 50px; - + float: left; + background-color: #f0f0f0; + border: 1px solid #ddd; + border-radius: 5px; + width: 71.3%; + height: 100%; + padding: 25px; + margin-bottom: 50px; } + .nicEdit-selected { - border: 1px solid #ddd !important; + border: 1px solid #ddd !important; } + .nicEdit-main { - background-color: #fefefe; + background-color: #fefefe; } - + .nicEdit-button { - background-color: #fff !important; + background-color: #fff !important; } + iframe::-webkit-scrollbar { width: 10px; - + } - + iframe::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); border-radius: 3px; background-color: #E9E9E9; } - + iframe::-webkit-scrollbar-thumb { border-radius: 3px; background-color: #555; } -.widget{ - float:right; - background-color: #f0f0f0; - border: 1px solid #ddd; - border-radius: 5px; - width: 16%; - - height: 100%; - padding: 25px; - margin-bottom: 20px; - margin-left: 18px; - white-space: pre-wrap; /* CSS3 */ - white-space: -moz-pre-wrap; /* Firefox */ - white-space: -pre-wrap; /* Opera <7 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* IE */ +.widget { + float: right; + background-color: #f0f0f0; + border: 1px solid #ddd; + border-radius: 5px; + width: 16%; + + height: 100%; + padding: 25px; + margin-bottom: 20px; + margin-left: 18px; + white-space: pre-wrap; /* CSS3 */ + white-space: -moz-pre-wrap; /* Firefox */ + white-space: -pre-wrap; /* Opera <7 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* IE */ } + .container { - white-space: pre-wrap; /* CSS3 */ - white-space: -moz-pre-wrap; /* Firefox */ - white-space: -pre-wrap; /* Opera <7 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* IE */ + white-space: pre-wrap; /* CSS3 */ + white-space: -moz-pre-wrap; /* Firefox */ + white-space: -pre-wrap; /* Opera <7 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* IE */ } + .container h1 { - font-size: 70px; - font-family: sans-serif; - color: #505050; + font-size: 1.5em; + font-family: sans-serif; + color: #505050; } + .container p { - font-size: 20px; - font-family: sans-serif; - color: #393939; + font-size: 20px; + font-family: sans-serif; + color: #393939; } -#formNew{ - margin-left: 20px; - color:505050; + +#formNew { + margin-left: 20px; + color: 505050; } + #formNew input[type='text'] { - border: 1px solid #ddd; - border-radius: 5px; - width: 300px; - height: 40px; - font-size: 20px; - padding-left: 10px; - color:#505050; + border: 1px solid #ddd; + border-radius: 5px; + width: 300px; + height: 40px; + font-size: 20px; + padding-left: 10px; + color: #505050; } + #formNew input[type='submit'] { - border: 1px solid #A0A0A0; - border-radius: 5px; - width: 200px; - height: 60px; - font-size: 25px; - background-color: #858585; - color: #fff; + border: 1px solid #A0A0A0; + border-radius: 5px; + width: 200px; + height: 60px; + font-size: 25px; + background-color: #858585; + color: #fff; } -#formNew input[type='submit']:active{ - color:#858585; - background-color: #f6f6f6; + +#formNew input[type='submit']:active { + color: #858585; + background-color: #f6f6f6; } + #formNewTextarea { - border-radius: 5px; - border: 1px solid #ddd; - width: 98%; - height: 450px; - font-size: 25px; - padding: 10px; - resize:none; - outline:0px !important; - -webkit-appearance:none; + border-radius: 5px; + border: 1px solid #ddd; + width: 98%; + height: 450px; + font-size: 25px; + padding: 10px; + resize: none; + outline: 0px !important; + -webkit-appearance: none; background: #fff; - color:#505050; -} -#post_table{ - -} -.post{ - border: 1px solid #ccc; - background: #f7f7f7; - padding: 25px; - margin-bottom: 10px; - white-space: pre-wrap; /* CSS3 */ - white-space: -moz-pre-wrap; /* Firefox */ - white-space: -pre-wrap; /* Opera <7 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* IE */ -} -#left{ - float:left; - width:20px; -} -#right{ - float:left; - width: 140px; -} -#comment_form{ - border: 1px solid #ccc; - border-radius: 5px; - width: 500px; - height: 80px; - font-size: 20px; - padding: 10px; - resize:none; - outline:0px !important; - -webkit-appearance:none; -} - -#submitComment{ - border: 1px solid #A0A0A0; - border-radius: 5px; - width: 130px; - height: 30px; - font-size: 20px; - background-color: #858585; - color: #fff; -} -#submitComment:active{ - color:#858585; - background-color: #f6f6f6; - -} -#nameComment{ - border: 1px solid #ccc; - border-radius: 5px; - width: 200px; - height: 20px; - font-size:20px; - padding: 10px; - resize:none; - outline:0px !important; - -webkit-appearance:none; -} -#admin_check{ - border: 1px solid #ccc; - border-radius: 5px; - width: 300px; - height: 40px; - font-size:25px; - padding: 10px; - resize:none; - outline:0px !important; - -webkit-appearance:none; + color: #505050; +} + +#post_table { + +} + +.post { + border: 1px solid #ccc; + background: #f7f7f7; + padding: 25px; + margin-bottom: 10px; + white-space: pre-wrap; /* CSS3 */ + white-space: -moz-pre-wrap; /* Firefox */ + white-space: -pre-wrap; /* Opera <7 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* IE */ +} + +#left { + float: left; + width: 20px; +} + +#right { + float: left; + width: 140px; +} + +#comment_form { + border: 1px solid #ccc; + border-radius: 5px; + width: 500px; + height: 80px; + font-size: 20px; + padding: 10px; + resize: none; + outline: 0px !important; + -webkit-appearance: none; +} + +#submitComment { + border: 1px solid #A0A0A0; + border-radius: 5px; + width: 130px; + height: 30px; + font-size: 20px; + background-color: #858585; + color: #fff; +} + +#submitComment:active { + color: #858585; + background-color: #f6f6f6; + +} + +#nameComment { + border: 1px solid #ccc; + border-radius: 5px; + width: 200px; + height: 20px; + font-size: 20px; + padding: 10px; + resize: none; + outline: 0px !important; + -webkit-appearance: none; +} + +#admin_check { + border: 1px solid #ccc; + border-radius: 5px; + width: 300px; + height: 40px; + font-size: 25px; + padding: 10px; + resize: none; + outline: 0px !important; + -webkit-appearance: none; } diff --git a/views/about.jade b/views/about.jade index ca26cc9..443b87e 100644 --- a/views/about.jade +++ b/views/about.jade @@ -1,8 +1,26 @@ extends layout - + block wrapper_content - .container - h1 About page - br - p Stuff about me \ No newline at end of file + .container + h1 About Me + br + p + | I am Kenshiro (my real name is easy enough to find out. Just google it). The goal of this blog is to share my musings and projects with the rest of the connected world. + br + p + | I hope you will enjoy reading my posts and commenting. I am currently mastering the deadly arts of Node.js (I already am lethal on Core Java). + + br + br + + h1 What is Hackuto-Shinken? + p + em Hackuto-Shinken + | is a variant of the mighty + a(href="http://hokuto.wikia.com/wiki/Hokuto_Shin_Ken") Hokuto Shinken + | , an invincible fictional Martial Arts, which can only be passed down to one heir. + br + p + | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! + diff --git a/views/layout.jade b/views/layout.jade index 7ec4ead..96874e7 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -1,72 +1,69 @@ doctype 5 html - head - title= title - link(rel='stylesheet', href='/stylesheets/reset.css') - link(rel='stylesheet', href='/stylesheets/header.css') - link(rel='stylesheet', href='/stylesheets/wrapper.css') - link(rel='stylesheet', href='/stylesheets/footer.css') - link(rel='stylesheet', href='/stylesheets/nav.css') - script(type='text/javascript', src='/javascripts/main.js') - script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js') + head + title= title + link(rel='stylesheet', href='/stylesheets/reset.css') + link(rel='stylesheet', href='/stylesheets/header.css') + link(rel='stylesheet', href='/stylesheets/wrapper.css') + link(rel='stylesheet', href='/stylesheets/footer.css') + link(rel='stylesheet', href='/stylesheets/nav.css') + link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Lato") + script(type='text/javascript', src='/javascripts/main.js') + script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js') - body - div(id="fb-root") - script - (function(d, s, id) { - var js, fjs = d.getElementsByTagName(s)[0]; - if (d.getElementById(id)) return; - js = d.createElement(s); js.id = id; - js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=388558531205027"; - fjs.parentNode.insertBefore(js, fjs); - }(document, 'script', 'facebook-jssdk')); + body + #box + #header + #titleContainer + h1.title= title + #subTitleContainer + h2.subTitle= subTitle + div(id="fb-root") + script + (function(d, s, id) { + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=388558531205027"; + fjs.parentNode.insertBefore(js, fjs); + }(document, 'script', 'facebook-jssdk')); + #nav + ol + a(href="/") + li Home + a(href="/about") + li About + if(typeof admin == 'undefined') + - null + else + a(href="/admin/new") + li Admin-New + a(href="/admin/delete") + li Admin-Delete + a(href="/admin/logout") + li Admin-Logout - #header - #title - h1= title - #subTitle - label= subTitle - #nav - ol - a(href="/") - li Home - a(href="/about") - li About - if(typeof admin == 'undefined') - - null - else - a(href="/admin/new") - li Admin-New - a(href="/admin/delete") - li Admin-Delete - a(href="/admin/logout") - li Admin-Logout + #content + #wrapper + block wrapper_content + if(typeof posts == 'undefined') + .widget + a(href='/') Back to home + else + .widget + p(style='font-size: 150%; font-weight:bold; border-bottom: 1px solid #b1b1b1;padding-bottom: 5px;margin-bottom: 4px;') Latest Posts + br + pre + for post in posts + table(id='post_table', style='padding-top:10px;border-bottom:1px solid #ddd') + tr + td + #left + label(style='font-size: 130%;') -   + td + #right + a(href='/post/#{post._id}/#{post.title_sub}', style='font-size: 130%;')= post.title + tr(style='height:10px;') - - #box - #content - #wrapper - block wrapper_content - if(typeof posts == 'undefined') - .widget - a(href='/') Back to home - else - .widget - p(style='font-size: 150%; font-weight:bold; border-bottom: 1px solid #b1b1b1;padding-bottom: 5px;margin-bottom: 4px;') Latest Posts - br - pre - for post in posts - table(id='post_table', style='padding-top:10px;border-bottom:1px solid #ddd') - tr - td - #left - label(style='font-size: 130%;') -   - td - #right - a(href='/post/#{post._id}/#{post.title_sub}', style='font-size: 130%;')= post.title - tr(style='height:10px;') - - #footer - #footer_text - p - | Using the Node2Blog template and running on a Node.js server \ No newline at end of file + #footerContainer + p Using the Node2Blog template and running on a Node.js server \ No newline at end of file From c7356a68b5bd38ed304aed06da246c0fc64b3303 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 17 Feb 2013 19:38:53 +0000 Subject: [PATCH 02/28] Reverting file --- README.md | 237 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 188 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 8f1c3cd..f736594 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,189 @@ -Node Boilerplate Version 2 -========================== -*Requires Node v0.6.6 (or newer)* -node-boilerplate takes html-boilerplate, express, connect, jade and Socket.IO and organizes them into a ready to use website project. It's a fast way to get working on your Node website without having to worry about the setup. It takes care of all the boring parts, like setting up your views, 404 page, 500 page, getting the modules organized, etc... - -Node Boilerplate has 4 goals: - -1. To end the repetition involved with starting a new Node website project -2. To never install anything outside of the project directory (For easier production deployment) -3. To make it easy to install additional modules within the project directory -4. To enable easy upgrade or freezing of project dependencies -(These goals are much easier to meet now that node includes the node_modules convention) - -To start a project: - - git clone git://github.com/robrighter/node-boilerplate.git mynewproject - cd mynewproject - ./initproject.sh -This will copy down all of the boilerplate files, organize them appropriately and init a fresh new git repository within which you can build your next big thing. - - -To run the boilerplate template app: - - node server.js - -Go to http://0.0.0.0:8081 and click on the send message link to see socket.io in action. - - -Additional Features: - -1. Creates a package.json file consistent with associated best practices (http://blog.nodejitsu.com/package-dependencies-done-right) -2. Adds .gitignore for the node_modules directory -3. Includes 404 page and associated route -4. Includes 500 page - -To add additional modules: - -Update the package.json file to include new module dependencies and run 'npm install'. - -**If you have a different set of default modules that you like to use, the structure is setup such that you can fork the project and replace the module dependencies outlined in the ./templates/apps/package.json file to best fit your needs and the initproject.sh script will initialize projects with your new set of modules.** - -Deployment -=============== - -node-boilerplate is setup to be easily deployed on a Joyent Node SmartMachine. This means that: - -1. The version of Node is defined in config.json and in package.json -2. The main script to run is server.js -3. The web server port is pulled from process.env.PORT +

Node2Blog

+

Node2Blog is a simple and easy to use blog template for the casual blogger. For those who wish to setup an operable blog in minutes, this is the project for you. The blog is built with Node.js, Express.js, and Mongodb (with the mongoose driver). The instructions for quickly building a blog with the Node2Blog template is shown below.

+ +

Features

+ +

Prerequisites

+ + +

Tutorial: Getting Started

+

*Note: Forking Node2Blog is the best way to get the repository.

+

In your terminal, 'cd' to the directory where you want to develop the blog and do the following commands

+
$ git clone git@github.com:jawerty/Node2Blog.git blog-folder-name
$ cd blog-folder-name
$ npm install .
+

In order to initiate the blog server on your local machine, do the following command (You need node.js to run the following command)

+
$ node blog
+

The blog should be running on your localhost at the 3000 port; go to http://localhost:3000 to view it. And it should look similar to the screenshot below.

+ + + +

If you would like to make a post, go to the the url http://localhost:3000/admin in order to log in and use the admin settings. The password is 'narwhal' by default. To change the password, go to the file 'blog.js' and change the p variable at the top to your desired password.

+

*Note: Change the t variable in 'blog.js' to your blog title (i.e t = 'Jared's Tech Blog').
Also, Change the st variable in 'blog.js' to whatever you would like your subtitle to be (i.e st = 'I am an App developer').

+ + + +

When successfully logged in, your navigation bar should have three new options appended to it...

+
    +
  1. Admin-New (create a new post)
  2. +
  3. Admin-Delete (delete a post)
  4. +
  5. Admin-Logout (log out of admin view)
  6. +
+ + + +

Creating and deleting posts should be self-explanatory; however, creating a new static page similar to the default 'about' page is detailed below.

+

Adding a static page

+

To create a new page, first you must go to the 'layout.jade' file in the /views folder. Add the following code under the about 'li' tag which is in the 'ol' tag in the #nav div.

+
+
+a(href="/new-page-name")
+	li new_page_name
+
+
+

Now create a new view with whatever name you want (i.e. new_page_name.jade) in the /views folder. Add the following code to your new view

+
+
+extends layout
+
+block wrapper_content
+	.container
+		h1 new_page_name
+		br 
+		p random information
+
+
+

Now modify the get functions in the 'blog.js' file.

+ + +

+////////get////////
+app.get('/', home.index);
+app.get('/admin/delete', admin.delete);
+app.get('/admin/new', admin.new);
+app.get('/post/:id', post.post_view);
+app.get('/admin' || '/admin/', admin.admin_check);
+app.get('/admin/logout', function(req,res){
+  delete req.session.admin;
+  console.log('logged-out')
+  res.redirect('/');
+});
+
+app.get('/about', function(req, res) {
+  res.render('about', { title: t, admin:req.session.admin});
+      
+});
+
+//The code you added
+app.get('/new-page-name', function(req, res) {
+  res.render('new_page_name', { title: t, admin:req.session.admin});
+});
+///////////////////
+
+ +

You should now be able to go the the '/new-page-name' route and have a view similar to what is below

+ + + +

Adding a side widget

+

In order to add a side widget, or simply a box under the "Latest Posts" box, you must go to the file 'layout.jade' and insert this line

+
+
+.widget
+
+
+

Exactly where it is inserted below

+
+
+#box
+	#content
+		#wrapper
+			block wrapper_content
+		if(typeof posts == 'undefined')
+			.widget
+				a(href='/') Back to home
+		else
+			.widget
+				p(style='font-size: 150%; font-weight:bold; border-bottom: 1px solid #b1b1b1;padding-bottom: 5px;margin-bottom: 4px;') Latest Posts
+					br
+					pre
+					for post in posts
+						table(id='post_table', style='padding-top:10px;border-bottom:1px solid #ddd')
+							tr
+								td
+									#left
+										label(style='font-size: 130%;') -  
+								td
+									#right
+										a(href='/post/#{post._id}/#{post.title_sub}', style='font-size: 130%;')=  post.title
+							tr(style='height:10px;')
+		.widget //the inserted code
+
+
+

Now you can input any sort of information you'd like in your new widget box.*Note: Without any posts on your blog, the widget boxes will not be positioned adequately.

+
+
+

Congratulations you now have a working blog suitable to your basic blogger needs.

+
+
+

Optional: Heroku Setup

+

*Note: You must have a heroku account along with the Heroku Toolbelt to follow this part of the tutorial

+

Simply follow the directions on this page to deploy the blog with heroku. However, in order to use MongoDB, you must enter the following command in the directory of your project

+
+
+$ heroku addons:add mongohq:sandbox
+
+
+

This addon is a free starter package for running a server with a MongoDB backend by MongoHQ. This is essentially all you need to setup the basic functions to your new blog.

+

Contact

+

Contact the developer here
Email: jawerty210@gmail.com
Website: http://wrightdev.herokuapp.com

+ +

MIT LICENSE

+The MIT License (MIT) Copyright (c) 2012 Jared Wright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From bdf4c6c989f0aabb5d0b518841a91584e754da0d Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 17 Feb 2013 19:44:20 +0000 Subject: [PATCH 03/28] Updating readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f736594..03eb16d 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,9 @@ -

If you would like to make a post, go to the the url http://localhost:3000/admin in order to log in and use the admin settings. The password is 'narwhal' by default. To change the password, go to the file 'blog.js' and change the p variable at the top to your desired password.

-

*Note: Change the t variable in 'blog.js' to your blog title (i.e t = 'Jared's Tech Blog').
Also, Change the st variable in 'blog.js' to whatever you would like your subtitle to be (i.e st = 'I am an App developer').

+

If you would like to make a post, go to the the url http://localhost:3000/admin in order to log in and use the admin settings. The password is 'kenshiro-o' by default. T +o change the password, go to the file /config/blogConfig.json and change the 'password' value

+

*Note: You can also change the subtitle and title of the blog in the /config/blogConfig.json file (i.e st = 'Exploring the path of the Hackuto-Shinken').

From a836a50ac2b0becb5f62910a50088eb98ba07043 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 17 Feb 2013 19:46:41 +0000 Subject: [PATCH 04/28] Updating readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03eb16d..483549f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Node2Blog

+

Node2Blog (fork from jawerty210 project)

Node2Blog is a simple and easy to use blog template for the casual blogger. For those who wish to setup an operable blog in minutes, this is the project for you. The blog is built with Node.js, Express.js, and Mongodb (with the mongoose driver). The instructions for quickly building a blog with the Node2Blog template is shown below.

Features

@@ -177,7 +177,7 @@ $ heroku addons:add mongohq:sandbox

This addon is a free starter package for running a server with a MongoDB backend by MongoHQ. This is essentially all you need to setup the basic functions to your new blog.

Contact

-

Contact the developer here
Email: jawerty210@gmail.com
Website: http://wrightdev.herokuapp.com

+

Contact the developer here
Email: kenshiro@kenshiro.me
Website: http://kenshiro.me

MIT LICENSE

The MIT License (MIT) Copyright (c) 2012 Jared Wright From 6d6f9a19b03fa37397dbfd989584551dd3b24810 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sat, 23 Feb 2013 22:11:39 +0000 Subject: [PATCH 05/28] adding favicon --- History.md | 4 ++++ blog.js | 2 +- package.json | 2 +- public/favicon.ico | Bin 0 -> 6311 bytes 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 public/favicon.ico diff --git a/History.md b/History.md index dc10240..ef58990 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +0.2.2 / 2013-02-23 +==================== +* Added favicon + 0.2.1 / 2013-02-03 ==================== * Improved style diff --git a/blog.js b/blog.js index 9d6f412..190bf64 100644 --- a/blog.js +++ b/blog.js @@ -32,7 +32,7 @@ app.configure(function () { app.set('port', process.env.PORT || 3000); app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); - app.use(express.favicon()); + app.use(express.favicon(path.join(__dirname, 'public/favicon.ico'))); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(express.cookieParser()); diff --git a/package.json b/package.json index 3487129..a6f3846 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Node2Blog", "description": "Simple and easy to use blog template for your casual blogger.", - "version": "0.2.1", + "version": "0.2.2", "author": { "name": "kenshiro-o", "email": "kenshiro@kenshiro.me", diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..604801326cd891b4aee1b923d95a25f3788fb882 GIT binary patch literal 6311 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 literal 0 HcmV?d00001 From 44d4085650bb03c858b35a2bf1c440c4ba40a278 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Wed, 31 Jul 2013 22:06:16 +0100 Subject: [PATCH 06/28] * MAJOR refactoring of API (in progress). * Revamping of layout as well (in progress). --- History.md | 5 ++ blog.js | 21 ++++--- config/blogConfig.json | 9 ++- db.js | 23 -------- db/model/comment.js | 12 ++++ db/model/post.js | 12 ++++ package.json | 8 +-- public/stylesheets/style.css | 74 +++++++++++++++++++++++ routes/admin.js | 64 +++++++++----------- routes/home.js | 21 ++++--- routes/post.js | 110 ++++++++++++++++++----------------- views/about.jade | 21 +++---- views/default-layout.jade | 39 +++++++++++++ views/index.jade | 11 ++++ views/post.jade | 7 +++ 15 files changed, 289 insertions(+), 148 deletions(-) create mode 100644 db/model/comment.js create mode 100644 db/model/post.js create mode 100644 public/stylesheets/style.css create mode 100644 views/default-layout.jade create mode 100644 views/index.jade create mode 100644 views/post.jade diff --git a/History.md b/History.md index ef58990..d36ad30 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +1.0.0 / 2013-07-31 +==================== +* MAJOR refactoring of API (in progress). +* Revamping of layout as well (in progress). + 0.2.2 / 2013-02-23 ==================== * Added favicon diff --git a/blog.js b/blog.js index 190bf64..4a3d4d2 100644 --- a/blog.js +++ b/blog.js @@ -1,16 +1,12 @@ var blogConfig = require("./config/blogConfig"); -console.log("Blog config loaded [name=%s, subtitle=%s]", blogConfig.name, blogConfig.st); +console.log("Blog config loaded [name=%s, subtitle=%s]", blogConfig.name, blogConfig.subTitle); -t = blogConfig.name; -st = blogConfig.st; p = blogConfig.password; /** * Module dependencies. */ -var mongoose = require('mongoose'); var db = require('./db'); -var post = mongoose.model('post'); admin = null; var error; @@ -47,12 +43,20 @@ app.configure('development', function () { app.use(express.errorHandler()); }); +//Setting up "global locals" that will be used across the whole application +app.locals = ({ + blogTitle: blogConfig.name, + blogSubtitle: blogConfig.subTitle +}); + ////////get methods//////// app.get('/', home.index); app.get('/admin/delete', admin.delete); app.get('/admin/new', admin.new); -app.get('/post/:id/:title', post.post_view); + +app.get('/post/:id/:title', post.get); + app.get('/admin' || '/admin/', admin.admin_check); app.get('/admin/:id/edit', admin.admin_edit); app.get('/admin/logout', function (req, res) { @@ -62,7 +66,7 @@ app.get('/admin/logout', function (req, res) { }); app.get('/about', function (req, res) { - res.render('about', { title: t, subTitle: st, admin: req.session.admin}); + res.render('about', { title: t, subTitle: st, admin: req.session.admin, posts: []}); }); @@ -75,7 +79,8 @@ app.post('/admin/new', admin.new_post_handler); app.post('/admin' || '/admin/', admin.admin_check_post_handler); app.post('/admin/:id/edit', admin.admin_edit_post_handler); app.post('/', home.home_post_handler); -app.post('/post/:id/:title', post.post_view_post_handler); + +app.post('/post/:id/:title', post.saveComment); /////////////////////////// diff --git a/config/blogConfig.json b/config/blogConfig.json index 6ba09c2..c79eeba 100644 --- a/config/blogConfig.json +++ b/config/blogConfig.json @@ -1,5 +1,8 @@ { - "name": "Kenshiro's Journey", - "st": "Exploring the path of the Hackuto-Shinken", - "password": "kenshiro-o" + "name": "Your blog name", + "subTitle": "Your blog subtitle name", + "password": "Your mongoDB password", + "port": "Your blog port" , + "facebookAppId": "Your Facebook APP ID", + "facebookAppSecret": "your facebook APP Secret" } \ No newline at end of file diff --git a/db.js b/db.js index 8d58bb0..1e38ca4 100644 --- a/db.js +++ b/db.js @@ -9,26 +9,3 @@ var Schema = mongoose.Schema var db_url = process.env.MONGOHQ_URL || "mongodb://localhost:27017/your_database_name", db = mongoose.connect(db_url); -//The MongoDB Schema for your posts - -var postSchema = new Schema({ - id: ObjectId, - title: String, - title_sub: String, - content: String, - date: String -}) - -//The MongoDB Schema for your each post's comments -var commentSchema = new Schema({ - id: ObjectId, - postid: String, - title_sub: String, - name: String, - comment: String, - date: String -}) - - -var post = db.model('post', postSchema); -var comment = db.model('comment', commentSchema); \ No newline at end of file diff --git a/db/model/comment.js b/db/model/comment.js new file mode 100644 index 0000000..f261e92 --- /dev/null +++ b/db/model/comment.js @@ -0,0 +1,12 @@ +var mongoose = require("mongoose"), + ObjectId = mongoose.Schema.ObjectId; + +var commentSchema = new mongoose.Schema({ + post_id: {type: ObjectId, ref: "post"}, + title: String, + name: {type: String, require: true}, + comment: {type: String, required: true}, + date: {type: Date, default: new Date()} +}); + +module.exports = mongoose.model("comment", commentSchema); \ No newline at end of file diff --git a/db/model/post.js b/db/model/post.js new file mode 100644 index 0000000..f0d21d4 --- /dev/null +++ b/db/model/post.js @@ -0,0 +1,12 @@ +var mongoose = require("mongoose"), + ObjectId = mongoose.Schema.ObjectId; + +var PostSchema = new mongoose.Schema({ + title: {type: String, required: true, index: {unique: true}}, + //TODO: Is this really needed? No in my opinion. Posts only have a title, sub-titles are useless + title_sub: String, + content: {type: String, required: true}, + date: {type: Date, default: new Date()} +}); + +module.exports = mongoose.model("post", PostSchema); \ No newline at end of file diff --git a/package.json b/package.json index a6f3846..7136b2d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Node2Blog", "description": "Simple and easy to use blog template for your casual blogger.", - "version": "0.2.2", + "version": "1.0.0", "author": { "name": "kenshiro-o", "email": "kenshiro@kenshiro.me", @@ -9,14 +9,14 @@ }, "private": true, "engines": { - "node": "0.8.x", - "npm": "1.1.x" + "node": ">=0.8.x", + "npm": ">=1.1.x" }, "scripts": { "start": "node blog" }, "dependencies": { - "express": "3.0.0rc5", + "express": "*", "jade": "*", "mongoose": "*", "mongodb": "*" diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..d23f609 --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,74 @@ +header{ + min-width: 940px; + width: 100%; + margin: 0; + display: block; + border-bottom: 2px solid #100d2c; + text-align: center; + +} + +#content{ + padding-top: 20px; + margin-left: auto; + margin-right: auto; + width: 940px; + overflow: auto; +} + +#main-content{ + width: 70%; + margin: 5px 10px 100px 5px; + float: left; + +} + +.post-extract{ + max-height: 150px; + overflow-y: hidden; +} + +.post-full{ + +} + +.post-content{ + font-family: Tahoma, Arial, Helvetica, sans-serif; + font-size: 0.8em; + color: #000; +} + +#widgets{ + width: 20%; + float: left; +} + + +#widget-pages{ + border: 1px solid #100d2c; + margin-bottom: 10px; +} + +#widget-pages > h4{ + border-bottom: 1px solid red; + margin: 0; + text-align: center; +} + +#widget-pages > h4:last-of-type{ + border-bottom: none; +} + +#widget-posts{ + border: 1px solid #100d2c; +} + +footer{ + clear: both; + width: 100%; + height: 30px; + margin-bottom: 0; + background: #d9d4d7; + color: #FFF; + text-align: center; +} \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js index 943b89e..2d6382a 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -1,7 +1,8 @@ //Prerequisites var mongoose = require('mongoose'); var db = require('../db'); -var post = mongoose.model('post'); +var PostModel = require('./../db/model/post'); + var error; var date = new Date(); //var post = mongoose.model( 'Post' ); @@ -9,7 +10,7 @@ var date = new Date(); //new post functions exports.new = function(req, res){ if(req.session.admin == 'true'){ - post.find({}).sort('-_id').execFind(function(err, posts){ + PostModel.find({}).sort({date: "desc"}).execFind(function(err, posts){ if(posts){ res.render('admin', { title: t, subTitle:st, posts:posts, admin:req.session.admin}); }else{ @@ -21,52 +22,41 @@ exports.new = function(req, res){ } }; + + exports.new_post_handler = function(req, res){ - - //specific time - var hours = date.getHours(); - var minutes = date.getMinutes(); - var seconds = date.getSeconds(); - //date - var month = date.getMonth() + 1; - var year = date.getFullYear(); - var day = date.getDate(); - - function formatAMPM(date) { - var hours = date.getHours(); - var minutes = date.getMinutes(); - var ampm = hours >= 12 ? 'pm' : 'am'; - hours = hours % 12; - hours = hours ? hours : 12; // the hour '0' should be '12' - minutes = minutes < 10 ? '0'+minutes : minutes; - var strTime = hours + ':' + minutes + ' ' + ampm; - return strTime; - } - //organize time so it looks nice - var time = month + '/' + day + '/' + year + " at " + formatAMPM(date); + //TODO: Perform some kind of input validation here + var title = req.body.title; var title_sub = title.split(' ').join('-'); - var body = req.body.body; //Submitting to database - var newPost = post({ + var newPost = new PostModel({ title: title, title_sub: title_sub, - content: body, - date: time + content: body + }); + + newPost.save(function(err){ + if(err){ + //TODO: Gracefully handle this error + console.log("An error occurred while trying to save post [post-title=%s, error=%s]", title, err); + }else{ + console.log("Successfully saved new post [post-title=%s]", title); + } + + //redirecting to homepage + res.redirect('/'); }); - newPost.save(); - //redirecting to homepage - res.redirect('/'); }; //deleting posts functions exports.delete = function(req, res){ if (req.session.admin == 'true'){ - post.find({}).sort('-_id').execFind(function(err, posts){ + PostModel.find({}).sort('-_id').execFind(function(err, posts){ if(posts){ res.render('admin_delete', { title: t, subTitle:st, posts:posts, admin:req.session.admin}); }else{ @@ -77,11 +67,13 @@ exports.delete = function(req, res){ res.redirect('/') } }; + + exports.delete_post_handler = function(req, res){ var title = req.body.title; var time = req.body.time; console.log(title); - post.findOne({"title": title , "date":time}, function(err, match){ + PostModel.findOne({"title": title , "date":time}, function(err, match){ if(match){ match.remove() console.log('removed') @@ -93,7 +85,7 @@ exports.delete_post_handler = function(req, res){ }; exports.admin_edit = function(req, res){ if (req.session.admin == 'true'){ - post.findOne({_id: req.params.id}, function(err, post){ + PostModel.findOne({_id: req.params.id}, function(err, post){ if(post){ res.render('admin_edit', {title: t, subTitle:st, post:post, admin:req.session.admin}) }else{ @@ -108,13 +100,15 @@ exports.admin_edit_post_handler = function(req, res){ body = req.body.body; title = req.body.title; - post.findOne({title: title}, function(err, post){ + PostModel.findOne({title: title}, function(err, post){ post.content = body; post.save() console.log('edited post complete') res.redirect('/') }) } + + //admin check functions exports.admin_check_post_handler = function(req, res){ var password1 = req.body.password; diff --git a/routes/home.js b/routes/home.js index d997273..59343cd 100644 --- a/routes/home.js +++ b/routes/home.js @@ -1,20 +1,23 @@ //PREREQUISITES -var mongoose = require('mongoose'); -var db = require('../db'); -var post = mongoose.model('post'); + +var PostModel = require('./../db/model/post'); //Homepage functions -exports.index = function(req, res){ +exports.index = function(req, res){ + + PostModel.find({}).sort({date: "desc"}).execFind(function(err, posts){ + //TODO: Handle errors gracefully + if(!posts){ + console.log("No posts found"); + posts = []; - post.find({}).sort('-_id').execFind(function(err, posts){ - if(posts){ - res.render('home', { title: t, subTitle:st, posts:posts, admin:req.session.admin}); - }else{ - res.render('home', { title: t, subTitle:st, posts:null, admin:req.session.admin }) } + res.render("index", {pageTitle: "Blog test", blogTitle: "Kenshiro Blog", subTitle: "Hackuto stuff", posts: posts}); }); }; + + exports.home_post_handler = function(req, res){ diff --git a/routes/post.js b/routes/post.js index 07651d4..a72c769 100644 --- a/routes/post.js +++ b/routes/post.js @@ -1,62 +1,68 @@ //PREREQUISITES var mongoose = require('mongoose'); -var db = require('../db'); -var post = mongoose.model('post'); -var commentss = mongoose.model('comment'); -var date = new Date(); +var PostModel = require("./../db/model/post"); +var CommentModel = require('./../db/model/comment'); //Single post view -exports.post_view = function(req, res){ - - id = req.params.id; - post.find({'_id': id}, function(err, post){ - if(post){ - commentss.find({'postid': id}, function(err, comment){ - if(comment){ - res.render('post_view', {title:t, subTitle:st, post:post, comment:comment, admin:req.session.admin}) - }else{ - res.render('post_view', {title:t, subTitle:st, post:post, comment:null, admin:req.session.admin}) - } - }); +exports.get = function(req, res){ + var identifier = mongoose.Types.ObjectId(req.params.id); + + PostModel.findById(identifier, function(err, post){ + if(err){ + //TODO Handle this error + console.log("Unable to retrieve post [post-id=%s]", req.params.id); + }else if(post){ + //TODO: Get the comments associated with the post +// CommentModel.find({'post_id': identifier}, function(err, comment){ +// if(err){ +// console.log("An error occurred when trying to retrieve comments [post-id=%s]", req.params.id); +// } else{ + + res.render("post", {title :post.title, subTitle: post.title_sub, post: post, comment:null, admin:req.session.admin, posts: []}); +// } +// }); }else{ - res.render('post_view', {title:t, subTitle:st, post:null, comment:null, admin:req.session.admin}) + //TODO: We should redirect toward an error page here + res.render('post_view', {title:t, subTitle:st, post:null, comment:null, admin:req.session.admin}) } }); } -exports.post_view_post_handler = function(req, res){ - id = req.params.id; - title_sub = req.params.title; - name = req.body.name || 'anon'; - comment = req.body.comment || 'Nothing'; - console.log(name + ' said ' + comment); - console.log(id); - - - //specific time - var hours = date.getHours(); - var minutes = date.getMinutes(); - var seconds = date.getSeconds(); - //date - var month = date.getMonth() + 1; - var year = date.getFullYear(); - var day = date.getDate(); - - - //organize time so it looks nice - var time = month + '/' + day + '/' + year + ' at ' + hours + ':' + minutes + ':' + seconds; - - - //Submitting to database - var newComment = commentss({ - postid: id, - title_sub: title_sub, - name: name, - comment: comment, - date: time - }); - newComment.save(); - - //redirecting to homepage - res.redirect('/post/' + id + '/' + title_sub); + + + + +exports.saveComment = function(req, res){ + if(!req.body.comment){ + //TODO: We should handle this error case properly when no comment is sent + console.log("No comment has been sent!"); + res.redirect("/"); + }else{ + var commentWritten = req.body.comment; + var postId = mongoose.Types.ObjectId(req.params.id); + var commenterName = req.body.name || 'anon'; + var title = req.params.title; + + console.log("New comment [post-id=%s, commenter-name=%s, comment=%s]", req.params.id, + commenterName, commentWritten); + + //Submitting to database + var newComment = CommentModel({ + post_id: postId, + title: title, + name: commenterName, + comment: commentWritten + }); + newComment.save(function(err){ + if(err){ + //TODO: Gracefully handle this error in the future + console.log("Unable to save new comment [post-id=%s, error=%s]", req.params.id, err) + } + + //redirecting to homepage + res.redirect('/post/' + req.params.id + '/' + req.params.title); + }); + + + } } \ No newline at end of file diff --git a/views/about.jade b/views/about.jade index 443b87e..0e76bf7 100644 --- a/views/about.jade +++ b/views/about.jade @@ -1,26 +1,19 @@ -extends layout +extends default-layout -block wrapper_content - .container - h1 About Me - br +block blog-content + .post-full + h2 About Me p | I am Kenshiro (my real name is easy enough to find out. Just google it). The goal of this blog is to share my musings and projects with the rest of the connected world. - br p | I hope you will enjoy reading my posts and commenting. I am currently mastering the deadly arts of Node.js (I already am lethal on Core Java). - br - br - - h1 What is Hackuto-Shinken? + h2 What is Hackuto-Shinken? p em Hackuto-Shinken - | is a variant of the mighty + | is a variant of the mighty a(href="http://hokuto.wikia.com/wiki/Hokuto_Shin_Ken") Hokuto Shinken | , an invincible fictional Martial Arts, which can only be passed down to one heir. - br p - | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! - + | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! \ No newline at end of file diff --git a/views/default-layout.jade b/views/default-layout.jade new file mode 100644 index 0000000..e14128f --- /dev/null +++ b/views/default-layout.jade @@ -0,0 +1,39 @@ +doctype +html(lang="en") + head + title= pageTile + link(rel="stylesheet", href="/stylesheets/style.css") + + body + header + h1= blogTitle + h2= blogSubtitle + + + #content + #main-content + block blog-content + + + + #widgets + #widget-pages + h3 Pages + h4 + a(href="/") Home + h4 + a(href="/about") About + h4 + a(href="#") Contact + + + #widget-posts + h3 Latest Posts + + ul + for post in posts + li #{post.title} + + + footer + p Powered by Node2Blog \ No newline at end of file diff --git a/views/index.jade b/views/index.jade new file mode 100644 index 0000000..1492863 --- /dev/null +++ b/views/index.jade @@ -0,0 +1,11 @@ +extends default-layout + +block blog-content + for post in posts + .post-extract + h2.post-title + - var postTitleLink = encodeURI(post.title) + a(href="/post/#{post._id}/#{postTitleLink}") #{post.title} + - var content = post.content + p.post-content!= content + diff --git a/views/post.jade b/views/post.jade new file mode 100644 index 0000000..84d81bf --- /dev/null +++ b/views/post.jade @@ -0,0 +1,7 @@ +extends default-layout + +block blog-content + .post-full + h2.post-title #{post.title} + - var content = post.content + p.post-content!= content From ede786c55846e4a5dbfe31fbe9f3c10ec12244ed Mon Sep 17 00:00:00 2001 From: kenshiro Date: Fri, 2 Aug 2013 00:02:51 +0100 Subject: [PATCH 07/28] * Work on Admin, new post view --- History.md | 1 + blog.js | 6 +-- public/stylesheets/style.css | 71 +++++++++++++++++++++++++++++++++++- routes/admin.js | 26 +++++++------ views/admin.jade | 2 +- views/admin_view.jade | 24 ++++++++++++ views/default-layout.jade | 4 +- 7 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 views/admin_view.jade diff --git a/History.md b/History.md index d36ad30..4e27020 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ ==================== * MAJOR refactoring of API (in progress). * Revamping of layout as well (in progress). +* Work on Admin, new post view 0.2.2 / 2013-02-23 ==================== diff --git a/blog.js b/blog.js index 4a3d4d2..db02054 100644 --- a/blog.js +++ b/blog.js @@ -74,10 +74,10 @@ app.get('/about', function (req, res) { ///////post methods//////// -app.post('/admin/delete', admin.delete_post_handler); -app.post('/admin/new', admin.new_post_handler); +app.post('/admin/delete', admin.deletePost); +app.post('/admin/new', admin.createNewPost); app.post('/admin' || '/admin/', admin.admin_check_post_handler); -app.post('/admin/:id/edit', admin.admin_edit_post_handler); +app.post('/admin/:id/edit', admin.editPost); app.post('/', home.home_post_handler); app.post('/post/:id/:title', post.saveComment); diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index d23f609..92a7d97 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -8,18 +8,32 @@ header{ } +#blog-main-title{ + margin: 2px 0 2px 0; +} + +#blog-sub-title{ + margin: 0 0 5px 0; +} + + #content{ padding-top: 20px; margin-left: auto; margin-right: auto; + margin-top: 10px; + padding-bottom: 20px; width: 940px; overflow: auto; + + border: 1px solid blue; } #main-content{ width: 70%; - margin: 5px 10px 100px 5px; + margin: 5px 10px 0px 5px; float: left; + background-color: #f0f0f0; } @@ -38,6 +52,61 @@ header{ color: #000; } +#admin-bar{ + width: 100%; + border: 2px solid black; + margin-bottom: 5px; +} + +#admin-bar > ol > li{ + display: inline; + padding: 5px; +} + + +.post-new{ + width: 100%; + padding-left: 5px; +} + +#new-post-header{ + margin-bottom: 10px; + margin-top: 2px; + margin-left: 5px ; +} + +#new-post-form{ + width: 95%; + padding-left: 5px; +} + +#post-title-box{ + width: 40%; + height: 1.5em; + border: 1px solid #DDD; + font-size: 1em; + margin-bottom: 10px; + border-radius: 5px; +} + +#post-new-content{ + width: 100%; + min-height: 500px; + margin-bottom: 15px; +} + + +#submit-post-btn{ + margin-top: 10px; + height: 2em; + font-size: 1.1em; + border-radius: 5px; + padding: 2px 5px 5px 2px; + border: 1px solid #DDD; + background-color: #0dd257; +} + + #widgets{ width: 20%; float: left; diff --git a/routes/admin.js b/routes/admin.js index 2d6382a..c58a39d 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -1,21 +1,23 @@ //Prerequisites var mongoose = require('mongoose'); -var db = require('../db'); var PostModel = require('./../db/model/post'); var error; var date = new Date(); -//var post = mongoose.model( 'Post' ); //new post functions exports.new = function(req, res){ if(req.session.admin == 'true'){ PostModel.find({}).sort({date: "desc"}).execFind(function(err, posts){ - if(posts){ - res.render('admin', { title: t, subTitle:st, posts:posts, admin:req.session.admin}); - }else{ - res.render('admin', { title: t, subTitle:st, posts:null, admin:req.session.admin }) - } + //TODO: Handle errors gracefully + res.render("admin_view", {posts: posts, admin:req.session.admin}); + + +// if(posts){ +// res.render('admin', { title: "TEST", subTitle: "TEST", posts:posts, admin:req.session.admin}); +// }else{ +// res.render('admin', { title: "TEST", subTitle: "TEST", posts: [], admin:req.session.admin }) +// } }); }else{ res.redirect('/') @@ -24,7 +26,7 @@ exports.new = function(req, res){ }; -exports.new_post_handler = function(req, res){ +exports.createNewPost = function(req, res){ //TODO: Perform some kind of input validation here var title = req.body.title; @@ -58,9 +60,9 @@ exports.delete = function(req, res){ if (req.session.admin == 'true'){ PostModel.find({}).sort('-_id').execFind(function(err, posts){ if(posts){ - res.render('admin_delete', { title: t, subTitle:st, posts:posts, admin:req.session.admin}); + res.render('admin_delete', { title: "Test", subTitle: "TEST", posts:posts, admin:req.session.admin}); }else{ - res.render('admin_delete', { title: t, subTitle:st, posts:null, admin:req.session.admin }) + res.render('admin_delete', { title: "Test", subTitle: "TEST", posts:null, admin:req.session.admin }) } }); }else{ @@ -69,7 +71,7 @@ exports.delete = function(req, res){ }; -exports.delete_post_handler = function(req, res){ +exports.deletePost = function(req, res){ var title = req.body.title; var time = req.body.time; console.log(title); @@ -96,7 +98,7 @@ exports.admin_edit = function(req, res){ res.redirect('/') } }; -exports.admin_edit_post_handler = function(req, res){ +exports.editPost = function(req, res){ body = req.body.body; title = req.body.title; diff --git a/views/admin.jade b/views/admin.jade index 2e584dd..b8fc8f9 100644 --- a/views/admin.jade +++ b/views/admin.jade @@ -2,7 +2,7 @@ extends layout block wrapper_content script(src="../javascripts/nicEdit.js", type="text/javascript") - script(type='text/javascript') + script(type='text/javascript'). bkLib.onDomLoaded(nicEditors.allTextAreas); .container diff --git a/views/admin_view.jade b/views/admin_view.jade new file mode 100644 index 0000000..8456244 --- /dev/null +++ b/views/admin_view.jade @@ -0,0 +1,24 @@ +extends default-layout + +block blog-content + script(src="../javascripts/nicEdit.js", type="text/javascript") + script(type='text/javascript') + bkLib.onDomLoaded(nicEditors.allTextAreas); + + #admin-bar + ol + li + a(href="/admin/new") Create Post + li + a(href="#") Edit Post + li + a(href="/admin/delete") Delete Post + li + a(href="/admin/logout") Log out + + .post-new + h2#new-post-header New Post + form(id="new-post-form", method="post") + input(id="post-title-box" ,type="text", name="title", placeholder="Title") + textarea(id="post-new-content", name="body", placeholder="Write your post here") + input(id="submit-post-btn", type="submit", value="Create") \ No newline at end of file diff --git a/views/default-layout.jade b/views/default-layout.jade index e14128f..86276d3 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -6,8 +6,8 @@ html(lang="en") body header - h1= blogTitle - h2= blogSubtitle + h1#blog-main-title= blogTitle + h2#blog-sub-title= blogSubtitle #content From 9244eaaefff12a72159b563f75bd59c2895daab8 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 4 Aug 2013 14:43:10 +0100 Subject: [PATCH 08/28] * Work on Admin, new post view --- History.md | 1 + blog.js | 51 +++++-- cache/postCache.js | 31 ++++ db.js => db/dbConnection.js | 6 +- db/model/post.js | 3 +- public/images/HokutoNoKen.jpg | Bin 0 -> 76094 bytes public/images/kenshiro.jpg | Bin 0 -> 22343 bytes public/images/node-js-logo.jpeg | Bin 0 -> 1213 bytes public/stylesheets/style.css | 243 +++++++++++++++++++++++++++++--- routes/admin.js | 52 ++++--- routes/home.js | 18 +-- routes/post.js | 10 +- views/404.jade | 7 + views/500.jade | 8 ++ views/about.jade | 5 +- views/admin_edit.jade | 2 +- views/default-layout.jade | 53 ++++--- views/index.jade | 8 +- views/layout.jade | 4 +- views/post.jade | 15 +- 20 files changed, 413 insertions(+), 104 deletions(-) create mode 100644 cache/postCache.js rename db.js => db/dbConnection.js (75%) create mode 100644 public/images/HokutoNoKen.jpg create mode 100644 public/images/kenshiro.jpg create mode 100644 public/images/node-js-logo.jpeg create mode 100644 views/404.jade create mode 100644 views/500.jade diff --git a/History.md b/History.md index 4e27020..41f1bff 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,7 @@ * MAJOR refactoring of API (in progress). * Revamping of layout as well (in progress). * Work on Admin, new post view +* Improved look and feel, error handling (404 + 500) and code quality 0.2.2 / 2013-02-23 ==================== diff --git a/blog.js b/blog.js index db02054..174a06e 100644 --- a/blog.js +++ b/blog.js @@ -1,12 +1,11 @@ -var blogConfig = require("./config/blogConfig"); +var blogConfig = require("./config/blogConfig"), + postCache = require("./cache/postCache"), + db = require('./db/dbConnection'); + console.log("Blog config loaded [name=%s, subtitle=%s]", blogConfig.name, blogConfig.subTitle); p = blogConfig.password; -/** - * Module dependencies. - */ -var db = require('./db'); admin = null; var error; @@ -37,6 +36,19 @@ app.configure(function () { })); app.use(express.static(path.join(__dirname, 'public'))); app.use(app.router); + + //Handling of 404 and 500 pages + app.use(function(req, res){ + res.status(400); + res.render("404"); + }); + + app.use(function(error, req, res, next) { + res.status(500); + res.render("500"); + }); + + }); app.configure('development', function () { @@ -46,7 +58,8 @@ app.configure('development', function () { //Setting up "global locals" that will be used across the whole application app.locals = ({ blogTitle: blogConfig.name, - blogSubtitle: blogConfig.subTitle + blogSubtitle: blogConfig.subTitle, + fbAppId: blogConfig.facebookAppId }); @@ -66,12 +79,10 @@ app.get('/admin/logout', function (req, res) { }); app.get('/about', function (req, res) { - res.render('about', { title: t, subTitle: st, admin: req.session.admin, posts: []}); + res.render('about', {admin: req.session.admin}); }); -/////////////////////////// - ///////post methods//////// app.post('/admin/delete', admin.deletePost); @@ -82,10 +93,22 @@ app.post('/', home.home_post_handler); app.post('/post/:id/:title', post.saveComment); -/////////////////////////// -//Server start -http.createServer(app).listen(app.get('port'), function () { - console.log("Your blog is running on port " + app.get('port')); -}); +//Load the latest posts +postCache.setApp(app); +postCache.loadPosts(function(err, posts){ + if(err){ + //Cannot start the server as an error occured loading posts + console.log("An error occurred while trying to load posts [error=%s]", err); + throw new Error("" + err); + }else{ + console.log("Loaded %d posts", posts.length); + //Server start + http.createServer(app).listen(app.get('port'), function () { + console.log("Your blog is running on port " + app.get('port')); + }); + } +}) + + diff --git a/cache/postCache.js b/cache/postCache.js new file mode 100644 index 0000000..d8a25ee --- /dev/null +++ b/cache/postCache.js @@ -0,0 +1,31 @@ +var PostModel = require('./../db/model/post'); + +function PostCache(){} + +PostCache.prototype = {} + +var app = null; +var posts = null; + +PostCache.prototype.loadPosts = function(callback){ + PostModel.find({}).sort({date: "desc"}).execFind(function(err, posts){ + if(err){ + process.nextTick(function(){ + callback(true); + }) + }else{ + if(app){ + app.locals.posts = posts; + } + callback(false, posts); + } + }); +} + +PostCache.prototype.setApp = function (application){ + app = application; +} + +var cache = new PostCache(); + +module.exports = cache; diff --git a/db.js b/db/dbConnection.js similarity index 75% rename from db.js rename to db/dbConnection.js index 1e38ca4..d95131f 100644 --- a/db.js +++ b/db/dbConnection.js @@ -1,7 +1,7 @@ //PREREQUISITES -var mongoose = require('mongoose') -var Schema = mongoose.Schema - ,ObjectId = Schema.ObjectId; +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + ObjectId = Schema.ObjectId; //process.env.MONGOHQ_URL is for deploying on heroku diff --git a/db/model/post.js b/db/model/post.js index f0d21d4..3062584 100644 --- a/db/model/post.js +++ b/db/model/post.js @@ -3,8 +3,7 @@ var mongoose = require("mongoose"), var PostSchema = new mongoose.Schema({ title: {type: String, required: true, index: {unique: true}}, - //TODO: Is this really needed? No in my opinion. Posts only have a title, sub-titles are useless - title_sub: String, + friendly_link_title: {type: String, required: true}, content: {type: String, required: true}, date: {type: Date, default: new Date()} }); diff --git a/public/images/HokutoNoKen.jpg b/public/images/HokutoNoKen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f6de01bd1822097aa6b5c75335db8daed273ed78 GIT binary patch literal 76094 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ñO-P@s^I(J;l_FvnsFM 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 Y4hij69PPr62V4p$uI2EYa(bWt*#JT^e*gdg literal 0 HcmV?d00001 diff --git a/public/images/kenshiro.jpg b/public/images/kenshiro.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a8cca41084f531c0fb6b88c2ff1e21103b3f7601 GIT binary patch literal 22343 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 a:link{ + text-decoration: none; + color: #2227a9; +} +.post-title > a:visited{ + text-decoration: none; + color: #2227a9; +} + +.post-title > a:hover{ + text-decoration: none; + color: #7b3ab7; +} + +.post-title > a:active{ + text-decoration: none; + color: #7b3ab7; } .post-content{ font-family: Tahoma, Arial, Helvetica, sans-serif; - font-size: 0.8em; + font-size: 1em; color: #000; } +/*We are hacking forcefully overriding the inline style set by nicEdit*/ +.post-content > span{ + font-family: Tahoma, Arial, Helvetica, sans-serif !important; + font-size: 1em !important; +} + +.post-content > p{ + font-family: Tahoma, Arial, Helvetica, sans-serif !important; + font-size: 1em !important; +} + +/*We are hacking forcefully overriding the inline style set by nicEdit*/ +.post-content > div{ + font-family: Tahoma, Arial, Helvetica, sans-serif !important; + font-size: 1em !important; +} + +.post-comments{ + border-top: 1px solid #000000; + width: 100%; + padding-top: 2px; +} + + #admin-bar{ width: 100%; border: 2px solid black; @@ -107,21 +234,67 @@ header{ } + +/*********************************************************************************************************************** +***************************************************** WIDGETS ********************************************************** +************************************************************************************************************************/ + #widgets{ width: 20%; float: left; + background-color: #FFF; + margin-top: 5px; } #widget-pages{ border: 1px solid #100d2c; + border-radius: 5px; margin-bottom: 10px; + background-color: #131313; + color: #FFF; + text-align: center; + box-shadow: 2px 2px 2px 2px #131313; } -#widget-pages > h4{ - border-bottom: 1px solid red; - margin: 0; +.widget-title{ text-align: center; + border-bottom: 1px solid #000000; +} + +a.widget-section-link{ + display: block; + border-bottom: 1px solid #d9d4d7; + margin: 0; + + font-size: 1em; +} + +a.widget-section-link:last-of-type{ + border-bottom: none; + color: #FFF; +} + +a.widget-section-link:link{ + text-decoration: none; + color: #FFF; +} + +a.widget-section-link:visited{ + text-decoration: none; + color: #FFF; +} + +a.widget-section-link:hover{ + text-decoration: none; + color: #131313; + background-color: #FFF; +} + +a.widget-section-link:active{ + text-decoration: none; + color: #131313; + background-color: #FFF; } #widget-pages > h4:last-of-type{ @@ -130,14 +303,52 @@ header{ #widget-posts{ border: 1px solid #100d2c; + border-radius: 5px; + margin-bottom: 10px; + background-color: #131313; + color: #FFF; + text-align: left; + box-shadow: 2px 2px 2px 2px #131313; } footer{ - clear: both; + position: relative; + margin-top: -150px; + height: 150px; + clear:both; + width: 100%; - height: 30px; + /*box-shadow: 0px -2px 2px 2px #817c7f;*/ + border-bottom: none; margin-bottom: 0; - background: #d9d4d7; - color: #FFF; text-align: center; + + background-color: #131313; + color: #FFF; +} + +.footer-note{ + padding-top: 30px; + font-size: 1em; + font-weight: bold; +} + +a.footer-link:link{ + color: #2227a9; + text-decoration: none; +} + +a.footer-link:visited{ + color: #2227a9; + text-decoration: none; +} + +a.footer-link:hover{ + color: #7b3ab7; + text-decoration: none; +} + +a.footer-link:active{ + color: #7b3ab7; + text-decoration: none; } \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js index c58a39d..6a25cb9 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -1,6 +1,7 @@ //Prerequisites var mongoose = require('mongoose'); var PostModel = require('./../db/model/post'); +var postCache = require("./../cache/postCache"); var error; var date = new Date(); @@ -8,17 +9,7 @@ var date = new Date(); //new post functions exports.new = function(req, res){ if(req.session.admin == 'true'){ - PostModel.find({}).sort({date: "desc"}).execFind(function(err, posts){ - //TODO: Handle errors gracefully - res.render("admin_view", {posts: posts, admin:req.session.admin}); - - -// if(posts){ -// res.render('admin', { title: "TEST", subTitle: "TEST", posts:posts, admin:req.session.admin}); -// }else{ -// res.render('admin', { title: "TEST", subTitle: "TEST", posts: [], admin:req.session.admin }) -// } - }); + res.render("admin_view", {admin:req.session.admin}); }else{ res.redirect('/') } @@ -27,30 +18,51 @@ exports.new = function(req, res){ exports.createNewPost = function(req, res){ - //TODO: Perform some kind of input validation here - var title = req.body.title; - var title_sub = title.split(' ').join('-'); + if(!title || title.length == 0){ + console.log("Post title provided is either null or empty. Returning an error message"); + res.status(500); + res.render('500'); + return; + } + + var friendly_link_title = title.split(" ").join("-"); + friendly_link_title = encodeURI(friendly_link_title); var body = req.body.body; + if(!body || body.length == 0){ + console.log("Post content provided is either null or empty. Returning an error message"); + res.status(500); + res.render('500'); + return; + } //Submitting to database var newPost = new PostModel({ title: title, - title_sub: title_sub, + friendly_link_title: friendly_link_title, content: body }); newPost.save(function(err){ if(err){ - //TODO: Gracefully handle this error console.log("An error occurred while trying to save post [post-title=%s, error=%s]", title, err); + res.status(500); + res.render('500'); }else{ console.log("Successfully saved new post [post-title=%s]", title); - } + postCache.loadPosts(function(err, posts){ + if(err){ + console.log("Unable to reload posts after saving a new one [error=%s]", err); + res.status(500); + res.render('500'); + }else{ + console.log("Successfully reloaded posts [number-of-posts=%d]", posts.length); + res.redirect('/'); + } + }); - //redirecting to homepage - res.redirect('/'); + } }); }; @@ -105,7 +117,7 @@ exports.editPost = function(req, res){ PostModel.findOne({title: title}, function(err, post){ post.content = body; post.save() - console.log('edited post complete') + console.log('edited post complete'); res.redirect('/') }) } diff --git a/routes/home.js b/routes/home.js index 59343cd..1e8ee00 100644 --- a/routes/home.js +++ b/routes/home.js @@ -1,20 +1,10 @@ -//PREREQUISITES +/* + Homepage functions + */ -var PostModel = require('./../db/model/post'); -//Homepage functions exports.index = function(req, res){ - - PostModel.find({}).sort({date: "desc"}).execFind(function(err, posts){ - //TODO: Handle errors gracefully - if(!posts){ - console.log("No posts found"); - posts = []; - - } - res.render("index", {pageTitle: "Blog test", blogTitle: "Kenshiro Blog", subTitle: "Hackuto stuff", posts: posts}); - }); - + res.render("index"); }; diff --git a/routes/post.js b/routes/post.js index a72c769..5c4cc8a 100644 --- a/routes/post.js +++ b/routes/post.js @@ -9,8 +9,9 @@ exports.get = function(req, res){ PostModel.findById(identifier, function(err, post){ if(err){ - //TODO Handle this error console.log("Unable to retrieve post [post-id=%s]", req.params.id); + res.status(500); + res.render('500'); }else if(post){ //TODO: Get the comments associated with the post // CommentModel.find({'post_id': identifier}, function(err, comment){ @@ -18,13 +19,14 @@ exports.get = function(req, res){ // console.log("An error occurred when trying to retrieve comments [post-id=%s]", req.params.id); // } else{ - res.render("post", {title :post.title, subTitle: post.title_sub, post: post, comment:null, admin:req.session.admin, posts: []}); + res.render("post", {title :post.title, post: post, comments:[], admin:req.session.admin}); // } // }); }else{ - //TODO: We should redirect toward an error page here - res.render('post_view', {title:t, subTitle:st, post:null, comment:null, admin:req.session.admin}) + //Post does not exist - return 404 + res.status(400); + res.render("404"); } }); } diff --git a/views/404.jade b/views/404.jade new file mode 100644 index 0000000..eaa5e2e --- /dev/null +++ b/views/404.jade @@ -0,0 +1,7 @@ +extends default-layout + + +block blog-content + #page-error-container + h2 Page Not Found + p The page you are looking for does not exist. \ No newline at end of file diff --git a/views/500.jade b/views/500.jade new file mode 100644 index 0000000..3bc81b9 --- /dev/null +++ b/views/500.jade @@ -0,0 +1,8 @@ +extends default-layout + + +block wrapper_content + #page-error-container + h2 Error + p An error occured on the previous page! I will sort this out as soon as possible with some awesome Hackuto trick! + img(src="/images/kenshiro.jpg", width="300px", height="250px") diff --git a/views/about.jade b/views/about.jade index 0e76bf7..1dc3209 100644 --- a/views/about.jade +++ b/views/about.jade @@ -5,7 +5,7 @@ block blog-content .post-full h2 About Me p - | I am Kenshiro (my real name is easy enough to find out. Just google it). The goal of this blog is to share my musings and projects with the rest of the connected world. + | I am Kenshiro (You guessed right - this is not my real name!). The goal of this blog is to share my musings and projects with the rest of the connected world. p | I hope you will enjoy reading my posts and commenting. I am currently mastering the deadly arts of Node.js (I already am lethal on Core Java). @@ -16,4 +16,5 @@ block blog-content a(href="http://hokuto.wikia.com/wiki/Hokuto_Shin_Ken") Hokuto Shinken | , an invincible fictional Martial Arts, which can only be passed down to one heir. p - | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! \ No newline at end of file + | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! + img(src="/images/HokutoNoKen.jpg", width="300px", height="250px") \ No newline at end of file diff --git a/views/admin_edit.jade b/views/admin_edit.jade index d24fb13..2c6fa20 100644 --- a/views/admin_edit.jade +++ b/views/admin_edit.jade @@ -17,5 +17,5 @@ block wrapper_content textarea(id='formNewTextarea', name='body', placeholder='Write your post here') #{post.content} br br - input(type="submit", value="Create") + input(type="submit", value="Edit") diff --git a/views/default-layout.jade b/views/default-layout.jade index 86276d3..99da8ad 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -2,38 +2,47 @@ doctype html(lang="en") head title= pageTile + link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Lato") link(rel="stylesheet", href="/stylesheets/style.css") - body - header - h1#blog-main-title= blogTitle - h2#blog-sub-title= blogSubtitle - + #fb-root + script. + (function(d, s, id) { + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=#{fbAppId}"; + fjs.parentNode.insertBefore(js, fjs); + }(document, 'script', 'facebook-jssdk')); - #content - #main-content - block blog-content + body + #page-wrapper + header + h1#blog-main-title= blogTitle + h2#blog-sub-title= blogSubtitle + #content + #main-content + block blog-content - #widgets - #widget-pages - h3 Pages - h4 - a(href="/") Home - h4 - a(href="/about") About - h4 - a(href="#") Contact + #widgets + #widget-pages + h3.widget-title Pages + a.widget-section-link(href="/") Home + a.widget-section-link(href="/about") About + a.widget-section-link(href="#") Contact - #widget-posts - h3 Latest Posts - ul + #widget-posts + h3.widget-title Latest Posts for post in posts - li #{post.title} + a.widget-section-link(href="/post/#{post._id}/#{post.friendly_link_title}") #{post.title} footer - p Powered by Node2Blog \ No newline at end of file + p.footer-note Powered by + a.footer-link(href="https://github.com/kenshiro-o/Node2Blog") Node2Blog + .footer-logos + img(src="/images/node-js-logo.jpeg", alt="node.js official logo") \ No newline at end of file diff --git a/views/index.jade b/views/index.jade index 1492863..e387442 100644 --- a/views/index.jade +++ b/views/index.jade @@ -4,8 +4,10 @@ block blog-content for post in posts .post-extract h2.post-title - - var postTitleLink = encodeURI(post.title) - a(href="/post/#{post._id}/#{postTitleLink}") #{post.title} + a(href="/post/#{post._id}/#{post.friendly_link_title}") #{post.title} + h5.post-date #{post.date.toDateString()} - var content = post.content - p.post-content!= content + .post-content!= content + + diff --git a/views/layout.jade b/views/layout.jade index 96874e7..f8f43df 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -19,12 +19,12 @@ html #subTitleContainer h2.subTitle= subTitle div(id="fb-root") - script + script. (function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; - js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=388558531205027"; + js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=#{facebookAppId}"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); #nav diff --git a/views/post.jade b/views/post.jade index 84d81bf..99cf445 100644 --- a/views/post.jade +++ b/views/post.jade @@ -1,7 +1,20 @@ extends default-layout block blog-content + .post-full h2.post-title #{post.title} + h6.post-date #{post.date.toDateString()} - var content = post.content - p.post-content!= content + .post-content!= content + .post-comments + h5 Comments + for comment in comments + p.post-comment #{comment} + + #social-media-container + .fb-like(data-href="http://kenshiro.me", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") + .tweetbtn + a(href="https://twitter.com/share", class="twitter-share-button", data-size="medium") Tweet + script. + !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs"); From c016e7faf9ea171672d5bd4d66d50d61a8912e63 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Mon, 5 Aug 2013 23:38:13 +0100 Subject: [PATCH 09/28] * Readers can now submit comments to a given blog post * Using the configured port in blogConfig.json to bind the server to the specified port --- History.md | 4 +- blog.js | 7 +-- db/model/comment.js | 1 - public/stylesheets/style.css | 56 +++++++++++++++++-- routes/post.js | 102 ++++++++++++++++++++++------------- views/post.jade | 13 ++++- 6 files changed, 135 insertions(+), 48 deletions(-) diff --git a/History.md b/History.md index 41f1bff..9d6c427 100644 --- a/History.md +++ b/History.md @@ -1,9 +1,11 @@ -1.0.0 / 2013-07-31 +1.0.0 ==================== * MAJOR refactoring of API (in progress). * Revamping of layout as well (in progress). * Work on Admin, new post view * Improved look and feel, error handling (404 + 500) and code quality +* Readers can now submit comments to a given blog post +* Using the configured port in blogConfig.json to bind the server to the specified port 0.2.2 / 2013-02-23 ==================== diff --git a/blog.js b/blog.js index 174a06e..838fdc7 100644 --- a/blog.js +++ b/blog.js @@ -91,7 +91,7 @@ app.post('/admin' || '/admin/', admin.admin_check_post_handler); app.post('/admin/:id/edit', admin.editPost); app.post('/', home.home_post_handler); -app.post('/post/:id/:title', post.saveComment); +app.post('/post/:id/:friendlyLink', post.saveComment); @@ -105,8 +105,9 @@ postCache.loadPosts(function(err, posts){ }else{ console.log("Loaded %d posts", posts.length); //Server start - http.createServer(app).listen(app.get('port'), function () { - console.log("Your blog is running on port " + app.get('port')); + var serverPort = blogConfig.port; + http.createServer(app).listen(serverPort, function () { + console.log("Your blog is running on port " + serverPort); }); } }) diff --git a/db/model/comment.js b/db/model/comment.js index f261e92..2694d99 100644 --- a/db/model/comment.js +++ b/db/model/comment.js @@ -3,7 +3,6 @@ var mongoose = require("mongoose"), var commentSchema = new mongoose.Schema({ post_id: {type: ObjectId, ref: "post"}, - title: String, name: {type: String, require: true}, comment: {type: String, required: true}, date: {type: Date, default: new Date()} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index f105060..aa3086b 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -171,13 +171,63 @@ header{ font-family: Tahoma, Arial, Helvetica, sans-serif !important; font-size: 1em !important; } - .post-comments{ - border-top: 1px solid #000000; + margin-top: 10px; + padding-top: 5px; + border-top: 1px solid #000; +} + +#comment-form{ + width: 60%; + margin-left: 2px; +} + +.commenter-name-input{ + display: block; + width: 50%; + margin: 5px 0; + border-radius: 5px; +} + +.comment-area{ + display: block; + width: 80%; + margin: 0; + border-radius: 5px; +} + +.submit-comment-button{ + margin: 5px 0; + padding: 3px 5px; + background-color: #0dd257; + border-radius: 5px; + font-weight: bold; +} + +.post-comment-container{ width: 100%; - padding-top: 2px; + padding-top: 3px; + margin: 5px 0; + background-color: #e6e6e6; } +.post-comment{ + margin-bottom: 3px; + margin-top: 2px; + font-size: 0.9em; + color: #131313; +} + +.comment-signature{ + margin: 0; + color: #100d2c; + font-size: 0.8em; + font-style: italic; +} + +/*********************************************************************************************************************** +****************************************************** ADMIN *********************************************************** +************************************************************************************************************************/ #admin-bar{ width: 100%; diff --git a/routes/post.js b/routes/post.js index 5c4cc8a..3023305 100644 --- a/routes/post.js +++ b/routes/post.js @@ -13,15 +13,17 @@ exports.get = function(req, res){ res.status(500); res.render('500'); }else if(post){ - //TODO: Get the comments associated with the post -// CommentModel.find({'post_id': identifier}, function(err, comment){ -// if(err){ -// console.log("An error occurred when trying to retrieve comments [post-id=%s]", req.params.id); -// } else{ - - res.render("post", {title :post.title, post: post, comments:[], admin:req.session.admin}); -// } -// }); + CommentModel.find({'post_id': identifier}).sort({date: "desc"}).exec(function(err, comments){ + if(err){ + console.log("An error occurred when trying to retrieve comments [post-id=%s, error=%s]", req.params.id, err); + res.status(500); + res.render("500"); + + } else{ + comments = comments ? comments : []; + res.render("post", {title :post.title, post: post, comments: comments, admin:req.session.admin}); + } + }); }else{ //Post does not exist - return 404 @@ -35,36 +37,60 @@ exports.get = function(req, res){ exports.saveComment = function(req, res){ - if(!req.body.comment){ - //TODO: We should handle this error case properly when no comment is sent - console.log("No comment has been sent!"); - res.redirect("/"); - }else{ - var commentWritten = req.body.comment; - var postId = mongoose.Types.ObjectId(req.params.id); - var commenterName = req.body.name || 'anon'; - var title = req.params.title; - - console.log("New comment [post-id=%s, commenter-name=%s, comment=%s]", req.params.id, - commenterName, commentWritten); - - //Submitting to database - var newComment = CommentModel({ - post_id: postId, - title: title, - name: commenterName, - comment: commentWritten - }); - newComment.save(function(err){ - if(err){ - //TODO: Gracefully handle this error in the future - console.log("Unable to save new comment [post-id=%s, error=%s]", req.params.id, err) - } - - //redirecting to homepage - res.redirect('/post/' + req.params.id + '/' + req.params.title); - }); + var postId = req.params.id; + + if(!postId){ + console.log("No post id provided to save commentWritten"); + res.status(500); + res.render("500"); + + return; + } + + var commentWritten = req.body.comment; + if(!commentWritten){ + console.log("No comment has been sent [post-id=%s]", postId); + res.status(500); + res.render("500"); + return; + } + + var commenterName = req.body.name; + if(!commenterName){ + console.log("No name was sent [post-id=%s]", postId); + res.status(500); + res.render("500"); + return; } + + + postId = mongoose.Types.ObjectId(postId); + + console.log("New commentWritten [post-id=%s, commenter-name=%s, commentWritten=%s]", postId, + commenterName, commentWritten); + + //Submitting to database + var newComment = CommentModel({ + post_id: postId, + name: commenterName, + comment: commentWritten + }); + newComment.save(function(err){ + if(err){ + console.log("Unable to save new commentWritten [post-id=%s, error=%s]", req.params.id, err) + res.status(500); + res.render("500"); + + }else{ + //redirecting to post where the comment was just made + console.log("Successfully saved a comment for post [post-id=%s, commenter-name=%s]", req.params.id, commenterName) + res.redirect('/post/' + req.params.id + '/' + req.params.friendlyLink); + } + + }); + + + } \ No newline at end of file diff --git a/views/post.jade b/views/post.jade index 99cf445..da63bea 100644 --- a/views/post.jade +++ b/views/post.jade @@ -8,9 +8,18 @@ block blog-content - var content = post.content .post-content!= content .post-comments - h5 Comments + - var commentStr = comments.length == 1 ? "Comment" : "Comments" + h4 #{comments.length} #{commentStr} + + form#comment-form(method="post", action="/post/#{post._id}/#{post.friendly_link_title}") + input.commenter-name-input(name="name", type="text", placeholder="name") + textarea.comment-area(name="comment", placeholder="comment", rows="5") + input.submit-comment-button(type="submit", value="Submit comment") + for comment in comments - p.post-comment #{comment} + .post-comment-container + p.post-comment #{comment.comment} + p.comment-signature #{comment.name} on #{comment.date.toDateString()} at #{comment.date.toLocaleTimeString()} #social-media-container .fb-like(data-href="http://kenshiro.me", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") From 6f018d452e74d1a2c32b9f0adf418b11abbe1cf1 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Tue, 6 Aug 2013 00:05:17 +0100 Subject: [PATCH 10/28] * Displaying correct page title --- History.md | 1 + blog.js | 11 ++--------- routes/admin.js | 8 ++++---- routes/home.js | 2 +- routes/post.js | 16 +++++++--------- views/default-layout.jade | 2 +- 6 files changed, 16 insertions(+), 24 deletions(-) diff --git a/History.md b/History.md index 9d6c427..a4f2166 100644 --- a/History.md +++ b/History.md @@ -6,6 +6,7 @@ * Improved look and feel, error handling (404 + 500) and code quality * Readers can now submit comments to a given blog post * Using the configured port in blogConfig.json to bind the server to the specified port +* Displaying correct page title 0.2.2 / 2013-02-23 ==================== diff --git a/blog.js b/blog.js index 838fdc7..2d6d1ce 100644 --- a/blog.js +++ b/blog.js @@ -40,15 +40,8 @@ app.configure(function () { //Handling of 404 and 500 pages app.use(function(req, res){ res.status(400); - res.render("404"); + res.render("404", {title: "Page not found"}); }); - - app.use(function(error, req, res, next) { - res.status(500); - res.render("500"); - }); - - }); app.configure('development', function () { @@ -79,7 +72,7 @@ app.get('/admin/logout', function (req, res) { }); app.get('/about', function (req, res) { - res.render('about', {admin: req.session.admin}); + res.render('about', {title: "Kenshiro's Hackuto blog - About", admin: req.session.admin}); }); diff --git a/routes/admin.js b/routes/admin.js index 6a25cb9..611d997 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -22,7 +22,7 @@ exports.createNewPost = function(req, res){ if(!title || title.length == 0){ console.log("Post title provided is either null or empty. Returning an error message"); res.status(500); - res.render('500'); + res.render("500", {title: "Error"}); return; } @@ -33,7 +33,7 @@ exports.createNewPost = function(req, res){ if(!body || body.length == 0){ console.log("Post content provided is either null or empty. Returning an error message"); res.status(500); - res.render('500'); + res.render("500", {title: "Error"}); return; } @@ -48,14 +48,14 @@ exports.createNewPost = function(req, res){ if(err){ console.log("An error occurred while trying to save post [post-title=%s, error=%s]", title, err); res.status(500); - res.render('500'); + res.render("500", {title: "Error"}); }else{ console.log("Successfully saved new post [post-title=%s]", title); postCache.loadPosts(function(err, posts){ if(err){ console.log("Unable to reload posts after saving a new one [error=%s]", err); res.status(500); - res.render('500'); + res.render("500", {title: "Error"}); }else{ console.log("Successfully reloaded posts [number-of-posts=%d]", posts.length); res.redirect('/'); diff --git a/routes/home.js b/routes/home.js index 1e8ee00..ff3a47f 100644 --- a/routes/home.js +++ b/routes/home.js @@ -4,7 +4,7 @@ exports.index = function(req, res){ - res.render("index"); + res.render("index", {title: "Kenshiro's Hackuto blog - Home"}); }; diff --git a/routes/post.js b/routes/post.js index 3023305..8047275 100644 --- a/routes/post.js +++ b/routes/post.js @@ -11,13 +11,13 @@ exports.get = function(req, res){ if(err){ console.log("Unable to retrieve post [post-id=%s]", req.params.id); res.status(500); - res.render('500'); + res.render("500", {title: "Error"}); }else if(post){ CommentModel.find({'post_id': identifier}).sort({date: "desc"}).exec(function(err, comments){ if(err){ console.log("An error occurred when trying to retrieve comments [post-id=%s, error=%s]", req.params.id, err); res.status(500); - res.render("500"); + res.render("500", {title: "Error"}); } else{ comments = comments ? comments : []; @@ -28,7 +28,7 @@ exports.get = function(req, res){ }else{ //Post does not exist - return 404 res.status(400); - res.render("404"); + res.render("404", {title: "Page not found"}); } }); } @@ -42,7 +42,7 @@ exports.saveComment = function(req, res){ if(!postId){ console.log("No post id provided to save commentWritten"); res.status(500); - res.render("500"); + res.render("500", {title: "Error"}); return; } @@ -51,7 +51,7 @@ exports.saveComment = function(req, res){ if(!commentWritten){ console.log("No comment has been sent [post-id=%s]", postId); res.status(500); - res.render("500"); + res.render("500", {title: "Error"}); return; } @@ -60,7 +60,7 @@ exports.saveComment = function(req, res){ if(!commenterName){ console.log("No name was sent [post-id=%s]", postId); res.status(500); - res.render("500"); + res.render("500", {title: "Error"}); return; } @@ -81,7 +81,7 @@ exports.saveComment = function(req, res){ if(err){ console.log("Unable to save new commentWritten [post-id=%s, error=%s]", req.params.id, err) res.status(500); - res.render("500"); + res.render("500", {title: "Error"}); }else{ //redirecting to post where the comment was just made @@ -91,6 +91,4 @@ exports.saveComment = function(req, res){ }); - - } \ No newline at end of file diff --git a/views/default-layout.jade b/views/default-layout.jade index 99da8ad..0e24731 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -1,7 +1,7 @@ doctype html(lang="en") head - title= pageTile + title= title link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Lato") link(rel="stylesheet", href="/stylesheets/style.css") From 90342c402f4d6b7b73c0fbdd617c64efa82edef2 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Tue, 6 Aug 2013 23:24:05 +0100 Subject: [PATCH 11/28] * Added RSS support --- History.md | 1 + blog.js | 27 +++++++++++++++++---------- cache/postCache.js | 7 ++++++- config/blogConfig.json | 3 +++ package.json | 5 +++-- routes/misc.js | 40 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 routes/misc.js diff --git a/History.md b/History.md index a4f2166..343fd80 100644 --- a/History.md +++ b/History.md @@ -7,6 +7,7 @@ * Readers can now submit comments to a given blog post * Using the configured port in blogConfig.json to bind the server to the specified port * Displaying correct page title +* Added RSS support 0.2.2 / 2013-02-23 ==================== diff --git a/blog.js b/blog.js index 2d6d1ce..def9dd9 100644 --- a/blog.js +++ b/blog.js @@ -2,7 +2,7 @@ var blogConfig = require("./config/blogConfig"), postCache = require("./cache/postCache"), db = require('./db/dbConnection'); -console.log("Blog config loaded [name=%s, subtitle=%s]", blogConfig.name, blogConfig.subTitle); +console.log("Blog config loaded [name=%s, subtitle=%s]", blogConfig.title, blogConfig.subTitle); p = blogConfig.password; @@ -11,12 +11,13 @@ admin = null; var error; -var express = require('express') - , home = require('./routes/home') - , admin = require('./routes/admin') - , post = require('./routes/post') - , http = require('http') - , path = require('path'); +var express = require('express'), + home = require('./routes/home'), + admin = require('./routes/admin'), + post = require('./routes/post'), + misc = require("./routes/misc"), + http = require('http'), + path = require('path'); var app = express(); var store = new express.session.MemoryStore; @@ -50,13 +51,15 @@ app.configure('development', function () { //Setting up "global locals" that will be used across the whole application app.locals = ({ - blogTitle: blogConfig.name, + blogTitle: blogConfig.title, blogSubtitle: blogConfig.subTitle, fbAppId: blogConfig.facebookAppId }); -////////get methods//////// +/*********************************************************************************************************************** + ************************************************ GET HANDLERS ********************************************************* + ***********************************************************************************************************************/ app.get('/', home.index); app.get('/admin/delete', admin.delete); app.get('/admin/new', admin.new); @@ -76,8 +79,12 @@ app.get('/about', function (req, res) { }); +app.get('/rss.xml', misc.getRss); -///////post methods//////// + +/********************************************************************************************************************** +*********************************************** POST HANDLERS ********************************************************* +***********************************************************************************************************************/ app.post('/admin/delete', admin.deletePost); app.post('/admin/new', admin.createNewPost); app.post('/admin' || '/admin/', admin.admin_check_post_handler); diff --git a/cache/postCache.js b/cache/postCache.js index d8a25ee..31e67df 100644 --- a/cache/postCache.js +++ b/cache/postCache.js @@ -8,12 +8,13 @@ var app = null; var posts = null; PostCache.prototype.loadPosts = function(callback){ - PostModel.find({}).sort({date: "desc"}).execFind(function(err, posts){ + PostModel.find({}).sort({date: "desc"}).execFind(function(err, blogPosts){ if(err){ process.nextTick(function(){ callback(true); }) }else{ + posts = blogPosts ? blogPosts : []; if(app){ app.locals.posts = posts; } @@ -26,6 +27,10 @@ PostCache.prototype.setApp = function (application){ app = application; } +PostCache.prototype.get = function(){ + return posts; +} + var cache = new PostCache(); module.exports = cache; diff --git a/config/blogConfig.json b/config/blogConfig.json index c79eeba..5f1c94c 100644 --- a/config/blogConfig.json +++ b/config/blogConfig.json @@ -1,4 +1,7 @@ { + "author": "Your blog author's name", + "siteUrl": "Your blog's URL (must finish with a '/' character)", + "iconUrl": "Your blog's icon URL", "name": "Your blog name", "subTitle": "Your blog subtitle name", "password": "Your mongoDB password", diff --git a/package.json b/package.json index 7136b2d..f4f6fdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Node2Blog", - "description": "Simple and easy to use blog template for your casual blogger.", + "description": "Simple and easy to use blog template for the casual blogger.", "version": "1.0.0", "author": { "name": "kenshiro-o", @@ -19,7 +19,8 @@ "express": "*", "jade": "*", "mongoose": "*", - "mongodb": "*" + "mongodb": "*", + "rss": "0.2.0" } } diff --git a/routes/misc.js b/routes/misc.js new file mode 100644 index 0000000..fe4fe6f --- /dev/null +++ b/routes/misc.js @@ -0,0 +1,40 @@ +var rss = require("rss"), + blogConfig = require("./../config/blogConfig"), + postCache = require("./../cache/postCache"); + +//Refreshing the feed every 10 minutes (in +var RSS_REFRESH_TIME_IN_MILLIS = 60 * 10 * 1000; +var feedLastRefreshTime = null; +var lastProducedXMLFeed = null; + + +exports.getRss = function(req, res){ + //TODO: This is actually inefficient. Instead register for an event that is fired upon PostCache update and only then refresh the feed + var now = new Date(); + if(!feedLastRefreshTime || now.getTime() - feedLastRefreshTime.getTime() >= RSS_REFRESH_TIME_IN_MILLIS){ + var feed = new rss({ + title: blogConfig.title, + description: blogConfig.subTitle, + feed_url: blogConfig.siteUrl + "rss.xml", + site_url: blogConfig.siteUrl, + image_url: blogConfig.iconUrl, + author: blogConfig.author + }); + + var posts = postCache.get(); + posts.forEach(function(post){ + var dateOnly = post.date.toDateString(); + feed.item({ + title: post.title, + description: post.content, + url: blogConfig.siteUrl + "/post" + post._id + "/" + post.friendly_link_title, + date: dateOnly + }); + }); + + lastProducedXMLFeed = feed.xml(); + feedLastRefreshTime = now; + } + + res.send(lastProducedXMLFeed); +}; \ No newline at end of file From d182b62575dfbaf2df2f7db4dc09ca265040f32c Mon Sep 17 00:00:00 2001 From: kenshiro Date: Wed, 7 Aug 2013 00:30:55 +0100 Subject: [PATCH 12/28] * Fixing small bug on rss article link --- routes/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/misc.js b/routes/misc.js index fe4fe6f..3889f8d 100644 --- a/routes/misc.js +++ b/routes/misc.js @@ -27,7 +27,7 @@ exports.getRss = function(req, res){ feed.item({ title: post.title, description: post.content, - url: blogConfig.siteUrl + "/post" + post._id + "/" + post.friendly_link_title, + url: blogConfig.siteUrl + "post" + "/" + post._id + "/" + post.friendly_link_title, date: dateOnly }); }); From 83d2be2d63828f36dd44dfaeaddd42a7bbc1572a Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 18 Aug 2013 18:50:19 +0100 Subject: [PATCH 13/28] * Added Google Analytics * Fixed issue FBK like URL --- History.md | 2 ++ views/default-layout.jade | 8 ++++++++ views/post.jade | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 343fd80..84df35b 100644 --- a/History.md +++ b/History.md @@ -8,6 +8,8 @@ * Using the configured port in blogConfig.json to bind the server to the specified port * Displaying correct page title * Added RSS support +* Added Google Analytics +* Fixed issue FBK like URL 0.2.2 / 2013-02-23 ==================== diff --git a/views/default-layout.jade b/views/default-layout.jade index 0e24731..44ea8bf 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -4,6 +4,14 @@ html(lang="en") title= title link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Lato") link(rel="stylesheet", href="/stylesheets/style.css") + script. + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-33728113-2', 'kenshiro.me'); + ga('send', 'pageview'); #fb-root script. diff --git a/views/post.jade b/views/post.jade index da63bea..034d09d 100644 --- a/views/post.jade +++ b/views/post.jade @@ -22,7 +22,7 @@ block blog-content p.comment-signature #{comment.name} on #{comment.date.toDateString()} at #{comment.date.toLocaleTimeString()} #social-media-container - .fb-like(data-href="http://kenshiro.me", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") + .fb-like(data-href="http://kenshiro.me/post/#{post._id}/#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") .tweetbtn a(href="https://twitter.com/share", class="twitter-share-button", data-size="medium") Tweet script. From 2e1dba8302e733ef7e98dddd190837d75cb03efd Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 18 Aug 2013 18:55:56 +0100 Subject: [PATCH 14/28] * Using correct google analytics ID --- views/default-layout.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/views/default-layout.jade b/views/default-layout.jade index 44ea8bf..d3b5228 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -10,9 +10,10 @@ html(lang="en") m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', 'UA-33728113-2', 'kenshiro.me'); + ga('create', 'UA-43277669-1', 'kenshiro.me'); ga('send', 'pageview'); + #fb-root script. (function(d, s, id) { From 8a52a8dbfa4084a6618148ff071e66da9188eea9 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Sun, 18 Aug 2013 19:59:37 +0100 Subject: [PATCH 15/28] * Merging edit and delete posts pages into a single one --- blog.js | 4 +-- public/stylesheets/style.css | 28 +++++++++++++++++++ routes/admin.js | 54 +++++++++++++++++++++++------------- views/admin_delete.jade | 15 ---------- views/admin_edit_delete.jade | 11 ++++++++ views/admin_view.jade | 4 +-- 6 files changed, 77 insertions(+), 39 deletions(-) delete mode 100644 views/admin_delete.jade create mode 100644 views/admin_edit_delete.jade diff --git a/blog.js b/blog.js index def9dd9..d4dd911 100644 --- a/blog.js +++ b/blog.js @@ -61,7 +61,7 @@ app.locals = ({ ************************************************ GET HANDLERS ********************************************************* ***********************************************************************************************************************/ app.get('/', home.index); -app.get('/admin/delete', admin.delete); +app.get('/admin/editOrDelete', admin.showPostsToEditOrDelete); app.get('/admin/new', admin.new); app.get('/post/:id/:title', post.get); @@ -85,7 +85,7 @@ app.get('/rss.xml', misc.getRss); /********************************************************************************************************************** *********************************************** POST HANDLERS ********************************************************* ***********************************************************************************************************************/ -app.post('/admin/delete', admin.deletePost); +app.post('/admin/delete/:id', admin.deletePost); app.post('/admin/new', admin.createNewPost); app.post('/admin' || '/admin/', admin.admin_check_post_handler); app.post('/admin/:id/edit', admin.editPost); diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index aa3086b..2c0d882 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -283,6 +283,34 @@ header{ background-color: #0dd257; } +.post-edit-form{ + display: inline; + margin-right: 10px; +} + +.post-delete-form{ + display: inline; + margin-right: 10px; +} + +.post-delete-button{ + margin: 5px 0; + padding: 3px 5px; + background-color: #a90005; + border-radius: 5px; + font-weight: bold; + color: #FFF; +} + +.post-edit-button{ + margin: 5px 0; + padding: 3px 5px; + background-color: #2227a9; + border-radius: 5px; + font-weight: bold; + color: #FFF; +} + /*********************************************************************************************************************** diff --git a/routes/admin.js b/routes/admin.js index 611d997..fc5aecf 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -1,6 +1,7 @@ //Prerequisites var mongoose = require('mongoose'); var PostModel = require('./../db/model/post'); +var CommentModel = require("./../db/model/comment"); var postCache = require("./../cache/postCache"); var error; @@ -68,15 +69,9 @@ exports.createNewPost = function(req, res){ }; //deleting posts functions -exports.delete = function(req, res){ +exports.showPostsToEditOrDelete = function(req, res){ if (req.session.admin == 'true'){ - PostModel.find({}).sort('-_id').execFind(function(err, posts){ - if(posts){ - res.render('admin_delete', { title: "Test", subTitle: "TEST", posts:posts, admin:req.session.admin}); - }else{ - res.render('admin_delete', { title: "Test", subTitle: "TEST", posts:null, admin:req.session.admin }) - } - }); + res.render('admin_edit_delete', { title: "Edit Or Delete Posts", subTitle: "Edit Or Delete Posts", posts: postCache.get(), admin:req.session.admin}); }else{ res.redirect('/') } @@ -84,18 +79,39 @@ exports.delete = function(req, res){ exports.deletePost = function(req, res){ - var title = req.body.title; - var time = req.body.time; - console.log(title); - PostModel.findOne({"title": title , "date":time}, function(err, match){ - if(match){ - match.remove() - console.log('removed') - res.redirect('/admin/delete') - }else{ - res.redirect('/') - } + var identifier = mongoose.Types.ObjectId(req.params.id); + if(!identifier){ + res.status(500); + res.render("500", {title: "Error while trying to delete post."}); + return; + } + + var postToDelete = postCache.get().filter(function(post){ + return post._id.equals(identifier); }); + + if(!postToDelete || postToDelete.length != 1){ + res.status(500); + res.render("500", {title: "Error: post to delete not found"}); + }else{ + postToDelete = postToDelete[0]; + CommentModel.remove({post_id: identifier}, function(err){ + if(err){ + console.log("An error occurred while trying to delete comments [error=" + err + "]"); + } + //Now delete the post + postToDelete.remove(function(err){ + if(err){ + console.log("Unable to remove post [id=" + identifier + "]"); + }else{ + console.log("Succesfully deleted post with title: " + postToDelete.title); + } + postCache.loadPosts(function(err, postsLoaded){ + res.redirect("/"); + }); + }); + }); + } }; exports.admin_edit = function(req, res){ if (req.session.admin == 'true'){ diff --git a/views/admin_delete.jade b/views/admin_delete.jade deleted file mode 100644 index 5ae3c72..0000000 --- a/views/admin_delete.jade +++ /dev/null @@ -1,15 +0,0 @@ -extends layout - -block wrapper_content - .container - h1 Delete A Post - for post in posts - form(method='post') - .post - h1(style='font-size: 150%;')= post.title - label(style='font-size: 100%;font-weight:normal;') Created on -> #{post.date} - input(type='hidden', name='title', value='#{post.title}') - input(type='hidden', name='time', value='#{post.date}') - br - br - input(type='submit', id='delete_btn', value='DELETE POST') \ No newline at end of file diff --git a/views/admin_edit_delete.jade b/views/admin_edit_delete.jade new file mode 100644 index 0000000..de4b42e --- /dev/null +++ b/views/admin_edit_delete.jade @@ -0,0 +1,11 @@ +extends default-layout + +block blog-content + for post in posts + .post-extract + h2.post-title #{post.title} + h5.post-date #{post.date.toDateString()} + form.post-edit-form(method='post', action="/admin/edit/#{post._id}") + input.post-edit-button(type="submit", value="EDIT POST") + form.post-delete-form(method='post', action="/admin/delete/#{post._id}") + input.post-delete-button(type="submit", value="DELETE POST") diff --git a/views/admin_view.jade b/views/admin_view.jade index 8456244..6d90199 100644 --- a/views/admin_view.jade +++ b/views/admin_view.jade @@ -10,9 +10,7 @@ block blog-content li a(href="/admin/new") Create Post li - a(href="#") Edit Post - li - a(href="/admin/delete") Delete Post + a(href="/admin/editOrDelete") Edit/Delete Post li a(href="/admin/logout") Log out From c66c42bb627f285c7561acc5e99341ebe81c0b68 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Wed, 21 Aug 2013 00:21:06 +0100 Subject: [PATCH 16/28] * Added edit post feature --- History.md | 1 + blog.js | 3 +- public/javascripts/nicEdit.js | 2 +- public/stylesheets/style.css | 27 +++++++++++++--- routes/admin.js | 61 +++++++++++++++++++++++++++++------ views/admin.jade | 2 +- views/admin_edit.jade | 40 ++++++++++++----------- views/admin_view.jade | 10 +++--- 8 files changed, 104 insertions(+), 42 deletions(-) diff --git a/History.md b/History.md index 84df35b..7058126 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,7 @@ * Added RSS support * Added Google Analytics * Fixed issue FBK like URL +* Added edit post feature 0.2.2 / 2013-02-23 ==================== diff --git a/blog.js b/blog.js index d4dd911..35adb8e 100644 --- a/blog.js +++ b/blog.js @@ -88,7 +88,8 @@ app.get('/rss.xml', misc.getRss); app.post('/admin/delete/:id', admin.deletePost); app.post('/admin/new', admin.createNewPost); app.post('/admin' || '/admin/', admin.admin_check_post_handler); -app.post('/admin/:id/edit', admin.editPost); +app.post('/admin/edit/:id', admin.showPostToEdit); +app.post("/admin/postEdit/:id", admin.editAndSavePost); app.post('/', home.home_post_handler); app.post('/post/:id/:friendlyLink', post.saveComment); diff --git a/public/javascripts/nicEdit.js b/public/javascripts/nicEdit.js index 9a9951a..57515ec 100755 --- a/public/javascripts/nicEdit.js +++ b/public/javascripts/nicEdit.js @@ -28,7 +28,7 @@ var nicEditorConfig = bkClass.extend({ 'outdent' : {name : __('Remove Indent'), command : 'outdent', noActive : true}, 'hr' : {name : __('Horizontal Rule'), command : 'insertHorizontalRule', noActive : true} }, - iconsPath : '../images/nicEditorIcons.gif', + iconsPath : '/images/nicEditorIcons.gif', buttonList : ['save','bold','italic','underline','left','center','right','justify','ol','ul','fontSize','fontFamily','fontFormat','indent','outdent','image','upload','link','unlink','forecolor','bgcolor'], iconList : {"bgcolor":1,"forecolor":2,"bold":3,"center":4,"hr":5,"indent":6,"italic":7,"justify":8,"left":9,"ol":10,"outdent":11,"removeformat":12,"right":13,"save":24,"strikethrough":15,"subscript":16,"superscript":17,"ul":18,"underline":19,"image":20,"link":21,"unlink":22,"close":23,"arrow":25} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 2c0d882..efafd49 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -230,29 +230,33 @@ header{ ************************************************************************************************************************/ #admin-bar{ - width: 100%; + width: 95%; border: 2px solid black; + border-radius: 5px; + margin-left: 5px; margin-bottom: 5px; } #admin-bar > ol > li{ display: inline; padding: 5px; + font-size: 1.1em; + font-weight: bold; } -.post-new{ +.post-new-edit{ width: 100%; padding-left: 5px; } -#new-post-header{ +#new-edit-post-header{ margin-bottom: 10px; margin-top: 2px; margin-left: 5px ; } -#new-post-form{ +#new-edit-post-form{ width: 95%; padding-left: 5px; } @@ -266,7 +270,7 @@ header{ border-radius: 5px; } -#post-new-content{ +#post-new-edit-content{ width: 100%; min-height: 500px; margin-bottom: 15px; @@ -283,6 +287,19 @@ header{ background-color: #0dd257; } +#submit-edit-post-btn{ + margin-top: 10px; + height: 2em; + font-size: 1.1em; + border-radius: 5px; + padding: 2px 5px 5px 2px; + border: 1px solid #DDD; + background-color: #2227a9; + color: #FFF; +} + + + .post-edit-form{ display: inline; margin-right: 10px; diff --git a/routes/admin.js b/routes/admin.js index fc5aecf..d2520bb 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -126,16 +126,57 @@ exports.admin_edit = function(req, res){ res.redirect('/') } }; -exports.editPost = function(req, res){ - body = req.body.body; - title = req.body.title; - - PostModel.findOne({title: title}, function(err, post){ - post.content = body; - post.save() - console.log('edited post complete'); - res.redirect('/') - }) + +exports.editAndSavePost = function(req, res){ + var identifier = mongoose.Types.ObjectId(req.params.id); + if(!identifier){ + res.status(500); + res.render("500", {title: "Error while trying to edit post."}); + return; + } + + PostModel.update({_id: identifier}, {$set: { content: req.body.body }}, function(err){ + if(err){ + console.log("An error occurred when trying to update document [id= " + identifier + "]"); + res.status(500); + res.render("500", {title: "Error while trying to save edited post."}); + return; + }else{ + console.log("Successfully updated post with [id=" + identifier + "]") + postCache.loadPosts(function(err, posts){ + if(err){ + console.log("Unable to reload posts after saving a new one [error=%s]", err); + res.status(500); + res.render("500", {title: "Error"}); + }else{ + console.log("Successfully reloaded posts [number-of-posts=%d]", posts.length); + res.redirect('/'); + } + }); + } + }) ; + +} + +exports.showPostToEdit = function(req, res){ + var identifier = mongoose.Types.ObjectId(req.params.id); + if(!identifier){ + res.status(500); + res.render("500", {title: "Error while trying to edit post."}); + return; + } + + var postToEdit = postCache.get().filter(function(post){ + return post._id.equals(identifier); + }); + + if(!postToEdit || postToEdit.length != 1){ + res.status(500); + res.render("500", {title: "Error: post to edit not found"}); + }else{ + postToEdit = postToEdit[0]; + res.render("admin_edit", {post: postToEdit}); + } } diff --git a/views/admin.jade b/views/admin.jade index b8fc8f9..5e6e4bd 100644 --- a/views/admin.jade +++ b/views/admin.jade @@ -1,7 +1,7 @@ extends layout block wrapper_content - script(src="../javascripts/nicEdit.js", type="text/javascript") + script(src="/javascripts/nicEdit.js", type="text/javascript") script(type='text/javascript'). bkLib.onDomLoaded(nicEditors.allTextAreas); .container diff --git a/views/admin_edit.jade b/views/admin_edit.jade index 2c6fa20..8af6b31 100644 --- a/views/admin_edit.jade +++ b/views/admin_edit.jade @@ -1,21 +1,23 @@ -extends layout +extends default-layout -block wrapper_content - script(src="../javascripts/nicEdit.js", type="text/javascript") - script(type='text/javascript') - bkLib.onDomLoaded(nicEditors.allTextAreas); - - .container - - #formNew - h1 Edit Post - form(method='post') - input(type='text', name='title', value='#{post.title}',placeholder='Title (do not change)') - label do not change title!! - br - br - textarea(id='formNewTextarea', name='body', placeholder='Write your post here') #{post.content} - br - br - input(type="submit", value="Edit") +block blog-content + script(src="/javascripts/nicEdit.js", type="text/javascript") + script(type='text/javascript') + bkLib.onDomLoaded(nicEditors.allTextAreas); + #admin-bar + ol + li + a(href="/admin/new") Create Post + li + a(href="/admin/editOrDelete") Edit/Delete Post + li + a(href="/admin/logout") Log out + + .post-new-edit + h2#new-edit-post-header Edit Post + form(id="new-edit-post-form", method="post", action="/admin/postEdit/#{post._id}") + input(id="post-title-box" ,type="text", name="title", readonly="true", value="#{post.title}") + textarea(id="post-new-edit-content", name="body") #{post.content} + + input(id="submit-edit-post-btn", type="submit", value="Edit") \ No newline at end of file diff --git a/views/admin_view.jade b/views/admin_view.jade index 6d90199..83fa5f3 100644 --- a/views/admin_view.jade +++ b/views/admin_view.jade @@ -1,7 +1,7 @@ extends default-layout block blog-content - script(src="../javascripts/nicEdit.js", type="text/javascript") + script(src="/javascripts/nicEdit.js", type="text/javascript") script(type='text/javascript') bkLib.onDomLoaded(nicEditors.allTextAreas); @@ -14,9 +14,9 @@ block blog-content li a(href="/admin/logout") Log out - .post-new - h2#new-post-header New Post - form(id="new-post-form", method="post") + .post-new-edit + h2#new-edit-post-header New Post + form(id="new-edit-post-form", method="post") input(id="post-title-box" ,type="text", name="title", placeholder="Title") - textarea(id="post-new-content", name="body", placeholder="Write your post here") + textarea(id="post-new-edit-content", name="body", placeholder="Write your post here") input(id="submit-post-btn", type="submit", value="Create") \ No newline at end of file From f4917ac24171bb8c52604faa335f06804f7fd7b7 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Thu, 22 Aug 2013 22:14:09 +0100 Subject: [PATCH 17/28] * Removed Contact page. Email address should be displayed in the "About" section --- views/about.jade | 12 +++++++----- views/default-layout.jade | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/views/about.jade b/views/about.jade index 1dc3209..0946ec3 100644 --- a/views/about.jade +++ b/views/about.jade @@ -5,16 +5,18 @@ block blog-content .post-full h2 About Me p - | I am Kenshiro (You guessed right - this is not my real name!). The goal of this blog is to share my musings and projects with the rest of the connected world. + | I am Kenshiro. The goal of this blog is to share my musings and projects with the rest of the connected world. p - | I hope you will enjoy reading my posts and commenting. I am currently mastering the deadly arts of Node.js (I already am lethal on Core Java). + | I hope you will enjoy reading my posts and commenting. I am currently mastering the deadly arts of Node.js & Google Go (I already am lethal on Core Java). + p + | I am also reachable via email on kenshiro@kenshiro.me: drop me a line and I'll make sure to respond! h2 What is Hackuto-Shinken? p em Hackuto-Shinken - | is a variant of the mighty - a(href="http://hokuto.wikia.com/wiki/Hokuto_Shin_Ken") Hokuto Shinken - | , an invincible fictional Martial Arts, which can only be passed down to one heir. + | is a variant of the mighty Hokuto Shinken + + | , an invincible fictional Martial Arts, which can only be passed down to one heir (and... his name is Kenshiro;) ). p | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! img(src="/images/HokutoNoKen.jpg", width="300px", height="250px") \ No newline at end of file diff --git a/views/default-layout.jade b/views/default-layout.jade index d3b5228..4d8edf4 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -41,7 +41,6 @@ html(lang="en") h3.widget-title Pages a.widget-section-link(href="/") Home a.widget-section-link(href="/about") About - a.widget-section-link(href="#") Contact #widget-posts From a049b7d593c9a0103bac1e6acfa3bc8f9baf477c Mon Sep 17 00:00:00 2001 From: kenshiro Date: Thu, 22 Aug 2013 22:35:58 +0100 Subject: [PATCH 18/28] * Started editing Node2Blog documentation --- README.md | 10 +++++++--- docs/node2blog_main_screen.png | Bin 0 -> 30918 bytes 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 docs/node2blog_main_screen.png diff --git a/README.md b/README.md index 483549f..3ed473f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@

Node2Blog (fork from jawerty210 project)

-

Node2Blog is a simple and easy to use blog template for the casual blogger. For those who wish to setup an operable blog in minutes, this is the project for you. The blog is built with Node.js, Express.js, and Mongodb (with the mongoose driver). The instructions for quickly building a blog with the Node2Blog template is shown below.

+

Node2Blog is a simple and easy to use blog template for the casual blogger. +For those who wish to setup an operable blog in minutes, this is the project for you. +The blog is built with Node.js, Express.js, and Mongodb (with the mongoose driver). +The instructions for quickly building a blog with the Node2Blog template is shown below.

Features

  • - Intergration of the lightweight rich text editor, NicEdit. + Integration of the lightweight rich text editor, NicEdit.
  • Utilizes the Express framework. @@ -33,6 +36,7 @@ And much more...
+

Prerequisites

@@ -79,100 +86,88 @@ and change the 'password' value

-

Creating, editing and deleting posts should be self-explanatory; however, creating a new static page similar to the default 'about' page is detailed below.

+

Creating, editing and deleting posts should be self-explanatory. +However, creating a new static page similar to the default 'about' page is detailed below.

Adding a static page

-

To create a new page, first you must go to the 'layout.jade' file in the /views folder. Add the following code under the about 'li' tag which is in the 'ol' tag in the #nav div.

+

To create a new page, first you must go to the 'default-layout.jade' file in the /views folder. +Add the following code under the "About" 'a' tag which is in the div with the id 'widget-pages'

+
 
-a(href="/new-page-name")
-	li new_page_name
+        #widgets
+          #widget-pages
+            h3.widget-title Pages
+            a.widget-section-link(href="/") Home
+            a.widget-section-link(href="/about") About
+            a.widget-section-link(href="/newPage") New Page
 
+
 
-

Now create a new view with whatever name you want (i.e. new_page_name.jade) in the /views folder. Add the following code to your new view

+

Now create a new view with whatever name you want (i.e. newPage.jade) in the /views folder. +Add the following code to your new view

 
-extends layout
+extends default-layout
 
-block wrapper_content
-	.container
-		h1 new_page_name
-		br 
-		p random information
+
+block blog-content
+  .container
+    h1 This is the new page
+    br
+    p Random information
 
 

Now modify the get functions in the 'blog.js' file.


-////////get////////
 app.get('/', home.index);
-app.get('/admin/delete', admin.delete);
-app.get('/admin/new', admin.new);
-app.get('/post/:id', post.post_view);
-app.get('/admin' || '/admin/', admin.admin_check);
-app.get('/admin/logout', function(req,res){
-  delete req.session.admin;
-  console.log('logged-out')
-  res.redirect('/');
-});
+app.get('/post/:id/:title', post.get);
+app.get('/about', function (req, res) {
+    res.render('about', {title: blogConfig.title + " - About", admin: req.session.admin});
 
-app.get('/about', function(req, res) {
-  res.render('about', { title: t, admin:req.session.admin});
-      
 });
 
-//The code you added
-app.get('/new-page-name', function(req, res) {
-  res.render('new_page_name', { title: t, admin:req.session.admin});
+
+// The code you added
+app.get('/newPage', function(req, res){
+    res.render("newPage", {title: "New Page", admin: req.session.admin});
 });
-///////////////////
 
-

You should now be able to go the the '/new-page-name' route and have a view similar to what is below

+

You should now be able to go the the '/newPage' route and have a view similar to what is below

- +

Adding a side widget

-

In order to add a side widget, or simply a box under the "Latest Posts" box, you must go to the file 'layout.jade' and insert this line

+

In order to add a side widget, you must go to the file 'default-layout.jade' and insert your widget inside +the div with id "widgets"

 
-.widget
-
+          #widgets
 
-

Exactly where it is inserted below

+ + + + +

For instance the "Latest Posts widgets looks like this

 
-#box
-	#content
-		#wrapper
-			block wrapper_content
-		if(typeof posts == 'undefined')
-			.widget
-				a(href='/') Back to home
-		else
-			.widget
-				p(style='font-size: 150%; font-weight:bold; border-bottom: 1px solid #b1b1b1;padding-bottom: 5px;margin-bottom: 4px;') Latest Posts
-					br
-					pre
-					for post in posts
-						table(id='post_table', style='padding-top:10px;border-bottom:1px solid #ddd')
-							tr
-								td
-									#left
-										label(style='font-size: 130%;') -  
-								td
-									#right
-										a(href='/post/#{post._id}/#{post.title_sub}', style='font-size: 130%;')=  post.title
-							tr(style='height:10px;')
-		.widget //the inserted code
+          #widget-posts
+            h3.widget-title Latest Posts
+            for post in posts
+              a.widget-section-link(href="/post/#{post._id}/#{post.friendly_link_title}") #{post.title}
 
 
-

Now you can input any sort of information you'd like in your new widget box.*Note: Without any posts on your blog, the widget boxes will not be positioned adequately.

-
+ +

Now you can input any sort of information you'd like in your new widget box. Remember that you can add any style +you want to your widgets by editing the style.css file +
-

Congratulations you now have a working blog suitable to your basic blogger needs.

+

Congratulations! You now have a working blog suitable to your basic blogger needs.



+

Optional: Heroku Setup

*Note: You must have a heroku account along with the Heroku Toolbelt to follow this part of the tutorial

Simply follow the directions on this page to deploy the blog with heroku. However, in order to use MongoDB, you must enter the following command in the directory of your project

@@ -182,8 +177,22 @@ $ heroku addons:add mongohq:sandbox

This addon is a free starter package for running a server with a MongoDB backend by MongoHQ. This is essentially all you need to setup the basic functions to your new blog.

+ +

Contributors

+ +

Contact

-

Contact the developer here
Email: kenshiro@kenshiro.me
Website: http://kenshiro.me

+

Contact the developers here +
+Email: jawerty210@gmail.com
Website: http://jawerty.github.io +
+Email: kenshiro@kenshiro.me
Website: http://kenshiro.me + +

MIT LICENSE

The MIT License (MIT) Copyright (c) 2012 Jared Wright diff --git a/docs/node2blog_new_page.png b/docs/node2blog_new_page.png new file mode 100644 index 0000000000000000000000000000000000000000..e71a3e9f515277be573b0ea2df09fc031496e232 GIT binary patch literal 40879 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 z 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 literal 0 HcmV?d00001 diff --git a/views/default-layout.jade b/views/default-layout.jade index 4d8edf4..3cc0e65 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -41,6 +41,8 @@ html(lang="en") h3.widget-title Pages a.widget-section-link(href="/") Home a.widget-section-link(href="/about") About + a.widget-section-link(href="/newPage") New Page + #widget-posts From 8199a73fc3e03caeee3c67c46e9eeede20cbceed Mon Sep 17 00:00:00 2001 From: kenshiro Date: Thu, 22 Aug 2013 23:17:32 +0100 Subject: [PATCH 21/28] * Rewriting documentation --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 56bfc01..9bfc188 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Node2Blog (fork from jawerty210 project)

+

Node2Blog

Node2Blog is a simple and easy to use blog template for the casual blogger. For those who wish to setup an operable blog in minutes, this is the project for you. The blog is built with Node.js, Express.js, and Mongodb (with the mongoose driver). @@ -180,9 +180,9 @@ $ heroku addons:add mongohq:sandbox

Contributors

Contact

From 29808e1a44f4f8b89605a3d5ecb531dd5eefd57e Mon Sep 17 00:00:00 2001 From: kenshiro Date: Thu, 22 Aug 2013 23:20:29 +0100 Subject: [PATCH 22/28] * Adding mailto links --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9bfc188..87e5ca1 100644 --- a/README.md +++ b/README.md @@ -187,10 +187,12 @@ $ heroku addons:add mongohq:sandbox

Contact

Contact the developers here +
-Email: jawerty210@gmail.com
Website: http://jawerty.github.io +Email: jawerty210@gmail.com, website: http://jawerty.github.io +
-Email: kenshiro@kenshiro.me
Website: http://kenshiro.me +Email: kenshiro@kenshiro.me, website: http://kenshiro.me

From 7c9dcc21f2105a1298d76c158bd8d136436ea1b1 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Thu, 22 Aug 2013 23:35:36 +0100 Subject: [PATCH 23/28] * Removed Google Analytics and some other custom stuff which I added. --- blog.js | 15 ++++++++------- views/about.jade | 16 ---------------- views/default-layout.jade | 10 ---------- views/post.jade | 2 +- 4 files changed, 9 insertions(+), 34 deletions(-) diff --git a/blog.js b/blog.js index 35adb8e..fffc7f3 100644 --- a/blog.js +++ b/blog.js @@ -53,7 +53,8 @@ app.configure('development', function () { app.locals = ({ blogTitle: blogConfig.title, blogSubtitle: blogConfig.subTitle, - fbAppId: blogConfig.facebookAppId + fbAppId: blogConfig.facebookAppId, + blogSite: blogConfig.siteUrl }); @@ -61,11 +62,14 @@ app.locals = ({ ************************************************ GET HANDLERS ********************************************************* ***********************************************************************************************************************/ app.get('/', home.index); -app.get('/admin/editOrDelete', admin.showPostsToEditOrDelete); -app.get('/admin/new', admin.new); - app.get('/post/:id/:title', post.get); +app.get('/about', function (req, res) { + res.render('about', {title: blogConfig.title + " - About", admin: req.session.admin}); + +}); +app.get('/admin/editOrDelete', admin.showPostsToEditOrDelete); +app.get('/admin/new', admin.new); app.get('/admin' || '/admin/', admin.admin_check); app.get('/admin/:id/edit', admin.admin_edit); app.get('/admin/logout', function (req, res) { @@ -74,10 +78,7 @@ app.get('/admin/logout', function (req, res) { res.redirect('/'); }); -app.get('/about', function (req, res) { - res.render('about', {title: "Kenshiro's Hackuto blog - About", admin: req.session.admin}); -}); app.get('/rss.xml', misc.getRss); diff --git a/views/about.jade b/views/about.jade index 0946ec3..6fc103f 100644 --- a/views/about.jade +++ b/views/about.jade @@ -4,19 +4,3 @@ extends default-layout block blog-content .post-full h2 About Me - p - | I am Kenshiro. The goal of this blog is to share my musings and projects with the rest of the connected world. - p - | I hope you will enjoy reading my posts and commenting. I am currently mastering the deadly arts of Node.js & Google Go (I already am lethal on Core Java). - p - | I am also reachable via email on kenshiro@kenshiro.me: drop me a line and I'll make sure to respond! - - h2 What is Hackuto-Shinken? - p - em Hackuto-Shinken - | is a variant of the mighty Hokuto Shinken - - | , an invincible fictional Martial Arts, which can only be passed down to one heir (and... his name is Kenshiro;) ). - p - | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! - img(src="/images/HokutoNoKen.jpg", width="300px", height="250px") \ No newline at end of file diff --git a/views/default-layout.jade b/views/default-layout.jade index 3cc0e65..91e557a 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -4,15 +4,6 @@ html(lang="en") title= title link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Lato") link(rel="stylesheet", href="/stylesheets/style.css") - script. - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-43277669-1', 'kenshiro.me'); - ga('send', 'pageview'); - #fb-root script. @@ -41,7 +32,6 @@ html(lang="en") h3.widget-title Pages a.widget-section-link(href="/") Home a.widget-section-link(href="/about") About - a.widget-section-link(href="/newPage") New Page diff --git a/views/post.jade b/views/post.jade index 034d09d..f94c196 100644 --- a/views/post.jade +++ b/views/post.jade @@ -22,7 +22,7 @@ block blog-content p.comment-signature #{comment.name} on #{comment.date.toDateString()} at #{comment.date.toLocaleTimeString()} #social-media-container - .fb-like(data-href="http://kenshiro.me/post/#{post._id}/#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") + .fb-like(data-href="#{blogSite}#{post._id}/#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") .tweetbtn a(href="https://twitter.com/share", class="twitter-share-button", data-size="medium") Tweet script. From 84dc2a0237864369a6b942e3c8cba50f0e5ac0bb Mon Sep 17 00:00:00 2001 From: kenshiro Date: Tue, 17 Sep 2013 23:50:53 +0100 Subject: [PATCH 24/28] * Removed MongoDB ID from post URLs so as to make them more SEO-friendly * Added projects page, removed some unused jade template and improved style --- History.md | 7 +++- blog.js | 6 +++- db/dbConnection.js | 9 +++-- public/stylesheets/style.css | 11 +++--- routes/post.js | 43 +++++++++++----------- views/about.jade | 16 +++++++++ views/default-layout.jade | 15 ++++++-- views/home.jade | 38 -------------------- views/index.jade | 2 +- views/layout.jade | 69 ------------------------------------ views/post.jade | 2 +- views/projects.jade | 20 +++++++++++ views/this.html | 9 ----- 13 files changed, 92 insertions(+), 155 deletions(-) delete mode 100644 views/home.jade delete mode 100644 views/layout.jade create mode 100644 views/projects.jade delete mode 100644 views/this.html diff --git a/History.md b/History.md index 7058126..4e3f3de 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ -1.0.0 +1.0.1 +==================== +* Removed MongoDB ID from post URLs so as to make them more SEO-friendly +* Added projects page, removed some unused jade template and improved style + +1.0.0 / 22/08/2013 ==================== * MAJOR refactoring of API (in progress). * Revamping of layout as well (in progress). diff --git a/blog.js b/blog.js index fffc7f3..3a75ba3 100644 --- a/blog.js +++ b/blog.js @@ -62,10 +62,14 @@ app.locals = ({ ************************************************ GET HANDLERS ********************************************************* ***********************************************************************************************************************/ app.get('/', home.index); -app.get('/post/:id/:title', post.get); +app.get('/post/:title', post.get); app.get('/about', function (req, res) { res.render('about', {title: blogConfig.title + " - About", admin: req.session.admin}); +}); +app.get('/projects', function (req, res) { + res.render('projects', {title: blogConfig.title + " - Projects", admin: req.session.admin}); + }); app.get('/admin/editOrDelete', admin.showPostsToEditOrDelete); diff --git a/db/dbConnection.js b/db/dbConnection.js index d95131f..6a50bc9 100644 --- a/db/dbConnection.js +++ b/db/dbConnection.js @@ -1,11 +1,10 @@ //PREREQUISITES var mongoose = require('mongoose'), - Schema = mongoose.Schema, - ObjectId = Schema.ObjectId; + Schema = mongoose.Schema; //process.env.MONGOHQ_URL is for deploying on heroku -//node2blog can be chagens to whatever you want your local database to be called i.e. 'my database' -var db_url = process.env.MONGOHQ_URL || "mongodb://localhost:27017/your_database_name", - db = mongoose.connect(db_url); +//node2blog can be changed to whatever you want your local database to be called i.e. 'my database' +var db_url = process.env.MONGOHQ_URL || "mongodb://localhost:27017/your_database_name", +db = mongoose.connect(db_url); diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index efafd49..f100488 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -428,22 +428,25 @@ footer{ font-weight: bold; } -a.footer-link:link{ + +/* Misc */ + +a.no-link:link{ color: #2227a9; text-decoration: none; } -a.footer-link:visited{ +a.no-link:visited{ color: #2227a9; text-decoration: none; } -a.footer-link:hover{ +a.no-link:hover{ color: #7b3ab7; text-decoration: none; } -a.footer-link:active{ +a.no-link:active{ color: #7b3ab7; text-decoration: none; } \ No newline at end of file diff --git a/routes/post.js b/routes/post.js index 8047275..9bb78f2 100644 --- a/routes/post.js +++ b/routes/post.js @@ -1,41 +1,38 @@ //PREREQUISITES var mongoose = require('mongoose'); -var PostModel = require("./../db/model/post"); var CommentModel = require('./../db/model/comment'); +var postCache = require("./../cache/postCache"); //Single post view exports.get = function(req, res){ - var identifier = mongoose.Types.ObjectId(req.params.id); - - PostModel.findById(identifier, function(err, post){ - if(err){ - console.log("Unable to retrieve post [post-id=%s]", req.params.id); - res.status(500); - res.render("500", {title: "Error"}); - }else if(post){ - CommentModel.find({'post_id': identifier}).sort({date: "desc"}).exec(function(err, comments){ - if(err){ + var friendLinkTitle = req.params.title; + var posts = postCache.get(); + var matchedPosts = posts.filter(function(post){ + return post.friendly_link_title === friendLinkTitle; + }); + + if (matchedPosts.length == 0){ + console.log("Unable to retrieve post [post-link=%s]", friendLinkTitle); + //Post does not exist - return 404 + res.status(400); + res.render("404", {title: "Page not found"}); + }else{ + var post = matchedPosts[0]; + CommentModel.find({'post_id': post._id}).sort({date: "desc"}).exec(function(err, comments){ + if(err){ console.log("An error occurred when trying to retrieve comments [post-id=%s, error=%s]", req.params.id, err); res.status(500); res.render("500", {title: "Error"}); } else{ - comments = comments ? comments : []; + comments = comments ? comments : []; res.render("post", {title :post.title, post: post, comments: comments, admin:req.session.admin}); - } - }); - - }else{ - //Post does not exist - return 404 - res.status(400); - res.render("404", {title: "Page not found"}); - } - }); + } + }); + } } - - exports.saveComment = function(req, res){ var postId = req.params.id; diff --git a/views/about.jade b/views/about.jade index 6fc103f..dc183a8 100644 --- a/views/about.jade +++ b/views/about.jade @@ -4,3 +4,19 @@ extends default-layout block blog-content .post-full h2 About Me + p + | I am Kenshiro. The goal of this blog is to share my musings and projects with the rest of the connected world. + p + | I hope you will enjoy reading my posts and commenting. I am currently mastering the deadly arts of Node.js & Google Go (I already am lethal on Core Java). + p + | I am also reachable via email on kenshiro@kenshiro.me: drop me a line and I'll make sure to respond! + + h2 What is Hackuto-Shinken? + p + em Hackuto-Shinken + | is a variant of the mighty Hokuto Shinken + + | , an invincible fictional Martial Arts, which can only be passed down to one heir (and... his name is Kenshiro;) ). + p + | Hackuto-Shinken is the collection of techniques that I have gleaned on my journey as a hacker and wannabe tech entrepreneur. I hope you will all embrace the path of the Hackuto! + img(src="/images/HokutoNoKen.jpg", width="300px", height="250px") \ No newline at end of file diff --git a/views/default-layout.jade b/views/default-layout.jade index 91e557a..beedb53 100644 --- a/views/default-layout.jade +++ b/views/default-layout.jade @@ -4,6 +4,15 @@ html(lang="en") title= title link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Lato") link(rel="stylesheet", href="/stylesheets/style.css") + script. + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-43277669-1', 'kenshiro.me'); + ga('send', 'pageview'); + #fb-root script. @@ -32,17 +41,17 @@ html(lang="en") h3.widget-title Pages a.widget-section-link(href="/") Home a.widget-section-link(href="/about") About - + a.widget-section-link(href="/projects") Projects #widget-posts h3.widget-title Latest Posts for post in posts - a.widget-section-link(href="/post/#{post._id}/#{post.friendly_link_title}") #{post.title} + a.widget-section-link(href="/post/#{post.friendly_link_title}") #{post.title} footer p.footer-note Powered by - a.footer-link(href="https://github.com/kenshiro-o/Node2Blog") Node2Blog + a.no-link(href="https://github.com/kenshiro-o/Node2Blog") Node2Blog .footer-logos img(src="/images/node-js-logo.jpeg", alt="node.js official logo") \ No newline at end of file diff --git a/views/home.jade b/views/home.jade deleted file mode 100644 index 2ded3e7..0000000 --- a/views/home.jade +++ /dev/null @@ -1,38 +0,0 @@ -extends layout - - -block wrapper_content - - .container - for post in posts - - if post.content.length < 200 - - var content = post.content; - else - - var content = post.content.substring(0, 200) + '...'; - .post - h1 - a(href='post/#{post._id}/#{post.title_sub}')= post.title - br - iframe(id='#{post._id}', scrolling="auto", frameborder="0", style='width:100%;height:70px;') - label(value='#{content}', name='#{post._id}') - script(type='text/javascript') - c = stripScripts($('[name="#{post._id}"]').attr('value')) - var iframe = document.getElementById('#{post._id}') - var doc = iframe.document; - if(iframe.contentDocument) - doc = iframe.contentDocument; - else if(iframe.contentWindow) - doc = iframe.contentWindow.document; - doc.open() - - doc.writeln(c) - doc.close() - - br - br - label Created at: #{post.date} - br - br - if(typeof admin != 'undefined') - a(href='/admin/#{post._id}/edit') Edit Post \ No newline at end of file diff --git a/views/index.jade b/views/index.jade index e387442..0a108c9 100644 --- a/views/index.jade +++ b/views/index.jade @@ -4,7 +4,7 @@ block blog-content for post in posts .post-extract h2.post-title - a(href="/post/#{post._id}/#{post.friendly_link_title}") #{post.title} + a(href="/post/#{post.friendly_link_title}") #{post.title} h5.post-date #{post.date.toDateString()} - var content = post.content .post-content!= content diff --git a/views/layout.jade b/views/layout.jade deleted file mode 100644 index f8f43df..0000000 --- a/views/layout.jade +++ /dev/null @@ -1,69 +0,0 @@ -doctype 5 -html - head - title= title - link(rel='stylesheet', href='/stylesheets/reset.css') - link(rel='stylesheet', href='/stylesheets/header.css') - link(rel='stylesheet', href='/stylesheets/wrapper.css') - link(rel='stylesheet', href='/stylesheets/footer.css') - link(rel='stylesheet', href='/stylesheets/nav.css') - link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Lato") - script(type='text/javascript', src='/javascripts/main.js') - script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js') - - body - #box - #header - #titleContainer - h1.title= title - #subTitleContainer - h2.subTitle= subTitle - div(id="fb-root") - script. - (function(d, s, id) { - var js, fjs = d.getElementsByTagName(s)[0]; - if (d.getElementById(id)) return; - js = d.createElement(s); js.id = id; - js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=#{facebookAppId}"; - fjs.parentNode.insertBefore(js, fjs); - }(document, 'script', 'facebook-jssdk')); - #nav - ol - a(href="/") - li Home - a(href="/about") - li About - if(typeof admin == 'undefined') - - null - else - a(href="/admin/new") - li Admin-New - a(href="/admin/delete") - li Admin-Delete - a(href="/admin/logout") - li Admin-Logout - - #content - #wrapper - block wrapper_content - if(typeof posts == 'undefined') - .widget - a(href='/') Back to home - else - .widget - p(style='font-size: 150%; font-weight:bold; border-bottom: 1px solid #b1b1b1;padding-bottom: 5px;margin-bottom: 4px;') Latest Posts - br - pre - for post in posts - table(id='post_table', style='padding-top:10px;border-bottom:1px solid #ddd') - tr - td - #left - label(style='font-size: 130%;') -   - td - #right - a(href='/post/#{post._id}/#{post.title_sub}', style='font-size: 130%;')= post.title - tr(style='height:10px;') - - #footerContainer - p Using the Node2Blog template and running on a Node.js server \ No newline at end of file diff --git a/views/post.jade b/views/post.jade index f94c196..dafd172 100644 --- a/views/post.jade +++ b/views/post.jade @@ -22,7 +22,7 @@ block blog-content p.comment-signature #{comment.name} on #{comment.date.toDateString()} at #{comment.date.toLocaleTimeString()} #social-media-container - .fb-like(data-href="#{blogSite}#{post._id}/#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") + .fb-like(data-href="#{blogSite}#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") .tweetbtn a(href="https://twitter.com/share", class="twitter-share-button", data-size="medium") Tweet script. diff --git a/views/projects.jade b/views/projects.jade new file mode 100644 index 0000000..c19aefa --- /dev/null +++ b/views/projects.jade @@ -0,0 +1,20 @@ +extends default-layout + + +block blog-content + .post-full + h2 Projects + p + | I have a couple of projects hosted on github. + | They are all currently using node.js but I will also add so Go and Java projects too. + + h2 Project List + + p + | login-utils: login-utils is a very simple user authentication library. The library has a default User MangoDB model (using mongoose). Moreover, the encryption of the password is performed using bcrypt. + + p + | RapGenius-js: rapgenius-js is a simple client that enables you to query RapGenius and retrieve information about rap artists and songs. + + p + | wikipedia-js: wikipedia-js is a simple client that enables you to query Wikipedia articles in english. The format of the result is among json, jsonfm, wddx, wddxfm, xml, rawfm. In the case of html the result is formatted in basic HTML. You can retrieve either a summary of an article (i.e. before the table of contents) or a full article. \ No newline at end of file diff --git a/views/this.html b/views/this.html deleted file mode 100644 index f258e46..0000000 --- a/views/this.html +++ /dev/null @@ -1,9 +0,0 @@ - - YouTwitFace -

YouTwitFace

- -
-

Welcome to YouTwitFace, this is an awesome website for a lot of things and such. I am Jared, and I am awesome.

-
- Google - \ No newline at end of file From 638bb05b6c227ac634fbbbeaa30109873e3f0a3b Mon Sep 17 00:00:00 2001 From: kenshiro Date: Wed, 18 Sep 2013 00:20:05 +0100 Subject: [PATCH 25/28] * Correcting spelling mistake --- views/projects.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/projects.jade b/views/projects.jade index c19aefa..8f31d8d 100644 --- a/views/projects.jade +++ b/views/projects.jade @@ -6,7 +6,7 @@ block blog-content h2 Projects p | I have a couple of projects hosted on github. - | They are all currently using node.js but I will also add so Go and Java projects too. + | They are all currently using node.js but I will also add Go and Java projects too. h2 Project List From 75b973e2e14641abe83ed132f74c7c4efb6ec28c Mon Sep 17 00:00:00 2001 From: kenshiro Date: Wed, 25 Sep 2013 00:15:02 +0100 Subject: [PATCH 26/28] * Fixing fblike link bug --- views/post.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/post.jade b/views/post.jade index dafd172..98d0b0f 100644 --- a/views/post.jade +++ b/views/post.jade @@ -22,7 +22,7 @@ block blog-content p.comment-signature #{comment.name} on #{comment.date.toDateString()} at #{comment.date.toLocaleTimeString()} #social-media-container - .fb-like(data-href="#{blogSite}#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") + .fb-like(data-href="#{blogSite}/post#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") .tweetbtn a(href="https://twitter.com/share", class="twitter-share-button", data-size="medium") Tweet script. From da27f37679248243718e7696abed5613171c59e0 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Wed, 25 Sep 2013 00:18:00 +0100 Subject: [PATCH 27/28] * Changing text in projects jade file --- views/projects.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/projects.jade b/views/projects.jade index 8f31d8d..9ad571f 100644 --- a/views/projects.jade +++ b/views/projects.jade @@ -6,7 +6,7 @@ block blog-content h2 Projects p | I have a couple of projects hosted on github. - | They are all currently using node.js but I will also add Go and Java projects too. + | They are all currently using node.js but I will also add Go and Java projects soon too. h2 Project List From f494febfa521b20525d07c7d822ad43fdbe6a3f7 Mon Sep 17 00:00:00 2001 From: kenshiro Date: Wed, 25 Sep 2013 00:20:50 +0100 Subject: [PATCH 28/28] * Getting FBK like link correct --- views/post.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/post.jade b/views/post.jade index 98d0b0f..7e0cdb1 100644 --- a/views/post.jade +++ b/views/post.jade @@ -22,7 +22,7 @@ block blog-content p.comment-signature #{comment.name} on #{comment.date.toDateString()} at #{comment.date.toLocaleTimeString()} #social-media-container - .fb-like(data-href="#{blogSite}/post#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") + .fb-like(data-href="#{blogSite}post/#{post.friendly_link_title}", data-width="100px", data-show-faces="false", data-send="false", data-layout="button_count") .tweetbtn a(href="https://twitter.com/share", class="twitter-share-button", data-size="medium") Tweet script.