diff options
Diffstat (limited to 'static/js')
| -rw-r--r-- | static/js/.gitignore | 1 | ||||
| -rw-r--r-- | static/js/config.js | 4 | ||||
| -rw-r--r-- | static/js/controllers/auth_controller.js | 34 | ||||
| -rw-r--r-- | static/js/controllers/comments_controller.js | 13 | ||||
| -rw-r--r-- | static/js/controllers/help_controller.js | 13 | ||||
| -rw-r--r-- | static/js/controllers/history_controller.js | 13 | ||||
| -rw-r--r-- | static/js/controllers/home_controller.js | 17 | ||||
| -rw-r--r-- | static/js/controllers/posts_controller.js | 25 | ||||
| -rw-r--r-- | static/js/controllers/tags_controller.js | 13 | ||||
| -rw-r--r-- | static/js/controllers/top_navigation_controller.js | 69 | ||||
| -rw-r--r-- | static/js/controllers/users_controller.js | 38 | ||||
| -rw-r--r-- | static/js/main.js | 70 | ||||
| -rw-r--r-- | static/js/views/base_view.js | 37 | ||||
| -rw-r--r-- | static/js/views/registration_view.js | 35 | ||||
| -rw-r--r-- | static/js/views/top_navigation_view.js | 30 |
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; |