client/top-nav: trying out actual mvc
This commit is contained in:
parent
e93af8b577
commit
0f1e234a5d
22 changed files with 240 additions and 174 deletions
|
@ -1,6 +1,6 @@
|
||||||
$main-color = #24AADD
|
$main-color = #24AADD
|
||||||
$window-color = white
|
$window-color = white
|
||||||
$top-nav-color = #F5F5F5
|
$top-navigation-color = #F5F5F5
|
||||||
$text-color = #111
|
$text-color = #111
|
||||||
$inactive-link-color = #888
|
$inactive-link-color = #888
|
||||||
$line-color = #DDD
|
$line-color = #DDD
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
line-height: 16pt
|
line-height: 16pt
|
||||||
vertical-align: middle
|
vertical-align: middle
|
||||||
margin-bottom: 0.5em
|
margin-bottom: 0.5em
|
||||||
background: $top-nav-color
|
background: $top-navigation-color
|
||||||
padding: 0.2em 0.5em
|
padding: 0.2em 0.5em
|
||||||
|
|
||||||
.date, .score-container, .edit, .delete
|
.date, .score-container, .edit, .delete
|
||||||
|
|
|
@ -68,7 +68,7 @@ form .fa-question-circle-o
|
||||||
>*:first-child, form h1
|
>*:first-child, form h1
|
||||||
margin-top: 0
|
margin-top: 0
|
||||||
>.content-wrapper:not(.transparent)
|
>.content-wrapper:not(.transparent)
|
||||||
background: $top-nav-color
|
background: $top-navigation-color
|
||||||
padding: 2vw
|
padding: 2vw
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
@ -113,8 +113,8 @@ nav
|
||||||
background: $focused-tab-background-color
|
background: $focused-tab-background-color
|
||||||
outline: 0
|
outline: 0
|
||||||
|
|
||||||
&#top-nav
|
&#top-navigation
|
||||||
background: $top-nav-color
|
background: $top-navigation-color
|
||||||
margin: 0
|
margin: 0
|
||||||
ul
|
ul
|
||||||
display: block
|
display: block
|
||||||
|
@ -207,7 +207,7 @@ a .access-key
|
||||||
top: 50%
|
top: 50%
|
||||||
right: 0
|
right: 0
|
||||||
height: 3px
|
height: 3px
|
||||||
background: $top-nav-color
|
background: $top-navigation-color
|
||||||
z-index: 1
|
z-index: 1
|
||||||
span
|
span
|
||||||
position: relative
|
position: relative
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
text-align: left
|
text-align: left
|
||||||
line-height: 1.3em
|
line-height: 1.3em
|
||||||
tr:hover td
|
tr:hover td
|
||||||
background: $top-nav-color
|
background: $top-navigation-color
|
||||||
th, td
|
th, td
|
||||||
padding: 0.1em 0.5em
|
padding: 0.1em 0.5em
|
||||||
th
|
th
|
||||||
background: $top-nav-color
|
background: $top-navigation-color
|
||||||
.names
|
.names
|
||||||
width: 28%
|
width: 28%
|
||||||
.implications
|
.implications
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
margin: 0 0.5em 1em 0.5em
|
margin: 0 0.5em 1em 0.5em
|
||||||
padding: 0.75em
|
padding: 0.75em
|
||||||
vertical-align: top
|
vertical-align: top
|
||||||
background: $top-nav-color
|
background: $top-navigation-color
|
||||||
text-align: left
|
text-align: left
|
||||||
.wrapper
|
.wrapper
|
||||||
display: flex
|
display: flex
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<link rel='shortcut icon' type='image/png' href='/img/favicon.png'/>
|
<link rel='shortcut icon' type='image/png' href='/img/favicon.png'/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id='top-nav-holder'></div>
|
<div id='top-navigation-holder'></div>
|
||||||
<div id='content-holder'></div>
|
<div id='content-holder'></div>
|
||||||
<script type='text/javascript' src='/js/vendor.min.js'></script>
|
<script type='text/javascript' src='/js/vendor.min.js'></script>
|
||||||
<script type='text/javascript' src='/js/app.min.js'></script>
|
<script type='text/javascript' src='/js/app.min.js'></script>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<nav id='top-nav' class='buttons'>
|
<nav id='top-navigation' class='buttons'><!--
|
||||||
<ul><!--
|
--><ul><!--
|
||||||
--><% for (let [key, item] of ctx.items) { %><!--
|
--><% for (let item of ctx.items) { %><!--
|
||||||
--><% if (item.available) { %><!--
|
--><% if (item.available) { %><!--
|
||||||
--><li data-name='<%= key %>'><!--
|
--><li data-name='<%= item.key %>'><!--
|
||||||
--><a href='<%= item.url %>' accesskey='<%= item.accessKey %>'><!--
|
--><a href='<%= item.url %>' accesskey='<%= item.accessKey %>'><!--
|
||||||
--><% if (item.imageUrl) { print(ctx.makeThumbnail(item.imageUrl)); } %><!--
|
--><% if (item.imageUrl) { print(ctx.makeThumbnail(item.imageUrl)); } %><!--
|
||||||
--><span class='text'><%= ctx.makeAccessKey(item.name, item.accessKey) %></span><!--
|
--><span class='text'><%= ctx.makeAccessKey(item.title, item.accessKey) %></span><!--
|
||||||
--></a><!--
|
--></a><!--
|
||||||
--></li><!--
|
--></li><!--
|
||||||
--><% } %><!--
|
--><% } %><!--
|
||||||
--><% } %><!--
|
--><% } %><!--
|
||||||
--></ul>
|
--></ul><!--
|
||||||
</nav>
|
--></nav>
|
|
@ -3,7 +3,7 @@
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const LoginView = require('../views/login_view.js');
|
const LoginView = require('../views/login_view.js');
|
||||||
const PasswordResetView = require('../views/password_reset_view.js');
|
const PasswordResetView = require('../views/password_reset_view.js');
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class AuthController {
|
||||||
|
|
||||||
_loginRoute() {
|
_loginRoute() {
|
||||||
api.forget();
|
api.forget();
|
||||||
topNavController.activate('login');
|
TopNavigation.activate('login');
|
||||||
this._loginView.render({
|
this._loginView.render({
|
||||||
login: (name, password, doRemember) => {
|
login: (name, password, doRemember) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -58,7 +58,7 @@ class AuthController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_passwordResetRoute() {
|
_passwordResetRoute() {
|
||||||
topNavController.activate('login');
|
TopNavigation.activate('login');
|
||||||
this._passwordResetView.render({
|
this._passwordResetView.render({
|
||||||
proceed: (...args) => {
|
proceed: (...args) => {
|
||||||
return this._passwordReset(...args);
|
return this._passwordReset(...args);
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
|
||||||
const pageController = require('../controllers/page_controller.js');
|
const pageController = require('../controllers/page_controller.js');
|
||||||
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const CommentsPageView = require('../views/comments_page_view.js');
|
const CommentsPageView = require('../views/comments_page_view.js');
|
||||||
const EmptyView = require('../views/empty_view.js');
|
const EmptyView = require('../views/empty_view.js');
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class CommentsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_listCommentsRoute(ctx) {
|
_listCommentsRoute(ctx) {
|
||||||
topNavController.activate('comments');
|
TopNavigation.activate('comments');
|
||||||
|
|
||||||
pageController.run({
|
pageController.run({
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const HelpView = require('../views/help_view.js');
|
const HelpView = require('../views/help_view.js');
|
||||||
|
|
||||||
class HelpController {
|
class HelpController {
|
||||||
|
@ -24,7 +24,7 @@ class HelpController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_showHelpRoute(section, subsection) {
|
_showHelpRoute(section, subsection) {
|
||||||
topNavController.activate('help');
|
TopNavigation.activate('help');
|
||||||
this._helpView.render({
|
this._helpView.render({
|
||||||
section: section,
|
section: section,
|
||||||
subsection: subsection,
|
subsection: subsection,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
|
|
||||||
class HistoryController {
|
class HistoryController {
|
||||||
registerRoutes() {
|
registerRoutes() {
|
||||||
|
@ -11,7 +11,7 @@ class HistoryController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_listHistoryRoute() {
|
_listHistoryRoute() {
|
||||||
topNavController.activate('');
|
TopNavigation.activate('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const HomeView = require('../views/home_view.js');
|
const HomeView = require('../views/home_view.js');
|
||||||
const NotFoundView = require('../views/not_found_view.js');
|
const NotFoundView = require('../views/not_found_view.js');
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class HomeController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_indexRoute() {
|
_indexRoute() {
|
||||||
topNavController.activate('home');
|
TopNavigation.activate('home');
|
||||||
|
|
||||||
api.get('/info')
|
api.get('/info')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -45,7 +45,7 @@ class HomeController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_notFoundRoute(ctx) {
|
_notFoundRoute(ctx) {
|
||||||
topNavController.activate('');
|
TopNavigation.activate('');
|
||||||
this._notFoundView.render({path: ctx.canonicalPath});
|
this._notFoundView.render({path: ctx.canonicalPath});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ const api = require('../api.js');
|
||||||
const settings = require('../settings.js');
|
const settings = require('../settings.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
|
||||||
const pageController = require('../controllers/page_controller.js');
|
const pageController = require('../controllers/page_controller.js');
|
||||||
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const PostsHeaderView = require('../views/posts_header_view.js');
|
const PostsHeaderView = require('../views/posts_header_view.js');
|
||||||
const PostsPageView = require('../views/posts_page_view.js');
|
const PostsPageView = require('../views/posts_page_view.js');
|
||||||
const PostView = require('../views/post_view.js');
|
const PostView = require('../views/post_view.js');
|
||||||
|
@ -37,12 +37,12 @@ class PostsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_uploadPostsRoute() {
|
_uploadPostsRoute() {
|
||||||
topNavController.activate('upload');
|
TopNavigation.activate('upload');
|
||||||
this._emptyView.render();
|
this._emptyView.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
_listPostsRoute(ctx) {
|
_listPostsRoute(ctx) {
|
||||||
topNavController.activate('posts');
|
TopNavigation.activate('posts');
|
||||||
|
|
||||||
pageController.run({
|
pageController.run({
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
|
@ -67,7 +67,7 @@ class PostsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_showPostRoute(id, editMode) {
|
_showPostRoute(id, editMode) {
|
||||||
topNavController.activate('posts');
|
TopNavigation.activate('posts');
|
||||||
Promise.all([
|
Promise.all([
|
||||||
api.get('/post/' + id),
|
api.get('/post/' + id),
|
||||||
api.get(`/post/${id}/around?fields=id&query=` +
|
api.get(`/post/${id}/around?fields=id&query=` +
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const settings = require('../settings.js');
|
const settings = require('../settings.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const SettingsView = require('../views/settings_view.js');
|
const SettingsView = require('../views/settings_view.js');
|
||||||
|
|
||||||
class SettingsController {
|
class SettingsController {
|
||||||
|
@ -15,7 +15,7 @@ class SettingsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_settingsRoute() {
|
_settingsRoute() {
|
||||||
topNavController.activate('settings');
|
TopNavigation.activate('settings');
|
||||||
this._settingsView.render({
|
this._settingsView.render({
|
||||||
getSettings: () => settings.getSettings(),
|
getSettings: () => settings.getSettings(),
|
||||||
saveSettings: newSettings => settings.saveSettings(newSettings),
|
saveSettings: newSettings => settings.saveSettings(newSettings),
|
||||||
|
|
|
@ -5,8 +5,8 @@ const api = require('../api.js');
|
||||||
const tags = require('../tags.js');
|
const tags = require('../tags.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
|
||||||
const pageController = require('../controllers/page_controller.js');
|
const pageController = require('../controllers/page_controller.js');
|
||||||
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const TagView = require('../views/tag_view.js');
|
const TagView = require('../views/tag_view.js');
|
||||||
const TagsHeaderView = require('../views/tags_header_view.js');
|
const TagsHeaderView = require('../views/tags_header_view.js');
|
||||||
const TagsPageView = require('../views/tags_page_view.js');
|
const TagsPageView = require('../views/tags_page_view.js');
|
||||||
|
@ -114,7 +114,7 @@ class TagsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_show(tag, section) {
|
_show(tag, section) {
|
||||||
topNavController.activate('tags');
|
TopNavigation.activate('tags');
|
||||||
const categories = {};
|
const categories = {};
|
||||||
for (let category of tags.getAllCategories()) {
|
for (let category of tags.getAllCategories()) {
|
||||||
categories[category.name] = category.name;
|
categories[category.name] = category.name;
|
||||||
|
@ -174,7 +174,7 @@ class TagsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_tagCategoriesRoute(ctx, next) {
|
_tagCategoriesRoute(ctx, next) {
|
||||||
topNavController.activate('tags');
|
TopNavigation.activate('tags');
|
||||||
api.get('/tag-categories/').then(response => {
|
api.get('/tag-categories/').then(response => {
|
||||||
this._tagCategoriesView.render({
|
this._tagCategoriesView.render({
|
||||||
tagCategories: response.results,
|
tagCategories: response.results,
|
||||||
|
@ -201,7 +201,7 @@ class TagsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_listTagsRoute(ctx, next) {
|
_listTagsRoute(ctx, next) {
|
||||||
topNavController.activate('tags');
|
TopNavigation.activate('tags');
|
||||||
|
|
||||||
pageController.run({
|
pageController.run({
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const api = require('../api.js');
|
|
||||||
const events = require('../events.js');
|
|
||||||
const TopNavView = require('../views/top_nav_view.js');
|
|
||||||
|
|
||||||
function _createNavigationItemMap() {
|
|
||||||
const ret = new Map();
|
|
||||||
ret.set('home', new NavigationItem('H', 'Home', '/'));
|
|
||||||
ret.set('posts', new NavigationItem('P', 'Posts', '/posts'));
|
|
||||||
ret.set('upload', new NavigationItem('U', 'Upload', '/upload'));
|
|
||||||
ret.set('comments', new NavigationItem('C', 'Comments', '/comments'));
|
|
||||||
ret.set('tags', new NavigationItem('T', 'Tags', '/tags'));
|
|
||||||
ret.set('users', new NavigationItem('S', 'Users', '/users'));
|
|
||||||
ret.set('account', new NavigationItem('A', 'Account', '/user/{me}'));
|
|
||||||
ret.set('register', new NavigationItem('R', 'Register', '/register'));
|
|
||||||
ret.set('login', new NavigationItem('L', 'Log in', '/login'));
|
|
||||||
ret.set('logout', new NavigationItem('O', 'Logout', '/logout'));
|
|
||||||
ret.set('help', new NavigationItem('E', 'Help', '/help'));
|
|
||||||
ret.set(
|
|
||||||
'settings',
|
|
||||||
new NavigationItem(null, '<i class=\'fa fa-cog\'></i>', '/settings'));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NavigationItem {
|
|
||||||
constructor(accessKey, name, url) {
|
|
||||||
this.accessKey = accessKey;
|
|
||||||
this.name = name;
|
|
||||||
this.url = url;
|
|
||||||
this.available = true;
|
|
||||||
this.imageUrl = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TopNavController {
|
|
||||||
constructor() {
|
|
||||||
this._topNavView = new TopNavView();
|
|
||||||
this._activeItem = null;
|
|
||||||
this._items = _createNavigationItemMap();
|
|
||||||
|
|
||||||
const rerender = () => {
|
|
||||||
this._updateVisibility();
|
|
||||||
this._topNavView.render({
|
|
||||||
items: this._items,
|
|
||||||
activeItem: this._activeItem});
|
|
||||||
this._topNavView.activate(this._activeItem);
|
|
||||||
};
|
|
||||||
|
|
||||||
events.listen(
|
|
||||||
events.Authentication,
|
|
||||||
() => { rerender(); return true; });
|
|
||||||
rerender();
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateVisibility() {
|
|
||||||
this._items.get('account').url = '/user/' + api.userName;
|
|
||||||
this._items.get('account').imageUrl = api.user ?
|
|
||||||
api.user.avatarUrl : null;
|
|
||||||
|
|
||||||
for (let [key, item] of this._items) {
|
|
||||||
item.available = true;
|
|
||||||
}
|
|
||||||
if (!api.hasPrivilege('posts:list')) {
|
|
||||||
this._items.get('posts').available = false;
|
|
||||||
}
|
|
||||||
if (!api.hasPrivilege('posts:create')) {
|
|
||||||
this._items.get('upload').available = false;
|
|
||||||
}
|
|
||||||
if (!api.hasPrivilege('comments:list')) {
|
|
||||||
this._items.get('comments').available = false;
|
|
||||||
}
|
|
||||||
if (!api.hasPrivilege('tags:list')) {
|
|
||||||
this._items.get('tags').available = false;
|
|
||||||
}
|
|
||||||
if (!api.hasPrivilege('users:list')) {
|
|
||||||
this._items.get('users').available = false;
|
|
||||||
}
|
|
||||||
if (api.isLoggedIn()) {
|
|
||||||
this._items.get('register').available = false;
|
|
||||||
this._items.get('login').available = false;
|
|
||||||
} else {
|
|
||||||
this._items.get('account').available = false;
|
|
||||||
this._items.get('logout').available = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activate(itemName) {
|
|
||||||
this._activeItem = itemName;
|
|
||||||
this._topNavView.activate(this._activeItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = new TopNavController();
|
|
70
client/js/controllers/top_navigation_controller.js
Normal file
70
client/js/controllers/top_navigation_controller.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const api = require('../api.js');
|
||||||
|
const events = require('../events.js');
|
||||||
|
const TopNavigationView = require('../views/top_navigation_view.js');
|
||||||
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
|
|
||||||
|
class TopNavigationController {
|
||||||
|
constructor() {
|
||||||
|
this._topNavigationView = new TopNavigationView();
|
||||||
|
|
||||||
|
TopNavigation.addEventListener(
|
||||||
|
'activate', e => this._evtActivate(e));
|
||||||
|
|
||||||
|
events.listen(
|
||||||
|
events.Authentication,
|
||||||
|
() => {
|
||||||
|
this._render();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._render();
|
||||||
|
}
|
||||||
|
|
||||||
|
_evtActivate(e) {
|
||||||
|
this._topNavigationView.activate(e.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateNavigationFromPrivileges() {
|
||||||
|
TopNavigation.get('account').url = '/user/' + api.userName;
|
||||||
|
TopNavigation.get('account').imageUrl =
|
||||||
|
api.user ? api.user.avatarUrl : null;
|
||||||
|
|
||||||
|
TopNavigation.showAll();
|
||||||
|
if (!api.hasPrivilege('posts:list')) {
|
||||||
|
TopNavigation.hide('posts');
|
||||||
|
}
|
||||||
|
if (!api.hasPrivilege('posts:create')) {
|
||||||
|
TopNavigation.hide('upload');
|
||||||
|
}
|
||||||
|
if (!api.hasPrivilege('comments:list')) {
|
||||||
|
TopNavigation.hide('comments');
|
||||||
|
}
|
||||||
|
if (!api.hasPrivilege('tags:list')) {
|
||||||
|
TopNavigation.hide('tags');
|
||||||
|
}
|
||||||
|
if (!api.hasPrivilege('users:list')) {
|
||||||
|
TopNavigation.hide('users');
|
||||||
|
}
|
||||||
|
if (api.isLoggedIn()) {
|
||||||
|
TopNavigation.hide('register');
|
||||||
|
TopNavigation.hide('login');
|
||||||
|
} else {
|
||||||
|
TopNavigation.hide('account');
|
||||||
|
TopNavigation.hide('logout');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_render() {
|
||||||
|
this._updateNavigationFromPrivileges();
|
||||||
|
console.log(TopNavigation.getAll());
|
||||||
|
this._topNavigationView.render({
|
||||||
|
items: TopNavigation.getAll(),
|
||||||
|
});
|
||||||
|
this._topNavigationView.activate(
|
||||||
|
TopNavigation.activeItem ? TopNavigation.activeItem.key : '');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new TopNavigationController();
|
|
@ -6,8 +6,8 @@ const config = require('../config.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
|
||||||
const pageController = require('../controllers/page_controller.js');
|
const pageController = require('../controllers/page_controller.js');
|
||||||
|
const TopNavigation = require('../models/top_navigation.js');
|
||||||
const RegistrationView = require('../views/registration_view.js');
|
const RegistrationView = require('../views/registration_view.js');
|
||||||
const UserView = require('../views/user_view.js');
|
const UserView = require('../views/user_view.js');
|
||||||
const UsersHeaderView = require('../views/users_header_view.js');
|
const UsersHeaderView = require('../views/users_header_view.js');
|
||||||
|
@ -65,7 +65,7 @@ class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_listUsersRoute(ctx, next) {
|
_listUsersRoute(ctx, next) {
|
||||||
topNavController.activate('users');
|
TopNavigation.activate('users');
|
||||||
|
|
||||||
pageController.run({
|
pageController.run({
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
|
@ -87,7 +87,7 @@ class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_createUserRoute(ctx, next) {
|
_createUserRoute(ctx, next) {
|
||||||
topNavController.activate('register');
|
TopNavigation.activate('register');
|
||||||
this._registrationView.render({
|
this._registrationView.render({
|
||||||
register: (...args) => {
|
register: (...args) => {
|
||||||
return this._register(...args);
|
return this._register(...args);
|
||||||
|
@ -237,9 +237,9 @@ class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
topNavController.activate('account');
|
TopNavigation.activate('account');
|
||||||
} else {
|
} else {
|
||||||
topNavController.activate('users');
|
TopNavigation.activate('users');
|
||||||
}
|
}
|
||||||
this._userView.render({
|
this._userView.render({
|
||||||
user: user,
|
user: user,
|
||||||
|
|
93
client/js/models/top_navigation.js
Normal file
93
client/js/models/top_navigation.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const events = require('../events.js');
|
||||||
|
|
||||||
|
class TopNavigationItem {
|
||||||
|
constructor(accessKey, title, url, available, imageUrl) {
|
||||||
|
this.accessKey = accessKey;
|
||||||
|
this.title = title;
|
||||||
|
this.url = url;
|
||||||
|
this.available = available === undefined ? true : available;
|
||||||
|
this.imageUrl = imageUrl === undefined ? null : imageUrl;
|
||||||
|
this.key = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TopNavigation extends events.EventTarget {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.activeItem = null;
|
||||||
|
this._keyToItem = new Map();
|
||||||
|
this._items = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
return this._items;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
if (!this._keyToItem.has(key)) {
|
||||||
|
throw `An item with key ${key} does not exist.`;
|
||||||
|
}
|
||||||
|
return this._keyToItem.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(key, item) {
|
||||||
|
item.key = key;
|
||||||
|
if (this._keyToItem.has(key)) {
|
||||||
|
throw `An item with key ${key} was already added.`;
|
||||||
|
}
|
||||||
|
this._keyToItem.set(key, item);
|
||||||
|
this._items.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
activate(key) {
|
||||||
|
const event = new Event('activate');
|
||||||
|
event.key = key;
|
||||||
|
if (key) {
|
||||||
|
event.item = this.get(key);
|
||||||
|
} else {
|
||||||
|
event.item = null;
|
||||||
|
}
|
||||||
|
this.activeItem = null;
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
showAll() {
|
||||||
|
for (let item of this._items) {
|
||||||
|
item.available = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
show(key) {
|
||||||
|
this.get(key).available = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(key) {
|
||||||
|
this.get(key).available = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function _makeTopNavigation() {
|
||||||
|
const ret = new TopNavigation();
|
||||||
|
ret.add('home', new TopNavigationItem('H', 'Home', '/'));
|
||||||
|
ret.add('posts', new TopNavigationItem('P', 'Posts', '/posts'));
|
||||||
|
ret.add('upload', new TopNavigationItem('U', 'Upload', '/upload'));
|
||||||
|
ret.add('comments', new TopNavigationItem('C', 'Comments', '/comments'));
|
||||||
|
ret.add('tags', new TopNavigationItem('T', 'Tags', '/tags'));
|
||||||
|
ret.add('users', new TopNavigationItem('S', 'Users', '/users'));
|
||||||
|
ret.add('account', new TopNavigationItem('A', 'Account', '/user/{me}'));
|
||||||
|
ret.add('register', new TopNavigationItem('R', 'Register', '/register'));
|
||||||
|
ret.add('login', new TopNavigationItem('L', 'Log in', '/login'));
|
||||||
|
ret.add('logout', new TopNavigationItem('O', 'Logout', '/logout'));
|
||||||
|
ret.add('help', new TopNavigationItem('E', 'Help', '/help'));
|
||||||
|
ret.add(
|
||||||
|
'settings',
|
||||||
|
new TopNavigationItem(
|
||||||
|
null,
|
||||||
|
'<i class=\'fa fa-cog\'></i>',
|
||||||
|
'/settings'));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = _makeTopNavigation();
|
|
@ -29,12 +29,13 @@ class PostView {
|
||||||
views.listenToMessages(source);
|
views.listenToMessages(source);
|
||||||
views.showView(target, source);
|
views.showView(target, source);
|
||||||
|
|
||||||
const topNavNode = document.body.querySelector('#top-nav');
|
|
||||||
const postViewNode = document.body.querySelector('.content-wrapper');
|
const postViewNode = document.body.querySelector('.content-wrapper');
|
||||||
|
const topNavigationNode =
|
||||||
|
document.body.querySelector('#top-navigation');
|
||||||
|
|
||||||
const margin = (
|
const margin = (
|
||||||
postViewNode.getBoundingClientRect().top -
|
postViewNode.getBoundingClientRect().top -
|
||||||
topNavNode.getBoundingClientRect().height);
|
topNavigationNode.getBoundingClientRect().height);
|
||||||
|
|
||||||
this._postContentControl = new PostContentControl(
|
this._postContentControl = new PostContentControl(
|
||||||
postContainerNode,
|
postContainerNode,
|
||||||
|
@ -45,7 +46,7 @@ class PostView {
|
||||||
postContainerNode.getBoundingClientRect().left -
|
postContainerNode.getBoundingClientRect().left -
|
||||||
margin,
|
margin,
|
||||||
window.innerHeight -
|
window.innerHeight -
|
||||||
topNavNode.getBoundingClientRect().height -
|
topNavigationNode.getBoundingClientRect().height -
|
||||||
margin * 2,
|
margin * 2,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const views = require('../util/views.js');
|
|
||||||
|
|
||||||
class TopNavView {
|
|
||||||
constructor() {
|
|
||||||
this._template = views.getTemplate('top-nav');
|
|
||||||
this._navHolder = document.getElementById('top-nav-holder');
|
|
||||||
this._lastCtx = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(ctx) {
|
|
||||||
this._lastCtx = ctx;
|
|
||||||
const target = this._navHolder;
|
|
||||||
const source = this._template(ctx);
|
|
||||||
views.showView(this._navHolder, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = TopNavView;
|
|
29
client/js/views/top_navigation_view.js
Normal file
29
client/js/views/top_navigation_view.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const views = require('../util/views.js');
|
||||||
|
|
||||||
|
class TopNavigationView {
|
||||||
|
constructor() {
|
||||||
|
this._template = views.getTemplate('top-navigation');
|
||||||
|
this._navHolder = document.getElementById('top-navigation-holder');
|
||||||
|
this._lastCtx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ctx) {
|
||||||
|
this._lastCtx = ctx;
|
||||||
|
const target = this._navHolder;
|
||||||
|
const source = this._template(ctx);
|
||||||
|
views.showView(this._navHolder, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
activate(key) {
|
||||||
|
const allItemNodes = document.querySelectorAll(
|
||||||
|
'#top-navigation-holder [data-name]');
|
||||||
|
for (let itemNode of allItemNodes) {
|
||||||
|
itemNode.classList.toggle(
|
||||||
|
'active', itemNode.getAttribute('data-name') === key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TopNavigationView;
|
Loading…
Reference in a new issue