aboutsummaryrefslogtreecommitdiff
path: root/static/js
diff options
context:
space:
mode:
Diffstat (limited to 'static/js')
-rw-r--r--static/js/.gitignore1
-rw-r--r--static/js/config.js4
-rw-r--r--static/js/controllers/auth_controller.js34
-rw-r--r--static/js/controllers/comments_controller.js13
-rw-r--r--static/js/controllers/help_controller.js13
-rw-r--r--static/js/controllers/history_controller.js13
-rw-r--r--static/js/controllers/home_controller.js17
-rw-r--r--static/js/controllers/posts_controller.js25
-rw-r--r--static/js/controllers/tags_controller.js13
-rw-r--r--static/js/controllers/top_navigation_controller.js69
-rw-r--r--static/js/controllers/users_controller.js38
-rw-r--r--static/js/main.js70
-rw-r--r--static/js/views/base_view.js37
-rw-r--r--static/js/views/registration_view.js35
-rw-r--r--static/js/views/top_navigation_view.js30
15 files changed, 412 insertions, 0 deletions
diff --git a/static/js/.gitignore b/static/js/.gitignore
new file mode 100644
index 0000000..16e3bbe
--- /dev/null
+++ b/static/js/.gitignore
@@ -0,0 +1 @@
+.config.autogen.json
diff --git a/static/js/config.js b/static/js/config.js
new file mode 100644
index 0000000..4524be6
--- /dev/null
+++ b/static/js/config.js
@@ -0,0 +1,4 @@
+'use strict';
+
+const config = require('./.config.autogen.json');
+module.exports = config;
diff --git a/static/js/controllers/auth_controller.js b/static/js/controllers/auth_controller.js
new file mode 100644
index 0000000..e23d753
--- /dev/null
+++ b/static/js/controllers/auth_controller.js
@@ -0,0 +1,34 @@
+'use strict';
+
+class AuthController {
+ constructor(topNavigationController) {
+ this.topNavigationController = topNavigationController;
+ this.currentUser = null;
+ }
+
+ isLoggedIn() {
+ return this.currentUser !== null;
+ }
+
+ hasPrivilege() {
+ return true;
+ }
+
+ login(user) {
+ this.currentUser = user;
+ }
+
+ logout(user) {
+ this.currentUser = null;
+ }
+
+ loginRoute() {
+ this.topNavigationController.activate('login');
+ }
+
+ logoutRoute() {
+ this.topNavigationController.activate('logout');
+ }
+}
+
+module.exports = AuthController;
diff --git a/static/js/controllers/comments_controller.js b/static/js/controllers/comments_controller.js
new file mode 100644
index 0000000..b6ff02c
--- /dev/null
+++ b/static/js/controllers/comments_controller.js
@@ -0,0 +1,13 @@
+'use strict';
+
+class CommentsController {
+ constructor(topNavigationController) {
+ this.topNavigationController = topNavigationController;
+ }
+
+ listCommentsRoute() {
+ this.topNavigationController.activate('comments');
+ }
+}
+
+module.exports = CommentsController;
diff --git a/static/js/controllers/help_controller.js b/static/js/controllers/help_controller.js
new file mode 100644
index 0000000..78eeab3
--- /dev/null
+++ b/static/js/controllers/help_controller.js
@@ -0,0 +1,13 @@
+'use strict';
+
+class HelpController {
+ constructor(topNavigationController) {
+ this.topNavigationController = topNavigationController;
+ }
+
+ showHelpRoute() {
+ this.topNavigationController.activate('help');
+ }
+}
+
+module.exports = HelpController;
diff --git a/static/js/controllers/history_controller.js b/static/js/controllers/history_controller.js
new file mode 100644
index 0000000..ff0b264
--- /dev/null
+++ b/static/js/controllers/history_controller.js
@@ -0,0 +1,13 @@
+'use strict';
+
+class HistoryController {
+ constructor(topNavigationController) {
+ this.topNavigationController = topNavigationController;
+ }
+
+ listHistoryRoute() {
+ this.topNavigationController.activate('');
+ }
+}
+
+module.exports = HistoryController;
diff --git a/static/js/controllers/home_controller.js b/static/js/controllers/home_controller.js
new file mode 100644
index 0000000..1b913fa
--- /dev/null
+++ b/static/js/controllers/home_controller.js
@@ -0,0 +1,17 @@
+'use strict';
+
+class HomeController {
+ constructor(topNavigationController) {
+ this.topNavigationController = topNavigationController;
+ }
+
+ indexRoute() {
+ this.topNavigationController.activate('home');
+ }
+
+ notFoundRoute() {
+ this.topNavigationController.activate('');
+ }
+}
+
+module.exports = HomeController;
diff --git a/static/js/controllers/posts_controller.js b/static/js/controllers/posts_controller.js
new file mode 100644
index 0000000..bbd540c
--- /dev/null
+++ b/static/js/controllers/posts_controller.js
@@ -0,0 +1,25 @@
+'use strict';
+
+class PostsController {
+ constructor(topNavigationController) {
+ this.topNavigationController = topNavigationController;
+ }
+
+ uploadPostsRoute() {
+ this.topNavigationController.activate('upload');
+ }
+
+ listPostsRoute() {
+ this.topNavigationController.activate('posts');
+ }
+
+ showPostRoute(id) {
+ this.topNavigationController.activate('posts');
+ }
+
+ editPostRoute(id) {
+ this.topNavigationController.activate('posts');
+ }
+}
+
+module.exports = PostsController;
diff --git a/static/js/controllers/tags_controller.js b/static/js/controllers/tags_controller.js
new file mode 100644
index 0000000..3124d40
--- /dev/null
+++ b/static/js/controllers/tags_controller.js
@@ -0,0 +1,13 @@
+'use strict';
+
+class TagsController {
+ constructor(topNavigationController) {
+ this.topNavigationController = topNavigationController;
+ }
+
+ listTagsRoute() {
+ this.topNavigationController.activate('tags');
+ }
+}
+
+module.exports = TagsController;
diff --git a/static/js/controllers/top_navigation_controller.js b/static/js/controllers/top_navigation_controller.js
new file mode 100644
index 0000000..2bd098d
--- /dev/null
+++ b/static/js/controllers/top_navigation_controller.js
@@ -0,0 +1,69 @@
+'use strict';
+
+class NavigationItem {
+ constructor(name, url) {
+ this.name = name;
+ this.url = url;
+ this.available = true;
+ }
+}
+
+class TopNavigationController {
+ constructor(topNavigationView, authController) {
+ this.authController = authController;
+ this.topNavigationView = topNavigationView;
+
+ this.items = {
+ 'home': new NavigationItem('Home', '/'),
+ 'posts': new NavigationItem('Posts', '/posts'),
+ 'upload': new NavigationItem('Upload', '/upload'),
+ 'comments': new NavigationItem('Comments', '/comments'),
+ 'tags': new NavigationItem('Tags', '/tags'),
+ 'users': new NavigationItem('Users', '/users'),
+ 'account': new NavigationItem('Account', '/user/{me}'),
+ 'register': new NavigationItem('Register', '/register'),
+ 'login': new NavigationItem('Login', '/login'),
+ 'logout': new NavigationItem('Logout', '/logout'),
+ 'help': new NavigationItem('Help', '/help'),
+ };
+
+ this.updateVisibility();
+
+ this.topNavigationView.render(this.items);
+ }
+
+ updateVisibility() {
+ const b = Object.keys(this.items);
+ for (let key of b) {
+ this.items[key].available = true;
+ }
+ if (!this.authController.hasPrivilege('posts:list')) {
+ this.items.posts.available = false;
+ }
+ if (!this.authController.hasPrivilege('posts:upload')) {
+ this.items.upload.available = false;
+ }
+ if (!this.authController.hasPrivilege('comments:list')) {
+ this.items.comments.available = false;
+ }
+ if (!this.authController.hasPrivilege('tags:list')) {
+ this.items.tags.available = false;
+ }
+ if (!this.authController.hasPrivilege('users:list')) {
+ this.items.users.available = false;
+ }
+ if (this.authController.isLoggedIn()) {
+ this.items.register.available = false;
+ this.items.login.available = false;
+ } else {
+ this.items.account.available = false;
+ this.items.logout.available = false;
+ }
+ }
+
+ activate(itemName) {
+ this.topNavigationView.activate(itemName);
+ }
+}
+
+module.exports = TopNavigationController;
diff --git a/static/js/controllers/users_controller.js b/static/js/controllers/users_controller.js
new file mode 100644
index 0000000..a5e800d
--- /dev/null
+++ b/static/js/controllers/users_controller.js
@@ -0,0 +1,38 @@
+'use strict';
+
+class UsersController {
+ constructor(topNavigationController, authController, registrationView) {
+ this.topNavigationController = topNavigationController;
+ this.authController = authController;
+ this.registrationView = registrationView;
+ }
+
+ listUsersRoute() {
+ this.topNavigationController.activate('users');
+ }
+
+ createUserRoute() {
+ const self = this;
+ this.topNavigationController.activate('register');
+ this.registrationView.render({
+ onRegistered: (user) => {
+ alert(user);
+ self.authController.login(user);
+ }});
+ }
+
+ showUserRoute(user) {
+ if (this.authController.isLoggedIn() &&
+ user == this.authController.getCurrentUser().name) {
+ this.topNavigationController.activate('account');
+ } else {
+ this.topNavigationController.activate('users');
+ }
+ }
+
+ editUserRoute(user) {
+ this.topNavigationController.activate('users');
+ }
+}
+
+module.exports = UsersController;
diff --git a/static/js/main.js b/static/js/main.js
new file mode 100644
index 0000000..bd9216e
--- /dev/null
+++ b/static/js/main.js
@@ -0,0 +1,70 @@
+'use strict';
+
+// ------------------
+// - import objects -
+// ------------------
+const page = require('page');
+const handlebars = require('handlebars');
+
+const RegistrationView = require('./views/registration_view.js');
+const TopNavigationView = require('./views/top_navigation_view.js');
+const TopNavigationController
+ = require('./controllers/top_navigation_controller.js');
+
+const HomeController = require('./controllers/home_controller.js');
+const PostsController = require('./controllers/posts_controller.js');
+const UsersController = require('./controllers/users_controller.js');
+const HelpController = require('./controllers/help_controller.js');
+const AuthController = require('./controllers/auth_controller.js');
+const CommentsController = require('./controllers/comments_controller.js');
+const HistoryController = require('./controllers/history_controller.js');
+const TagsController = require('./controllers/tags_controller.js');
+
+// -------------------
+// - resolve objects -
+// -------------------
+const topNavigationView = new TopNavigationView(handlebars);
+const registrationView = new RegistrationView(handlebars);
+
+const authController = new AuthController(null);
+const topNavigationController
+ = new TopNavigationController(topNavigationView, authController);
+// break cyclic dependency topNavigationView<->authController
+authController.topNavigationController = topNavigationController;
+
+const homeController = new HomeController(topNavigationController);
+const postsController = new PostsController(topNavigationController);
+const usersController = new UsersController(
+ topNavigationController,
+ authController,
+ registrationView);
+const helpController = new HelpController(topNavigationController);
+const commentsController = new CommentsController(topNavigationController);
+const historyController = new HistoryController(topNavigationController);
+const tagsController = new TagsController(topNavigationController);
+
+// -----------------
+// - setup routing -
+// -----------------
+page('/', () => { homeController.indexRoute(); });
+
+page('/upload', () => { postsController.uploadPostsRoute(); });
+page('/posts', () => { postsController.listPostsRoute(); });
+page('/post/:id', (id) => { postsController.showPostRoute(id); });
+page('/post/:id/edit', (id) => { postsController.editPostRoute(id); });
+
+page('/register', () => { usersController.createUserRoute(); });
+page('/users', () => { usersController.listUsersRoute(); });
+page('/user/:user', (user) => { usersController.showUserRoute(user); });
+page('/user/:user/edit', (user) => { usersController.editUserRoute(user); });
+
+page('/history', () => { historyController.showHistoryRoute(); });
+page('/tags', () => { tagsController.listTagsRoute(); });
+page('/comments', () => { commentsController.listCommentsRoute(); });
+page('/login', () => { authController.loginRoute(); });
+page('/logout', () => { authController.logoutRoute(); });
+page('/help', () => { helpController.showHelpRoute(); });
+
+page('*', () => { homeController.notFoundRoute(); });
+
+page();
diff --git a/static/js/views/base_view.js b/static/js/views/base_view.js
new file mode 100644
index 0000000..06baabd
--- /dev/null
+++ b/static/js/views/base_view.js
@@ -0,0 +1,37 @@
+'use strict';
+
+// fix iterating over NodeList in Chrome and Opera
+NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
+
+class BaseView {
+ constructor(handlebars) {
+ this.handlebars = handlebars;
+ this.contentHolder = document.getElementById('content-holder');
+ }
+
+ getTemplate(templatePath) {
+ const templateElement = document.getElementById(templatePath);
+ if (!templateElement) {
+ console.log('Missing template: ' + templatePath);
+ return null;
+ }
+ const templateText = templateElement.innerHTML;
+ return this.handlebars.compile(templateText);
+ }
+
+ decorateValidator(form) {
+ // postpone showing form fields validity until user actually tries
+ // to submit it (seeing red/green form w/o doing anything breaks POLA)
+ const submitButton
+ = document.querySelector('#content-holder .buttons input');
+ submitButton.addEventListener('click', (e) => {
+ form.classList.add('show-validation');
+ });
+ }
+
+ showView(html) {
+ this.contentHolder.innerHTML = html;
+ }
+}
+
+module.exports = BaseView;
diff --git a/static/js/views/registration_view.js b/static/js/views/registration_view.js
new file mode 100644
index 0000000..2f1a31b
--- /dev/null
+++ b/static/js/views/registration_view.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const config = require('../config.js');
+const BaseView = require('./base_view.js');
+
+class RegistrationView extends BaseView {
+ constructor(handlebars) {
+ super(handlebars);
+ this.template = this.getTemplate('user-registration-template');
+ }
+
+ render(settings) {
+ this.showView(this.template());
+ const form = document.querySelector('#content-holder form');
+ this.decorateValidator(form);
+
+ const userNameField = document.getElementById('user-name');
+ const passwordField = document.getElementById('user-password');
+ const emailField = document.getElementById('user-email');
+ userNameField.setAttribute('pattern', config.service.userNameRegex);
+ passwordField.setAttribute('pattern', config.service.passwordRegex);
+
+ form.addEventListener('submit', (e) => {
+ e.preventDefault();
+ const user = {
+ name: userNameField.value,
+ password: passwordField.value,
+ email: emailField.value,
+ };
+ settings.onRegistered(user);
+ });
+ }
+}
+
+module.exports = RegistrationView;
diff --git a/static/js/views/top_navigation_view.js b/static/js/views/top_navigation_view.js
new file mode 100644
index 0000000..e7a6d0e
--- /dev/null
+++ b/static/js/views/top_navigation_view.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const BaseView = require('./base_view.js');
+
+class TopNavigationView extends BaseView {
+ constructor(handlebars) {
+ super(handlebars);
+ this.template = this.getTemplate('top-nav-template');
+ this.navHolder = document.getElementById('top-nav-holder');
+ }
+
+ render(items) {
+ this.navHolder.innerHTML = this.template({items: items});
+ }
+
+ activate(itemName) {
+ const allItemsSelector = '#top-nav-holder [data-name]';
+ const currentItemSelector =
+ '#top-nav-holder [data-name="' + itemName + '"]';
+ for (let item of document.querySelectorAll(allItemsSelector)) {
+ item.className = '';
+ }
+ const currentItem = document.querySelectorAll(currentItemSelector);
+ if (currentItem.length > 0) {
+ currentItem[0].className = 'active';
+ }
+ }
+}
+
+module.exports = TopNavigationView;

© 2015 - 2026 Jakob L. Kreuze