From b8f0b373a1426b49c05470d2b775c83b4ab07c87 Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Sat, 15 Nov 2014 18:47:23 +0200 Subject: [PATCH] Implement everything --- gulpfile.js | 8 ++- src/app/router.js | 63 ++++++++--------- src/app/routes.js | 66 ++++++++++++++++++ src/app/startup.js | 12 ++++ .../address-service/address-service.js | 67 +++++++++++++++++++ src/components/db/db.js | 33 ++++++--- src/components/generic-route/generic-route.js | 25 +++++++ src/components/home-page/home.html | 2 - src/components/home-page/home.js | 19 ++++-- src/components/month-page/month-page.html | 7 ++ src/components/month-page/month-page.js | 30 +++++++++ src/components/pagination/pagination.html | 15 ++++- src/components/pagination/pagination.js | 24 ++++--- src/components/post-page/post-page.html | 12 ++++ src/components/post-page/post-page.js | 32 +++++++++ src/components/single-post/single-post.html | 38 +++++++++-- src/components/single-post/single-post.js | 31 +++++++-- src/components/tag-page/tag-page.html | 7 ++ src/components/tag-page/tag-page.js | 30 +++++++++ src/components/year-page/year-page.html | 7 ++ src/components/year-page/year-page.js | 30 +++++++++ 21 files changed, 483 insertions(+), 75 deletions(-) create mode 100644 src/app/routes.js create mode 100644 src/components/address-service/address-service.js create mode 100644 src/components/generic-route/generic-route.js create mode 100644 src/components/month-page/month-page.html create mode 100644 src/components/month-page/month-page.js create mode 100644 src/components/post-page/post-page.html create mode 100644 src/components/post-page/post-page.js create mode 100644 src/components/tag-page/tag-page.html create mode 100644 src/components/tag-page/tag-page.js create mode 100644 src/components/year-page/year-page.html create mode 100644 src/components/year-page/year-page.js diff --git a/gulpfile.js b/gulpfile.js index b39cdd5..5b40cf2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -26,12 +26,18 @@ var requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('src/app/require 'requireLib', 'lodashLib', 'momentLib', + 'components/address-service/address-service', 'components/nav-bar/nav-bar', 'components/home-page/home', 'components/db/db', + 'components/generic-route/generic-route', 'components/transfer/transfer', 'components/single-post/single-post', - 'components/pagination/pagination' + 'components/pagination/pagination', + 'components/tag-page/tag-page', + 'components/year-page/year-page', + 'components/month-page/month-page', + 'components/post-page/post-page' ], insertRequire: ['app/startup'], bundles: { diff --git a/src/app/router.js b/src/app/router.js index b0ff3f5..461a971 100644 --- a/src/app/router.js +++ b/src/app/router.js @@ -1,37 +1,38 @@ -define(["knockout", "crossroads", "hasher"], function(ko, crossroads, hasher) { +define(["knockout", "crossroads", "hasher", "./routes"], function(ko, crossroads, hasher, routes) { - // This module configures crossroads.js, a routing library. If you prefer, you - // can use any other routing library (or none at all) as Knockout is designed to - // compose cleanly with external libraries. - // - // You *don't* have to follow the pattern established here (each route entry - // specifies a 'page', which is a Knockout component) - there's nothing built into - // Knockout that requires or even knows about this technique. It's just one of - // many possible ways of setting up client-side routes. + // This module configures crossroads.js, a routing library. If you prefer, you + // can use any other routing library (or none at all) as Knockout is designed to + // compose cleanly with external libraries. + // + // You *don't* have to follow the pattern established here (each route entry + // specifies a 'page', which is a Knockout component) - there's nothing built into + // Knockout that requires or even knows about this technique. It's just one of + // many possible ways of setting up client-side routes. + return new Router({ + routes: routes + }); - return new Router({ - routes: [ - { url: '', params: { page: 'home-page' } } - ] + function Router(config) { + var currentRoute = this.currentRoute = ko.observable({}); + + ko.utils.arrayForEach(config.routes, function(route) { + var addedRoute = crossroads.addRoute(route.url, function(requestParams) { + currentRoute(ko.utils.extend(requestParams, route.params)); + }); + + if (!_.isUndefined(route.rules)) { + addedRoute.rules = route.rules; + } }); - function Router(config) { - var currentRoute = this.currentRoute = ko.observable({}); + activateCrossroads(); + } - ko.utils.arrayForEach(config.routes, function(route) { - crossroads.addRoute(route.url, function(requestParams) { - currentRoute(ko.utils.extend(requestParams, route.params)); - }); - }); - - activateCrossroads(); - } - - function activateCrossroads() { - function parseHash(newHash, oldHash) { crossroads.parse(newHash); } - crossroads.normalizeFn = crossroads.NORM_AS_OBJECT; - hasher.initialized.add(parseHash); - hasher.changed.add(parseHash); - hasher.init(); - } + function activateCrossroads() { + function parseHash(newHash, oldHash) { crossroads.parse(newHash); } + crossroads.normalizeFn = crossroads.NORM_AS_OBJECT; + hasher.initialized.add(parseHash); + hasher.changed.add(parseHash); + hasher.init(); + } }); \ No newline at end of file diff --git a/src/app/routes.js b/src/app/routes.js new file mode 100644 index 0000000..f15e9a9 --- /dev/null +++ b/src/app/routes.js @@ -0,0 +1,66 @@ +define([], function() { + var YEAR_RE = /^\d{4}$/; + var MONTH_RE = /^\d{2}$/; + var PAGENUMBER_RE = /^\d*$/; + + return [ + // Page view + { + url: '{slug}', + params: { page: 'post-page', mode: 'page' } + }, + + // Single post view + { + url: '{year}/{month}/{day}/{slug}', + params: { page: 'post-page', mode: 'post' }, + rules: { + year: YEAR_RE, + month: MONTH_RE + } + }, + + // Year archive + { + url: 'archives/{year}/:pageNumber:', + params: { page: 'year-page', mode: 'year' }, + rules: { + year: YEAR_RE, + pageNumber: PAGENUMBER_RE + } + }, + + // Month archive + { + url: 'archives/{year}/{month}/:pageNumber:', + params: { page: 'month-page', mode: 'month' }, + rules: { + year: YEAR_RE, + month: MONTH_RE, + pageNumber: PAGENUMBER_RE + } + }, + + // Tag archive + { + url: 'tag/{slug}/:pageNumber:', + params: { page: 'tag-page', mode: 'tag' }, + rules: { + pageNumber: PAGENUMBER_RE + } + }, + + // Home view + { + url: ':pageNumber:', + params: { page: 'home-page', mode: 'home' }, + rules: { + pageNumber: PAGENUMBER_RE + } + } + ]; +}); + + + + diff --git a/src/app/startup.js b/src/app/startup.js index 528cb95..a0de4b3 100644 --- a/src/app/startup.js +++ b/src/app/startup.js @@ -18,6 +18,18 @@ define(['jquery', 'knockout', './router', 'marked', 'bootstrap', 'knockout-proje ko.components.register('pagination', { require: 'components/pagination/pagination' }); + ko.components.register('address-service', { require: 'components/address-service/address-service' }); + + ko.components.register('tag-page', { require: 'components/tag-page/tag-page' }); + + ko.components.register('year-page', { require: 'components/year-page/year-page' }); + + ko.components.register('month-page', { require: 'components/month-page/month-page' }); + + ko.components.register('post-page', { require: 'components/post-page/post-page' }); + + ko.components.register('generic-route', { require: 'components/generic-route/generic-route' }); + // [Scaffolded component registrations will be inserted here. To retain this feature, don't remove this comment.] // Set Markdown parser options diff --git a/src/components/address-service/address-service.js b/src/components/address-service/address-service.js new file mode 100644 index 0000000..5efbee3 --- /dev/null +++ b/src/components/address-service/address-service.js @@ -0,0 +1,67 @@ +define(['knockout', '../../app/routes', '../../app/router', 'hasher'], + function(ko, routes, router, hasher) { + + function AddressService(params) { + var self = this; + + self.getCurrentRoute = function() { + return router.currentRoute(); + }; + + self.urlTo = function(arguments, mode) { + if (_.isUndefined(mode)) { + mode = self.getCurrentRoute().mode; + + // If we are moving to the same mode, use the old arguments as a base to + // extend with new arguments + arguments = _.assign(_.clone(self.getCurrentRoute()), arguments); + } + + var route = _.find(routes, function(route) { + return route.params.mode === mode; + }); + + if (_.isUndefined(route)) { + throw new Error('urlTo given non-existing mode!'); + } + + var url = route.url + _.forEach(arguments, function(value, key) { + url = url.replace('{' + key + '}', value); + url = url.replace(':' + key + ':', value); + }); + return url; + }; + + self.prefixUrlTo = function(arguments, mode) { + return '/#/' + self.urlTo(arguments, mode); + }; + + self.goTo = function(arguments, mode) { + var url = self.urlTo(arguments, mode); + hasher.setHash(url); + }; + + self.postUrl = function(post) { + return self.prefixUrlTo({ + year: post.date().format('YYYY'), + month: post.date().format('MM'), + day: post.date().format('DD'), + slug: post.slug() + }, 'post'); + }; + + self.pageUrl = function(page) { + return self.prefixUrlTo({ + slug: page.slug() + }, 'page'); + } + } + + // This runs when the component is torn down. Put here any logic necessary to clean up, + // for example cancelling setTimeouts or disposing Knockout subscriptions/computeds. + AddressService.prototype.dispose = function() {}; + + return new AddressService(); + +}); diff --git a/src/components/db/db.js b/src/components/db/db.js index 8758b5e..0968d0a 100644 --- a/src/components/db/db.js +++ b/src/components/db/db.js @@ -3,6 +3,9 @@ define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, mo function DB(params) { var self = this; + // Has the index been loaded yet? + self.synced = ko.observable(false); + // List of all posts self.posts = ko.observableArray(); // List of all pages @@ -35,13 +38,14 @@ define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, mo _.forEach(self.posts(), function(post) { _.forEach(post().tags(), function(tag) { if (!(tag in ret)) { - ret[tag] = ko.observableArray(post); + ret[tag] = ko.observableArray([post]); } else { ret[tag].push(post); } }); }); + return ret; }); @@ -52,12 +56,13 @@ define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, mo var key = post().date().format('YYYY'); if (!(key in ret)) { - ret[key] = ko.observableArray(post); + ret[key] = ko.observableArray([post]); } else { ret[key].push(post); } }); + return ret; }); // Dict of months ('YYYY-MM' as key) and the posts for those months @@ -67,12 +72,13 @@ define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, mo var key = post().date().format('YYYY-MM'); if (!(key in ret)) { - ret[key] = ko.observableArray(post); + ret[key] = ko.observableArray([post]); } else { ret[key].push(post); } }); + return ret; }); /** @@ -88,7 +94,7 @@ define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, mo }; /** - * Parse data of a single post from a regex match, returning and observable + * Parse data of a single post from a regex match, returning an observable */ self.parsePostData = function(matched) { var tags = {}; @@ -113,24 +119,28 @@ define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, mo title: ko.observable(null), content: ko.observable(null), isSplit: ko.observable(null), - shortContent: ko.observable(null) + shortContent: ko.observable(null), + isPage: ko.observable(false) }); return post; }; /** - * Parse data of a single page from a regex match, returning and observable + * Parse data of a single page from a regex match, returning an observable */ self.parsePageData = function(matched) { var slug = matched[1]; var page = ko.observable({ - slug: slug, - linkText: matched[2], + slug: ko.observable(slug), + linkText: ko.observable(matched[2]), - synced: false, - title: null, - isSplit: false + synced: ko.observable(false), + title: ko.observable(null), + content: ko.observable(null), + shortContent: ko.observable(null), + isSplit: ko.observable(false), + isPage: ko.observable(true) }); return page; }; @@ -191,6 +201,7 @@ define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, mo self.loadIndex = function() { transfer.loadIndex().success(function(data) { self.parseIndex(self.posts, self.pages, data); + self.synced(true); }); }; diff --git a/src/components/generic-route/generic-route.js b/src/components/generic-route/generic-route.js new file mode 100644 index 0000000..c24556a --- /dev/null +++ b/src/components/generic-route/generic-route.js @@ -0,0 +1,25 @@ +define(['knockout', '../db/db'], function(ko, DB) { + + function GenericRoute() { + var self = this; + + self.sync = function() { + if (!DB.synced()) { + DB.loadIndex(); + } + }; + + self.initPage = function(route, page) { + if (!_.isUndefined(route.pageNumber)) { + page(parseInt(route.pageNumber, 10)); + } + } + } + + // This runs when the component is torn down. Put here any logic necessary to clean up, + // for example cancelling setTimeouts or disposing Knockout subscriptions/computeds. + GenericRoute.prototype.dispose = function() {}; + + return new GenericRoute(); + +}); diff --git a/src/components/home-page/home.html b/src/components/home-page/home.html index 204f2e8..3bffce5 100644 --- a/src/components/home-page/home.html +++ b/src/components/home-page/home.html @@ -1,3 +1 @@ -

Page:

- diff --git a/src/components/home-page/home.js b/src/components/home-page/home.js index f633fe8..95b078b 100644 --- a/src/components/home-page/home.js +++ b/src/components/home-page/home.js @@ -1,13 +1,20 @@ -define(["knockout", "text!./home.html", "../db/db"], function(ko, homeTemplate, DB) { +define(['knockout', 'text!./home.html', '../generic-route/generic-route', '../db/db'], + function(ko, templateMarkup, GR, DB) { - function HomeViewModel(route) { + function HomePage(route) { var self = this; - self.posts = DB.posts; - self.page = ko.observable(1); - DB.loadIndex(); + self.page = ko.observable(1); + self.posts = DB.posts; + + GR.initPage(route, self.page); + GR.sync(); } - return { viewModel: HomeViewModel, template: homeTemplate }; + // This runs when the component is torn down. Put here any logic necessary to clean up, + // for example cancelling setTimeouts or disposing Knockout subscriptions/computeds. + HomePage.prototype.dispose = function() {}; + + return { viewModel: HomePage, template: templateMarkup }; }); diff --git a/src/components/month-page/month-page.html b/src/components/month-page/month-page.html new file mode 100644 index 0000000..803da34 --- /dev/null +++ b/src/components/month-page/month-page.html @@ -0,0 +1,7 @@ +
+

+ You are viewing the archive for the month . +

+
+ + diff --git a/src/components/month-page/month-page.js b/src/components/month-page/month-page.js new file mode 100644 index 0000000..afdbf3c --- /dev/null +++ b/src/components/month-page/month-page.js @@ -0,0 +1,30 @@ +define(['knockout', 'text!./month-page.html', '../generic-route/generic-route', '../db/db'], + function(ko, templateMarkup, GR, DB) { + + function MonthPage(route) { + var self = this; + + self.month = route.year + '-' + route.month; + self.page = ko.observable(1); + self.posts = ko.pureComputed(function() { + var months = DB.months(); + + if (self.month in months) { + return months[self.month](); + } + else { + return []; + } + }); + + GR.initPage(route, self.page); + GR.sync(); + } + + // This runs when the component is torn down. Put here any logic necessary to clean up, + // for example cancelling setTimeouts or disposing Knockout subscriptions/computeds. + MonthPage.prototype.dispose = function() {}; + + return { viewModel: MonthPage, template: templateMarkup }; + +}); diff --git a/src/components/pagination/pagination.html b/src/components/pagination/pagination.html index 725c7d7..5632069 100644 --- a/src/components/pagination/pagination.html +++ b/src/components/pagination/pagination.html @@ -1,20 +1,29 @@ + +
+

+ Oops! + Looks like there's nothing here… +

+
+ +