diff --git a/bower.json b/bower.json index 0e0a204..b50bf4a 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "laine", - "version": "0.0.0", + "version": "0.1.0", "private": true, "dependencies": { "components-bootstrap": "~3.1.1", diff --git a/gulpfile.js b/gulpfile.js index 5385147..4b543a3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -38,7 +38,8 @@ var requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('src/app/require 'components/year-page/year-page', 'components/month-page/month-page', 'components/post-page/post-page', - 'components/config-service/config-service' + 'components/config-service/config-service', + 'components/template-service/template-service' ], insertRequire: ['app/startup'], bundles: { diff --git a/src/app/router.js b/src/app/router.js index 5c16eda..da80840 100644 --- a/src/app/router.js +++ b/src/app/router.js @@ -1,4 +1,12 @@ -define(["knockout", "crossroads", "hasher", "./routes"], function(ko, crossroads, hasher, routes) { +define( + [ + "knockout", + "crossroads", + "hasher", + "./routes", + "../components/config-service/config-service", + "../components/ga-service/ga-service" + ], function(ko, crossroads, hasher, routes, configService, gaService) { // 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 @@ -14,6 +22,8 @@ define(["knockout", "crossroads", "hasher", "./routes"], function(ko, crossroads function Router(config) { var currentRoute = this.currentRoute = ko.observable({}); + this.crossroads = crossroads; + this.hasher = hasher; ko.utils.arrayForEach(config.routes, function(route) { var addedRoute = crossroads.addRoute(route.url, function(requestParams) { @@ -29,7 +39,15 @@ define(["knockout", "crossroads", "hasher", "./routes"], function(ko, crossroads } function activateCrossroads() { - function parseHash(newHash, oldHash) { crossroads.parse(newHash); } + function parseHash(newHash, oldHash) { + crossroads.parse(newHash); + + // Send google analytics page event if it's enabled + if (configService.useGa) { + gaService.sendPageEvent(); + } + } + crossroads.normalizeFn = crossroads.NORM_AS_OBJECT; hasher.prependHash = '!'; hasher.initialized.add(parseHash); diff --git a/src/app/routes.js b/src/app/routes.js index 649c2e4..35fe528 100644 --- a/src/app/routes.js +++ b/src/app/routes.js @@ -61,7 +61,3 @@ define([], function() { } ]; }); - - - - diff --git a/src/app/startup.js b/src/app/startup.js index b37bafc..f217eff 100644 --- a/src/app/startup.js +++ b/src/app/startup.js @@ -32,6 +32,10 @@ define(['jquery', 'knockout', './router', 'marked', 'bootstrap', 'knockout-proje ko.components.register('config-service', { require: 'components/config-service/config-service' }); + ko.components.register('template-service', { require: 'components/template-service/template-service' }); + + ko.components.register('ga-service', { require: 'components/ga-service/ga-service' }); + // [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/config-service/config-service.js b/src/components/config-service/config-service.js index 72e7c64..d32bcd7 100644 --- a/src/components/config-service/config-service.js +++ b/src/components/config-service/config-service.js @@ -1,5 +1,5 @@ -define(['knockout', '../../app/routes', '../../app/router', 'hasher'], - function(ko, routes, router, hasher) { +define(['knockout'], + function(ko) { function ConfigService() { var self = this; @@ -9,6 +9,18 @@ define(['knockout', '../../app/routes', '../../app/router', 'hasher'], document.title = self.blogName; self.baseTitle = self.blogName; + + // Load custom CSS if specified + if (self.customCss) { + var head = document.getElementsByTagName('head')[0]; + var link = document.createElement('link'); + link.id = 'laine-custom-css'; + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = self.customCss; + link.media = 'all'; + head.appendChild(link); + } } // This runs when the component is torn down. Put here any logic necessary to clean up, diff --git a/src/components/ga-service/ga-service.js b/src/components/ga-service/ga-service.js new file mode 100644 index 0000000..a2703fb --- /dev/null +++ b/src/components/ga-service/ga-service.js @@ -0,0 +1,34 @@ +define( + [ + 'knockout', + '../config-service/config-service' + ], function(ko, configService) { + + function GaService() { + var self = this; + + // The GA object that can be used in other components + self.ga = null; + + if (configService.useGa) { + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', configService.webPropertyID, 'auto'); + self.ga = ga; + } + + self.sendPageEvent = function() { + self.ga('send', 'pageview'); + }; + } + + // 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. + GaService.prototype.dispose = function() { }; + + return new GaService(); + +}); diff --git a/src/components/generic-route/generic-route.js b/src/components/generic-route/generic-route.js index c24556a..b0d20e7 100644 --- a/src/components/generic-route/generic-route.js +++ b/src/components/generic-route/generic-route.js @@ -1,4 +1,10 @@ -define(['knockout', '../db/db'], function(ko, DB) { +define( + [ + 'knockout', + '../db/db', + '../config-service/config-service', + '../template-service/template-service' + ], function(ko, DB, configService, templateService) { function GenericRoute() { var self = this; @@ -14,6 +20,16 @@ define(['knockout', '../db/db'], function(ko, DB) { page(parseInt(route.pageNumber, 10)); } } + + self.resolveTemplate = function(originalTemplate, templateName) { + if (templateName in configService.customTemplates + && configService.customTemplates[templateName]) { + return templateService.loadTemplate(templateName, configService.customTemplates[templateName]); + } + else { + return originalTemplate; + } + } } // This runs when the component is torn down. Put here any logic necessary to clean up, diff --git a/src/components/home-page/home.js b/src/components/home-page/home.js index 95b078b..ad9a732 100644 --- a/src/components/home-page/home.js +++ b/src/components/home-page/home.js @@ -1,4 +1,10 @@ -define(['knockout', 'text!./home.html', '../generic-route/generic-route', '../db/db'], +define( + [ + 'knockout', + 'text!./home.html', + '../generic-route/generic-route', + '../db/db' + ], function(ko, templateMarkup, GR, DB) { function HomePage(route) { @@ -15,6 +21,8 @@ define(['knockout', 'text!./home.html', '../generic-route/generic-route', '../db // for example cancelling setTimeouts or disposing Knockout subscriptions/computeds. HomePage.prototype.dispose = function() {}; + templateMarkup = GR.resolveTemplate(templateMarkup, 'home'); + return { viewModel: HomePage, template: templateMarkup }; }); diff --git a/src/components/template-service/template-service.js b/src/components/template-service/template-service.js new file mode 100644 index 0000000..99b9035 --- /dev/null +++ b/src/components/template-service/template-service.js @@ -0,0 +1,32 @@ +define(['knockout'], function(ko) { + + function TemplateService() { + var self = this; + + self.templateStore = {}; + + self.loadTemplate = function(templateName, templateUrl) { + if (templateName in self.templateStore) { + return self.templateStore[templateName]; + } + else { + var html = $.ajax(templateUrl, { + // We need to set async false to receive the template before rendering + // starts + async: false, + dataType: 'html' + }).responseText; + + self.templateStore[templateName] = html; + return html; + } + }; + } + + // 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. + TemplateService.prototype.dispose = function() { }; + + return new TemplateService(); + +}); diff --git a/src/config.js.dist b/src/config.js.dist index ce81cfe..91f51e6 100644 --- a/src/config.js.dist +++ b/src/config.js.dist @@ -18,5 +18,30 @@ LAINE_CONFIG = { pageCommenting: false, // Posts shown per page - postsPerPage: 5 + postsPerPage: 5, + + + // GOOGLE ANALYTICS + useGa: false, // Set to true to use Google Analytics + webPropertyID: '', // Your Google Analytics UA-XXXX-Y code + + + + // CUSTOM TEMPLATE AND CSS OVERRIDES + + // To load a custom CSS file, insert the file name below + customCss: '', + + // To replace builtin templates with custom teplates, insert the file names + // below in the correct places. The paths should be relative to the + // index.html path. + customTemplates: { + navbar: '', // Navigation bar + home: '', // Home page + post: '', // Single post/page + pagination: '', // A list of posts with included pagination + year: '', // Year archives + month: '', // Month archives + tag: '' // Tag archives + } };