Initial commit
This commit is contained in:
commit
44c090b5fd
20 changed files with 663 additions and 0 deletions
3
.bowerrc
Normal file
3
.bowerrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"directory": "src/bower_modules"
|
||||
}
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
bower_modules/
|
||||
|
||||
# Don't track build output
|
||||
dist/
|
||||
dev/
|
17
bower.json
Normal file
17
bower.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "laine",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"components-bootstrap": "~3.1.1",
|
||||
"crossroads": "~0.12.0",
|
||||
"hasher": "~1.2.0",
|
||||
"requirejs": "~2.1.11",
|
||||
"requirejs-text": "~2.0.10",
|
||||
"knockout": "~3.2.0",
|
||||
"knockout-projections": "~1.1.0-pre",
|
||||
"lodash": "~2.4.1",
|
||||
"moment": "~2.8.3",
|
||||
"marked": "~0.3.2"
|
||||
}
|
||||
}
|
98
gulpfile.js
Normal file
98
gulpfile.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Node modules
|
||||
var fs = require('fs'), vm = require('vm'), merge = require('deeply'), chalk = require('chalk'), es = require('event-stream');
|
||||
|
||||
// Gulp and plugins
|
||||
var gulp = require('gulp'), rjs = require('gulp-requirejs-bundler'), concat = require('gulp-concat'), clean = require('gulp-clean'),
|
||||
replace = require('gulp-replace'), uglify = require('gulp-uglify'), htmlreplace = require('gulp-html-replace');
|
||||
|
||||
// Gulp minify for smallinizing our CSS
|
||||
var minify = require('gulp-minify-css');
|
||||
|
||||
// Gulp filesize for printing sizes before and after minification
|
||||
var size = require('gulp-size');
|
||||
|
||||
// Config
|
||||
var requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('src/app/require.config.js') + '; require;');
|
||||
requireJsOptimizerConfig = merge(requireJsRuntimeConfig, {
|
||||
out: 'scripts.js',
|
||||
baseUrl: './src',
|
||||
name: 'app/startup',
|
||||
paths: {
|
||||
requireLib: 'bower_modules/requirejs/require',
|
||||
lodashLib: 'bower_modules/lodash/dist/lodash',
|
||||
momentLib: 'bower_modules/moment/moment'
|
||||
},
|
||||
include: [
|
||||
'requireLib',
|
||||
'lodashLib',
|
||||
'momentLib',
|
||||
'components/nav-bar/nav-bar',
|
||||
'components/home-page/home',
|
||||
'components/db/db',
|
||||
'components/transfer/transfer',
|
||||
'components/single-post/single-post',
|
||||
'components/pagination/pagination'
|
||||
],
|
||||
insertRequire: ['app/startup'],
|
||||
bundles: {
|
||||
// If you want parts of the site to load on demand, remove them from the 'include' list
|
||||
// above, and group them into bundles here.
|
||||
// 'bundle-name': [ 'some/module', 'another/module' ],
|
||||
// 'another-bundle-name': [ 'yet-another-module' ]
|
||||
}
|
||||
});
|
||||
|
||||
// Discovers all AMD dependencies, concatenates together all required .js files, minifies dist files
|
||||
gulp.task('js', function() {
|
||||
return rjs(requireJsOptimizerConfig)
|
||||
.pipe(size({title: 'Original JS'}))
|
||||
.pipe(gulp.dest('./dev/'))
|
||||
.pipe(uglify({ preserveComments: false }))
|
||||
.pipe(size({title: 'Minified JS'}))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
// Concatenates CSS files, rewrites relative paths to Bootstrap fonts, copies Bootstrap fonts
|
||||
gulp.task('css', function() {
|
||||
var bowerCss = gulp.src('src/bower_modules/components-bootstrap/css/bootstrap.min.css')
|
||||
.pipe(replace(/url\((')?\.\.\/fonts\//g, 'url($1fonts/')),
|
||||
appCss = gulp.src('src/css/*.css');
|
||||
|
||||
return es.concat(bowerCss, appCss)
|
||||
.pipe(concat('css.css'))
|
||||
.pipe(size({title: 'Original CSS'}))
|
||||
.pipe(gulp.dest('./dev/'))
|
||||
.pipe(minify())
|
||||
.pipe(size({title: 'Minified CSS'}))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
// Copies fonts
|
||||
gulp.task('fonts', function() {
|
||||
return gulp.src('./src/bower_modules/components-bootstrap/fonts/*', { base: './src/bower_modules/components-bootstrap/' })
|
||||
.pipe(gulp.dest('./dev/'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
// Copies index.html, replacing <script> and <link> tags to reference production URLs
|
||||
gulp.task('html', function() {
|
||||
return gulp.src('./src/index.html')
|
||||
.pipe(htmlreplace({
|
||||
'css': 'css.css',
|
||||
'js': 'scripts.js'
|
||||
}))
|
||||
.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) {
|
||||
callback();
|
||||
console.log('\nPlaced optimized files in ' + chalk.magenta('dist/')
|
||||
+ ' and dev files in ' + chalk.magenta('dev/\n'));
|
||||
});
|
18
package.json
Normal file
18
package.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "laine",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"chalk": "~0.4.0",
|
||||
"deeply": "~0.1.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"gulp": "^3.8.9",
|
||||
"gulp-clean": "~0.2.4",
|
||||
"gulp-concat": "~2.2.0",
|
||||
"gulp-html-replace": "~1.0.0",
|
||||
"gulp-replace": "~0.2.0",
|
||||
"gulp-requirejs-bundler": "^0.1.1",
|
||||
"gulp-uglify": "~0.2.1",
|
||||
"gulp-size": "~1.1.0",
|
||||
"gulp-minify-css": "~0.3.11"
|
||||
}
|
||||
}
|
18
src/app/require.config.js
Normal file
18
src/app/require.config.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
// require.js looks for the following global when initializing
|
||||
var require = {
|
||||
baseUrl: ".",
|
||||
paths: {
|
||||
"bootstrap": "bower_modules/components-bootstrap/js/bootstrap.min",
|
||||
"crossroads": "bower_modules/crossroads/dist/crossroads.min",
|
||||
"hasher": "bower_modules/hasher/dist/js/hasher.min",
|
||||
"jquery": "bower_modules/jquery/dist/jquery",
|
||||
"knockout": "bower_modules/knockout/dist/knockout",
|
||||
"knockout-projections": "bower_modules/knockout-projections/dist/knockout-projections",
|
||||
"signals": "bower_modules/js-signals/dist/signals.min",
|
||||
"text": "bower_modules/requirejs-text/text",
|
||||
"marked": "bower_modules/marked/lib/marked"
|
||||
},
|
||||
shim: {
|
||||
"bootstrap": { deps: ["jquery"] }
|
||||
}
|
||||
};
|
37
src/app/router.js
Normal file
37
src/app/router.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
define(["knockout", "crossroads", "hasher"], function(ko, crossroads, hasher) {
|
||||
|
||||
// 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: [
|
||||
{ url: '', params: { page: 'home-page' } }
|
||||
]
|
||||
});
|
||||
|
||||
function Router(config) {
|
||||
var currentRoute = this.currentRoute = ko.observable({});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
30
src/app/startup.js
Normal file
30
src/app/startup.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
define(['jquery', 'knockout', './router', 'marked', 'bootstrap', 'knockout-projections'],
|
||||
function($, ko, router, marked) {
|
||||
|
||||
// Components can be packaged as AMD modules, such as the following:
|
||||
ko.components.register('nav-bar', { require: 'components/nav-bar/nav-bar' });
|
||||
ko.components.register('home-page', { require: 'components/home-page/home' });
|
||||
|
||||
// ... or for template-only components, you can just point to a .html file directly:
|
||||
ko.components.register('about-page', {
|
||||
template: { require: 'text!components/about-page/about.html' }
|
||||
});
|
||||
|
||||
ko.components.register('db', { require: 'components/db/db' });
|
||||
|
||||
ko.components.register('transfer', { require: 'components/transfer/transfer' });
|
||||
|
||||
ko.components.register('single-post', { require: 'components/single-post/single-post' });
|
||||
|
||||
ko.components.register('pagination', { require: 'components/pagination/pagination' });
|
||||
|
||||
// [Scaffolded component registrations will be inserted here. To retain this feature, don't remove this comment.]
|
||||
|
||||
// Set Markdown parser options
|
||||
marked.setOptions({
|
||||
smartypants: true
|
||||
});
|
||||
|
||||
// Start the application
|
||||
ko.applyBindings({ route: router.currentRoute });
|
||||
});
|
223
src/components/db/db.js
Normal file
223
src/components/db/db.js
Normal file
|
@ -0,0 +1,223 @@
|
|||
define(['knockout', '../transfer/transfer', 'moment'], function(ko, transfer, moment) {
|
||||
|
||||
function DB(params) {
|
||||
var self = this;
|
||||
|
||||
// List of all posts
|
||||
self.posts = ko.observableArray();
|
||||
// List of all pages
|
||||
self.pages = ko.observableArray();
|
||||
|
||||
// Below are a few pure computeds for convenience, they should be autobuilt
|
||||
// when the posts / pages are loaded
|
||||
|
||||
// All posts in a dict (slug as key) for faster retrieval of single post
|
||||
self.postDict = ko.pureComputed(function() {
|
||||
var ret = {};
|
||||
_.forEach(self.posts(), function(post) {
|
||||
ret[post().slug()] = post;
|
||||
});
|
||||
return ret;
|
||||
});
|
||||
|
||||
// Dict of pages (slug as key)
|
||||
self.pageDict = ko.pureComputed(function() {
|
||||
var ret = {};
|
||||
_.forEach(self.pages(), function(page) {
|
||||
ret[page().slug()] = page;
|
||||
});
|
||||
return ret;
|
||||
});
|
||||
|
||||
// Dict of tags (tag name as key) and the posts they have
|
||||
self.tags = ko.pureComputed(function() {
|
||||
var ret = {};
|
||||
_.forEach(self.posts(), function(post) {
|
||||
_.forEach(post().tags(), function(tag) {
|
||||
if (!(tag in ret)) {
|
||||
ret[tag] = ko.observableArray(post);
|
||||
}
|
||||
else {
|
||||
ret[tag].push(post);
|
||||
}
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
});
|
||||
|
||||
// Dict of years (year number as key) and the posts for those years
|
||||
self.years = ko.pureComputed(function() {
|
||||
var ret = {};
|
||||
_.forEach(self.posts(), function(post) {
|
||||
var key = post().date().format('YYYY');
|
||||
|
||||
if (!(key in ret)) {
|
||||
ret[key] = ko.observableArray(post);
|
||||
}
|
||||
else {
|
||||
ret[key].push(post);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Dict of months ('YYYY-MM' as key) and the posts for those months
|
||||
self.months = ko.pureComputed(function() {
|
||||
var ret = {};
|
||||
_.forEach(self.posts(), function(post) {
|
||||
var key = post().date().format('YYYY-MM');
|
||||
|
||||
if (!(key in ret)) {
|
||||
ret[key] = ko.observableArray(post);
|
||||
}
|
||||
else {
|
||||
ret[key].push(post);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Parse a line of tags separated by commas and return the tags in a list.
|
||||
*/
|
||||
self.parseTagLine = function(tagStr) {
|
||||
var tags = tagStr.split(',');
|
||||
var retTags = [];
|
||||
_.forEach(tags, function(tag) {
|
||||
retTags.push(tag.trim());
|
||||
});
|
||||
return retTags;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse data of a single post from a regex match, returning and observable
|
||||
*/
|
||||
self.parsePostData = function(matched) {
|
||||
var tags = {};
|
||||
|
||||
if (!_.isUndefined(matched[3])) {
|
||||
tags = self.parseTagLine(matched[3]);
|
||||
}
|
||||
|
||||
var date = moment(matched[1], 'YYYY-MM-DD');
|
||||
var slug = matched[2];
|
||||
|
||||
var post = ko.observable({
|
||||
slug: ko.observable(slug),
|
||||
date: ko.observable(date),
|
||||
tags: ko.observable(tags),
|
||||
|
||||
// True if this posts title and content have been fetched from the
|
||||
// individual post file
|
||||
synced: ko.observable(false),
|
||||
|
||||
// These will be parsed later from the individual post file
|
||||
title: ko.observable(null),
|
||||
content: ko.observable(null),
|
||||
isSplit: ko.observable(null),
|
||||
shortContent: ko.observable(null)
|
||||
});
|
||||
return post;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse data of a single page from a regex match, returning and observable
|
||||
*/
|
||||
self.parsePageData = function(matched) {
|
||||
var slug = matched[1];
|
||||
|
||||
var page = ko.observable({
|
||||
slug: slug,
|
||||
linkText: matched[2],
|
||||
|
||||
synced: false,
|
||||
title: null,
|
||||
isSplit: false
|
||||
});
|
||||
return page;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the index file, putting its contents into the given
|
||||
* observableArrays
|
||||
*/
|
||||
self.parseIndex = function(posts, pages, indexStr) {
|
||||
var post_data_regex = /^(\d{4}-\d{2}-\d{2}) ([a-z0-9\-]+)(\s([^,]+?,?)*)?$/;
|
||||
var page_data_regex = /^([a-z0-9\-]+) (.*)$/;
|
||||
|
||||
var lines = indexStr.split('\n');
|
||||
var line_no = 1;
|
||||
|
||||
_.forEach(lines, function(line) {
|
||||
var post_match = post_data_regex.exec(line);
|
||||
var page_match = page_data_regex.exec(line);
|
||||
|
||||
if (post_match !== null) {
|
||||
posts.push(self.parsePostData(post_match));
|
||||
}
|
||||
else if (page_match !== null) {
|
||||
pages.push(self.parsePageData(page_match));
|
||||
}
|
||||
else {
|
||||
console.log('Ignoring unmatched line ' + line_no + ': "' + line + '"');
|
||||
}
|
||||
|
||||
++line_no;
|
||||
});
|
||||
|
||||
// After loading all posts, sort them descending by date. Pages will be
|
||||
// left as-is
|
||||
self.posts.sort(function(left, right) {
|
||||
return left().date().isSame(right().date())? 0
|
||||
: (left().date().isBefore(right().date())? 1 : -1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse data from a data file, updating the given observable
|
||||
*/
|
||||
self.parseData = function(obj, dataStr) {
|
||||
var lines = dataStr.split('\n');
|
||||
obj().title(lines[0]);
|
||||
obj().synced(true);
|
||||
obj().content(lines.slice(2).join('\n'));
|
||||
|
||||
var parts = obj().content().split('<!--SPLIT-->');
|
||||
obj().shortContent(parts[0]);
|
||||
obj().isSplit(parts.length > 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse index data and store it in the system.
|
||||
*/
|
||||
self.loadIndex = function() {
|
||||
transfer.loadIndex().success(function(data) {
|
||||
self.parseIndex(self.posts, self.pages, data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a post with the given slug and update its data in the index.
|
||||
*/
|
||||
self.loadPost = function(slug) {
|
||||
transfer.loadPost(slug).success(function(data) {
|
||||
var post = self.postDict()[slug];
|
||||
self.parseData(post, data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a page with the given slug and update its dat a in the index.
|
||||
*/
|
||||
self.loadPage = function(slug) {
|
||||
transfer.loadPage(slug).success(function(data) {
|
||||
var page = self.pageDict()[slug];
|
||||
self.parseData(page, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
DB.prototype.dispose = function() {};
|
||||
|
||||
return new DB();
|
||||
});
|
3
src/components/home-page/home.html
Normal file
3
src/components/home-page/home.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<h2>Page: <!-- ko text: page --><!-- /ko --></h2>
|
||||
|
||||
<pagination params="posts: posts, page: page"></pagination>
|
13
src/components/home-page/home.js
Normal file
13
src/components/home-page/home.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
define(["knockout", "text!./home.html", "../db/db"], function(ko, homeTemplate, DB) {
|
||||
|
||||
function HomeViewModel(route) {
|
||||
var self = this;
|
||||
self.posts = DB.posts;
|
||||
self.page = ko.observable(1);
|
||||
|
||||
DB.loadIndex();
|
||||
}
|
||||
|
||||
return { viewModel: HomeViewModel, template: homeTemplate };
|
||||
|
||||
});
|
27
src/components/nav-bar/nav-bar.html
Normal file
27
src/components/nav-bar/nav-bar.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!--
|
||||
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>
|
||||
<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>
|
13
src/components/nav-bar/nav-bar.js
Normal file
13
src/components/nav-bar/nav-bar.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
define(['knockout', 'text!./nav-bar.html'], function(ko, template) {
|
||||
|
||||
function NavBarViewModel(params) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
return { viewModel: NavBarViewModel, template: template };
|
||||
});
|
21
src/components/pagination/pagination.html
Normal file
21
src/components/pagination/pagination.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<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>
|
||||
</li>
|
||||
<!-- ko foreach: _.range(1, pageCount() + 1) -->
|
||||
<li data-bind="css: { active: $data === $parent.page() } ">
|
||||
<a data-bind="click: $parent.goTo, text: $data"></a>
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
<li data-bind="css: { disabled: page() === pageCount() }">
|
||||
<a data-bind="click: goForward">
|
||||
»
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
45
src/components/pagination/pagination.js
Normal file
45
src/components/pagination/pagination.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
define(['knockout', 'text!./pagination.html'], function(ko, templateMarkup) {
|
||||
|
||||
function Pagination(params) {
|
||||
var self = this;
|
||||
self.posts = params.posts;
|
||||
self.page = params.page;
|
||||
|
||||
self.postsPerPage = 3;
|
||||
|
||||
self.pageCount = ko.pureComputed(function() {
|
||||
return Math.ceil(params.posts().length / self.postsPerPage);
|
||||
});
|
||||
|
||||
self.goTo = function(page) {
|
||||
if (page < 1) {
|
||||
page = 1;
|
||||
}
|
||||
else if (page > self.pageCount()) {
|
||||
page = self.pageCount();
|
||||
}
|
||||
|
||||
self.page(page);
|
||||
};
|
||||
|
||||
self.goBack = function() {
|
||||
self.goTo(self.page() - 1);
|
||||
}
|
||||
|
||||
self.goForward = function() {
|
||||
self.goTo(self.page() + 1);
|
||||
}
|
||||
|
||||
self.postsForPage = ko.pureComputed(function() {
|
||||
var start = (self.page() - 1) * self.postsPerPage;
|
||||
return self.posts.slice(start, start + self.postsPerPage);
|
||||
});
|
||||
}
|
||||
|
||||
// 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.
|
||||
Pagination.prototype.dispose = function() {};
|
||||
|
||||
return { viewModel: Pagination, template: templateMarkup };
|
||||
|
||||
});
|
9
src/components/single-post/single-post.html
Normal file
9
src/components/single-post/single-post.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div class="single-post">
|
||||
<h2 data-bind="text: post.title()"></h2>
|
||||
|
||||
<p class="post-tags" data-bind="foreach: post.tags()">
|
||||
<span data-bind="text: $data"></span><!-- ko if: ($index() < post.tags().length - 1) -->,<!-- /ko -->
|
||||
</p>
|
||||
|
||||
<div class="post-content" data-bind="html: content() !== null? marked(content()) : ''"></div>
|
||||
</div>
|
32
src/components/single-post/single-post.js
Normal file
32
src/components/single-post/single-post.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
define(['knockout', 'text!./single-post.html', '../db/db', 'marked'],
|
||||
function(ko, templateMarkup, DB, marked) {
|
||||
|
||||
function SinglePost(params) {
|
||||
var self = this;
|
||||
self.post = params.post;
|
||||
self.marked = marked;
|
||||
|
||||
// Is this post shown as short (in pagination) or not?
|
||||
self.short = params.short;
|
||||
|
||||
self.content = self.post.content;
|
||||
if (self.short) {
|
||||
self.content = self.post.shortContent;
|
||||
}
|
||||
|
||||
// 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…");
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {};
|
||||
|
||||
return { viewModel: SinglePost, template: templateMarkup };
|
||||
|
||||
});
|
22
src/components/transfer/transfer.js
Normal file
22
src/components/transfer/transfer.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
define(['knockout'], function(ko) {
|
||||
|
||||
function Transfer(params) {
|
||||
this.loadIndex = function() {
|
||||
return $.get('index');
|
||||
};
|
||||
|
||||
this.loadPost = function(slug) {
|
||||
return $.get('posts/' + slug + '.md');
|
||||
};
|
||||
|
||||
this.loadPage = function(slug) {
|
||||
return $.get('pages/' + slug + '.md')
|
||||
};
|
||||
}
|
||||
|
||||
// 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.
|
||||
Transfer.prototype.dispose = function() {};
|
||||
|
||||
return new Transfer();
|
||||
});
|
3
src/css/styles.css
Normal file
3
src/css/styles.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
#page {
|
||||
margin-top: 80px;
|
||||
}
|
24
src/index.html
Normal file
24
src/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Laine</title>
|
||||
<!-- build:css -->
|
||||
<link href="bower_modules/components-bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="css/styles.css" rel="stylesheet">
|
||||
<!-- endbuild -->
|
||||
<!-- build:js -->
|
||||
<script src="app/require.config.js"></script>
|
||||
<script data-main="app/startup" src="bower_modules/requirejs/require.js"></script>
|
||||
<!-- endbuild -->
|
||||
</head>
|
||||
<body>
|
||||
<nav-bar params="route: route"></nav-bar>
|
||||
<div id="page" class="container" data-bind="component: { name: route().page, params: route }">
|
||||
<div class="loading">
|
||||
<h1>Loading…</h1>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in a new issue