diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..b879557
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..3b31283
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..01c3f40
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/frontend/.bowerrc b/frontend/.bowerrc
new file mode 100644
index 0000000..0bee623
--- /dev/null
+++ b/frontend/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "build/bower_components"
+}
\ No newline at end of file
diff --git a/frontend/.jshintrc b/frontend/.jshintrc
new file mode 100644
index 0000000..cf0dc70
--- /dev/null
+++ b/frontend/.jshintrc
@@ -0,0 +1,9 @@
+{
+ "browser": true,
+ "globalstrict": true,
+ "esnext": true,
+
+ "predef": [
+ "require", "module", "exports", "confirm"
+ ]
+}
diff --git a/frontend/bower.json b/frontend/bower.json
new file mode 100644
index 0000000..ec74478
--- /dev/null
+++ b/frontend/bower.json
@@ -0,0 +1,15 @@
+{
+ "name": "yoke",
+ "license": "proprietary",
+ "private": true,
+ "dependencies": {
+ "angular-route": "1.2.16",
+ "angular-i18n": "1.2.16",
+ "angular-sanitize": "1.2.16",
+ "angular-bootstrap": "0.10.0",
+ "bootstrap": "3.1.1",
+ "stomp-websocket": "2.3.1",
+ "sockjs": "0.3.4",
+ "font-awesome": "4.0.3"
+ }
+}
diff --git a/frontend/build.gradle b/frontend/build.gradle
new file mode 100644
index 0000000..f934d18
--- /dev/null
+++ b/frontend/build.gradle
@@ -0,0 +1,50 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.moowork.gradle:gradle-node-plugin:0.5'
+ }
+}
+
+apply plugin: 'base'
+apply plugin: 'node'
+
+repositories {
+ mavenCentral()
+ jcenter()
+}
+
+task gulpBuild(type: NodeTask, dependsOn: 'npmInstall') {
+ inputs.dir 'src'
+ inputs.files 'gulpfile.js', 'bower.json', 'package.json'
+ outputs.dir 'build/gulp/optimized'
+
+ script = file('node_modules/.bin/gulp')
+ args = ['build-production']
+}
+
+task gulpServe(type: NodeTask, dependsOn: 'npmInstall') {
+ script = file('node_modules/.bin/gulp')
+ args = ['default']
+}
+
+task deleteNodeModules(type: Delete) {
+ delete 'node_modules'
+}
+
+clean.dependsOn deleteNodeModules
+
+task build {
+ dependsOn gulpBuild
+}
+
+task serve {
+ dependsOn gulpServe
+}
+
+node {
+ version = '0.10.26'
+ download = true
+}
diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js
new file mode 100644
index 0000000..85e0bf9
--- /dev/null
+++ b/frontend/gulpfile.js
@@ -0,0 +1,197 @@
+"use strict";
+
+var gulp = require('gulp');
+var clean = require('gulp-clean');
+var path = require('path');
+var browserify = require('browserify');
+var es6ify = require('es6ify');
+var source = require('vinyl-source-stream');
+var express = require('express');
+var http = require('http');
+var morgan = require('morgan');
+var livereload = require('gulp-livereload');
+var gulpOpen = require('gulp-open');
+var sass = require('gulp-sass');
+var size = require('gulp-size');
+var notify = require('gulp-notify');
+var templateCache = require('gulp-angular-templatecache');
+var rename = require("gulp-rename");
+var handlebars = require('gulp-compile-handlebars');
+var envifyCustom = require('envify/custom');
+var revall = require('gulp-rev-all');
+var uglify = require('gulp-uglify');
+var gulpif = require('gulp-if');
+var streamify = require('gulp-streamify');
+
+var config = {
+ production: false,
+ port: '3000'
+};
+
+var paths = {
+ sass: './src/css/*.scss',
+ templates: './src/templates/**/*.html',
+ views: './src/views/**/*.hbs',
+ build: {
+ dest: './build/gulp/static',
+ tmp: './build/gulp/tmp'
+ },
+ vendor: {
+ stylesheets: [
+ './build/bower_components/bootstrap/dist/css/bootstrap.min.css',
+ './build/bower_components/font-awesome/css/font-awesome.min.css'
+ ],
+ fonts: [
+ './build/bower_components/bootstrap/dist/fonts/*',
+ './build/bower_components/font-awesome/fonts/*'
+ ]
+ }
+};
+
+var handleErrors = function() {
+ // Send error to notification center with gulp-notify
+ notify.onError({
+ title: "Compile Error",
+ message: "<%= error.message %>"
+ }).apply(this, arguments);
+
+ // Keep gulp from hanging on this task
+ this.emit('end');
+};
+
+gulp.task('browserify', ['compile-angular-templates'], function () {
+ var env = {
+ API_BASE: config.production ? '/api' : 'http://localhost:8080/api'
+ };
+
+ var bundleStream = browserify()
+ .add(es6ify.runtime)
+ .transform(es6ify.configure(/^(?!.*(node_modules|bower_components))+.+\.js$/))
+ .transform(envifyCustom(env))
+ .require(require.resolve('./src/js/main.js'), { entry: true })
+ .bundle({debug: !config.production});
+
+ return bundleStream
+ .on('error', handleErrors)
+ .pipe(source('bundle.js'))
+ .pipe(gulpif(config.production, streamify(uglify())))
+ .pipe(streamify(size({showFiles: true})))
+ .pipe(gulp.dest(path.join(paths.build.dest, 'js')));
+});
+
+gulp.task('serve', ['watch'], function() {
+
+ /** @type Object */
+ var app = express();
+
+ app.use(morgan('dev'));
+ app.use(express.static(paths.build.dest));
+
+ http.createServer(app).listen(config.port);
+
+ app.get(/^\/(post)(\/.*)?$/, function(req, res) {
+ res.sendfile(path.join(paths.build.dest, 'index.html'));
+ });
+
+ /** @type Object */
+ var lrServer = livereload();
+ gulp.watch(path.join(paths.build.dest, '**')).on('change', function(file) {
+ lrServer.changed(file.path);
+ });
+});
+
+gulp.task('open', ['serve'], function(cb) {
+
+ var options = {
+ url: "http://localhost:" + config.port,
+ app: "Google Chrome"
+ };
+
+ gulp.src(path.join(paths.build.dest, 'index.html')).pipe(gulpOpen("", options));
+ cb();
+});
+
+gulp.task('sass', function() {
+ var options = { };
+ if (config.production) {
+ options.outputStyle = 'compressed';
+ } else {
+ options.outputStyle = 'nested';
+ options.sourceComments = 'map';
+ }
+
+ return gulp.src(paths.sass)
+ .pipe(sass(options))
+ .pipe(size({showFiles: true}))
+ .pipe(gulp.dest(path.join(paths.build.dest, 'css')))
+ .on('error', handleErrors);
+});
+
+gulp.task('vendor-css', function() {
+ return gulp.src(paths.vendor.stylesheets)
+ .pipe(gulp.dest(path.join(paths.build.dest, 'css')))
+ .on('error', handleErrors);
+});
+
+gulp.task('fonts', function() {
+ return gulp.src(paths.vendor.fonts)
+ .pipe(gulp.dest(path.join(paths.build.dest, 'fonts')))
+ .on('error', handleErrors);
+});
+
+gulp.task('styles', ['sass', 'vendor-css', 'fonts']);
+
+gulp.task('watch', ['build'], function() {
+ gulp.watch(['./src/js/**/*.js', './build/gulp/tmp/templates.js'], ['browserify']);
+ gulp.watch(paths.sass, ['sass']);
+ gulp.watch(paths.templates, ['compile-angular-templates']);
+ gulp.watch(paths.views, ['compile-views']);
+});
+
+gulp.task('compile-views', function() {
+ return gulp.src(paths.views)
+ .pipe(handlebars({}))
+ .pipe(rename(function(path) {
+ path.extname = '.html';
+ }))
+ .pipe(gulp.dest(paths.build.dest));
+});
+
+gulp.task('compile-angular-templates', function () {
+ gulp.src(paths.templates)
+ .pipe(templateCache({
+ 'module': 'blogular.templates',
+ 'standalone': true,
+ 'root': '/templates/'
+ }))
+ .pipe(size({showFiles: true}))
+ .pipe(gulp.dest(paths.build.tmp));
+});
+
+gulp.task('templates', ['compile-views', 'compile-angular-templates']);
+
+gulp.task('clean', function () {
+ return gulp.src(['build/gulp'], { read: false }).pipe(clean());
+});
+
+gulp.task('build', ['browserify', 'styles', 'templates']);
+
+gulp.task('optimize', ['build'], function() {
+ gulp.src(path.join(paths.build.dest, '**'))
+ .pipe(revall({ ignoredExtensions: ['.html'] }))
+ .pipe(gulp.dest('./build/gulp/optimized'));
+});
+
+gulp.task('build-production', ['clean'], function() {
+ config.production = true;
+ gulp.start('optimize');
+});
+
+gulp.task('build-development', ['clean'], function() {
+ config.production = false;
+ gulp.start('build');
+});
+
+gulp.task('default', ['clean'], function () {
+ gulp.start('open');
+});
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..21a3bb2
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,75 @@
+{
+ "name": "blogular",
+ "description": "npm packages needed to build blogular",
+ "private": true,
+ "version": "0.0.0",
+ "dependencies": {
+ "angular": "1.2.16",
+ "jquery": "2.1.0",
+ "lodash": "2.4.1"
+ },
+ "devDependencies": {
+ "node-sass": "0.8.4",
+ "browserify-shim": "3.4.1",
+ "browserify": "3.36.0",
+ "connect": "2.14.3",
+ "gulp-changed": "0.3.0",
+ "gulp-compass": "1.1.8",
+ "gulp-imagemin": "0.1.5",
+ "gulp-livereload": "1.2.0",
+ "gulp-notify": "1.2.4",
+ "gulp-open": "0.2.8",
+ "gulp": "3.6.0",
+ "vinyl-source-stream": "0.1.1",
+ "gulp-sass": "0.7.1",
+ "gulp-angular-templatecache": "1.1.3",
+ "express": "4.1.1",
+ "morgan": "1.0.1",
+ "bower": "1.3.3",
+ "es6ify": "1.1.0",
+ "gulp-size": "0.3.1",
+ "http-proxy": "1.1.2",
+ "gulp-clean": "0.2.4",
+ "gulp-compile-handlebars": "0.2.0",
+ "gulp-rename": "1.2.0",
+ "envify": "1.2.1",
+ "gulp-rev-all": "0.1.5",
+ "gulp-uglify": "0.2.1",
+ "gulp-if": "1.1.0",
+ "gulp-streamify": "0.0.5"
+ },
+ "browserify": {
+ "transform": [
+ "browserify-shim"
+ ]
+ },
+ "browserify-shim": {
+ "angular": {
+ "exports": "angular",
+ "depends": [
+ "jquery:$"
+ ]
+ },
+ "sockjs": {
+ "exports": "SockJS"
+ },
+ "stomp": {
+ "exports": "Stomp"
+ }
+ },
+ "scripts": {
+ "install": "./node_modules/.bin/bower install"
+ },
+ "browser": {
+ "angular": "./build/bower_components/angular/angular.js",
+ "angular-route": "./build/bower_components/angular-route/angular-route.js",
+ "angular-resource": "./build/bower_components/angular-resource/angular-resource.js",
+ "angular-sanitize": "./build/bower_components/angular-sanitize/angular-sanitize.js",
+ "angular-i18n/angular-locale_fi": "./build/bower_components/angular-i18n/angular-locale_fi.js",
+ "angular-bootstrap/ui-bootstrap-tpls": "./build/bower_components/angular-bootstrap/ui-bootstrap-tpls.js",
+ "bootstrap": "./build/bower_components/bootstrap/dist/js/bootstrap.js",
+ "sockjs": "./build/bower_components/sockjs/sockjs.js",
+ "stomp": "./build/bower_components/stomp-websocket/lib/stomp.js",
+ "templates": "./build/gulp/tmp/templates.js"
+ }
+}
diff --git a/frontend/src/css/_blog.scss b/frontend/src/css/_blog.scss
new file mode 100644
index 0000000..08e0a8d
--- /dev/null
+++ b/frontend/src/css/_blog.scss
@@ -0,0 +1,163 @@
+/*
+ * Globals
+ */
+
+body {
+ font-family: Georgia, "Times New Roman", Times, serif;
+ color: #555;
+}
+
+h1, .h1,
+h2, .h2,
+h3, .h3,
+h4, .h4,
+h5, .h5,
+h6, .h6 {
+ margin-top: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: normal;
+ color: #333;
+}
+
+
+/*
+ * Override Bootstrap's default container.
+ */
+
+@media (min-width: 1200px) {
+ .container {
+ width: 970px;
+ }
+}
+
+
+/*
+ * Masthead for nav
+ */
+
+.blog-masthead {
+ background-color: #428bca;
+ box-shadow: inset 0 -2px 5px rgba(0,0,0,.1);
+}
+
+/* Nav links */
+.blog-nav-item {
+ position: relative;
+ display: inline-block;
+ padding: 10px;
+ font-weight: 500;
+ color: #cdddeb;
+}
+.blog-nav-item:hover,
+.blog-nav-item:focus {
+ color: #fff;
+ text-decoration: none;
+}
+
+/* Active state gets a caret at the bottom */
+.blog-nav .active {
+ color: #fff;
+}
+.blog-nav .active:after {
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ width: 0;
+ height: 0;
+ margin-left: -5px;
+ vertical-align: middle;
+ content: " ";
+ border-right: 5px solid transparent;
+ border-bottom: 5px solid;
+ border-left: 5px solid transparent;
+}
+
+
+/*
+ * Blog name and description
+ */
+
+.blog-header {
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+.blog-title {
+ margin-top: 30px;
+ margin-bottom: 0;
+ font-size: 60px;
+ font-weight: normal;
+}
+.blog-description {
+ font-size: 20px;
+ color: #999;
+}
+
+
+/*
+ * Main column and sidebar layout
+ */
+
+.blog-main {
+ font-size: 18px;
+ line-height: 1.5;
+}
+
+/* Sidebar modules for boxing content */
+.sidebar-module {
+ padding: 15px;
+ margin: 0 -15px 15px;
+}
+.sidebar-module-inset {
+ padding: 15px;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+}
+.sidebar-module-inset p:last-child,
+.sidebar-module-inset ul:last-child,
+.sidebar-module-inset ol:last-child {
+ margin-bottom: 0;
+}
+
+
+
+/* Pagination */
+.pager {
+ margin-bottom: 60px;
+ text-align: left;
+}
+.pager > li > a {
+ width: 140px;
+ padding: 10px 20px;
+ text-align: center;
+ border-radius: 30px;
+}
+
+
+/*
+ * Blog posts
+ */
+
+.blog-post {
+ margin-bottom: 60px;
+}
+.blog-post-title {
+ margin-bottom: 5px;
+ font-size: 40px;
+}
+.blog-post-meta {
+ margin-bottom: 20px;
+ color: #999;
+}
+
+
+/*
+ * Footer
+ */
+
+.blog-footer {
+ padding: 40px 0;
+ color: #999;
+ text-align: center;
+ background-color: #f9f9f9;
+ border-top: 1px solid #e5e5e5;
+}
diff --git a/frontend/src/css/_external.scss b/frontend/src/css/_external.scss
new file mode 100644
index 0000000..5d92e53
--- /dev/null
+++ b/frontend/src/css/_external.scss
@@ -0,0 +1,11 @@
+// Style rules to support external components
+
+// Cloaking for AngularJS
+[ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
+ display: none !important
+}
+
+// Proper pointer behaviour for Angular-UI
+.nav, .pagination, .carousel, .panel-title a {
+ cursor: pointer;
+}
diff --git a/frontend/src/css/default.scss b/frontend/src/css/default.scss
new file mode 100644
index 0000000..ef90597
--- /dev/null
+++ b/frontend/src/css/default.scss
@@ -0,0 +1,2 @@
+@import 'external';
+@import 'blog';
diff --git a/frontend/src/js/config.js b/frontend/src/js/config.js
new file mode 100644
index 0000000..33f43fa
--- /dev/null
+++ b/frontend/src/js/config.js
@@ -0,0 +1,10 @@
+"use strict";
+
+var process = require('process');
+
+// This will be replaced at build time by envify
+var apiBase = process.env.API_BASE;
+
+module.exports = {
+ apiBase: apiBase
+};
diff --git a/frontend/src/js/controllers/controllers.js b/frontend/src/js/controllers/controllers.js
new file mode 100644
index 0000000..a338258
--- /dev/null
+++ b/frontend/src/js/controllers/controllers.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = require('angular').module('blogular.controllers', []);
+
+require('./main-controller');
+require('./post-controller');
+require('./sidebar-controller');
diff --git a/frontend/src/js/controllers/main-controller.js b/frontend/src/js/controllers/main-controller.js
new file mode 100644
index 0000000..9c8d2f9
--- /dev/null
+++ b/frontend/src/js/controllers/main-controller.js
@@ -0,0 +1,26 @@
+"use strict";
+
+var controllers = require('angular').module('blogular.controllers');
+
+controllers.controller('MainController', ['$scope', ($scope) => {
+ $scope.posts = [
+ {
+ title: 'Sample blog post',
+ author: 'Mark',
+ body: '
This blog post shows a few different types of content that\'s supported and styled with Bootstrap. Basic typography, images, and code are all supported.
\n \n Cum sociis natoque penatibus et magnis dis parturient montes , nascetur ridiculus mus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
\n \n Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit.
\n \n Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.
\n Heading \n Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
\n Sub-heading \n Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
\n Example code block
\n Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa.
\n Sub-heading \n Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
\n \n Praesent commodo cursus magna, vel scelerisque nisl consectetur et. \n Donec id elit non mi porta gravida at eget metus. \n Nulla vitae elit libero, a pharetra augue. \n \n Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.
\n \n Vestibulum id ligula porta felis euismod semper. \n Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \n Maecenas sed diam eget risus varius blandit sit amet non magna. \n \n Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis.
',
+ date: new Date(2014, 0, 1)
+ },
+ {
+ title: 'Another blog post',
+ author: 'Jacob',
+ body: 'Cum sociis natoque penatibus et magnis dis parturient montes , nascetur ridiculus mus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
\n\n Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit.
\n \nEtiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.
\nVivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
\n',
+ date: new Date(2013, 11, 23)
+ },
+ {
+ title: 'New feature',
+ author: 'Chris',
+ body: 'Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
\n\n Praesent commodo cursus magna, vel scelerisque nisl consectetur et. \n Donec id elit non mi porta gravida at eget metus. \n Nulla vitae elit libero, a pharetra augue. \n \nEtiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.
\nDonec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.
\n',
+ date: new Date(2013, 11, 14)
+ }
+ ];
+}]);
diff --git a/frontend/src/js/controllers/post-controller.js b/frontend/src/js/controllers/post-controller.js
new file mode 100644
index 0000000..e95844c
--- /dev/null
+++ b/frontend/src/js/controllers/post-controller.js
@@ -0,0 +1,14 @@
+"use strict";
+
+var controllers = require('angular').module('blogular.controllers');
+
+controllers.controller('PostController', ['$scope', '$location', ($scope, $location) => {
+ $scope.newPost = {
+ title: '',
+ text: ''
+ };
+
+ $scope.savePost = () => {
+ $location.path('/');
+ }
+}]);
diff --git a/frontend/src/js/controllers/sidebar-controller.js b/frontend/src/js/controllers/sidebar-controller.js
new file mode 100644
index 0000000..2709597
--- /dev/null
+++ b/frontend/src/js/controllers/sidebar-controller.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var controllers = require('angular').module('blogular.controllers');
+
+controllers.controller('SidebarController', ['$scope', ($scope) => {
+ $scope.sidebar = {
+ about: 'Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.
',
+
+ archiveLinks: [
+ "2014-01-01",
+ "2013-12-01",
+ "2013-11-01",
+ "2013-10-01",
+ "2013-09-01",
+ "2013-08-01",
+ "2013-07-01",
+ "2013-06-01",
+ "2013-05-01",
+ "2013-04-01",
+ "2013-03-01",
+ "2013-02-01"
+ ],
+
+ externalLinks: [
+ {
+ title: 'Bitbucket',
+ href: 'https://bitbucket.org/evidentsolutions',
+ icon: 'twitter'
+ },
+ {
+ title: 'GitHub',
+ href: 'https://bitbucket.org/evidentsolutions',
+ icon: 'github'
+ },
+ {
+ title: 'Twitter',
+ href: 'https://twitter.com/EvidentSolution',
+ icon: 'twitter'
+ },
+ {
+ title: 'Facebook',
+ href: 'https://www.facebook.com/evidentsolutions',
+ icon: 'facebook'
+ }
+ ]
+ };
+}]);
diff --git a/frontend/src/js/directives/active-on-location.js b/frontend/src/js/directives/active-on-location.js
new file mode 100644
index 0000000..15925a3
--- /dev/null
+++ b/frontend/src/js/directives/active-on-location.js
@@ -0,0 +1,18 @@
+"use strict";
+
+var directives = require('angular').module('blogular.directives');
+
+/**
+ * A directive which toggles 'active' class of its containing element if current location
+ * matches the href of the element.
+ */
+directives.directive('activeOnLocation', ['$location', ($location) => {
+ return ($scope, $element) => {
+ function updateActiveState() {
+ $element.toggleClass("active", $location.path() == $element.attr('href'));
+ }
+
+ updateActiveState();
+ $scope.$on('$locationChangeSuccess', updateActiveState);
+ };
+}]);
diff --git a/frontend/src/js/directives/directives.js b/frontend/src/js/directives/directives.js
new file mode 100644
index 0000000..c8c658f
--- /dev/null
+++ b/frontend/src/js/directives/directives.js
@@ -0,0 +1,7 @@
+"use strict";
+
+var directives = require('angular').module('blogular.directives', []);
+
+require('./active-on-location');
+
+module.exports = directives;
diff --git a/frontend/src/js/filters/filters.js b/frontend/src/js/filters/filters.js
new file mode 100644
index 0000000..19929cc
--- /dev/null
+++ b/frontend/src/js/filters/filters.js
@@ -0,0 +1,5 @@
+"use strict";
+
+var filters = require('angular').module('blogular.filters', []);
+
+module.exports = filters;
diff --git a/frontend/src/js/main.js b/frontend/src/js/main.js
new file mode 100644
index 0000000..ef73d40
--- /dev/null
+++ b/frontend/src/js/main.js
@@ -0,0 +1,34 @@
+"use strict";
+
+window._ = require('lodash');
+window.jQuery = require('jquery');
+var angular = require('angular');
+
+require('angular-route');
+require('angular-sanitize');
+require('angular-i18n/angular-locale_fi');
+require('bootstrap');
+require('angular-bootstrap/ui-bootstrap-tpls');
+require('templates');
+
+var app = angular.module('blogular', [
+ 'ngRoute',
+ 'ngSanitize',
+ 'ui.bootstrap.modal',
+ 'ui.bootstrap.tabs',
+ 'ui.bootstrap.dropdownToggle',
+ 'ui.bootstrap.tpls',
+ 'blogular.templates',
+ require('./controllers/controllers').name,
+ require('./directives/directives').name,
+ require('./filters/filters').name,
+ require('./services/services').name
+]);
+
+app.config(['$locationProvider', '$routeProvider', ($locationProvider, $routeProvider) => {
+ $locationProvider.html5Mode(true);
+
+ require('./routes').initializeRoutes($routeProvider);
+}]);
+
+module.exports = app;
diff --git a/frontend/src/js/routes.js b/frontend/src/js/routes.js
new file mode 100644
index 0000000..5db4d6d
--- /dev/null
+++ b/frontend/src/js/routes.js
@@ -0,0 +1,7 @@
+"use strict";
+
+exports.initializeRoutes = ($routeProvider) => {
+ $routeProvider
+ .when('/', { controller: 'MainController', templateUrl: '/templates/views/main.html' })
+ .when('/post', { controller: 'PostController', templateUrl: '/templates/views/post.html' });
+};
diff --git a/frontend/src/js/services/services.js b/frontend/src/js/services/services.js
new file mode 100644
index 0000000..8898bb3
--- /dev/null
+++ b/frontend/src/js/services/services.js
@@ -0,0 +1,5 @@
+"use strict";
+
+var services = require('angular').module('visualisti.services', []);
+
+module.exports = services;
diff --git a/frontend/src/templates/nav/sidebar.html b/frontend/src/templates/nav/sidebar.html
new file mode 100644
index 0000000..512191e
--- /dev/null
+++ b/frontend/src/templates/nav/sidebar.html
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/frontend/src/templates/nav/topnav.html b/frontend/src/templates/nav/topnav.html
new file mode 100644
index 0000000..6c3db07
--- /dev/null
+++ b/frontend/src/templates/nav/topnav.html
@@ -0,0 +1,8 @@
+
diff --git a/frontend/src/templates/views/main.html b/frontend/src/templates/views/main.html
new file mode 100644
index 0000000..f09273d
--- /dev/null
+++ b/frontend/src/templates/views/main.html
@@ -0,0 +1,11 @@
+
+
{{post.title}}
+
{{post.date | date}} by {{post.author}}
+
+
+
+
+
diff --git a/frontend/src/templates/views/post.html b/frontend/src/templates/views/post.html
new file mode 100644
index 0000000..fd3fd55
--- /dev/null
+++ b/frontend/src/templates/views/post.html
@@ -0,0 +1,22 @@
+
diff --git a/frontend/src/views/index.hbs b/frontend/src/views/index.hbs
new file mode 100644
index 0000000..2c48821
--- /dev/null
+++ b/frontend/src/views/index.hbs
@@ -0,0 +1,31 @@
+
+
+
+ Blogular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index be356e7..38a1df9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,3 @@
rootProject.name = 'blogular'
-include ':web-api'
+include ':frontend', ':web-api'
diff --git a/web-api/build.gradle b/web-api/build.gradle
index 244b55d..fd45b8b 100644
--- a/web-api/build.gradle
+++ b/web-api/build.gradle
@@ -41,3 +41,8 @@ dependencies {
compile.exclude module: 'commons-logging'
}
}
+
+war {
+ from webAppDir
+ from project(':frontend').tasks.gulpBuild
+}