diff --git a/server.js b/server.js index 9e9e439..ad2a93c 100644 --- a/server.js +++ b/server.js @@ -5,12 +5,8 @@ var bodyParser = require('body-parser'); // Data conversion, JSON parsing // Set up Express var app = express(); -app.use(express.static("./static/")); // Serve static files from /static/ +app.use(express.static("./static/")); // Serve static files from /static app.use(express.json()); // Parse JSON in requests -// app.use("/", (req, res, next) => // Finally send a 404 page -// { -// res.status(404).sendFile("404.html", {"root": "./"}); -// }); // Posts route, return posts.json app.get("/api/posts", function(req, res) @@ -18,32 +14,39 @@ app.get("/api/posts", function(req, res) res.json(JSON.parse(fs.readFileSync('./data/posts.json'))); }); -// Posts route, digest received post +// Posts route, digest incoming post app.post("/api/posts", function(req, res) { + // Read in posts.json var postsFile = fs.readFileSync('./data/posts.json'); var posts = JSON.parse(postsFile); + // Verify incoming post matches expected schema var incomingData = req.body; if (validatePost(incomingData)) { + // Prepend the incoming post to the post list posts.unshift(incomingData); } else { + // Complain that we haven't been given what we're expecting res.status(400).json({"error":"Failed to validate post"}); return; } + // Try to save the updated posts object fs.writeFile('./data/posts.json', JSON.stringify(posts), (e) => { if (e) { + // Tell the client we've hit a snag and dump the error to the console res.sendStatus(500); throw e; } }); + // Tell the client all is good and that they shouldn't expect any content res.sendStatus(204); }); @@ -71,7 +74,17 @@ app.listen(8080, function() console.log("Listening on port 8080"); }); -// Validates a post object conforms to the standard we're expecting +// Verifies the post object conforms to the standard we're expecting +// Each post should conform to the following template: +// { +// "title":"", +// "author":"", +// "date":0, +// "tags":[ +// "" +// ], +// "content":"" +// } function validatePost(post) { try diff --git a/static/scripts/app.js b/static/scripts/app.js index b269f40..2572e97 100644 --- a/static/scripts/app.js +++ b/static/scripts/app.js @@ -1,8 +1,10 @@ var app = angular.module("app", ["ngRoute", "ngSanitize", "ngTagsInput"]); +// Provider configuration app.config( function ($routeProvider, $locationProvider) { + // Configure routes $routeProvider .when("/", {templateUrl: "views/blog.html", controller: "blog"}) .when("/create", {templateUrl: "views/create.html", controller: "create"}) @@ -10,35 +12,45 @@ app.config( .when("/faq", {templateUrl: "views/faq.html", controller: "faq"}) .otherwise({templateUrl: "404.html"}); + // Enable HTML5-style routing + // URLs will be shown as http://host/view instead of http://host/#!/view $locationProvider.html5Mode({enabled: true, requireBase: false}); } ); +// Non-provider configuration app.run(($anchorScroll) => { + // Apply 100px scroll offset to compensate for fixed header $anchorScroll.yOffset = 100; }) +// Blog view controller app.controller("blog", ["$scope", "$http", function($scope, $http) { $scope.posts = []; + // Retrieve posts from server and populate array $http.get("/api/posts") .then((r) => { $scope.posts = angular.fromJson(r.data); }); } ]); +// Create view controller app.controller("create", ["$scope", "$http", function($scope, $http) { + // Initialise fields $scope.title = ""; $scope.author = ""; $scope.content = ""; $scope.tags = []; + // Disable preview by default $scope.previewing = false; + // Handle post submission $scope.onSubmit = function() { // Create the post object @@ -59,24 +71,31 @@ app.controller("create", ["$scope", "$http", $scope.content = ""; $scope.tags = []; + // Alert the user that it has been submitted alert("Post has been submitted."); } + // Handle tag additions $scope.onTagAdding = ($tag) => { + // Prepend a hash if the tag doesn't already start with one if (!$tag.text.startsWith("#")) $tag.text = "#" + $tag.text; + // Return whether the tag has already been added return $scope.tags.indexOf($tag.text) === -1; } + // Toggle post preview $scope.onPreview = () => { + // Invert preview switch $scope.previewing = !$scope.previewing; // Update HTML on preview to avoid parsing for each character change $scope.previewHtml = () => { + // Only parse if there is content and if the preview is toggling on return $scope.content && $scope.previewing ? marked($scope.content) : ""; @@ -85,46 +104,54 @@ app.controller("create", ["$scope", "$http", } ]); +// About view controller app.controller("about", ["$scope", "$http", function($scope, $http) { $scope.aboutContent = ""; + // Get about content from the server and populate the view $http.get("/api/about") .then((r) => { $scope.aboutContent = angular.fromJson(r.data).content; }); } ]); +// FAQ view controller app.controller("faq", ["$scope", "$http", "$location", "$anchorScroll", function($scope, $http, $location, $anchorScroll) { $scope.entries = [] + // Get FAQ entries from the server and populate the array $http.get("/api/faq") .then((r) => { $scope.entries = angular.fromJson(r.data) }); + // Handle scrolling to different entries $scope.scrollTo = (hash) => { + // Use $anchorScroll to avoid weird issues with hashes in the URL $anchorScroll(hash); }; } ]); +// Navbar controller app.controller("navButtons", ["$scope", "$location", function($scope, $location) { // Set the current relative path - // Helpful to set the navButtons appropriately when refreshing + // Useful for setting the navButtons appropriately when navigating to a deep linked view $scope.current = $location.path(); - // Changes location to the given target and sets the current path + // Handle navButton clicks $scope.navClick = (target) => { + // Change location to the given target and set the current path $location.path(target); $scope.current = target; } - // Applies the 'current' class if the target matches the current path + // Apply the 'current' class if the target matches the current path $scope.isCurrent = (target) => { return $scope.current == target ? "current" : ""; @@ -136,21 +163,26 @@ app.controller("navButtons", ["$scope", "$location", app.controller("head", ["$scope", function($scope) { + // Set the pag title $scope.title = "Lemonblog"; } ]); +// Footer controller app.controller("footer", ["$scope", function($scope) { + // Set the current year for the copyright string $scope.year = new Date().getFullYear(); } ]); +// Markdown filter app.filter("markdown", () => { return function(input) { + // Parse input as markdown and return resulting HTML return marked(input); } });