Implement everything
This commit is contained in:
parent
7fe251b7ed
commit
b8f0b373a1
21 changed files with 483 additions and 75 deletions
|
@ -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: {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
66
src/app/routes.js
Normal file
66
src/app/routes.js
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
67
src/components/address-service/address-service.js
Normal file
67
src/components/address-service/address-service.js
Normal file
|
@ -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();
|
||||
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
25
src/components/generic-route/generic-route.js
Normal file
25
src/components/generic-route/generic-route.js
Normal file
|
@ -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();
|
||||
|
||||
});
|
|
@ -1,3 +1 @@
|
|||
<h2>Page: <!-- ko text: page --><!-- /ko --></h2>
|
||||
|
||||
<pagination params="posts: posts, page: page"></pagination>
|
||||
|
|
|
@ -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 };
|
||||
|
||||
});
|
||||
|
|
7
src/components/month-page/month-page.html
Normal file
7
src/components/month-page/month-page.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<div class="alert alert-info">
|
||||
<p>
|
||||
You are viewing the archive for the month <span data-bind="text: month"></span>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<pagination params="posts: posts, page: page"></pagination>
|
30
src/components/month-page/month-page.js
Normal file
30
src/components/month-page/month-page.js
Normal file
|
@ -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 };
|
||||
|
||||
});
|
|
@ -1,20 +1,29 @@
|
|||
<!-- ko if: postsForPage().length === 0 -->
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
<strong>Oops!</strong>
|
||||
Looks like there's nothing here…
|
||||
</p>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<div class="post-list" data-bind="foreach: { data: postsForPage(), as: 'post' }">
|
||||
<single-post params="post: post, short: true"></single-post>
|
||||
</div>
|
||||
|
||||
<ul class="pagination" data-bind="if: pageCount() > 1">
|
||||
<li data-bind="css: { disabled: page() === 1 }">
|
||||
<a data-bind="click: goBack">
|
||||
<a data-bind="attr: { href: goBackUrl() }">
|
||||
«
|
||||
</a>
|
||||
</li>
|
||||
<!-- ko foreach: _.range(1, pageCount() + 1) -->
|
||||
<li data-bind="css: { active: $data === $parent.page() } ">
|
||||
<a data-bind="click: $parent.goTo, text: $data"></a>
|
||||
<a data-bind="attr: { href: $parent.goToUrl($data) }, text: $data"></a>
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
<li data-bind="css: { disabled: page() === pageCount() }">
|
||||
<a data-bind="click: goForward">
|
||||
<a data-bind="attr: { href: goForwardUrl() }">
|
||||
»
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
define(['knockout', 'text!./pagination.html'], function(ko, templateMarkup) {
|
||||
define(['knockout', 'text!./pagination.html', '../address-service/address-service'],
|
||||
function(ko, templateMarkup, addressService) {
|
||||
|
||||
function Pagination(params) {
|
||||
var self = this;
|
||||
|
@ -8,10 +9,10 @@ define(['knockout', 'text!./pagination.html'], function(ko, templateMarkup) {
|
|||
self.postsPerPage = 3;
|
||||
|
||||
self.pageCount = ko.pureComputed(function() {
|
||||
return Math.ceil(params.posts().length / self.postsPerPage);
|
||||
return Math.ceil(self.posts().length / self.postsPerPage);
|
||||
});
|
||||
|
||||
self.goTo = function(page) {
|
||||
self.sanitizePage = function(page) {
|
||||
if (page < 1) {
|
||||
page = 1;
|
||||
}
|
||||
|
@ -19,20 +20,25 @@ define(['knockout', 'text!./pagination.html'], function(ko, templateMarkup) {
|
|||
page = self.pageCount();
|
||||
}
|
||||
|
||||
self.page(page);
|
||||
return page;
|
||||
};
|
||||
|
||||
self.goBack = function() {
|
||||
self.goTo(self.page() - 1);
|
||||
self.goToUrl = function(page) {
|
||||
page = self.sanitizePage(page);
|
||||
return addressService.prefixUrlTo({pageNumber: page});
|
||||
};
|
||||
|
||||
self.goBackUrl = function() {
|
||||
return self.goToUrl(self.page() - 1);
|
||||
}
|
||||
|
||||
self.goForward = function() {
|
||||
self.goTo(self.page() + 1);
|
||||
self.goForwardUrl = function() {
|
||||
return self.goToUrl(self.page() + 1);
|
||||
}
|
||||
|
||||
self.postsForPage = ko.pureComputed(function() {
|
||||
var start = (self.page() - 1) * self.postsPerPage;
|
||||
return self.posts.slice(start, start + self.postsPerPage);
|
||||
return self.posts().slice(start, start + self.postsPerPage);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
12
src/components/post-page/post-page.html
Normal file
12
src/components/post-page/post-page.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!-- ko if: _.isNull(post()) -->
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
<strong>Oops!</strong>
|
||||
Looks like there's nothing here…
|
||||
</p>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: !_.isNull(post()) -->
|
||||
<single-post params="post: post(), short: false"></single-post>
|
||||
<!-- /ko -->
|
32
src/components/post-page/post-page.js
Normal file
32
src/components/post-page/post-page.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
define(['knockout', 'text!./post-page.html', '../generic-route/generic-route', '../db/db'],
|
||||
function(ko, templateMarkup, GR, DB) {
|
||||
|
||||
function PostPage(route) {
|
||||
var self = this;
|
||||
|
||||
self.post = ko.pureComputed(function() {
|
||||
if (route.mode === 'post') {
|
||||
var posts = DB.postDict();
|
||||
}
|
||||
else if (route.mode === 'page') {
|
||||
var posts = DB.pageDict();
|
||||
}
|
||||
|
||||
if (route.slug in posts) {
|
||||
return posts[route.slug]();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
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.
|
||||
PostPage.prototype.dispose = function() {};
|
||||
|
||||
return { viewModel: PostPage, template: templateMarkup };
|
||||
|
||||
});
|
|
@ -1,9 +1,37 @@
|
|||
<div class="single-post">
|
||||
<h2 data-bind="text: post.title()"></h2>
|
||||
<!-- ko if: !short -->
|
||||
<h2 data-bind="text: post.title()">Loading post contents…</h2>
|
||||
<!-- /ko -->
|
||||
|
||||
<p class="post-tags" data-bind="foreach: post.tags()">
|
||||
<span data-bind="text: $data"></span><!-- ko if: ($index() < post.tags().length - 1) -->,<!-- /ko -->
|
||||
</p>
|
||||
<!-- ko if: short -->
|
||||
<a data-bind="attr: { href: AS.postUrl(post) }">
|
||||
<h2 data-bind="text: post.title()">Loading post contents…</h2>
|
||||
</a>
|
||||
<!-- /ko -->
|
||||
|
||||
<div class="post-content" data-bind="html: content() !== null? marked(content()) : ''"></div>
|
||||
<!-- ko if: !post.isPage() -->
|
||||
<div class="post-info">
|
||||
Posted on
|
||||
|
||||
<span class="post-date">
|
||||
<a data-bind="attr: { href: AS.prefixUrlTo({pageNumber: 1, year: year}, 'year') }, text: year"></a>–<a data-bind="attr: { href: AS.prefixUrlTo({pageNumber: 1, year: year, month: month}, 'month') }, text: month"></a>
|
||||
</span>
|
||||
|
||||
with tags
|
||||
|
||||
<span class="post-tags" data-bind="foreach: post.tags()">
|
||||
<a data-bind="attr: { href: $parent.AS.prefixUrlTo({pageNumber: 1, slug: $data}, 'tag') }, text: $data"></a><!-- ko if: ($index() < $parent.post.tags().length - 1) -->,<!-- /ko --><!-- ko if: ($index() == $parent.post.tags().length - 1) -->.<!-- /ko -->
|
||||
</span>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<div class="post-content-container">
|
||||
<div class="post-content" data-bind="html: content() !== null? marked(content()) : ''"></div>
|
||||
|
||||
<!-- ko if: short && post.isSplit() -->
|
||||
<a data-bind="attr: { href: AS.postUrl(post) }">
|
||||
Read more…
|
||||
</a>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
define(['knockout', 'text!./single-post.html', '../db/db', 'marked'],
|
||||
function(ko, templateMarkup, DB, marked) {
|
||||
define(['knockout', 'text!./single-post.html', '../db/db', 'marked', '../address-service/address-service'],
|
||||
function(ko, templateMarkup, DB, marked, addressService) {
|
||||
|
||||
function SinglePost(params) {
|
||||
var self = this;
|
||||
self.post = params.post;
|
||||
|
||||
// For some reason post-page gives params.post as an observable, not an
|
||||
// object
|
||||
if (_.isUndefined(params.post.date)) {
|
||||
self.post = params.post();
|
||||
}
|
||||
else {
|
||||
self.post = params.post;
|
||||
}
|
||||
|
||||
if (!self.post.isPage()) {
|
||||
self.year = self.post.date().format('YYYY');
|
||||
self.month = self.post.date().format('MM');
|
||||
}
|
||||
|
||||
self.marked = marked;
|
||||
self.AS = addressService;
|
||||
|
||||
// Is this post shown as short (in pagination) or not?
|
||||
self.short = params.short;
|
||||
|
@ -16,10 +31,12 @@ define(['knockout', 'text!./single-post.html', '../db/db', 'marked'],
|
|||
|
||||
// If this post is not fully fetched into the index yet, do that now
|
||||
if (!self.post.synced()) {
|
||||
DB.loadPost(self.post.slug());
|
||||
|
||||
// Show loading indicator before loading is done
|
||||
self.post.title("Loading post contents…");
|
||||
if (!self.post.isPage()) {
|
||||
DB.loadPost(self.post.slug());
|
||||
}
|
||||
else {
|
||||
DB.loadPage(self.post.slug());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
7
src/components/tag-page/tag-page.html
Normal file
7
src/components/tag-page/tag-page.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<div class="alert alert-info">
|
||||
<p>
|
||||
You are viewing the archive for the tag <span data-bind="text: tag"></span>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<pagination params="posts: posts, page: page"></pagination>
|
30
src/components/tag-page/tag-page.js
Normal file
30
src/components/tag-page/tag-page.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
define(['knockout', 'text!./tag-page.html', '../generic-route/generic-route', '../db/db'],
|
||||
function(ko, templateMarkup, GR, DB) {
|
||||
|
||||
function TagPage(route) {
|
||||
var self = this;
|
||||
|
||||
self.tag = route.slug;
|
||||
self.page = ko.observable(1);
|
||||
self.posts = ko.pureComputed(function() {
|
||||
var tags = DB.tags();
|
||||
|
||||
if (route.slug in tags) {
|
||||
return tags[route.slug]();
|
||||
}
|
||||
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.
|
||||
TagPage.prototype.dispose = function() {};
|
||||
|
||||
return { viewModel: TagPage, template: templateMarkup };
|
||||
|
||||
});
|
7
src/components/year-page/year-page.html
Normal file
7
src/components/year-page/year-page.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<div class="alert alert-info">
|
||||
<p>
|
||||
You are viewing the archive for the year <span data-bind="text: year"></span>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<pagination params="posts: posts, page: page"></pagination>
|
30
src/components/year-page/year-page.js
Normal file
30
src/components/year-page/year-page.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
define(['knockout', 'text!./year-page.html', '../generic-route/generic-route', '../db/db'],
|
||||
function(ko, templateMarkup, GR, DB) {
|
||||
|
||||
function YearPage(route) {
|
||||
var self = this;
|
||||
|
||||
self.year = route.year;
|
||||
self.page = ko.observable(1);
|
||||
self.posts = ko.pureComputed(function() {
|
||||
var years = DB.years();
|
||||
|
||||
if (route.year in years) {
|
||||
return years[route.year]();
|
||||
}
|
||||
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.
|
||||
YearPage.prototype.dispose = function() {};
|
||||
|
||||
return { viewModel: YearPage, template: templateMarkup };
|
||||
|
||||
});
|
Reference in a new issue