Implement disqus and config and page listing in menu

This commit is contained in:
Mikko Ahlroth 2014-11-16 00:28:45 +02:00
parent b8f0b373a1
commit 1d925ea17d
14 changed files with 227 additions and 95 deletions

View file

@ -37,7 +37,8 @@ var requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('src/app/require
'components/tag-page/tag-page',
'components/year-page/year-page',
'components/month-page/month-page',
'components/post-page/post-page'
'components/post-page/post-page',
'components/config-service/config-service'
],
insertRequire: ['app/startup'],
bundles: {
@ -91,13 +92,20 @@ gulp.task('html', function() {
.pipe(gulp.dest('./dev/'));
});
// Copies misc files
gulp.task('misc', function() {
return gulp.src('./src/config.js.dist')
.pipe(gulp.dest('./dist/'))
.pipe(gulp.dest('./dev/'));
});
// Removes all files from ./dist/
gulp.task('clean', function() {
return gulp.src('./dist/**/*', { read: false })
.pipe(clean());
});
gulp.task('default', ['html', 'js', 'css', 'fonts'], function(callback) {
gulp.task('default', ['html', 'js', 'css', 'fonts', 'misc'], function(callback) {
callback();
console.log('\nPlaced optimized files in ' + chalk.magenta('dist/')
+ ' and dev files in ' + chalk.magenta('dev/\n'));

View file

@ -31,6 +31,7 @@ define(["knockout", "crossroads", "hasher", "./routes"], function(ko, crossroads
function activateCrossroads() {
function parseHash(newHash, oldHash) { crossroads.parse(newHash); }
crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
hasher.prependHash = '!';
hasher.initialized.add(parseHash);
hasher.changed.add(parseHash);
hasher.init();

View file

@ -4,61 +4,62 @@ define([], function() {
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
}
// Home view
// This is above page view because page view will catch the rest
{
url: ':pageNumber:',
params: { page: 'home-page', mode: 'home' },
rules: {
pageNumber: PAGENUMBER_RE
}
];
},
// 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
}
}
];
});

View file

@ -30,6 +30,8 @@ define(['jquery', 'knockout', './router', 'marked', 'bootstrap', 'knockout-proje
ko.components.register('generic-route', { require: 'components/generic-route/generic-route' });
ko.components.register('config-service', { require: 'components/config-service/config-service' });
// [Scaffolded component registrations will be inserted here. To retain this feature, don't remove this comment.]
// Set Markdown parser options

View file

@ -1,7 +1,7 @@
define(['knockout', '../../app/routes', '../../app/router', 'hasher'],
function(ko, routes, router, hasher) {
function AddressService(params) {
function AddressService() {
var self = this;
self.getCurrentRoute = function() {
@ -34,7 +34,7 @@ define(['knockout', '../../app/routes', '../../app/router', 'hasher'],
};
self.prefixUrlTo = function(arguments, mode) {
return '/#/' + self.urlTo(arguments, mode);
return '/#!/' + self.urlTo(arguments, mode);
};
self.goTo = function(arguments, mode) {

View file

@ -0,0 +1,22 @@
define(['knockout', '../../app/routes', '../../app/router', 'hasher'],
function(ko, routes, router, hasher) {
function ConfigService() {
var self = this;
// Load configs from variables defined in config.js
self.blogName = CONF_BLOG_NAME;
self.authorName = CONF_AUTHOR_NAME;
self.useDisqus = CONF_USE_DISQUS;
self.disqusShortname = CONF_DISQUS_SHORTNAME;
self.pageCommenting = CONF_PAGE_COMMENTING;
self.postsPerPage = CONF_POSTS_PER_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.
ConfigService.prototype.dispose = function() {};
return new ConfigService();
});

View file

@ -1,27 +1,20 @@
<!--
The navigation UI that is docked to the top of the window. Most of this markup simply
follows Bootstrap conventions. The only Knockout-specific parts are the data-bind
attributes on the <li> elements.
-->
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Laine</a>
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#!">Laine</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav" data-bind="foreach: { data: pages, as: 'page' }">
<li data-bind="css: { active: $parent.route().slug === page.slug() }">
<a href="/" data-bind="attr: { href: $parent.AS.prefixUrlTo({ slug: page.slug() }, 'page') }, text: page.linkText()"></a>
</li>
</ul>
</div>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li data-bind="css: { active: route().page === 'home-page' }">
<a href="#">Home</a>
</li>
</ul>
</div>
</div>
</div>

View file

@ -1,12 +1,14 @@
define(['knockout', 'text!./nav-bar.html'], function(ko, template) {
define(['knockout', 'text!./nav-bar.html', '../db/db', '../address-service/address-service'],
function(ko, template, DB, addressService) {
function NavBarViewModel(params) {
var self = this;
// This viewmodel doesn't do anything except pass through the 'route' parameter to the view.
// You could remove this viewmodel entirely, and define 'nav-bar' as a template-only component.
// But in most apps, you'll want some viewmodel logic to determine what navigation options appear.
this.route = params.route;
self.AS = addressService;
self.route = params.route;
self.pages = ko.pureComputed(function() {
return DB.pages();
});
}
return { viewModel: NavBarViewModel, template: template };

View file

@ -1,12 +1,12 @@
define(['knockout', 'text!./pagination.html', '../address-service/address-service'],
function(ko, templateMarkup, addressService) {
define(['knockout', 'text!./pagination.html', '../address-service/address-service', '../config-service/config-service'],
function(ko, templateMarkup, addressService, configService) {
function Pagination(params) {
var self = this;
self.posts = params.posts;
self.page = params.page;
self.postsPerPage = 3;
self.postsPerPage = configService.postsPerPage;
self.pageCount = ko.pureComputed(function() {
return Math.ceil(self.posts().length / self.postsPerPage);

View file

@ -34,4 +34,8 @@
</a>
<!-- /ko -->
</div>
<!-- ko if: useDisqus -->
<div id="disqus_thread" class="post-comments-container"></div>
<!-- /ko -->
</div>

View file

@ -1,5 +1,39 @@
define(['knockout', 'text!./single-post.html', '../db/db', 'marked', '../address-service/address-service'],
function(ko, templateMarkup, DB, marked, addressService) {
define(['knockout', 'text!./single-post.html', '../db/db', 'marked', '../address-service/address-service', '../config-service/config-service', 'hasher'],
function(ko, templateMarkup, DB, marked, addressService, configService, hasher) {
var disqusSub = null;
function disqusIdentifier(post) {
return (post.isPage()? 'page-' : 'post-') + post.slug();
}
function insertDisqus(post) {
if (!_.isUndefined(window.DISQUS)) {
resetDisqus(post);
}
else {
var disqus_shortname = configService.disqusShortname;
var disqus_title = post.title();
var disqus_url = hasher.getURL();
var disqus_identifier = disqusIdentifier(post);
var dsq = document.createElement('script');
dsq.type = 'text/javascript';
dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
}
}
function resetDisqus(post) {
DISQUS.reset({
reload: true,
config: function() {
this.page.identifier = disqusIdentifier(post);
this.page.url = hasher.getURL();
}
});
}
function SinglePost(params) {
var self = this;
@ -38,11 +72,32 @@ define(['knockout', 'text!./single-post.html', '../db/db', 'marked', '../address
DB.loadPage(self.post.slug());
}
}
// Load disqus comments if they are in use on this page
self.useDisqus = !self.short && configService.useDisqus && (configService.pageComments || !self.post.isPage());
if (self.useDisqus) {
// Wait until the post title is available
if (!self.post.synced()) {
disqusSub = self.post.synced.subscribe(function (newVal) {
if (newVal === true) {
insertDisqus(self.post);
}
});
}
else {
insertDisqus(self.post);
}
}
}
// 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.
SinglePost.prototype.dispose = function() {};
SinglePost.prototype.dispose = function() {
if (!_.isNull(disqusSub)) {
disqusSub.dispose();
}
};
return { viewModel: SinglePost, template: templateMarkup };

20
src/config.js.dist Normal file
View file

@ -0,0 +1,20 @@
// Example configuration for Laine
// Don't edit the names of the variables or the configuration won't work.
// Blog name
CONF_BLOG_NAME = 'Laine';
// Author name
CONF_AUTHOR_NAME = 'Nicd';
// Set to false to disable Disqus comments
CONF_USE_DISQUS = true;
// Disqus shortname for your site
CONF_DISQUS_SHORTNAME = 'lainedemoblog';
// Set to true to show comments on pages in addition to posts
CONF_PAGE_COMMENTING = false;
// Posts shown per page
CONF_POSTS_PER_PAGE = 5;

View file

@ -1,3 +1,17 @@
#page {
margin-top: 80px;
}
.single-post {
margin: 20px;
padding: 20px;
}
.single-post>h2 {
margin-top: 40px;
}
.post-content-container {
padding-top: 20px;
border-top: 1px dashed #aaa;
}

View file

@ -3,11 +3,14 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Laine</title>
<title>Loading…</title>
<!-- build:css -->
<link href="bower_modules/components-bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="css/styles.css" rel="stylesheet">
<!-- endbuild -->
<script src="config.js"></script>
<!-- build:js -->
<script src="app/require.config.js"></script>
<script data-main="app/startup" src="bower_modules/requirejs/require.js"></script>
@ -18,6 +21,13 @@
<div id="page" class="container" data-bind="component: { name: route().page, params: route }">
<div class="loading">
<h1>Loading…</h1>
<noscript>
<p>
To read this blog, you need to turn JavaScript on in
your browser.
</p>
</noscript>
</div>
</div>
</body>