diff --git a/built-in/admin/admin-angular.js b/built-in/admin/admin-angular.js
index 6ebcb5e4..440df421 100644
--- a/built-in/admin/admin-angular.js
+++ b/built-in/admin/admin-angular.js
@@ -1,5 +1,6 @@
//register the modules (don't forget ui bootstrap and bootstrap switch)
var adminApp = angular.module('adminApp', ['ngRoute', 'frapontillo.bootstrap-switch', 'ui.bootstrap', 'infinite-scroll']);
+
adminApp.config(function($routeProvider) {
$routeProvider.
when('/', {
@@ -35,14 +36,34 @@ adminApp.directive('imgSelectionDirective', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
- $(elem).click(function() {
- $('.imgselected').removeClass('imgselected');
- $(elem).children('img').addClass('imgselected');
- });
+ $(elem).click(function() {
+ $('.imgselected').removeClass('imgselected');
+ $(elem).children('img').addClass('imgselected');
+ });
}
}
});
+//directive to add/remove the blog url to/from the navigation url fields
+adminApp.directive('evaluateUrl', function() {
+ return {
+ require: 'ngModel',
+ link: function(scope, elem, attrs, ctrl) {
+ elem.on('blur', function() {
+ var value = elem.val();
+ //if the url of this item doesn't start with http/https, add the blog url to it.
+ if (!(value.substring(0, 'http://'.length) === 'http://') && !(value.substring(0, 'https://'.length) === 'https://') && !(value.substring(0, scope.shared.blog.Url.length) === scope.shared.blog.Url)) {
+ if ((value.substring(0, 1) != '/') && (scope.shared.blog.Url.slice(-1) != '/')) {
+ value = '/' + value;
+ }
+ value = scope.shared.blog.Url + value;
+ elem.val(value);
+ }
+ });
+ }
+ };
+});
+
//factory to load items in infinite-scroll
adminApp.factory('infiniteScrollFactory', function($http) {
var infiniteScrollFactory = function(url) {
@@ -70,7 +91,7 @@ adminApp.factory('infiniteScrollFactory', function($http) {
adminApp.controller('ContentCtrl', function ($scope, $http, $sce, $location, infiniteScrollFactory, sharingService){
//change the navbar according to controller
- $scope.navbarHtml = $sce.trustAsHtml('
');
+ $scope.navbarHtml = $sce.trustAsHtml('');
$scope.infiniteScrollFactory = new infiniteScrollFactory('/admin/api/posts/');
$scope.openPost = function(postId) {
$location.url('/edit/' + postId);
@@ -91,13 +112,32 @@ adminApp.controller('ContentCtrl', function ($scope, $http, $sce, $location, inf
adminApp.controller('SettingsCtrl', function ($scope, $http, $timeout, $sce, $location, sharingService){
//change the navbar according to controller
- $scope.navbarHtml = $sce.trustAsHtml('');
+ $scope.navbarHtml = $sce.trustAsHtml('');
$scope.shared = sharingService.shared;
+ //variable to hold the field prefix
+ $scope.prefix = '';
$scope.loadData = function() {
$http.get('/admin/api/blog').success(function(data) {
$scope.shared.blog = data;
+ //select active theme
var themeIndex = $scope.shared.blog.Themes.indexOf($scope.shared.blog.ActiveTheme);
$scope.shared.blog.ActiveTheme = $scope.shared.blog.Themes[themeIndex];
+ //make sure NavigationItems is not null
+ if ($scope.shared.blog.NavigationItems == null) {
+ $scope.shared.blog.NavigationItems = []
+ }
+ //append the blog url to the navigation items if necessary
+ for (var i = 0; i < $scope.shared.blog.NavigationItems.length; i++) {
+ var value = $scope.shared.blog.NavigationItems[i].url;
+ //if the url of this item doesn't start with http/https, add the blog url to it.
+ if (!(value.substring(0, 'http://'.length) === 'http://') && !(value.substring(0, 'https://'.length) === 'https://') && !(value.substring(0, $scope.shared.blog.Url.length) === $scope.shared.blog.Url)) {
+ if ((value.substring(0, 1) != '/') && ($scope.shared.blog.Url.slice(-1) != '/')) {
+ value = '/' + value;
+ }
+ value = $scope.shared.blog.Url + value;
+ $scope.shared.blog.NavigationItems[i].url = value;
+ }
+ }
});
$http.get('/admin/api/userid').success(function(data) {
$scope.authenticatedUser = data;
@@ -107,6 +147,16 @@ adminApp.controller('SettingsCtrl', function ($scope, $http, $timeout, $sce, $lo
});
};
$scope.loadData();
+ $scope.deleteNavItem = function(index) {
+ $scope.shared.blog.NavigationItems.splice(index, 1);
+ };
+ $scope.addNavItem = function() {
+ var url = $scope.shared.blog.Url
+ if (url.slice(-1) != '/') {
+ url = url + '/';
+ }
+ $scope.shared.blog['NavigationItems'].push({label: 'Home', url: url});
+ };
$scope.save = function() {
$http.patch('/admin/api/blog', $scope.shared.blog);
$http.patch('/admin/api/user', $scope.shared.user).success(function(data) {
@@ -119,7 +169,7 @@ adminApp.controller('CreateCtrl', function ($scope, $http, $sce, $location, shar
//create markdown converter
var converter = new Showdown.converter();
//change the navbar according to controller
- $scope.navbarHtml = $sce.trustAsHtml('');
+ $scope.navbarHtml = $sce.trustAsHtml('');
$scope.shared = sharingService.shared;
$scope.shared.post = {Title: 'New Post', Slug: '', Markdown: 'Write something!', IsPublished: false, Image: '', Tags: ''}
$scope.change = function() {
@@ -139,7 +189,7 @@ adminApp.controller('EditCtrl', function ($scope, $routeParams, $http, $sce, $lo
//create markdown converter
var converter = new Showdown.converter();
//change the navbar according to controller
- $scope.navbarHtml = $sce.trustAsHtml('');
+ $scope.navbarHtml = $sce.trustAsHtml('');
$scope.shared = sharingService.shared;
$scope.shared.post = {}
$scope.change = function() {
diff --git a/built-in/admin/admin.css b/built-in/admin/admin.css
index 992bd9ce..cebe01da 100644
--- a/built-in/admin/admin.css
+++ b/built-in/admin/admin.css
@@ -65,6 +65,10 @@ img {
margin-right: 15px;
}
+.logout {
+ color: #f04124 !important;
+}
+
.post-content-row {
cursor: pointer;
}
@@ -147,4 +151,13 @@ img {
.navbar-label-text {
font-size: 16px;
color: #ffffff;
+}
+
+.navigation-item {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ background-color: #f8f8f8;
+ outline: 1px solid #dddddd;
+ margin-right: 0 !important;
+ margin-left: 0 !important;
}
\ No newline at end of file
diff --git a/built-in/admin/login.html b/built-in/admin/login.html
index e77ed5bd..541816f3 100644
--- a/built-in/admin/login.html
+++ b/built-in/admin/login.html
@@ -4,6 +4,8 @@
Admin Area
+
+
diff --git a/built-in/admin/post.html b/built-in/admin/post.html
index 112e4ea3..18f13201 100644
--- a/built-in/admin/post.html
+++ b/built-in/admin/post.html
@@ -64,7 +64,7 @@
diff --git a/built-in/admin/settings.html b/built-in/admin/settings.html
index 8f7c3942..d6bb19d6 100644
--- a/built-in/admin/settings.html
+++ b/built-in/admin/settings.html
@@ -55,6 +55,35 @@ Blog
+
+
@@ -113,7 +142,7 @@ User {{shared.user.Name}}
diff --git a/built-in/hbs/navigation.hbs b/built-in/hbs/navigation.hbs
new file mode 100644
index 00000000..1d302698
--- /dev/null
+++ b/built-in/hbs/navigation.hbs
@@ -0,0 +1,5 @@
+
+ {{#foreach navigation}}
+ - {{label}}
+ {{/foreach}}
+
\ No newline at end of file
diff --git a/built-in/hbs/pagination.hbs b/built-in/hbs/pagination.hbs
new file mode 100644
index 00000000..67d44efb
--- /dev/null
+++ b/built-in/hbs/pagination.hbs
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/config.json b/config.json
index cab0ff92..414a2617 100644
--- a/config.json
+++ b/config.json
@@ -1 +1,7 @@
-{"HttpHostAndPort":":8084","HttpsHostAndPort":":8085","HttpsUsage":"None","Url":"http://127.0.0.1:8084","HttpsUrl":"https://127.0.0.1:8085"}
\ No newline at end of file
+{
+ "HttpHostAndPort":":8084",
+ "HttpsHostAndPort":":8085",
+ "HttpsUsage":"None",
+ "Url":"http://127.0.0.1:8084",
+ "HttpsUrl":"https://127.0.0.1:8085"
+}
\ No newline at end of file
diff --git a/database/initialization.go b/database/initialization.go
index a744e25f..fca7d948 100644
--- a/database/initialization.go
+++ b/database/initialization.go
@@ -10,69 +10,70 @@ import (
"time"
)
-var readDB *sql.DB // Handler for read access
+// Handler for read access
+var readDB *sql.DB
var stmtInitialization = `CREATE TABLE IF NOT EXISTS
posts (
- id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+ id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
uuid varchar(36) NOT NULL,
- title varchar(150) NOT NULL,
+ title varchar(150) NOT NULL,
slug varchar(150) NOT NULL,
markdown text,
html text,
- image text,
+ image text,
featured tinyint NOT NULL DEFAULT '0',
page tinyint NOT NULL DEFAULT '0',
- status varchar(150) NOT NULL DEFAULT 'draft',
+ status varchar(150) NOT NULL DEFAULT 'draft',
language varchar(6) NOT NULL DEFAULT 'en_US',
- meta_title varchar(150),
+ meta_title varchar(150),
meta_description varchar(200),
- author_id integer NOT NULL,
- created_at datetime NOT NULL,
- created_by integer NOT NULL,
- updated_at datetime,
- updated_by integer,
+ author_id integer NOT NULL,
+ created_at datetime NOT NULL,
+ created_by integer NOT NULL,
+ updated_at datetime,
+ updated_by integer,
published_at datetime,
published_by integer
);
CREATE TABLE IF NOT EXISTS
users (
- id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+ id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
uuid varchar(36) NOT NULL,
name varchar(150) NOT NULL,
slug varchar(150) NOT NULL,
password varchar(60) NOT NULL,
- email varchar(254) NOT NULL,
- image text,
- cover text,
- bio varchar(200),
- website text,
+ email varchar(254) NOT NULL,
+ image text,
+ cover text,
+ bio varchar(200),
+ website text,
location text,
- accessibility text,
- status varchar(150) NOT NULL DEFAULT 'active',
+ accessibility text,
+ status varchar(150) NOT NULL DEFAULT 'active',
language varchar(6) NOT NULL DEFAULT 'en_US',
- meta_title varchar(150),
+ meta_title varchar(150),
meta_description varchar(200),
- last_login datetime,
- created_at datetime NOT NULL,
- created_by integer NOT NULL,
- updated_at datetime,
- updated_by integer
+ last_login datetime,
+ created_at datetime NOT NULL,
+ created_by integer NOT NULL,
+ updated_at datetime,
+ updated_by integer
);
CREATE TABLE IF NOT EXISTS
tags (
- id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+ id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
uuid varchar(36) NOT NULL,
name varchar(150) NOT NULL,
slug varchar(150) NOT NULL,
- description varchar(200),
- parent_id integer,
- meta_title varchar(150),
+ description varchar(200),
+ parent_id integer,
+ meta_title varchar(150),
meta_description varchar(200),
- created_at datetime NOT NULL,
- created_by integer NOT NULL,
- updated_at datetime,
- updated_by integer
+ created_at datetime NOT NULL,
+ created_by integer NOT NULL,
+ updated_at datetime,
+ updated_by integer
);
CREATE TABLE IF NOT EXISTS
posts_tags (
@@ -83,10 +84,10 @@ var stmtInitialization = `CREATE TABLE IF NOT EXISTS
CREATE TABLE IF NOT EXISTS
settings (
id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
- uuid varchar(36) NOT NULL,
+ uuid varchar(36) NOT NULL,
key varchar(150) NOT NULL,
value text,
- type varchar(150) NOT NULL DEFAULT 'core',
+ type varchar(150) NOT NULL DEFAULT 'core',
created_at datetime NOT NULL,
created_by integer NOT NULL,
updated_at datetime,
@@ -99,11 +100,12 @@ var stmtInitialization = `CREATE TABLE IF NOT EXISTS
INSERT OR IGNORE INTO settings (id, uuid, key, value, type, created_at, created_by, updated_at, updated_by) VALUES (5, ?, 'cover', '/public/images/blog-cover.jpg', 'blog', ?, 1, ?, 1);
INSERT OR IGNORE INTO settings (id, uuid, key, value, type, created_at, created_by, updated_at, updated_by) VALUES (6, ?, 'postsPerPage', 5, 'blog', ?, 1, ?, 1);
INSERT OR IGNORE INTO settings (id, uuid, key, value, type, created_at, created_by, updated_at, updated_by) VALUES (7, ?, 'activeTheme', 'promenade', 'theme', ?, 1, ?, 1);
+ INSERT OR IGNORE INTO settings (id, uuid, key, value, type, created_at, created_by, updated_at, updated_by) VALUES (8, ?, 'navigation', '[{"label":"Home", "url":"/"}]', 'blog', ?, 1, ?, 1);
CREATE TABLE IF NOT EXISTS
roles (
id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
- uuid varchar(36) NOT NULL,
- name varchar(150) NOT NULL,
+ uuid varchar(36) NOT NULL,
+ name varchar(150) NOT NULL,
description varchar(200),
created_at datetime NOT NULL,
created_by integer NOT NULL,
@@ -140,7 +142,7 @@ func Initialize() error {
return err
}
currentTime := time.Now()
- _, err = readDB.Exec(stmtInitialization, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime)
+ _, err = readDB.Exec(stmtInitialization, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime, uuid.Formatter(uuid.NewV4(), uuid.CleanHyphen), currentTime, currentTime)
// TODO: Is Commit()/Rollback() needed for DB.Exec()?
if err != nil {
return err
diff --git a/database/retrieval.go b/database/retrieval.go
index e420f35e..90e96b4c 100644
--- a/database/retrieval.go
+++ b/database/retrieval.go
@@ -2,6 +2,7 @@ package database
import (
"database/sql"
+ "encoding/json"
"github.com/kabukky/journey/structure"
"time"
)
@@ -347,11 +348,23 @@ func RetrieveBlog() (*structure.Blog, error) {
if err != nil {
return &tempBlog, err
}
+ // Post count
postCount, err := RetrieveNumberOfPosts()
if err != nil {
return &tempBlog, err
}
tempBlog.PostCount = postCount
+ // Navigation
+ var navigation []byte
+ row = readDB.QueryRow(stmtRetrieveBlog, "navigation")
+ err = row.Scan(&navigation)
+ if err != nil {
+ return &tempBlog, err
+ }
+ tempBlog.NavigationItems, err = makeNavigation(navigation)
+ if err != nil {
+ return &tempBlog, err
+ }
return &tempBlog, err
}
@@ -374,3 +387,12 @@ func RetrieveUsersCount() int {
}
return userCount
}
+
+func makeNavigation(navigation []byte) ([]structure.Navigation, error) {
+ navigationItems := make([]structure.Navigation, 0)
+ err := json.Unmarshal(navigation, &navigationItems)
+ if err != nil {
+ return navigationItems, err
+ }
+ return navigationItems, nil
+}
diff --git a/database/update.go b/database/update.go
index fd59550a..41e98f4f 100644
--- a/database/update.go
+++ b/database/update.go
@@ -8,6 +8,7 @@ const stmtUpdatePost = "UPDATE posts SET title = ?, slug = ?, markdown = ?, html
const stmtUpdatePostPublished = "UPDATE posts SET title = ?, slug = ?, markdown = ?, html = ?, featured = ?, page = ?, status = ?, image = ?, updated_at = ?, updated_by = ?, published_at = ?, published_by = ? WHERE id = ?"
const stmtUpdateSettings = "UPDATE settings SET value = ?, updated_at = ?, updated_by = ? WHERE key = ?"
const stmtUpdateUser = "UPDATE users SET email = ?, image = ?, cover = ?, bio = ?, website = ?, location = ?, updated_at = ?, updated_by = ? WHERE id = ?"
+const stmtUpdateLastLogin = "UPDATE users SET last_login = ? WHERE id = ?"
const stmtUpdateUserPassword = "UPDATE users SET password = ?, updated_at = ?, updated_by = ? WHERE id = ?"
func UpdatePost(id int64, title []byte, slug string, markdown []byte, html []byte, featured bool, isPage bool, published bool, image []byte, updated_at time.Time, updated_by int64) error {
@@ -37,7 +38,7 @@ func UpdatePost(id int64, title []byte, slug string, markdown []byte, html []byt
return writeDB.Commit()
}
-func UpdateSettings(title []byte, description []byte, logo []byte, cover []byte, postsPerPage int64, activeTheme string, updated_at time.Time, updated_by int64) error {
+func UpdateSettings(title []byte, description []byte, logo []byte, cover []byte, postsPerPage int64, activeTheme string, navigation []byte, updated_at time.Time, updated_by int64) error {
writeDB, err := readDB.Begin()
if err != nil {
writeDB.Rollback()
@@ -79,6 +80,12 @@ func UpdateSettings(title []byte, description []byte, logo []byte, cover []byte,
writeDB.Rollback()
return err
}
+ // Navigation
+ _, err = writeDB.Exec(stmtUpdateSettings, navigation, updated_at, updated_by, "navigation")
+ if err != nil {
+ writeDB.Rollback()
+ return err
+ }
return writeDB.Commit()
}
@@ -110,6 +117,20 @@ func UpdateUser(id int64, email []byte, image []byte, cover []byte, bio []byte,
return writeDB.Commit()
}
+func UpdateLastLogin(date time.Time, userId int64) error {
+ writeDB, err := readDB.Begin()
+ if err != nil {
+ writeDB.Rollback()
+ return err
+ }
+ _, err = writeDB.Exec(stmtUpdateLastLogin, date, userId)
+ if err != nil {
+ writeDB.Rollback()
+ return err
+ }
+ return writeDB.Commit()
+}
+
func UpdateUserPassword(id int64, password string, updated_at time.Time, updated_by int64) error {
writeDB, err := readDB.Begin()
if err != nil {
diff --git a/filenames/filenames.go b/filenames/filenames.go
index 4278165b..a171d781 100644
--- a/filenames/filenames.go
+++ b/filenames/filenames.go
@@ -31,6 +31,7 @@ var (
//For built-in files (e.g. the admin interface)
AdminFilepath = filepath.Join("built-in", "admin")
PublicFilepath = filepath.Join("built-in", "public")
+ HbsFilepath = filepath.Join("built-in", "hbs")
// For handlebars (this is a url string)
JqueryFilename = "/public/jquery/jquery.js"
diff --git a/main.go b/main.go
index c2d6c198..457a662f 100644
--- a/main.go
+++ b/main.go
@@ -9,6 +9,7 @@ import (
"github.com/kabukky/journey/flags"
"github.com/kabukky/journey/plugins"
"github.com/kabukky/journey/server"
+ "github.com/kabukky/journey/structure/methods"
"github.com/kabukky/journey/templates"
"log"
"net/http"
@@ -60,6 +61,13 @@ func main() {
return
}
+ // Global blog data
+ err = methods.GenerateBlog()
+ if err != nil {
+ log.Fatal("Error: Couldn't generate blog data: " + err.Error())
+ return
+ }
+
// Templates
err = templates.Generate()
if err != nil {
diff --git a/server/admin.go b/server/admin.go
index c060f3e8..a16af771 100644
--- a/server/admin.go
+++ b/server/admin.go
@@ -38,13 +38,15 @@ type JsonPost struct {
}
type JsonBlog struct {
- Title string
- Description string
- Logo string
- Cover string
- Themes []string
- ActiveTheme string
- PostsPerPage int64
+ Url string
+ Title string
+ Description string
+ Logo string
+ Cover string
+ Themes []string
+ ActiveTheme string
+ PostsPerPage int64
+ NavigationItems []structure.Navigation
}
type JsonUser struct {
@@ -85,6 +87,14 @@ func postLoginHandler(w http.ResponseWriter, r *http.Request, _ map[string]strin
if name != "" && password != "" {
if authentication.LoginIsCorrect(name, password) {
authentication.SetSession(name, w)
+ userId, err := getUserId(name)
+ if err != nil {
+ log.Println("Couldn't get id of logged in user:", err)
+ }
+ err = database.UpdateLastLogin(time.Now(), userId)
+ if err != nil {
+ log.Println("Couldn't update last login date of a user:", err)
+ }
} else {
log.Println("Failed login attempt for user " + name)
}
@@ -136,7 +146,7 @@ func postRegistrationHandler(w http.ResponseWriter, r *http.Request, _ map[strin
// Function to log out the user. Not used at the moment.
func logoutHandler(w http.ResponseWriter, r *http.Request, _ map[string]string) {
authentication.ClearSession(w)
- http.Redirect(w, r, "/admin/", 302)
+ http.Redirect(w, r, "/admin/login/", 302)
return
}
@@ -325,7 +335,7 @@ func deleteApiPostHandler(w http.ResponseWriter, r *http.Request, params map[str
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- err = database.DeletePostById(postId)
+ err = methods.DeletePost(postId)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -490,12 +500,10 @@ func deleteApiImageHandler(w http.ResponseWriter, r *http.Request, _ map[string]
func getApiBlogHandler(w http.ResponseWriter, r *http.Request, _ map[string]string) {
userName := authentication.GetUserName(r)
if userName != "" {
- blog, err := database.RetrieveBlog()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- blogJson := JsonBlog{Title: string(blog.Title), Description: string(blog.Description), Logo: string(blog.Logo), Cover: string(blog.Cover), PostsPerPage: blog.PostsPerPage, Themes: templates.GetAllThemes(), ActiveTheme: blog.ActiveTheme}
+ // Read lock the global blog
+ methods.Blog.RLock()
+ defer methods.Blog.RUnlock()
+ blogJson := blogToJson(methods.Blog)
json, err := json.Marshal(blogJson)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -530,13 +538,23 @@ func patchApiBlogHandler(w http.ResponseWriter, r *http.Request, _ map[string]st
if json.PostsPerPage < 1 {
json.PostsPerPage = 1
}
- // Retrieve old post settings for comparison
+ // Remove blog url in front of navigation urls
+ for index, _ := range json.NavigationItems {
+ if strings.HasPrefix(json.NavigationItems[index].Url, json.Url) {
+ json.NavigationItems[index].Url = strings.Replace(json.NavigationItems[index].Url, json.Url, "", 1)
+ // If we removed the blog url, there should be a / in front of the url
+ if !strings.HasPrefix(json.NavigationItems[index].Url, "/") {
+ json.NavigationItems[index].Url = "/" + json.NavigationItems[index].Url
+ }
+ }
+ }
+ // Retrieve old blog settings for comparison
blog, err := database.RetrieveBlog()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- tempBlog := structure.Blog{Url: []byte(configuration.Config.Url), Title: []byte(json.Title), Description: []byte(json.Description), Logo: []byte(json.Logo), Cover: []byte(json.Cover), AssetPath: []byte("/assets/"), PostCount: blog.PostCount, PostsPerPage: json.PostsPerPage, ActiveTheme: json.ActiveTheme}
+ tempBlog := structure.Blog{Url: []byte(configuration.Config.Url), Title: []byte(json.Title), Description: []byte(json.Description), Logo: []byte(json.Logo), Cover: []byte(json.Cover), AssetPath: []byte("/assets/"), PostCount: blog.PostCount, PostsPerPage: json.PostsPerPage, ActiveTheme: json.ActiveTheme, NavigationItems: json.NavigationItems}
err = methods.UpdateBlog(&tempBlog, userId)
// Check if active theme setting has been changed, if so, generate templates from new theme
if tempBlog.ActiveTheme != blog.ActiveTheme {
@@ -713,6 +731,20 @@ func postToJson(post *structure.Post) *JsonPost {
return &jsonPost
}
+func blogToJson(blog *structure.Blog) *JsonBlog {
+ var jsonBlog JsonBlog
+ jsonBlog.Url = string(blog.Url)
+ jsonBlog.Title = string(blog.Title)
+ jsonBlog.Description = string(blog.Description)
+ jsonBlog.Logo = string(blog.Logo)
+ jsonBlog.Cover = string(blog.Cover)
+ jsonBlog.PostsPerPage = blog.PostsPerPage
+ jsonBlog.Themes = templates.GetAllThemes()
+ jsonBlog.ActiveTheme = blog.ActiveTheme
+ jsonBlog.NavigationItems = blog.NavigationItems
+ return &jsonBlog
+}
+
func InitializeAdmin(router *httptreemux.TreeMux) {
// For admin panel
router.GET("/admin/", adminHandler)
diff --git a/server/blog.go b/server/blog.go
index b543ee97..1f59ab92 100644
--- a/server/blog.go
+++ b/server/blog.go
@@ -2,8 +2,8 @@ package server
import (
"github.com/dimfeld/httptreemux"
- "github.com/kabukky/journey/database"
"github.com/kabukky/journey/filenames"
+ "github.com/kabukky/journey/structure/methods"
"github.com/kabukky/journey/templates"
"net/http"
"path/filepath"
@@ -14,7 +14,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request, params map[string]stri
number := params["number"]
if number == "" {
// Render index template (first page)
- err := templates.ShowIndexTemplate(w, 1)
+ err := templates.ShowIndexTemplate(w, r, 1)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -27,7 +27,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request, params map[string]stri
return
}
// Render index template
- err = templates.ShowIndexTemplate(w, page)
+ err = templates.ShowIndexTemplate(w, r, page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -41,7 +41,7 @@ func authorHandler(w http.ResponseWriter, r *http.Request, params map[string]str
number := params["number"]
if function == "" {
// Render author template (first page)
- err := templates.ShowAuthorTemplate(w, slug, 1)
+ err := templates.ShowAuthorTemplate(w, r, slug, 1)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -62,7 +62,7 @@ func authorHandler(w http.ResponseWriter, r *http.Request, params map[string]str
return
}
// Render author template
- err = templates.ShowAuthorTemplate(w, slug, page)
+ err = templates.ShowAuthorTemplate(w, r, slug, page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -76,7 +76,7 @@ func tagHandler(w http.ResponseWriter, r *http.Request, params map[string]string
number := params["number"]
if function == "" {
// Render tag template (first page)
- err := templates.ShowTagTemplate(w, slug, 1)
+ err := templates.ShowTagTemplate(w, r, slug, 1)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -97,7 +97,7 @@ func tagHandler(w http.ResponseWriter, r *http.Request, params map[string]string
return
}
// Render tag template
- err = templates.ShowTagTemplate(w, slug, page)
+ err = templates.ShowTagTemplate(w, r, slug, page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -120,7 +120,7 @@ func postHandler(w http.ResponseWriter, r *http.Request, params map[string]strin
return
}
// Render post template
- err := templates.ShowPostTemplate(w, slug)
+ err := templates.ShowPostTemplate(w, r, slug)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -129,13 +129,10 @@ func postHandler(w http.ResponseWriter, r *http.Request, params map[string]strin
}
func assetsHandler(w http.ResponseWriter, r *http.Request, params map[string]string) {
- // TODO: It might be possible to do this more efficently. Getting the theme from the database at every request seems like too much.
- activeTheme, err := database.RetrieveActiveTheme()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- http.ServeFile(w, r, filepath.Join(filenames.ThemesFilepath, *activeTheme, "assets", params["filepath"]))
+ // Read lock global blog
+ methods.Blog.RLock()
+ defer methods.Blog.RUnlock()
+ http.ServeFile(w, r, filepath.Join(filenames.ThemesFilepath, methods.Blog.ActiveTheme, "assets", params["filepath"]))
return
}
diff --git a/server/pages.go b/server/pages.go
index 341a50bd..dc4f890a 100644
--- a/server/pages.go
+++ b/server/pages.go
@@ -3,12 +3,20 @@ package server
import (
"github.com/dimfeld/httptreemux"
"github.com/kabukky/journey/filenames"
+ "github.com/kabukky/journey/helpers"
"net/http"
"path/filepath"
+ "strings"
)
func pagesHandler(w http.ResponseWriter, r *http.Request, params map[string]string) {
- http.ServeFile(w, r, filepath.Join(filenames.PagesFilepath, params["filepath"]))
+ path := filepath.Join(filenames.PagesFilepath, params["filepath"])
+ // If the path points to a directory, add a trailing slash to the path (needed if the page loads relative assets).
+ if helpers.IsDirectory(path) && !strings.HasSuffix(r.RequestURI, "/") {
+ http.Redirect(w, r, r.RequestURI+"/", 301)
+ return
+ }
+ http.ServeFile(w, r, path)
return
}
diff --git a/slug/slug.go b/slug/slug.go
index 8f54163b..6b398615 100644
--- a/slug/slug.go
+++ b/slug/slug.go
@@ -34,7 +34,7 @@ func Generate(input string, table string) string {
// Don't allow a few specific slugs that are used by the blog
if table == "posts" && (output == "rss" || output == "tag" || output == "author" || output == "page" || output == "admin") {
output = generateUniqueSlug(output, table, 2)
- } else if table == "tags" { // We want duplicate tag slugs
+ } else if table == "tags" || table == "navigation" { // We want duplicate tag and navigation slugs
return output
}
return generateUniqueSlug(output, table, 1)
diff --git a/structure/blog.go b/structure/blog.go
index e15ed43e..37c8e032 100644
--- a/structure/blog.go
+++ b/structure/blog.go
@@ -1,14 +1,20 @@
package structure
+import (
+ "sync"
+)
+
// Blog: settings that are used for template execution
type Blog struct {
- Url []byte
- Title []byte
- Description []byte
- Logo []byte
- Cover []byte
- AssetPath []byte
- PostCount int64
- PostsPerPage int64
- ActiveTheme string
+ sync.RWMutex
+ Url []byte
+ Title []byte
+ Description []byte
+ Logo []byte
+ Cover []byte
+ AssetPath []byte
+ PostCount int64
+ PostsPerPage int64
+ ActiveTheme string
+ NavigationItems []Navigation
}
diff --git a/structure/methods/blog.go b/structure/methods/blog.go
index dc89e839..d90bd994 100644
--- a/structure/methods/blog.go
+++ b/structure/methods/blog.go
@@ -1,19 +1,35 @@
package methods
import (
+ "encoding/json"
"github.com/kabukky/journey/configuration"
"github.com/kabukky/journey/database"
+ "github.com/kabukky/journey/slug"
"github.com/kabukky/journey/structure"
+ "log"
"time"
)
+// Global blog - thread safe and accessible by all requests
+var Blog *structure.Blog
+
var assetPath = []byte("/assets/")
func UpdateBlog(b *structure.Blog, userId int64) error {
- err := database.UpdateSettings(b.Title, b.Description, b.Logo, b.Cover, b.PostsPerPage, b.ActiveTheme, time.Now(), userId)
+ // Marshal navigation items to json string
+ navigation, err := json.Marshal(b.NavigationItems)
+ if err != nil {
+ return err
+ }
+ err = database.UpdateSettings(b.Title, b.Description, b.Logo, b.Cover, b.PostsPerPage, b.ActiveTheme, navigation, time.Now(), userId)
if err != nil {
return err
}
+ // Generate new global blog
+ err = GenerateBlog()
+ if err != nil {
+ log.Panic("Error: couldn't generate blog data:", err)
+ }
return nil
}
@@ -22,17 +38,32 @@ func UpdateActiveTheme(activeTheme string, userId int64) error {
if err != nil {
return err
}
+ // Generate new global blog
+ err = GenerateBlog()
+ if err != nil {
+ log.Panic("Error: couldn't generate blog data:", err)
+ }
return nil
}
-func GenerateBlog() (*structure.Blog, error) {
+func GenerateBlog() error {
+ // Write lock the global blog
+ if Blog != nil {
+ Blog.Lock()
+ defer Blog.Unlock()
+ }
// Generate blog from db
blog, err := database.RetrieveBlog()
if err != nil {
- return nil, err
+ return err
}
// Add parameters that are not saved in db
blog.Url = []byte(configuration.Config.Url)
blog.AssetPath = assetPath
- return blog, nil
+ // Create navigation slugs
+ for index, _ := range blog.NavigationItems {
+ blog.NavigationItems[index].Slug = slug.Generate(blog.NavigationItems[index].Label, "navigation")
+ }
+ Blog = blog
+ return nil
}
diff --git a/structure/methods/post.go b/structure/methods/post.go
index 16d09024..aeda0e06 100644
--- a/structure/methods/post.go
+++ b/structure/methods/post.go
@@ -3,6 +3,7 @@ package methods
import (
"github.com/kabukky/journey/database"
"github.com/kabukky/journey/structure"
+ "log"
"time"
)
@@ -35,6 +36,11 @@ func SavePost(p *structure.Post) error {
return err
}
}
+ // Generate new global blog
+ err = GenerateBlog()
+ if err != nil {
+ log.Panic("Error: couldn't generate blog data:", err)
+ }
return nil
}
@@ -72,5 +78,23 @@ func UpdatePost(p *structure.Post) error {
return err
}
}
+ // Generate new global blog
+ err = GenerateBlog()
+ if err != nil {
+ log.Panic("Error: couldn't generate blog data:", err)
+ }
+ return nil
+}
+
+func DeletePost(postId int64) error {
+ err := database.DeletePostById(postId)
+ if err != nil {
+ return err
+ }
+ // Generate new global blog
+ err = GenerateBlog()
+ if err != nil {
+ log.Panic("Error: couldn't generate blog data:", err)
+ }
return nil
}
diff --git a/structure/navigation.go b/structure/navigation.go
new file mode 100644
index 00000000..5331df02
--- /dev/null
+++ b/structure/navigation.go
@@ -0,0 +1,8 @@
+package structure
+
+// Navigation: an entry in the navigation menu
+type Navigation struct {
+ Label string `json:"label"`
+ Url string `json:"url"`
+ Slug string `json:"-"`
+}
diff --git a/structure/requestdata.go b/structure/requestdata.go
index 6f46567e..69c5472e 100644
--- a/structure/requestdata.go
+++ b/structure/requestdata.go
@@ -6,13 +6,16 @@ import (
// RequestData: used for template/helper execution. Contains data specific to the incoming request.
type RequestData struct {
- PluginVMs map[string]*lua.LState
- Posts []Post
- Blog *Blog
- CurrentTag *Tag
- CurrentIndexPage int
- CurrentPostIndex int
- CurrentTagIndex int
- CurrentHelperContext int // 0 = index, 1 = post, 2 = tag, 3 = author - used by block helpers
- CurrentTemplate int // 0 = index, 1 = post, 2 = tag, 3 = author - never changes during execution. Used by funcs like body_classFunc etc to output the correct class
+ PluginVMs map[string]*lua.LState
+ Posts []Post
+ Blog *Blog
+ CurrentTag *Tag
+ CurrentIndexPage int
+ CurrentPostIndex int
+ CurrentTagIndex int
+ CurrentNavigationIndex int
+ CurrentHelperContext int // 0 = index, 1 = post, 2 = tag, 3 = author, 4 = navigation - used by block helpers
+ CurrentTemplate int // 0 = index, 1 = post, 2 = tag, 3 = author - never changes during execution. Used by funcs like body_classFunc etc to output the correct class
+ ContentForHelpers []Helper // contentFor helpers that are attached to the currently rendering helper
+ CurrentPath string // path of the the url of this request
}
diff --git a/templates/generation.go b/templates/generation.go
index 70eea741..80e389ce 100644
--- a/templates/generation.go
+++ b/templates/generation.go
@@ -12,6 +12,7 @@ import (
"github.com/kabukky/journey/structure/methods"
"github.com/kabukky/journey/watcher"
"io/ioutil"
+ "log"
"os"
"path/filepath"
"regexp"
@@ -170,10 +171,10 @@ func compileTemplate(data []byte, name string) *structure.Helper {
data, allHelpers = findHelper(data, allHelpers)
baseHelper.Block = data
baseHelper.Children = allHelpers
- // Handle extend helper
+ // Handle extend helpers
for index, child := range baseHelper.Children {
if child.Name == "body" {
- baseHelper.BodyHelper = &baseHelper.Children[index]
+ baseHelper.BodyHelper = &baseHelper.Children[index] //TODO: This handles only one body helper per hbs file. That is a potential bug source, but no theme should be using more than one per file anyway.
}
}
return &baseHelper
@@ -193,13 +194,21 @@ func createTemplateFromFile(filename string) (*structure.Helper, error) {
return helper, nil
}
+func compileFile(fileName string) error {
+ helper, err := createTemplateFromFile(fileName)
+ if err != nil {
+ return err
+ }
+ compiledTemplates.m[helper.Name] = helper
+ return nil
+}
+
func inspectTemplateFile(filePath string, info os.FileInfo, err error) error {
if !info.IsDir() && filepath.Ext(filePath) == ".hbs" {
- helper, err := createTemplateFromFile(filePath)
+ err := compileFile(filePath)
if err != nil {
return err
}
- compiledTemplates.m[helper.Name] = helper
}
return nil
}
@@ -220,6 +229,21 @@ func compileTheme(themePath string) error {
if _, ok := compiledTemplates.m["post"]; !ok {
return errors.New("Couldn't compile template 'post'. Is post.hbs missing?")
}
+ // Check if pagination and navigation templates have been provided by the theme.
+ // If not, use the build in ones.
+ if _, ok := compiledTemplates.m["pagination"]; !ok {
+ err = compileFile(filepath.Join(filenames.HbsFilepath, "pagination.hbs"))
+ if err != nil {
+ log.Println("Warning: Couldn't compile pagination template.")
+ }
+ }
+ if _, ok := compiledTemplates.m["navigation"]; !ok {
+ err = compileFile(filepath.Join(filenames.HbsFilepath, "navigation.hbs"))
+ if err != nil {
+ log.Println("Warning: Couldn't compile navigation template.")
+ }
+
+ }
return nil
}
diff --git a/templates/handlebars.go b/templates/handlebars.go
index b5d6f16a..90fbdc82 100644
--- a/templates/handlebars.go
+++ b/templates/handlebars.go
@@ -39,7 +39,75 @@ func nullFunc(helper *structure.Helper, values *structure.RequestData) []byte {
values.PluginVMs = nil
}
}
- //log.Println("Warning: This helper is not implemented:", helper.Name)
+ log.Println("Warning: This helper is not implemented:", helper.Name)
+ return []byte{}
+}
+
+func slugFunc(helper *structure.Helper, values *structure.RequestData) []byte {
+ if len(values.Blog.NavigationItems) != 0 {
+ return evaluateEscape([]byte(values.Blog.NavigationItems[values.CurrentNavigationIndex].Slug), helper.Unescaped)
+ }
+ return []byte{}
+}
+
+func currentFunc(helper *structure.Helper, values *structure.RequestData) []byte {
+ if len(values.Blog.NavigationItems) != 0 {
+ url := values.Blog.NavigationItems[values.CurrentNavigationIndex].Url
+ // Since the router rewrites all urls with a trailing slash, add / to url if not already there
+ if !strings.HasSuffix(url, "/") {
+ url = url + "/"
+ }
+ if values.CurrentPath == url {
+ return []byte{1}
+ }
+ }
+ return []byte{}
+}
+
+func navigationFunc(helper *structure.Helper, values *structure.RequestData) []byte {
+ if len(values.Blog.NavigationItems) == 0 {
+ return []byte{}
+ } else if templateHelper, ok := compiledTemplates.m["navigation"]; ok {
+ return executeHelper(templateHelper, values, values.CurrentHelperContext)
+ }
+ return []byte{}
+}
+
+func labelFunc(helper *structure.Helper, values *structure.RequestData) []byte {
+ if len(values.Blog.NavigationItems) != 0 {
+ return evaluateEscape([]byte(values.Blog.NavigationItems[values.CurrentNavigationIndex].Label), helper.Unescaped)
+ }
+ return []byte{}
+}
+
+func contentForFunc(helper *structure.Helper, values *structure.RequestData) []byte {
+ // If there is no array attached to the request data already, make one
+ if values.ContentForHelpers == nil {
+ values.ContentForHelpers = make([]structure.Helper, 0)
+ }
+ // Collect all contentFor helpers to use them with a block helper
+ values.ContentForHelpers = append(values.ContentForHelpers, *helper)
+ return []byte{}
+}
+
+func blockFunc(helper *structure.Helper, values *structure.RequestData) []byte {
+ if len(helper.Arguments) != 0 {
+ // Loop through the collected contentFor helpers and execute the appropriate one
+ for index, _ := range values.ContentForHelpers {
+ if len(values.ContentForHelpers[index].Arguments) != 0 {
+ if values.ContentForHelpers[index].Arguments[0].Name == helper.Arguments[0].Name {
+ return executeHelper(&values.ContentForHelpers[index], values, values.CurrentHelperContext)
+ }
+ }
+ }
+ }
+ return []byte{}
+}
+
+func paginationFunc(helper *structure.Helper, values *structure.RequestData) []byte {
+ if templateHelper, ok := compiledTemplates.m["pagination"]; ok {
+ return executeHelper(templateHelper, values, values.CurrentHelperContext)
+ }
return []byte{}
}
@@ -117,7 +185,7 @@ func nextFunc(helper *structure.Helper, values *structure.RequestData) []byte {
return []byte{}
}
}
- maxPages := int64((float64(count) / float64(values.Blog.PostsPerPage)) + 0.5)
+ maxPages := positiveCeilingInt64(float64(count) / float64(values.Blog.PostsPerPage))
if int64(values.CurrentIndexPage) < maxPages {
return []byte{1}
}
@@ -146,7 +214,11 @@ func pagesFunc(helper *structure.Helper, values *structure.RequestData) []byte {
return []byte{}
}
}
- maxPages := int64((float64(count) / float64(values.Blog.PostsPerPage)) + 0.5)
+ maxPages := positiveCeilingInt64(float64(count) / float64(values.Blog.PostsPerPage))
+ // Output at least 1 (even if there are no posts in the database)
+ if maxPages == 0 {
+ maxPages = 1
+ }
return []byte(strconv.FormatInt(maxPages, 10))
}
@@ -200,7 +272,7 @@ func page_urlFunc(helper *structure.Helper, values *structure.RequestData) []byt
return []byte{}
}
}
- maxPages := int64((float64(count) / float64(values.Blog.PostsPerPage)) + 0.5)
+ maxPages := positiveCeilingInt64(float64(count) / float64(values.Blog.PostsPerPage))
if int64(values.CurrentIndexPage) < maxPages {
var buffer bytes.Buffer
if values.CurrentTemplate == 3 { // author
@@ -344,7 +416,14 @@ func authorFunc(helper *structure.Helper, values *structure.RequestData) []byte
if len(helper.Block) != 0 {
return executeHelper(helper, values, 3) // context = author
}
- // Else return author.name
+ // Else return author name (as link)
+ arguments := methods.ProcessHelperArguments(helper.Arguments)
+ for key, value := range arguments {
+ // If link is set to false, just return the name
+ if key == "autolink" && value == "false" {
+ return evaluateEscape(values.Posts[values.CurrentPostIndex].Author.Name, helper.Unescaped)
+ }
+ }
var buffer bytes.Buffer
buffer.WriteString("")
- // TODO: Error handling if there is no Posts[values.CurrentPostIndex]
- buffer.Write(evaluateEscape(values.Posts[values.CurrentPostIndex].Author.Name, helper.Unescaped))
- buffer.WriteString("")
- return buffer.Bytes()
+ return evaluateEscape(values.Posts[values.CurrentPostIndex].Author.Name, helper.Unescaped)
}
func bioFunc(helper *structure.Helper, values *structure.RequestData) []byte {
@@ -496,7 +566,12 @@ func urlFunc(helper *structure.Helper, values *structure.RequestData) []byte {
for key, value := range arguments {
if key == "absolute" {
if value == "true" {
- buffer.Write(values.Blog.Url)
+ // Only write the blog url if navigation url does not begin with http/https
+ if values.CurrentHelperContext == 4 && (!strings.HasPrefix(values.Blog.NavigationItems[values.CurrentNavigationIndex].Url, "http://") && !strings.HasPrefix(values.Blog.NavigationItems[values.CurrentNavigationIndex].Url, "https://")) { // navigation
+ buffer.Write(values.Blog.Url)
+ } else if values.CurrentHelperContext != 4 {
+ buffer.Write(values.Blog.Url)
+ }
}
}
}
@@ -512,6 +587,9 @@ func urlFunc(helper *structure.Helper, values *structure.RequestData) []byte {
buffer.WriteString(values.Posts[values.CurrentPostIndex].Author.Slug)
buffer.WriteString("/")
return evaluateEscape(buffer.Bytes(), helper.Unescaped)
+ } else if values.CurrentHelperContext == 4 { // author
+ buffer.WriteString(values.Blog.NavigationItems[values.CurrentNavigationIndex].Url)
+ return evaluateEscape(buffer.Bytes(), helper.Unescaped)
}
return []byte{}
}
@@ -663,7 +741,7 @@ func atOddFunc(helper *structure.Helper, values *structure.RequestData) []byte {
}
func nameFunc(helper *structure.Helper, values *structure.RequestData) []byte {
- // If tag (commented out the code for generating a link. Ghost doesn't seem to do that either.
+ // If tag (commented out the code for generating a link. Ghost doesn't seem to do that either).
if values.CurrentHelperContext == 2 { // tag
//var buffer bytes.Buffer
//buffer.WriteString(" values.Blog.PostsPerPage {
- maxPages := int64((float64(count) / float64(values.Blog.PostsPerPage)) + 0.5)
- var buffer bytes.Buffer
- buffer.WriteString("")
- return buffer.Bytes()
- } else {
- return []byte("")
- }
-}
-
func idFunc(helper *structure.Helper, values *structure.RequestData) []byte {
return []byte(strconv.FormatInt(values.Posts[values.CurrentPostIndex].Id, 10))
}
@@ -821,6 +817,13 @@ func foreachFunc(helper *structure.Helper, values *structure.RequestData) []byte
//}
}
return buffer.Bytes()
+ case "navigation":
+ var buffer bytes.Buffer
+ for index, _ := range values.Blog.NavigationItems {
+ values.CurrentNavigationIndex = index
+ buffer.Write(executeHelper(helper, values, 4)) // context = navigation
+ }
+ return buffer.Bytes()
default:
return []byte{}
}
@@ -883,3 +886,11 @@ func evaluateEscape(value []byte, unescaped bool) []byte {
}
return []byte(html.EscapeString(string(value)))
}
+
+func positiveCeilingInt64(input float64) int64 {
+ output := int64(input)
+ if (input - float64(output)) > 0 {
+ output++
+ }
+ return output
+}
diff --git a/templates/helperfunctions.go b/templates/helperfunctions.go
index 687a0729..01deaa24 100644
--- a/templates/helperfunctions.go
+++ b/templates/helperfunctions.go
@@ -16,7 +16,6 @@ var helperFuctions = map[string]func(*structure.Helper, *structure.RequestData)
"!<": extendFunc,
"body": bodyFunc,
"asset": assetFunc,
- "pagination": paginationFunc,
"encode": encodeFunc,
">": insertFunc,
"meta_title": meta_titleFunc,
@@ -27,6 +26,8 @@ var helperFuctions = map[string]func(*structure.Helper, *structure.RequestData)
"plural": pluralFunc,
"date": dateFunc,
"image": imageFunc,
+ "contentFor": contentForFunc,
+ "block": blockFunc,
// @blog functions
"@blog.title": atBlogDotTitleFunc,
@@ -34,13 +35,13 @@ var helperFuctions = map[string]func(*structure.Helper, *structure.RequestData)
"@blog.logo": atBlogDotLogoFunc,
"@blog.cover": atBlogDotCoverFunc,
"@blog.description": atBlogDotDescriptionFunc,
+ "@blog.navigation": navigationFunc,
// Post functions
"post": postFunc,
"excerpt": excerptFunc,
"title": titleFunc,
"content": contentFunc,
- "url": urlFunc,
"post_class": post_classFunc,
"featured": featuredFunc,
"id": idFunc,
@@ -65,20 +66,28 @@ var helperFuctions = map[string]func(*structure.Helper, *structure.RequestData)
"author.cover": coverFunc,
"author.location": locationFunc,
+ // Navigation functions
+ "navigation": navigationFunc,
+ "label": labelFunc,
+ "current": currentFunc,
+ "slug": slugFunc,
+
// Multiple block functions
"@first": atFirstFunc,
"@last": atLastFunc,
"@even": atEvenFunc,
"@odd": atOddFunc,
"name": nameFunc,
+ "url": urlFunc,
// Pagination functions
- "prev": prevFunc,
- "next": nextFunc,
- "page": pageFunc,
- "pages": pagesFunc,
- "page_url": page_urlFunc,
- "pageUrl": page_urlFunc,
+ "pagination": paginationFunc,
+ "prev": prevFunc,
+ "next": nextFunc,
+ "page": pageFunc,
+ "pages": pagesFunc,
+ "page_url": page_urlFunc,
+ "pageUrl": page_urlFunc,
// Possible if arguments
"posts": postsFunc,
diff --git a/templates/templates.go b/templates/templates.go
index 37536e24..c6d3dbdb 100644
--- a/templates/templates.go
+++ b/templates/templates.go
@@ -21,23 +21,22 @@ type Templates struct {
func newTemplates() *Templates { return &Templates{m: make(map[string]*structure.Helper)} }
-// Global compiled templates - thread safe and accessible from all packages
+// Global compiled templates - thread safe and accessible by all requests
var compiledTemplates = newTemplates()
-func ShowPostTemplate(writer http.ResponseWriter, slug string) error {
+func ShowPostTemplate(writer http.ResponseWriter, r *http.Request, slug string) error {
+ // Read lock templates and global blog
compiledTemplates.RLock()
defer compiledTemplates.RUnlock()
- blog, err := methods.GenerateBlog()
- if err != nil {
- return err
- }
+ methods.Blog.RLock()
+ defer methods.Blog.RUnlock()
post, err := database.RetrievePostBySlug(slug)
if err != nil {
return err
} else if !post.IsPublished { // Make sure the post is published before rendering it
return errors.New("Post not published.")
}
- requestData := structure.RequestData{Posts: make([]structure.Post, 1), Blog: blog, CurrentTemplate: 1} // CurrentTemplate = post
+ requestData := structure.RequestData{Posts: make([]structure.Post, 1), Blog: methods.Blog, CurrentTemplate: 1, CurrentPath: r.URL.Path} // CurrentTemplate = post
requestData.Posts[0] = *post
// If the post is a page and the page template is available, use the page template
if post.IsPage {
@@ -54,26 +53,25 @@ func ShowPostTemplate(writer http.ResponseWriter, slug string) error {
return err
}
-func ShowAuthorTemplate(writer http.ResponseWriter, slug string, page int) error {
+func ShowAuthorTemplate(writer http.ResponseWriter, r *http.Request, slug string, page int) error {
+ // Read lock templates and global blog
compiledTemplates.RLock()
defer compiledTemplates.RUnlock()
+ methods.Blog.RLock()
+ defer methods.Blog.RUnlock()
postIndex := int64(page - 1)
if postIndex < 0 {
postIndex = 0
}
- blog, err := methods.GenerateBlog()
- if err != nil {
- return err
- }
author, err := database.RetrieveUserBySlug(slug)
if err != nil {
return err
}
- posts, err := database.RetrievePostsByUser(author.Id, blog.PostsPerPage, (blog.PostsPerPage * postIndex))
+ posts, err := database.RetrievePostsByUser(author.Id, methods.Blog.PostsPerPage, (methods.Blog.PostsPerPage * postIndex))
if err != nil {
return err
}
- requestData := structure.RequestData{Posts: posts, Blog: blog, CurrentIndexPage: page, CurrentTemplate: 3} // CurrentTemplate = author
+ requestData := structure.RequestData{Posts: posts, Blog: methods.Blog, CurrentIndexPage: page, CurrentTemplate: 3, CurrentPath: r.URL.Path} // CurrentTemplate = author
if template, ok := compiledTemplates.m["author"]; ok {
_, err = writer.Write(executeHelper(template, &requestData, 0)) // context = index
} else {
@@ -86,26 +84,25 @@ func ShowAuthorTemplate(writer http.ResponseWriter, slug string, page int) error
return err
}
-func ShowTagTemplate(writer http.ResponseWriter, slug string, page int) error {
+func ShowTagTemplate(writer http.ResponseWriter, r *http.Request, slug string, page int) error {
+ // Read lock templates and global blog
compiledTemplates.RLock()
defer compiledTemplates.RUnlock()
+ methods.Blog.RLock()
+ defer methods.Blog.RUnlock()
postIndex := int64(page - 1)
if postIndex < 0 {
postIndex = 0
}
- blog, err := methods.GenerateBlog()
- if err != nil {
- return err
- }
tag, err := database.RetrieveTagBySlug(slug)
if err != nil {
return err
}
- posts, err := database.RetrievePostsByTag(tag.Id, blog.PostsPerPage, (blog.PostsPerPage * postIndex))
+ posts, err := database.RetrievePostsByTag(tag.Id, methods.Blog.PostsPerPage, (methods.Blog.PostsPerPage * postIndex))
if err != nil {
return err
}
- requestData := structure.RequestData{Posts: posts, Blog: blog, CurrentIndexPage: page, CurrentTag: tag, CurrentTemplate: 2} // CurrentTemplate = tag
+ requestData := structure.RequestData{Posts: posts, Blog: methods.Blog, CurrentIndexPage: page, CurrentTag: tag, CurrentTemplate: 2, CurrentPath: r.URL.Path} // CurrentTemplate = tag
if template, ok := compiledTemplates.m["tag"]; ok {
_, err = writer.Write(executeHelper(template, &requestData, 0)) // context = index
} else {
@@ -118,23 +115,22 @@ func ShowTagTemplate(writer http.ResponseWriter, slug string, page int) error {
return err
}
-func ShowIndexTemplate(writer http.ResponseWriter, page int) error {
+func ShowIndexTemplate(w http.ResponseWriter, r *http.Request, page int) error {
+ // Read lock templates and global blog
compiledTemplates.RLock()
defer compiledTemplates.RUnlock()
+ methods.Blog.RLock()
+ defer methods.Blog.RUnlock()
postIndex := int64(page - 1)
if postIndex < 0 {
postIndex = 0
}
- blog, err := methods.GenerateBlog()
- if err != nil {
- return err
- }
- posts, err := database.RetrievePostsForIndex(blog.PostsPerPage, (blog.PostsPerPage * postIndex))
+ posts, err := database.RetrievePostsForIndex(methods.Blog.PostsPerPage, (methods.Blog.PostsPerPage * postIndex))
if err != nil {
return err
}
- requestData := structure.RequestData{Posts: posts, Blog: blog, CurrentIndexPage: page, CurrentTemplate: 0} // CurrentTemplate = index
- _, err = writer.Write(executeHelper(compiledTemplates.m["index"], &requestData, 0)) // context = index
+ requestData := structure.RequestData{Posts: posts, Blog: methods.Blog, CurrentIndexPage: page, CurrentTemplate: 0, CurrentPath: r.URL.Path} // CurrentTemplate = index
+ _, err = w.Write(executeHelper(compiledTemplates.m["index"], &requestData, 0)) // context = index
if requestData.PluginVMs != nil {
// Put the lua state map back into the pool
plugins.LuaPool.Put(requestData.PluginVMs)
diff --git a/watcher/watcher.go b/watcher/watcher.go
index 511e82da..7180e22e 100644
--- a/watcher/watcher.go
+++ b/watcher/watcher.go
@@ -1,6 +1,7 @@
package watcher
import (
+ "github.com/kabukky/journey/helpers"
"gopkg.in/fsnotify.v1"
"log"
"os"
@@ -58,7 +59,7 @@ func createWatcher(extensionsFunctions map[string]func() error) (*fsnotify.Watch
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
for key, value := range extensionsFunctions {
- if filepath.Ext(event.Name) == key {
+ if !helpers.IsDirectory(event.Name) && filepath.Ext(event.Name) == key {
// Call the function associated with this file extension
err := value()
if err != nil {