client/users: add user view prototype
This commit is contained in:
parent
c46dc08c1b
commit
8be93f6c70
14 changed files with 225 additions and 21 deletions
|
@ -83,6 +83,13 @@ nav ul li img {
|
||||||
vertical-align: top; /* fix ghost margin under the image */
|
vertical-align: top; /* fix ghost margin under the image */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav.plain-nav ul li {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
nav.plain-nav ul li a {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
nav.text-nav {
|
nav.text-nav {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,3 +36,27 @@
|
||||||
#login .buttons a {
|
#login .buttons a {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#user {
|
||||||
|
width: 30em;
|
||||||
|
}
|
||||||
|
#user .text-nav {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
#user-summary img {
|
||||||
|
width: 6em;
|
||||||
|
margin: 0 1.5em 1.5em 0;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
#user-summary div {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
#user-summary .basic-info {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#user-summary nav {
|
||||||
|
float: left;
|
||||||
|
width: 45%;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class='content-wrapper transparent' id='home'>
|
<div class='content-wrapper transparent' id='home'>
|
||||||
<div class='messages'></div>
|
<div class='messages'></div>
|
||||||
<h1>{{name}}</h1>
|
<h1>{{name}}</h1>
|
||||||
<footer>Version: <span class='version'>{{version}}</span> (built {{#reltime}}{{buildDate}}{{/reltime}})</footer>
|
<footer>Version: <span class='version'>{{version}}</span> (built {{reltime buildDate}})</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
12
client/html/user.hbs
Normal file
12
client/html/user.hbs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<div class='messages'></div>
|
||||||
|
|
||||||
|
<div class='content-wrapper' id='user'>
|
||||||
|
<h1>{{this.name}}</h1>
|
||||||
|
<nav class='text-nav'><!--
|
||||||
|
--><ul><!--
|
||||||
|
--><li data-name='summary'><a href='/user/{{this.name}}'>Summary</a></li><!--
|
||||||
|
--><li data-name='edit'><a href='/user/{{this.name}}/edit'>Account settings</a></li><!--
|
||||||
|
--></ul><!--
|
||||||
|
--></nav>
|
||||||
|
<div id='user-content-holder'></div>
|
||||||
|
</div>
|
3
client/html/user_edit.hbs
Normal file
3
client/html/user_edit.hbs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div id='user-edit'>
|
||||||
|
<strong>Placeholder for account settings form</strong>
|
||||||
|
</div>
|
29
client/html/user_summary.hbs
Normal file
29
client/html/user_summary.hbs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<div id='user-summary'>
|
||||||
|
<img src='{{this.user.avatarUrl}}' alt='{{this.user.name}}’s avatar'/>
|
||||||
|
<ul class='basic-info'>
|
||||||
|
<li>Registered: {{reltime this.user.creationTime}}</li>
|
||||||
|
<li>Last seen: {{reltime this.user.lastLoginTime}}</li>
|
||||||
|
<li>Rank: {{toLowerCase this.user.rankName}}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<nav class='plain-nav'>
|
||||||
|
<p><strong>Quick links</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><a href='/posts/submit:{{this.user.name}}'>Uploads</a></li>
|
||||||
|
<li><a href='/posts/fav:{{this.user.name}}'>Favorites</a></li>
|
||||||
|
<li><a href='/posts/commented:{{this.user.name}}'>Posts commented on</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{#if this.isPrivate}}
|
||||||
|
<nav class='plain-nav'>
|
||||||
|
<p><strong>Only visible to you</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><a href='/posts/special:liked'>Liked posts</a></li>
|
||||||
|
<li><a href='/posts/special:disliked'>Disliked posts</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -18,10 +18,13 @@ class UsersController {
|
||||||
page('/users', () => { this.listUsersRoute(); });
|
page('/users', () => { this.listUsersRoute(); });
|
||||||
page(
|
page(
|
||||||
'/user/:name',
|
'/user/:name',
|
||||||
(ctx, next) => { this.showUserRoute(ctx.params.name); });
|
(ctx, next) => { this.loadUserRoute(ctx, next); },
|
||||||
|
(ctx, next) => { this.showUserRoute(ctx, next); });
|
||||||
page(
|
page(
|
||||||
'/user/:name/edit',
|
'/user/:name/edit',
|
||||||
(ctx, next) => { this.editUserRoute(ctx.params.name); });
|
(ctx, next) => { this.loadUserRoute(ctx, next); },
|
||||||
|
(ctx, next) => { this.editUserRoute(ctx, next); });
|
||||||
|
page.exit('/user/', (ctx, next) => { this.user = null; });
|
||||||
}
|
}
|
||||||
|
|
||||||
listUsersRoute() {
|
listUsersRoute() {
|
||||||
|
@ -57,22 +60,42 @@ class UsersController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showUserRoute(name) {
|
loadUserRoute(ctx, next) {
|
||||||
if (api.isLoggedIn() && name == api.userName) {
|
if (ctx.state.user) {
|
||||||
|
next();
|
||||||
|
} else if (this.user && this.user.name == ctx.params.name) {
|
||||||
|
ctx.state.user = this.user;
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
api.get('/user/' + ctx.params.name).then(response => {
|
||||||
|
ctx.state.user = response.user;
|
||||||
|
ctx.save();
|
||||||
|
this.user = response.user;
|
||||||
|
next();
|
||||||
|
}).catch(response => {
|
||||||
|
this.userView.empty();
|
||||||
|
this.userView.notifyError(response.description);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_show(user, section) {
|
||||||
|
const isPrivate = api.isLoggedIn() && user.name == api.userName;
|
||||||
|
if (isPrivate) {
|
||||||
topNavController.activate('account');
|
topNavController.activate('account');
|
||||||
} else {
|
} else {
|
||||||
topNavController.activate('users');
|
topNavController.activate('users');
|
||||||
}
|
}
|
||||||
this.userView.empty();
|
this.userView.render({
|
||||||
api.get('/user/' + name).then(response => {
|
user: user, section: section, isPrivate: isPrivate});
|
||||||
this.userView.render({user: response.user});
|
|
||||||
}).catch(response => {
|
|
||||||
this.userView.notifyError(response.description);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
editUserRoute(user) {
|
showUserRoute(ctx, next) {
|
||||||
topNavController.activate('users');
|
this._show(ctx.state.user, 'summary');
|
||||||
|
}
|
||||||
|
|
||||||
|
editUserRoute(ctx, next) {
|
||||||
|
this._show(ctx.state.user, 'edit');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
const handlebars = require('handlebars');
|
const handlebars = require('handlebars');
|
||||||
const misc = require('./misc.js');
|
const misc = require('./misc.js');
|
||||||
|
|
||||||
handlebars.registerHelper('reltime', function(options) {
|
handlebars.registerHelper('reltime', function(time) {
|
||||||
return new handlebars.SafeString(
|
return new handlebars.SafeString(
|
||||||
'<time datetime="' +
|
'<time datetime="' + time + '" title="' + time + '">' +
|
||||||
options.fn(this) +
|
misc.formatRelativeTime(time) +
|
||||||
'" title="' +
|
|
||||||
options.fn(this) +
|
|
||||||
'">' +
|
|
||||||
misc.formatRelativeTime(options.fn(this)) +
|
|
||||||
'</time>');
|
'</time>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
handlebars.registerHelper('toLowerCase', function(str) {
|
||||||
|
return str.toLowerCase();
|
||||||
|
});
|
||||||
|
|
16
client/js/views/user_edit_view.js
Normal file
16
client/js/views/user_edit_view.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseView = require('./base_view.js');
|
||||||
|
|
||||||
|
class UserEditView extends BaseView {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.template = this.getTemplate('user-edit-template');
|
||||||
|
}
|
||||||
|
|
||||||
|
render(options) {
|
||||||
|
options.target.innerHTML = this.template(options.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UserEditView;
|
17
client/js/views/user_summary_view.js
Normal file
17
client/js/views/user_summary_view.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseView = require('./base_view.js');
|
||||||
|
|
||||||
|
class UserSummaryView extends BaseView {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.template = this.getTemplate('user-summary-template');
|
||||||
|
}
|
||||||
|
|
||||||
|
render(options) {
|
||||||
|
options.target.innerHTML = this.template({
|
||||||
|
user: options.user, isPrivate: options.isPrivate});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UserSummaryView;
|
45
client/js/views/user_view.js
Normal file
45
client/js/views/user_view.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseView = require('./base_view.js');
|
||||||
|
const UserSummaryView = require('./user_summary_view.js');
|
||||||
|
const UserEditView = require('./user_edit_view.js');
|
||||||
|
|
||||||
|
class UserView extends BaseView {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.template = this.getTemplate('user-template');
|
||||||
|
this.summaryView = new UserSummaryView();
|
||||||
|
this.editView = new UserEditView();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(options) {
|
||||||
|
let section = options.section;
|
||||||
|
if (!section) {
|
||||||
|
section = 'summary';
|
||||||
|
}
|
||||||
|
|
||||||
|
let view = null;
|
||||||
|
if (section == 'edit') {
|
||||||
|
view = this.editView;
|
||||||
|
} else {
|
||||||
|
view = this.summaryView;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showView(this.template(options.user));
|
||||||
|
|
||||||
|
options.target = this.contentHolder.querySelector(
|
||||||
|
'#user-content-holder');
|
||||||
|
view.render(options);
|
||||||
|
|
||||||
|
const allItemsSelector = '#content-holder [data-name]';
|
||||||
|
for (let item of document.querySelectorAll(allItemsSelector)) {
|
||||||
|
if (item.getAttribute('data-name') === section) {
|
||||||
|
item.className = 'active';
|
||||||
|
} else {
|
||||||
|
item.className = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UserView;
|
|
@ -7,6 +7,9 @@ secret: change
|
||||||
api_url: # where frontend connects to, example: http://api.example.com/
|
api_url: # where frontend connects to, example: http://api.example.com/
|
||||||
base_url: # used to form absolute links, example: http://example.com/
|
base_url: # used to form absolute links, example: http://example.com/
|
||||||
|
|
||||||
|
avatar_thumbnail_size: 200
|
||||||
|
post_thumbnail_size: 300
|
||||||
|
|
||||||
database:
|
database:
|
||||||
schema: postgres
|
schema: postgres
|
||||||
host: # example: localhost
|
host: # example: localhost
|
||||||
|
@ -42,6 +45,13 @@ ranks:
|
||||||
- mod
|
- mod
|
||||||
- admin
|
- admin
|
||||||
- nobody
|
- nobody
|
||||||
|
rank_names:
|
||||||
|
anonymous: 'Anonymous user'
|
||||||
|
regular_user: 'Regular user'
|
||||||
|
power_user: 'Power user'
|
||||||
|
mod: 'Moderator'
|
||||||
|
admin: 'Administrator'
|
||||||
|
nobody: 'God'
|
||||||
default_rank: regular_user
|
default_rank: regular_user
|
||||||
|
|
||||||
# don't change these, unless you want to annoy people. if you do customize
|
# don't change these, unless you want to annoy people. if you do customize
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from szurubooru import errors, search
|
import hashlib
|
||||||
|
from szurubooru import config, errors, search
|
||||||
from szurubooru.util import auth, users
|
from szurubooru.util import auth, users
|
||||||
from szurubooru.api.base_api import BaseApi
|
from szurubooru.api.base_api import BaseApi
|
||||||
|
|
||||||
|
@ -7,12 +8,23 @@ def _serialize_user(authenticated_user, user):
|
||||||
'id': user.user_id,
|
'id': user.user_id,
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
'rank': user.rank,
|
'rank': user.rank,
|
||||||
|
'rankName': config.config['rank_names'].get(user.rank, 'Unknown'),
|
||||||
'creationTime': user.creation_time,
|
'creationTime': user.creation_time,
|
||||||
'lastLoginTime': user.last_login_time,
|
'lastLoginTime': user.last_login_time,
|
||||||
'avatarStyle': user.avatar_style
|
'avatarStyle': user.avatar_style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.avatar_style == user.AVATAR_GRAVATAR:
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
md5.update((user.email or user.name).lower().encode('utf-8'))
|
||||||
|
digest = md5.hexdigest()
|
||||||
|
ret['avatarUrl'] = 'http://gravatar.com/avatar/%s?s=%d' % (
|
||||||
|
digest, config.config['avatar_thumbnail_size'])
|
||||||
|
# TODO: else construct a link
|
||||||
|
|
||||||
if authenticated_user.user_id == user.user_id:
|
if authenticated_user.user_id == user.user_id:
|
||||||
ret['email'] = user.email
|
ret['email'] = user.email
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class UserListApi(BaseApi):
|
class UserListApi(BaseApi):
|
||||||
|
|
|
@ -13,7 +13,9 @@ class TestRetrievingUsers(DatabaseTestCase):
|
||||||
'users:view': 'regular_user',
|
'users:view': 'regular_user',
|
||||||
'users:create': 'regular_user',
|
'users:create': 'regular_user',
|
||||||
},
|
},
|
||||||
|
'avatar_thumbnail_size': 200,
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
|
'rank_names': {},
|
||||||
})
|
})
|
||||||
util.mock_context(self)
|
util.mock_context(self)
|
||||||
|
|
||||||
|
@ -71,7 +73,9 @@ class TestCreatingUser(DatabaseTestCase):
|
||||||
'user_name_regex': '.{3,}',
|
'user_name_regex': '.{3,}',
|
||||||
'password_regex': '.{3,}',
|
'password_regex': '.{3,}',
|
||||||
'default_rank': 'regular_user',
|
'default_rank': 'regular_user',
|
||||||
|
'avatar_thumbnail_size': 200,
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
|
'rank_names': {},
|
||||||
'privileges': {
|
'privileges': {
|
||||||
'users:create': 'anonymous',
|
'users:create': 'anonymous',
|
||||||
},
|
},
|
||||||
|
@ -130,7 +134,9 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
'secret': '',
|
'secret': '',
|
||||||
'user_name_regex': '.{3,}',
|
'user_name_regex': '.{3,}',
|
||||||
'password_regex': '.{3,}',
|
'password_regex': '.{3,}',
|
||||||
|
'avatar_thumbnail_size': 200,
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
|
'rank_names': {},
|
||||||
'privileges': {
|
'privileges': {
|
||||||
'users:edit:self:name': 'regular_user',
|
'users:edit:self:name': 'regular_user',
|
||||||
'users:edit:self:pass': 'regular_user',
|
'users:edit:self:pass': 'regular_user',
|
||||||
|
|
Loading…
Reference in a new issue