client/users: add user view prototype
This commit is contained in:
parent
c46dc08c1b
commit
8be93f6c70
|
@ -83,6 +83,13 @@ nav ul li img {
|
|||
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 {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
|
|
@ -36,3 +36,27 @@
|
|||
#login .buttons a {
|
||||
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='messages'></div>
|
||||
<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>
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
<div id='user-edit'>
|
||||
<strong>Placeholder for account settings form</strong>
|
||||
</div>
|
|
@ -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(
|
||||
'/user/:name',
|
||||
(ctx, next) => { this.showUserRoute(ctx.params.name); });
|
||||
(ctx, next) => { this.loadUserRoute(ctx, next); },
|
||||
(ctx, next) => { this.showUserRoute(ctx, next); });
|
||||
page(
|
||||
'/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() {
|
||||
|
@ -57,22 +60,42 @@ class UsersController {
|
|||
});
|
||||
}
|
||||
|
||||
showUserRoute(name) {
|
||||
if (api.isLoggedIn() && name == api.userName) {
|
||||
loadUserRoute(ctx, next) {
|
||||
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');
|
||||
} else {
|
||||
topNavController.activate('users');
|
||||
}
|
||||
this.userView.empty();
|
||||
api.get('/user/' + name).then(response => {
|
||||
this.userView.render({user: response.user});
|
||||
}).catch(response => {
|
||||
this.userView.notifyError(response.description);
|
||||
});
|
||||
this.userView.render({
|
||||
user: user, section: section, isPrivate: isPrivate});
|
||||
}
|
||||
|
||||
editUserRoute(user) {
|
||||
topNavController.activate('users');
|
||||
showUserRoute(ctx, next) {
|
||||
this._show(ctx.state.user, 'summary');
|
||||
}
|
||||
|
||||
editUserRoute(ctx, next) {
|
||||
this._show(ctx.state.user, 'edit');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
const handlebars = require('handlebars');
|
||||
const misc = require('./misc.js');
|
||||
|
||||
handlebars.registerHelper('reltime', function(options) {
|
||||
handlebars.registerHelper('reltime', function(time) {
|
||||
return new handlebars.SafeString(
|
||||
'<time datetime="' +
|
||||
options.fn(this) +
|
||||
'" title="' +
|
||||
options.fn(this) +
|
||||
'">' +
|
||||
misc.formatRelativeTime(options.fn(this)) +
|
||||
'<time datetime="' + time + '" title="' + time + '">' +
|
||||
misc.formatRelativeTime(time) +
|
||||
'</time>');
|
||||
});
|
||||
|
||||
handlebars.registerHelper('toLowerCase', function(str) {
|
||||
return str.toLowerCase();
|
||||
});
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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/
|
||||
base_url: # used to form absolute links, example: http://example.com/
|
||||
|
||||
avatar_thumbnail_size: 200
|
||||
post_thumbnail_size: 300
|
||||
|
||||
database:
|
||||
schema: postgres
|
||||
host: # example: localhost
|
||||
|
@ -42,6 +45,13 @@ ranks:
|
|||
- mod
|
||||
- admin
|
||||
- nobody
|
||||
rank_names:
|
||||
anonymous: 'Anonymous user'
|
||||
regular_user: 'Regular user'
|
||||
power_user: 'Power user'
|
||||
mod: 'Moderator'
|
||||
admin: 'Administrator'
|
||||
nobody: 'God'
|
||||
default_rank: regular_user
|
||||
|
||||
# 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.api.base_api import BaseApi
|
||||
|
||||
|
@ -7,12 +8,23 @@ def _serialize_user(authenticated_user, user):
|
|||
'id': user.user_id,
|
||||
'name': user.name,
|
||||
'rank': user.rank,
|
||||
'rankName': config.config['rank_names'].get(user.rank, 'Unknown'),
|
||||
'creationTime': user.creation_time,
|
||||
'lastLoginTime': user.last_login_time,
|
||||
'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:
|
||||
ret['email'] = user.email
|
||||
|
||||
return ret
|
||||
|
||||
class UserListApi(BaseApi):
|
||||
|
|
|
@ -13,7 +13,9 @@ class TestRetrievingUsers(DatabaseTestCase):
|
|||
'users:view': 'regular_user',
|
||||
'users:create': 'regular_user',
|
||||
},
|
||||
'avatar_thumbnail_size': 200,
|
||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||
'rank_names': {},
|
||||
})
|
||||
util.mock_context(self)
|
||||
|
||||
|
@ -71,7 +73,9 @@ class TestCreatingUser(DatabaseTestCase):
|
|||
'user_name_regex': '.{3,}',
|
||||
'password_regex': '.{3,}',
|
||||
'default_rank': 'regular_user',
|
||||
'avatar_thumbnail_size': 200,
|
||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||
'rank_names': {},
|
||||
'privileges': {
|
||||
'users:create': 'anonymous',
|
||||
},
|
||||
|
@ -130,7 +134,9 @@ class TestUpdatingUser(DatabaseTestCase):
|
|||
'secret': '',
|
||||
'user_name_regex': '.{3,}',
|
||||
'password_regex': '.{3,}',
|
||||
'avatar_thumbnail_size': 200,
|
||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||
'rank_names': {},
|
||||
'privileges': {
|
||||
'users:edit:self:name': 'regular_user',
|
||||
'users:edit:self:pass': 'regular_user',
|
||||
|
|
Loading…
Reference in New Issue