{{vm.blog.title}}
+{{vm.blog.content}}
+ +diff --git a/.gitignore b/.gitignore index 123ae94..b00ae8f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,12 @@ coverage # node-waf configuration .lock-wscript -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release +# Compiled files +public/js/main.js +public/css/main.css +public/views # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules +.node-version diff --git a/assignment-rules.md b/assignment-rules.md deleted file mode 100644 index a5d1eb0..0000000 --- a/assignment-rules.md +++ /dev/null @@ -1,15 +0,0 @@ -# Rules for this assignment - -Please be sure to read carefully as this will be crucial for achieving the goals for this week's assignment project. - -1. I expect everyone to show up or be with their pair partner as soon as possible in the morning to review and start on the project, there is A LOT to do -1. Fork the assignment repo directly, no cross forking this time -1. Once someone has forked the repo, create the PR right away - 1. Give the PR a proper title and place `(wip)` in the title - 1. You can keep making updates to the `master` branch and push that to your repo, the PR will keep updating - 1. DO NOT submit a feature branch to the PR, only submit `master` - 1. Work in your feature branches and commit often until you are ready to merge in the code from the feature branch to `master` and the owner of the -1. In the PR, please be sure to write up a description of the project you are completing and submit the PR - 1. This will be the only PR that is used throughout this week's assignment -1. For the final assignment submission, update the PR title to remove `(wip)` and use `(final)`. Pushes to `master` after the title has been updated will be ignored, make sure you are done before you say you are done. -1. Make sure to submit the URL of the PR to Canvas for final grading diff --git a/comps/intelly-blog-III@2x.psd b/comps/intelly-blog-III@2x.psd deleted file mode 100644 index ebfdb1a..0000000 Binary files a/comps/intelly-blog-III@2x.psd and /dev/null differ diff --git a/comps/intelly-blog-post@2x.psd b/comps/intelly-blog-post@2x.psd deleted file mode 100644 index 0964434..0000000 Binary files a/comps/intelly-blog-post@2x.psd and /dev/null differ diff --git a/gulp/config.js b/gulp/config.js new file mode 100644 index 0000000..47bb6f9 --- /dev/null +++ b/gulp/config.js @@ -0,0 +1,18 @@ +var dest = "./public"; +var src = "./src"; + +module.exports = { + javascript: { + entryPoint: src + "/app/entry.js", + src: src + "/app/**/*.js", + dest: dest + "/js/" + }, + sass: { + src: src + "/sass/**/*.scss", + dest: dest + "/css/" + }, + html: { + src: src + "/app/**/*.html", + dest: dest + "/views/", + }, +}; diff --git a/gulp/tasks/csslint.js b/gulp/tasks/csslint.js new file mode 100644 index 0000000..4c02e9d --- /dev/null +++ b/gulp/tasks/csslint.js @@ -0,0 +1,9 @@ +var gulp = require("gulp"); +var config = require("../config").sass; +var csslint = require("gulp-csslint"); + +gulp.task("csslint", function () { + return gulp.src(config.dest + "**/*.css") + .pipe(csslint()) + .pipe(csslint.reporter()); +}); diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js new file mode 100644 index 0000000..11eee42 --- /dev/null +++ b/gulp/tasks/default.js @@ -0,0 +1,3 @@ +var gulp = require("gulp"); + +gulp.task("default", ["sass", "html", "webpack", "watch", "jshint", "serve"]); diff --git a/gulp/tasks/html.js b/gulp/tasks/html.js new file mode 100644 index 0000000..0b29aff --- /dev/null +++ b/gulp/tasks/html.js @@ -0,0 +1,7 @@ +var gulp = require("gulp"); +var config = require('../config').html; + +gulp.task('html', function() { + return gulp.src(config.src) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/tasks/jshint.js b/gulp/tasks/jshint.js new file mode 100644 index 0000000..8958d19 --- /dev/null +++ b/gulp/tasks/jshint.js @@ -0,0 +1,10 @@ +var gulp = require("gulp"); +var config = require("../config").javascript; +var jshint = require("gulp-jshint"); +var stylish = require("jshint-stylish"); + +gulp.task("jshint", function () { + return gulp.src(config.src) + .pipe(jshint()) + .pipe(jshint.reporter(stylish)); +}); diff --git a/gulp/tasks/sass.js b/gulp/tasks/sass.js new file mode 100644 index 0000000..6263a79 --- /dev/null +++ b/gulp/tasks/sass.js @@ -0,0 +1,14 @@ +var gulp = require('gulp'); +var sass = require('gulp-sass'); +var sourcemaps = require('gulp-sourcemaps'); +var handleErrors = require('../util/handleErrors'); +var config = require('../config').sass; + +gulp.task('sass', function () { + return gulp.src(config.src) + .pipe(sourcemaps.init()) + .pipe(sass()) + .on('error', handleErrors) + .pipe(sourcemaps.write()) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/tasks/serve.js b/gulp/tasks/serve.js new file mode 100644 index 0000000..0943cdb --- /dev/null +++ b/gulp/tasks/serve.js @@ -0,0 +1,7 @@ +var gulp = require("gulp"); +var server = require("gulp-express"); + +gulp.task("serve", function () { + server.run(["server.js"]); + gulp.watch(["server.js"], [server.run]); +}); diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js new file mode 100644 index 0000000..ae829af --- /dev/null +++ b/gulp/tasks/watch.js @@ -0,0 +1,8 @@ +var gulp = require("gulp"); +var config = require("../config"); + +gulp.task('watch', function() { + gulp.watch(config.html.src, ["html"]); + gulp.watch(config.javascript.src, ["webpack", "jshint"]); + gulp.watch(config.sass.src, ["sass"]); +}); diff --git a/gulp/tasks/webpack.js b/gulp/tasks/webpack.js new file mode 100644 index 0000000..12fa06a --- /dev/null +++ b/gulp/tasks/webpack.js @@ -0,0 +1,13 @@ +var gulp = require("gulp"); +var config = require("../config").javascript; +var webpack = require("webpack-stream"); + +gulp.task("webpack", function (callback) { + return gulp.src(config.entryPoint) + .pipe(webpack({ + output: { + filename: "main.js", + } + })) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/util/handleErrors.js b/gulp/util/handleErrors.js new file mode 100644 index 0000000..3c65f4f --- /dev/null +++ b/gulp/util/handleErrors.js @@ -0,0 +1,15 @@ +var notify = require("gulp-notify"); + +module.exports = function () { + + var args = Array.prototype.slice.call(arguments); + + // Send error to notification center with gulp-notify + notify.onError({ + title: "Compile Error", + message: "<%= error %>" + }).apply(this, args); + + // Keep gulp from hanging on this task + this.emit("end"); +}; diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..59686d1 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,17 @@ +/* + gulpfile.js + =========== + Rather than manage one giant configuration file responsible + for creating multiple tasks, each task has been broken out into + its own file in gulp/tasks. Any files in that directory get + automatically required below. + + To add a new task, simply add a new task file that directory. + gulp/tasks/default.js specifies the default set of tasks to run + when you run `gulp`. +*/ + +var requireDir = require('require-dir'); + +// Require all tasks in gulp/tasks, including subfolders +requireDir('./gulp/tasks', { recurse: true }); diff --git a/images/intelly_blog.png b/images/intelly_blog.png new file mode 100644 index 0000000..1149756 Binary files /dev/null and b/images/intelly_blog.png differ diff --git a/images/intelly_blog_form.png b/images/intelly_blog_form.png new file mode 100644 index 0000000..0b561fe Binary files /dev/null and b/images/intelly_blog_form.png differ diff --git a/images/intelly_blog_list.png b/images/intelly_blog_list.png new file mode 100644 index 0000000..ae010c7 Binary files /dev/null and b/images/intelly_blog_list.png differ diff --git a/images/intelly_footer.png b/images/intelly_footer.png new file mode 100644 index 0000000..e9c56d4 Binary files /dev/null and b/images/intelly_footer.png differ diff --git a/images/intelly_header.png b/images/intelly_header.png new file mode 100644 index 0000000..4fcd2f7 Binary files /dev/null and b/images/intelly_header.png differ diff --git a/models/blog.js b/models/blog.js new file mode 100644 index 0000000..21e22eb --- /dev/null +++ b/models/blog.js @@ -0,0 +1,12 @@ +"use strict"; + +var mongoose = require("mongoose"); + +var BlogSchema = new mongoose.Schema({ + title: String, + content: String, + author: String, + date: String, +}); + +module.exports = mongoose.model("Blog", BlogSchema); diff --git a/package.json b/package.json new file mode 100644 index 0000000..43215be --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "mean-stack-1", + "version": "1.0.0", + "description": "Intelly - a simple MEAN stack blog app", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/hollislau/mean-stack-1.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/hollislau/mean-stack-1/issues" + }, + "homepage": "https://github.com/hollislau/mean-stack-1", + "dependencies": { + "angular": "1.4.x", + "angular-route": "^1.4.7", + "body-parser": "^1.14.1", + "express": "^4.13.3", + "gulp": "^3.9.0", + "gulp-csslint": "^0.2.0", + "gulp-express": "^0.3.5", + "gulp-jshint": "^1.11.2", + "gulp-notify": "^2.2.0", + "gulp-sass": "^2.0.4", + "gulp-sourcemaps": "^1.6.0", + "jshint-stylish": "^2.0.1", + "mongoose": "^4.1.10", + "require-dir": "^0.3.0", + "webpack-stream": "^2.1.1" + }, + "engines": { + "node": "0.12.x" + } +} diff --git a/public/css/fonts/icomoon.eot b/public/css/fonts/icomoon.eot new file mode 100644 index 0000000..2761603 Binary files /dev/null and b/public/css/fonts/icomoon.eot differ diff --git a/public/css/fonts/icomoon.svg b/public/css/fonts/icomoon.svg new file mode 100644 index 0000000..ec48438 --- /dev/null +++ b/public/css/fonts/icomoon.svg @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/public/css/fonts/icomoon.ttf b/public/css/fonts/icomoon.ttf new file mode 100644 index 0000000..e8bc90d Binary files /dev/null and b/public/css/fonts/icomoon.ttf differ diff --git a/public/css/fonts/icomoon.woff b/public/css/fonts/icomoon.woff new file mode 100644 index 0000000..efeebf8 Binary files /dev/null and b/public/css/fonts/icomoon.woff differ diff --git a/readme.md b/readme.md index dde636c..e806e64 100644 --- a/readme.md +++ b/readme.md @@ -1,44 +1,27 @@ -# Week Six - MEAN Stack I +# Intelly Blog -Last week we focussed on JavaScript basics. This week we're going to build up a function web application. You'll be expected to build on knowledge you've already learned (semantic HTML, Sass instead of CSS, in addition to all of the Angular work we'll be doing) and produce a "production-ready" blog for your assignment this week. +A responsive, single-page blog app was reconstructed from a provided design comp using JavaScript, HTML5, Sass, and MEAN stack components. This simple app supports basic CRUD functionality and allows a user to read, post, edit, and delete blog entries. -We want this assignment to feel like a 'week on the job.' And by that we mean, we will provide a completed comp and an expected outcome specification. It is up to you to deliver on this project specification. + -## The Comp + -You can download the view that you are to complete [here](https://github.com/SEA-Design-Dev/mean-stack-1/tree/master/comps). It is provided as a Photoshop format, if you do not have access to Photoshop, other applications like Pixelmator are able to open these layered files as well. + + -## The spec + -As a product owner, I need a functional prototype of the application. The final prototype must be visible in a desktop browser. +## Instructions To Use The App: -Prototype is to use best practices in coding HTML/CSS as there is the potential that some or all of the code will made it into production. +*You must have Node.js, npm, and MongoDB installed locally.* -Aside from images as content, all visual assets should be produced via CSS or typography. The only background image asset allowed will be the main page header. +In the terminal: -The user should be able to create, read, update, and delete blog posts from the application. There should be at least two different views - one a list of available blog posts, the other a detail view of the blog post. You do not need to worry about authentication or user management for this assignment. +1. Clone this repository +2. Navigate to the root directory of the cloned repository +3. Run `npm install` +4. Start MongoDB +5. Run either `gulp` or `node server` -### Requirements - -1. Semantically correct HTML is required as this will be the model for prod app integration -1. Think in terms of '*components*'; if all parts of the UI were lego blocks, who would you code that? -1. Images are to be cropped correctly and compression is to take performance into account -1. All CSS measurements should use elastic units unless a pixel specific unit is required for both mobile and desktop -1. JavaScript code is expected to be written cleanly and maintainably using the best practices covered during lectures -1. After checking out the repository, I must be able to run `npm install` and `gulp serve` in order to access the application locally -1. Write up a description for every plugin used (no limit, but you must justify them) - 1. jQuery is not allowed. Angular's built-in DOM manipulation can manage most of what you'd need jQuery for. - -### Constraints - -1. Must work in all major browsers of latest versions; - * Desktop (IE Edge, Safari, Chrome and Firefox) -1. All interactions must be clearly functional -1. All code must pass HTML Tidy, CSS Lint, and JSHint. - -__DO NOT__ fence yourself in with invisible constraints. Unless it is specifically listed and/or we discussed it in lecture, there is not an expectation to meet an objective that has not been set. - -## The expectation - -In this assignment, you should be able to demonstrate mastery over the basics of Angular as well as creating a simple CRUD application. We're looking for you to build on best practices that you've already learned (proper HTML and CSS) as well as incorporate the new practices discussed during the course of the week. +Open a web browser and view the app at **http://localhost:5000**. diff --git a/server.js b/server.js new file mode 100644 index 0000000..48bb0c7 --- /dev/null +++ b/server.js @@ -0,0 +1,104 @@ +"use strict"; + +var express = require("express"); +var mongoose = require("mongoose"); +var bodyParser = require("body-parser"); +var app = express(); +var router = express.Router(); +var Blog = require("./models/blog"); + +mongoose.connect("mongodb://localhost/intelly"); + +app.set("port", (process.env.PORT || 5000)); +app.use(bodyParser.json()); + +router.get("/", function (req, res) { + res.sendFile(__dirname + "/public/views/index.html"); +}); + +app.use(express.static(__dirname + "/public")); +app.use("/", router); + +// CREATE + +var blogsRoute = router.route("/api/blogs"); + +blogsRoute.post(function (req, res) { + var blog = new Blog(); + + blog.title = req.body.title; + blog.content = req.body.content; + blog.author = req.body.author; + blog.date = req.body.date; + + blog.save(function (err) { + if (err) { + res.send(err); + } + + res.json(blog); + }); +}); + +// READ + +blogsRoute.get(function (req, res) { + Blog.find(function (err, blogs) { + if (err) { + res.send(err); + } + + res.json(blogs); + }); +}); + +var blogRoute = router.route("/api/blogs/:blog_id"); + +blogRoute.get(function (req, res) { + Blog.findById(req.params.blog_id, function (err, blog) { + if (err) { + res.send(err); + } + + res.json(blog); + }); +}); + +// UPDATE + +blogRoute.put(function (req, res) { + Blog.findById(req.params.blog_id, function (err, blog) { + if (err) { + res.send(blog); + } + + blog.title = req.body.title; + blog.content = req.body.content; + blog.author = req.body.author; + blog.date = req.body.date; + + blog.save(function (err) { + if (err) { + res.send(err); + } + + res.json(blog); + }); + }); +}); + +// DELETE + +blogRoute.delete(function (req, res) { + Blog.findByIdAndRemove(req.params.blog_id, function(err) { + if (err) { + res.send(err); + } + + res.json({ message: "Successfully removed blog." }); + }); +}); + +app.listen(app.get("port"), function() { + console.log("Express server is running on port", app.get("port")); +}); diff --git a/src/app/app.js b/src/app/app.js new file mode 100644 index 0000000..5f80af0 --- /dev/null +++ b/src/app/app.js @@ -0,0 +1,32 @@ +require("angular"); +require("angular-route"); + +(function () { + + "use strict"; + + var app = angular.module("intellyBlog", ["ngRoute"]); + + app.config(["$routeProvider", function ($routeProvider) { + $routeProvider.when("/blogs", { + templateUrl: "views/blogs/blogs_list.html", + controller: "BlogsCtrl as vm", + }) + .when("/blogs/new", { + templateUrl: "views/blog/blog_form.html", + controller: "BlogFormCtrl as vm", + }) + .when("/blogs/:blog_id/edit", { + templateUrl: "views/blog/blog_form.html", + controller: "BlogFormCtrl as vm", + }) + .when("/blogs/:blog_id", { + templateUrl: "views/blog/blog_detail.html", + controller: "BlogCtrl as vm", + }) + .otherwise({ + redirectTo: "/blogs", + }); + }]); + +})(); diff --git a/src/app/blog/blog.ctrl.js b/src/app/blog/blog.ctrl.js new file mode 100644 index 0000000..e00ac82 --- /dev/null +++ b/src/app/blog/blog.ctrl.js @@ -0,0 +1,30 @@ +require("../app.js"); + +(function () { + + "use strict"; + + angular.module("intellyBlog").controller("BlogCtrl", ["BlogsService", "$routeParams", "$location", function (BlogsService, $routeParams, $location) { + var vm = this; + + vm.delete = deleteBlog; + + initialize(); + + function initialize() { + BlogsService + .get($routeParams.blog_id) + .then(function (resp) { + vm.blog = resp.data; + }); + } + + function deleteBlog (blog) { + BlogsService.delete(blog).then(function () { + $location.path("/blogs"); + }); + } + + }]); + +})(); diff --git a/src/app/blog/blog_detail.html b/src/app/blog/blog_detail.html new file mode 100644 index 0000000..f054cde --- /dev/null +++ b/src/app/blog/blog_detail.html @@ -0,0 +1,14 @@ +
{{vm.blog.content}}
+ +