client/general: replace direct API with models
This commit is contained in:
parent
5f4b67a2bc
commit
eb09677bf8
33 changed files with 1025 additions and 572 deletions
|
@ -10,67 +10,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for (let category of ctx.tagCategories) { %>
|
||||
<% if (category.default) { %>
|
||||
<tr data-category='<%= category.name %>' class='default'>
|
||||
<% } else { %>
|
||||
<tr data-category='<%= category.name %>'>
|
||||
<% } %>
|
||||
<td class='name'>
|
||||
<% if (ctx.canEditName) { %>
|
||||
<%= ctx.makeTextInput({value: category.name, required: true}) %>
|
||||
<% } else { %>
|
||||
<%= category.name %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class='color'>
|
||||
<% if (ctx.canEditColor) { %>
|
||||
<%= ctx.makeColorInput({value: category.color}) %>
|
||||
<% } else { %>
|
||||
<%= category.color %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class='usages'>
|
||||
<a href='/tags/text=category:<%= category.name %>'>
|
||||
<%= category.usages %>
|
||||
</a>
|
||||
</td>
|
||||
<% if (ctx.canDelete) { %>
|
||||
<td class='remove'>
|
||||
<% if (category.usages) { %>
|
||||
<a class='inactive' title="Can't delete category in use">Remove</a>
|
||||
<% } else { %>
|
||||
<a href='#'>Remove</a>
|
||||
<% } %>
|
||||
</td>
|
||||
<% } %>
|
||||
<% if (ctx.canSetDefault) { %>
|
||||
<td class='set-default'>
|
||||
<a href='#'>Make default</a>
|
||||
</td>
|
||||
<% } %>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class='add-template'>
|
||||
<td class='name'>
|
||||
<%= ctx.makeTextInput({required: true}) %>
|
||||
</td>
|
||||
<td class='color'>
|
||||
<%= ctx.makeColorInput({value: '#000000'}) %>
|
||||
</td>
|
||||
<td class='usages'>
|
||||
0
|
||||
</td>
|
||||
<td class='remove'>
|
||||
<a href='#'>Remove</a>
|
||||
</td>
|
||||
<td class='set-default'>
|
||||
<a href='#'>Make default</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<% if (ctx.canCreate) { %>
|
||||
|
|
41
client/html/tag_category_row.tpl
Normal file
41
client/html/tag_category_row.tpl
Normal file
|
@ -0,0 +1,41 @@
|
|||
<tr data-category='<%= ctx.tagCategory.name %>'
|
||||
<% if (ctx.tagCategory.isDefault) { %> class='default' <% } %>
|
||||
>
|
||||
<td class='name'>
|
||||
<% if (ctx.canEditName) { %>
|
||||
<%= ctx.makeTextInput({value: ctx.tagCategory.name, required: true}) %>
|
||||
<% } else { %>
|
||||
<%= ctx.tagCategory.name %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class='color'>
|
||||
<% if (ctx.canEditColor) { %>
|
||||
<%= ctx.makeColorInput({value: ctx.tagCategory.color}) %>
|
||||
<% } else { %>
|
||||
<%= ctx.tagCategory.color %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class='usages'>
|
||||
<% if (ctx.tagCategory.name) { %>
|
||||
<a href='/tags/text=category:<%= ctx.tagCategory.name %>'>
|
||||
<%= ctx.tagCategory.tagCount %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<%= ctx.tagCategory.tagCount %>
|
||||
<% } %>
|
||||
</td>
|
||||
<% if (ctx.canDelete) { %>
|
||||
<td class='remove'>
|
||||
<% if (ctx.tagCategory.tagCount) { %>
|
||||
<a class='inactive' title="Can't delete category in use">Remove</a>
|
||||
<% } else { %>
|
||||
<a href='#'>Remove</a>
|
||||
<% } %>
|
||||
</td>
|
||||
<% } %>
|
||||
<% if (ctx.canSetDefault) { %>
|
||||
<td class='set-default'>
|
||||
<a href='#'>Make default</a>
|
||||
</td>
|
||||
<% } %>
|
||||
</tr>
|
|
@ -1,6 +1,6 @@
|
|||
<div class='tag-delete'>
|
||||
<form>
|
||||
<% if (ctx.tag.usages) { %>
|
||||
<% if (ctx.tag.postCount) { %>
|
||||
<p>For extra <s>paranoia</s> safety, only tags that are unused can be deleted.</p>
|
||||
<p>Check <a href='/posts/text=<%= ctx.tag.names[0] %>'>which posts</a> are tagged with <%= ctx.tag.names[0] %>.</p>
|
||||
<% } else { %>
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<% } %>
|
||||
</td>
|
||||
<td class='usages'>
|
||||
<%= tag.usages %>
|
||||
<%= tag.postCount %>
|
||||
</td>
|
||||
<td class='edit-time'>
|
||||
<%= ctx.makeRelativeTime(tag.lastEditTime) %>
|
||||
|
|
|
@ -22,6 +22,15 @@ class Api extends events.EventTarget {
|
|||
'administrator',
|
||||
'nobody',
|
||||
];
|
||||
this.rankNames = new Map([
|
||||
['anonymous', 'Anonymous'],
|
||||
['restricted', 'Restricted user'],
|
||||
['regular', 'Regular user'],
|
||||
['power', 'Power user'],
|
||||
['moderator', 'Moderator'],
|
||||
['administrator', 'Administrator'],
|
||||
['nobody', 'Nobody'],
|
||||
]);
|
||||
}
|
||||
|
||||
get(url, options) {
|
||||
|
|
|
@ -7,29 +7,18 @@ const topNavigation = require('../models/top_navigation.js');
|
|||
const PageController = require('../controllers/page_controller.js');
|
||||
const CommentsPageView = require('../views/comments_page_view.js');
|
||||
|
||||
const fields = ['id', 'comments', 'commentCount', 'thumbnailUrl'];
|
||||
|
||||
class CommentsController {
|
||||
constructor(ctx) {
|
||||
topNavigation.activate('comments');
|
||||
|
||||
const proxy = PageController.createHistoryCacheProxy(
|
||||
ctx, page => {
|
||||
const url =
|
||||
'/posts/?query=sort:comment-date+comment-count-min:1' +
|
||||
`&page=${page}&pageSize=10&fields=` +
|
||||
'id,comments,commentCount,thumbnailUrl';
|
||||
return api.get(url);
|
||||
});
|
||||
|
||||
this._pageController = new PageController({
|
||||
searchQuery: ctx.searchQuery,
|
||||
clientUrl: '/comments/' + misc.formatSearchQuery({page: '{page}'}),
|
||||
requestPage: page => {
|
||||
return proxy(page).then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: PostList.fromResponse(response.results)}));
|
||||
});
|
||||
return PostList.search(
|
||||
'sort:comment-date+comment-count-min:1', page, 10, fields);
|
||||
},
|
||||
pageRenderer: pageCtx => {
|
||||
Object.assign(pageCtx, {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const api = require('../api.js');
|
||||
const config = require('../config.js');
|
||||
const Info = require('../models/info.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const HomeView = require('../views/home_view.js');
|
||||
|
||||
|
@ -16,20 +17,20 @@ class HomeController {
|
|||
canListPosts: api.hasPrivilege('posts:list'),
|
||||
});
|
||||
|
||||
api.get('/info')
|
||||
.then(response => {
|
||||
Info.get()
|
||||
.then(info => {
|
||||
this._homeView.setStats({
|
||||
diskUsage: response.diskUsage,
|
||||
postCount: response.postCount,
|
||||
diskUsage: info.diskUsage,
|
||||
postCount: info.postCount,
|
||||
});
|
||||
this._homeView.setFeaturedPost({
|
||||
featuredPost: response.featuredPost,
|
||||
featuringUser: response.featuringUser,
|
||||
featuringTime: response.featuringTime,
|
||||
featuredPost: info.featuredPost,
|
||||
featuringUser: info.featuringUser,
|
||||
featuringTime: info.featuringTime,
|
||||
});
|
||||
},
|
||||
response => {
|
||||
this._homeView.showError(response.description);
|
||||
errorMessage => {
|
||||
this._homeView.showError(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -28,22 +28,6 @@ class PageController {
|
|||
showError(message) {
|
||||
this._view.showError(message);
|
||||
}
|
||||
|
||||
static createHistoryCacheProxy(routerCtx, requestPage) {
|
||||
return page => {
|
||||
if (routerCtx.state.response) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(routerCtx.state.response);
|
||||
});
|
||||
}
|
||||
const promise = requestPage(page);
|
||||
promise.then(response => {
|
||||
routerCtx.state.response = response;
|
||||
routerCtx.save();
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PageController;
|
||||
|
|
|
@ -5,6 +5,7 @@ const misc = require('../util/misc.js');
|
|||
const settings = require('../models/settings.js');
|
||||
const Comment = require('../models/comment.js');
|
||||
const Post = require('../models/post.js');
|
||||
const PostList = require('../models/post_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PostView = require('../views/post_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
|
@ -15,8 +16,8 @@ class PostController {
|
|||
|
||||
Promise.all([
|
||||
Post.get(id),
|
||||
api.get(`/post/${id}/around?fields=id&query=` +
|
||||
this._decorateSearchQuery(
|
||||
PostList.getAround(
|
||||
id, this._decorateSearchQuery(
|
||||
searchQuery ? searchQuery.text : '')),
|
||||
]).then(responses => {
|
||||
const [post, aroundResponse] = responses;
|
||||
|
@ -53,9 +54,9 @@ class PostController {
|
|||
this._view.commentListControl.addEventListener(
|
||||
'delete', e => this._evtDeleteComment(e));
|
||||
}
|
||||
}, response => {
|
||||
}, errorMessage => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(response.description);
|
||||
this._view.showError(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,16 @@
|
|||
const api = require('../api.js');
|
||||
const settings = require('../models/settings.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const PostList = require('../models/post_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const PostsHeaderView = require('../views/posts_header_view.js');
|
||||
const PostsPageView = require('../views/posts_page_view.js');
|
||||
|
||||
const fields = [
|
||||
'id', 'thumbnailUrl', 'type',
|
||||
'score', 'favoriteCount', 'commentCount', 'tags'];
|
||||
|
||||
class PostListController {
|
||||
constructor(ctx) {
|
||||
topNavigation.activate('posts');
|
||||
|
@ -16,16 +21,11 @@ class PostListController {
|
|||
searchQuery: ctx.searchQuery,
|
||||
clientUrl: '/posts/' + misc.formatSearchQuery({
|
||||
text: ctx.searchQuery.text, page: '{page}'}),
|
||||
requestPage: PageController.createHistoryCacheProxy(
|
||||
ctx,
|
||||
page => {
|
||||
const text
|
||||
= this._decorateSearchQuery(ctx.searchQuery.text);
|
||||
return api.get(
|
||||
`/posts/?query=${text}&page=${page}&pageSize=40` +
|
||||
'&fields=id,type,tags,score,favoriteCount,' +
|
||||
'commentCount,thumbnailUrl');
|
||||
}),
|
||||
requestPage: page => {
|
||||
return PostList.search(
|
||||
this._decorateSearchQuery(ctx.searchQuery.text),
|
||||
page, 40, fields);
|
||||
},
|
||||
headerRenderer: headerCtx => {
|
||||
return new PostsHeaderView(headerCtx);
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const api = require('../api.js');
|
||||
const tags = require('../tags.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const TagCategoryList = require('../models/tag_category_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const TagCategoriesView = require('../views/tag_categories_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
|
@ -10,66 +11,35 @@ const EmptyView = require('../views/empty_view.js');
|
|||
class TagCategoriesController {
|
||||
constructor() {
|
||||
topNavigation.activate('tags');
|
||||
api.get('/tag-categories/').then(response => {
|
||||
TagCategoryList.get().then(response => {
|
||||
this._tagCategories = response.results;
|
||||
this._view = new TagCategoriesView({
|
||||
tagCategories: response.results,
|
||||
tagCategories: this._tagCategories,
|
||||
canEditName: api.hasPrivilege('tagCategories:edit:name'),
|
||||
canEditColor: api.hasPrivilege('tagCategories:edit:color'),
|
||||
canDelete: api.hasPrivilege('tagCategories:delete'),
|
||||
canCreate: api.hasPrivilege('tagCategories:create'),
|
||||
canSetDefault: api.hasPrivilege('tagCategories:setDefault'),
|
||||
saveChanges: (...args) => {
|
||||
return this._saveTagCategories(...args);
|
||||
},
|
||||
getCategories: () => {
|
||||
return api.get('/tag-categories/').then(response => {
|
||||
return Promise.resolve(response.results);
|
||||
}, response => {
|
||||
return Promise.reject(response);
|
||||
});
|
||||
}
|
||||
});
|
||||
this._view.addEventListener('submit', e => this._evtSubmit(e));
|
||||
}, response => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
_saveTagCategories(
|
||||
addedCategories,
|
||||
changedCategories,
|
||||
removedCategories,
|
||||
defaultCategory) {
|
||||
let promises = [];
|
||||
for (let category of addedCategories) {
|
||||
promises.push(api.post('/tag-categories/', category));
|
||||
}
|
||||
for (let category of changedCategories) {
|
||||
promises.push(
|
||||
api.put('/tag-category/' + category.originalName, category));
|
||||
}
|
||||
for (let name of removedCategories) {
|
||||
promises.push(api.delete('/tag-category/' + name));
|
||||
}
|
||||
Promise.all(promises)
|
||||
.then(
|
||||
() => {
|
||||
if (!defaultCategory) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return api.put(
|
||||
'/tag-category/' + defaultCategory + '/default');
|
||||
}, response => {
|
||||
return Promise.reject(response);
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
tags.refreshExport();
|
||||
this._view.showSuccess('Changes saved.');
|
||||
},
|
||||
response => {
|
||||
this._view.showError(response.description);
|
||||
});
|
||||
_evtSubmit(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
this._tagCategories.save()
|
||||
.then(() => {
|
||||
tags.refreshExport();
|
||||
this._view.enableForm();
|
||||
this._view.showSuccess('Changes saved.');
|
||||
}, errorMessage => {
|
||||
this._view.enableForm();
|
||||
this._view.showError(errorMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,27 +3,19 @@
|
|||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const tags = require('../tags.js');
|
||||
const Tag = require('../models/tag.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const TagView = require('../views/tag_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
|
||||
class TagController {
|
||||
constructor(ctx, section) {
|
||||
new Promise((resolve, reject) => {
|
||||
if (ctx.state.tag) {
|
||||
resolve(ctx.state.tag);
|
||||
return;
|
||||
}
|
||||
api.get('/tag/' + ctx.params.name).then(response => {
|
||||
ctx.state.tag = response;
|
||||
ctx.save();
|
||||
resolve(ctx.state.tag);
|
||||
}, response => {
|
||||
reject(response.description);
|
||||
});
|
||||
}).then(tag => {
|
||||
Tag.get(ctx.params.name).then(tag => {
|
||||
topNavigation.activate('tags');
|
||||
|
||||
this._name = ctx.params.name;
|
||||
tag.addEventListener('change', e => this._evtSaved(e));
|
||||
|
||||
const categories = {};
|
||||
for (let category of tags.getAllCategories()) {
|
||||
categories[category.name] = category.name;
|
||||
|
@ -50,19 +42,20 @@ class TagController {
|
|||
});
|
||||
}
|
||||
|
||||
_evtSaved(e) {
|
||||
if (this._name !== e.detail.tag.names[0]) {
|
||||
router.replace('/tag/' + e.detail.tag.names[0], null, false);
|
||||
}
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
return api.put('/tag/' + e.detail.tag.names[0], {
|
||||
names: e.detail.names,
|
||||
category: e.detail.category,
|
||||
implications: e.detail.implications,
|
||||
suggestions: e.detail.suggestions,
|
||||
}).then(response => {
|
||||
// TODO: update header links and text
|
||||
if (e.detail.names && e.detail.names[0] !== e.detail.tag.names[0]) {
|
||||
router.replace('/tag/' + e.detail.names[0], null, false);
|
||||
}
|
||||
e.detail.tag.names = e.detail.names;
|
||||
e.detail.tag.category = e.detail.category;
|
||||
e.detail.tag.implications = e.detail.implications;
|
||||
e.detail.tag.suggestions = e.detail.suggestions;
|
||||
e.detail.tag.save().then(() => {
|
||||
this._view.showSuccess('Tag saved.');
|
||||
this._view.enableForm();
|
||||
}, response => {
|
||||
|
@ -74,17 +67,11 @@ class TagController {
|
|||
_evtMerge(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
return api.post(
|
||||
'/tag-merge/',
|
||||
{remove: e.detail.tag.names[0], mergeTo: e.detail.targetTagName}
|
||||
).then(response => {
|
||||
// TODO: update header links and text
|
||||
router.replace(
|
||||
'/tag/' + e.detail.targetTagName + '/merge', null, false);
|
||||
e.detail.tag.merge(e.detail.targetTagName).then(() => {
|
||||
this._view.showSuccess('Tag merged.');
|
||||
this._view.enableForm();
|
||||
}, response => {
|
||||
this._view.showError(response.description);
|
||||
}, errorMessage => {
|
||||
this._view.showError(errorMessage);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
|
@ -92,13 +79,14 @@ class TagController {
|
|||
_evtDelete(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
return api.delete('/tag/' + e.detail.tag.names[0]).then(response => {
|
||||
const ctx = router.show('/tags/');
|
||||
ctx.controller.showSuccess('Tag deleted.');
|
||||
}, response => {
|
||||
this._view.showError(response.description);
|
||||
this._view.enableForm();
|
||||
});
|
||||
e.detail.tag.delete()
|
||||
.then(() => {
|
||||
const ctx = router.show('/tags/');
|
||||
ctx.controller.showSuccess('Tag deleted.');
|
||||
}, errorMessage => {
|
||||
this._view.showError(errorMessage);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const TagList = require('../models/tag_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const TagsHeaderView = require('../views/tags_header_view.js');
|
||||
const TagsPageView = require('../views/tags_page_view.js');
|
||||
|
||||
const fields = [
|
||||
'names', 'suggestions', 'implications', 'lastEditTime', 'usages'];
|
||||
|
||||
class TagListController {
|
||||
constructor(ctx) {
|
||||
topNavigation.activate('tags');
|
||||
|
@ -15,15 +19,9 @@ class TagListController {
|
|||
searchQuery: ctx.searchQuery,
|
||||
clientUrl: '/tags/' + misc.formatSearchQuery({
|
||||
text: ctx.searchQuery.text, page: '{page}'}),
|
||||
requestPage: PageController.createHistoryCacheProxy(
|
||||
ctx,
|
||||
page => {
|
||||
const text = ctx.searchQuery.text;
|
||||
return api.get(
|
||||
`/tags/?query=${text}&page=${page}&pageSize=50` +
|
||||
'&fields=names,suggestions,implications,' +
|
||||
'lastEditTime,usages');
|
||||
}),
|
||||
requestPage: page => {
|
||||
return TagList.search(ctx.searchQuery.text, page, 50, fields);
|
||||
},
|
||||
headerRenderer: headerCtx => {
|
||||
Object.assign(headerCtx, {
|
||||
canEditTagCategories:
|
||||
|
|
|
@ -4,39 +4,20 @@ const router = require('../router.js');
|
|||
const api = require('../api.js');
|
||||
const config = require('../config.js');
|
||||
const views = require('../util/views.js');
|
||||
const User = require('../models/user.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const UserView = require('../views/user_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
|
||||
const rankNames = new Map([
|
||||
['anonymous', 'Anonymous'],
|
||||
['restricted', 'Restricted user'],
|
||||
['regular', 'Regular user'],
|
||||
['power', 'Power user'],
|
||||
['moderator', 'Moderator'],
|
||||
['administrator', 'Administrator'],
|
||||
['nobody', 'Nobody'],
|
||||
]);
|
||||
|
||||
class UserController {
|
||||
constructor(ctx, section) {
|
||||
new Promise((resolve, reject) => {
|
||||
if (ctx.state.user) {
|
||||
resolve(ctx.state.user);
|
||||
return;
|
||||
}
|
||||
api.get('/user/' + ctx.params.name).then(response => {
|
||||
response.rankName = rankNames.get(response.rank);
|
||||
ctx.state.user = response;
|
||||
ctx.save();
|
||||
resolve(ctx.state.user);
|
||||
}, response => {
|
||||
reject(response.description);
|
||||
});
|
||||
}).then(user => {
|
||||
User.get(ctx.params.name).then(user => {
|
||||
const isLoggedIn = api.isLoggedIn(user);
|
||||
const infix = isLoggedIn ? 'self' : 'any';
|
||||
|
||||
this._name = ctx.params.name;
|
||||
user.addEventListener('change', e => this._evtSaved(e));
|
||||
|
||||
const myRankIndex = api.user ?
|
||||
api.allRanks.indexOf(api.user.rank) :
|
||||
0;
|
||||
|
@ -48,7 +29,7 @@ class UserController {
|
|||
if (rankIdx > myRankIndex) {
|
||||
continue;
|
||||
}
|
||||
ranks[rankIdentifier] = rankNames.get(rankIdentifier);
|
||||
ranks[rankIdentifier] = api.rankNames.get(rankIdentifier);
|
||||
}
|
||||
|
||||
if (isLoggedIn) {
|
||||
|
@ -77,64 +58,64 @@ class UserController {
|
|||
});
|
||||
}
|
||||
|
||||
_evtSaved(e) {
|
||||
if (this._name !== e.detail.user.name) {
|
||||
router.replace(
|
||||
'/user/' + e.detail.user.name + '/edit', null, false);
|
||||
}
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
||||
const infix = isLoggedIn ? 'self' : 'any';
|
||||
|
||||
const files = [];
|
||||
const data = {};
|
||||
if (e.detail.name) {
|
||||
data.name = e.detail.name;
|
||||
if (e.detail.name !== undefined) {
|
||||
e.detail.user.name = e.detail.name;
|
||||
}
|
||||
if (e.detail.password) {
|
||||
data.password = e.detail.password;
|
||||
if (e.detail.email !== undefined) {
|
||||
e.detail.user.email = e.detail.email;
|
||||
}
|
||||
if (api.hasPrivilege('users:edit:' + infix + ':email')) {
|
||||
data.email = e.detail.email;
|
||||
}
|
||||
if (e.detail.rank) {
|
||||
data.rank = e.detail.rank;
|
||||
}
|
||||
if (e.detail.avatarStyle &&
|
||||
(e.detail.avatarStyle != e.detail.user.avatarStyle ||
|
||||
e.detail.avatarContent)) {
|
||||
data.avatarStyle = e.detail.avatarStyle;
|
||||
}
|
||||
if (e.detail.avatarContent) {
|
||||
files.avatar = e.detail.avatarContent;
|
||||
if (e.detail.rank !== undefined) {
|
||||
e.detail.user.rank = e.detail.rank;
|
||||
}
|
||||
|
||||
api.put('/user/' + e.detail.user.name, data, files)
|
||||
.then(response => {
|
||||
return isLoggedIn ?
|
||||
api.login(
|
||||
data.name || api.userName,
|
||||
data.password || api.userPassword,
|
||||
false) :
|
||||
Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
}).then(() => {
|
||||
if (data.name && data.name !== e.detail.user.name) {
|
||||
// TODO: update header links and text
|
||||
router.replace('/user/' + data.name + '/edit', null, false);
|
||||
}
|
||||
this._view.showSuccess('Settings updated.');
|
||||
this._view.enableForm();
|
||||
}, errorMessage => {
|
||||
this._view.showError(errorMessage);
|
||||
this._view.enableForm();
|
||||
});
|
||||
if (e.detail.password !== undefined) {
|
||||
e.detail.user.password = e.detail.password;
|
||||
}
|
||||
|
||||
if (e.detail.avatarStyle !== undefined) {
|
||||
e.detail.user.avatarStyle = e.detail.avatarStyle;
|
||||
if (e.detail.avatarContent) {
|
||||
e.detail.user.avatarContent = e.detail.avatarContent;
|
||||
}
|
||||
}
|
||||
|
||||
e.detail.user.save().then(() => {
|
||||
return isLoggedIn ?
|
||||
api.login(
|
||||
e.detail.name || api.userName,
|
||||
e.detail.password || api.userPassword,
|
||||
false) :
|
||||
Promise.resolve();
|
||||
}, errorMessage => {
|
||||
return Promise.reject(errorMessage);
|
||||
}).then(() => {
|
||||
this._view.showSuccess('Settings updated.');
|
||||
this._view.enableForm();
|
||||
}, errorMessage => {
|
||||
this._view.showError(errorMessage);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
|
||||
_evtDelete(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
||||
api.delete('/user/' + e.detail.user.name)
|
||||
.then(response => {
|
||||
e.detail.user.delete()
|
||||
.then(() => {
|
||||
if (isLoggedIn) {
|
||||
api.forget();
|
||||
api.logout();
|
||||
|
@ -146,7 +127,7 @@ class UserController {
|
|||
const ctx = router.show('/');
|
||||
ctx.controller.showSuccess('Account deleted.');
|
||||
}
|
||||
}, response => {
|
||||
}, errorMessage => {
|
||||
this._view.showError(response.description);
|
||||
this._view.enableForm();
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const UserList = require('../models/user_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const UsersHeaderView = require('../views/users_header_view.js');
|
||||
|
@ -15,13 +16,9 @@ class UserListController {
|
|||
searchQuery: ctx.searchQuery,
|
||||
clientUrl: '/users/' + misc.formatSearchQuery({
|
||||
text: ctx.searchQuery.text, page: '{page}'}),
|
||||
requestPage: PageController.createHistoryCacheProxy(
|
||||
ctx,
|
||||
page => {
|
||||
const text = ctx.searchQuery.text;
|
||||
return api.get(
|
||||
`/users/?query=${text}&page=${page}&pageSize=30`);
|
||||
}),
|
||||
requestPage: page => {
|
||||
return UserList.search(ctx.searchQuery.text, page);
|
||||
},
|
||||
headerRenderer: headerCtx => {
|
||||
return new UsersHeaderView(headerCtx);
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const User = require('../models/user.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const RegistrationView = require('../views/registration_view.js');
|
||||
|
||||
|
@ -15,11 +16,11 @@ class UserRegistrationController {
|
|||
_evtRegister(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
api.post('/users/', {
|
||||
name: e.detail.name,
|
||||
password: e.detail.password,
|
||||
email: e.detail.email
|
||||
}).then(() => {
|
||||
const user = new User();
|
||||
user.name = e.detail.name;
|
||||
user.email = e.detail.email;
|
||||
user.password = e.detail.password;
|
||||
user.save().then(() => {
|
||||
api.forget();
|
||||
return api.login(e.detail.name, e.detail.password, false);
|
||||
}, response => {
|
||||
|
|
|
@ -25,7 +25,7 @@ class TagAutoCompleteControl extends AutoCompleteControl {
|
|||
return Array.from(allTags.entries())
|
||||
.filter(kv => match(transform(kv[0]), text))
|
||||
.sort((kv1, kv2) => {
|
||||
return kv2[1].usages - kv1[1].usages;
|
||||
return kv2[1].postCount - kv1[1].postCount;
|
||||
})
|
||||
.map(kv => {
|
||||
const category = kv[1].category;
|
||||
|
|
59
client/js/models/abstract_list.js
Normal file
59
client/js/models/abstract_list.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
'use strict';
|
||||
|
||||
const events = require('../events.js');
|
||||
|
||||
class AbstractList extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._list = [];
|
||||
}
|
||||
|
||||
static fromResponse(response) {
|
||||
const ret = new this();
|
||||
for (let item of response) {
|
||||
const addedItem = this._itemClass.fromResponse(item);
|
||||
addedItem.addEventListener('delete', e => {
|
||||
ret.remove(addedItem);
|
||||
});
|
||||
ret._list.push(addedItem);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
add(item) {
|
||||
item.addEventListener('delete', e => {
|
||||
this.remove(item);
|
||||
});
|
||||
this._list.push(item);
|
||||
const detail = {};
|
||||
detail[this.constructor._itemName] = item;
|
||||
this.dispatchEvent(new CustomEvent('add', {
|
||||
detail: detail,
|
||||
}));
|
||||
}
|
||||
|
||||
remove(itemToRemove) {
|
||||
for (let [index, item] of this._list.entries()) {
|
||||
if (item !== itemToRemove) {
|
||||
continue;
|
||||
}
|
||||
this._list.splice(index, 1);
|
||||
const detail = {};
|
||||
detail[this.constructor._itemName] = itemToRemove;
|
||||
this.dispatchEvent(new CustomEvent('remove', {
|
||||
detail: detail,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._list.length;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._list[Symbol.iterator]();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AbstractList;
|
|
@ -6,16 +6,14 @@ const events = require('../events.js');
|
|||
class Comment extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this.commentList = null;
|
||||
|
||||
this._id = null;
|
||||
this._postId = null;
|
||||
this._text = null;
|
||||
this._user = null;
|
||||
this._id = null;
|
||||
this._postId = null;
|
||||
this._text = null;
|
||||
this._user = null;
|
||||
this._creationTime = null;
|
||||
this._lastEditTime = null;
|
||||
this._score = null;
|
||||
this._ownScore = null;
|
||||
this._score = null;
|
||||
this._ownScore = null;
|
||||
}
|
||||
|
||||
static create(postId) {
|
||||
|
@ -30,16 +28,16 @@ class Comment extends events.EventTarget {
|
|||
return comment;
|
||||
}
|
||||
|
||||
get id() { return this._id; }
|
||||
get postId() { return this._postId; }
|
||||
get text() { return this._text; }
|
||||
get user() { return this._user; }
|
||||
get id() { return this._id; }
|
||||
get postId() { return this._postId; }
|
||||
get text() { return this._text; }
|
||||
get user() { return this._user; }
|
||||
get creationTime() { return this._creationTime; }
|
||||
get lastEditTime() { return this._lastEditTime; }
|
||||
get score() { return this._score; }
|
||||
get ownScore() { return this._ownScore; }
|
||||
get score() { return this._score; }
|
||||
get ownScore() { return this._ownScore; }
|
||||
|
||||
set text(value) { this._text = value; }
|
||||
set text(value) { this._text = value; }
|
||||
|
||||
save() {
|
||||
let promise = null;
|
||||
|
@ -61,7 +59,7 @@ class Comment extends events.EventTarget {
|
|||
return promise.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
details: {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
}));
|
||||
|
@ -74,11 +72,8 @@ class Comment extends events.EventTarget {
|
|||
delete() {
|
||||
return api.delete('/comment/' + this._id)
|
||||
.then(response => {
|
||||
if (this.commentList) {
|
||||
this.commentList.remove(this);
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
details: {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
}));
|
||||
|
@ -93,7 +88,7 @@ class Comment extends events.EventTarget {
|
|||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
details: {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
}));
|
||||
|
@ -104,14 +99,14 @@ class Comment extends events.EventTarget {
|
|||
}
|
||||
|
||||
_updateFromResponse(response) {
|
||||
this._id = response.id;
|
||||
this._postId = response.postId;
|
||||
this._text = response.text;
|
||||
this._user = response.user;
|
||||
this._id = response.id;
|
||||
this._postId = response.postId;
|
||||
this._text = response.text;
|
||||
this._user = response.user;
|
||||
this._creationTime = response.creationTime;
|
||||
this._lastEditTime = response.lastEditTime;
|
||||
this._score = parseInt(response.score);
|
||||
this._ownScore = parseInt(response.ownScore);
|
||||
this._score = parseInt(response.score);
|
||||
this._ownScore = parseInt(response.ownScore);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,59 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
const events = require('../events.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Comment = require('./comment.js');
|
||||
|
||||
class CommentList extends events.EventTarget {
|
||||
constructor(comments) {
|
||||
super();
|
||||
this._list = [];
|
||||
}
|
||||
|
||||
static fromResponse(commentsResponse) {
|
||||
const commentList = new CommentList();
|
||||
for (let commentResponse of commentsResponse) {
|
||||
const comment = Comment.fromResponse(commentResponse);
|
||||
comment.commentList = commentList;
|
||||
commentList._list.push(comment);
|
||||
}
|
||||
return commentList;
|
||||
}
|
||||
|
||||
get comments() {
|
||||
return [...this._list];
|
||||
}
|
||||
|
||||
add(comment) {
|
||||
comment.commentList = this;
|
||||
this._list.push(comment);
|
||||
this.dispatchEvent(new CustomEvent('add', {
|
||||
detail: {
|
||||
comment: comment,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
remove(commentToRemove) {
|
||||
for (let [index, comment] of this._list.entries()) {
|
||||
if (comment.id === commentToRemove.id) {
|
||||
this._list.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('remove', {
|
||||
detail: {
|
||||
comment: commentToRemove,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._list.length;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._list[Symbol.iterator]();
|
||||
}
|
||||
class CommentList extends AbstractList {
|
||||
}
|
||||
|
||||
CommentList._itemClass = Comment;
|
||||
CommentList._itemName = 'comment';
|
||||
|
||||
module.exports = CommentList;
|
||||
|
|
16
client/js/models/info.js
Normal file
16
client/js/models/info.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
|
||||
class Info {
|
||||
static get() {
|
||||
return api.get('/info')
|
||||
.then(response => {
|
||||
return Promise.resolve(response);
|
||||
}, response => {
|
||||
return Promise.reject(response.errorMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Info;
|
|
@ -7,67 +7,66 @@ const CommentList = require('./comment_list.js');
|
|||
class Post extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._id = null;
|
||||
this._type = null;
|
||||
this._mimeType = null;
|
||||
this._creationTime = null;
|
||||
this._user = null;
|
||||
this._safety = null;
|
||||
this._contentUrl = null;
|
||||
this._thumbnailUrl = null;
|
||||
this._canvasWidth = null;
|
||||
this._canvasHeight = null;
|
||||
this._fileSize = null;
|
||||
this._id = null;
|
||||
this._type = null;
|
||||
this._mimeType = null;
|
||||
this._creationTime = null;
|
||||
this._user = null;
|
||||
this._safety = null;
|
||||
this._contentUrl = null;
|
||||
this._thumbnailUrl = null;
|
||||
this._canvasWidth = null;
|
||||
this._canvasHeight = null;
|
||||
this._fileSize = null;
|
||||
|
||||
this._tags = [];
|
||||
this._notes = [];
|
||||
this._comments = [];
|
||||
this._relations = [];
|
||||
this._tags = [];
|
||||
this._notes = [];
|
||||
this._comments = [];
|
||||
this._relations = [];
|
||||
|
||||
this._score = null;
|
||||
this._score = null;
|
||||
this._favoriteCount = null;
|
||||
this._ownScore = null;
|
||||
this._ownFavorite = null;
|
||||
this._ownScore = null;
|
||||
this._ownFavorite = null;
|
||||
}
|
||||
|
||||
get id() { return this._id; }
|
||||
get type() { return this._type; }
|
||||
get mimeType() { return this._mimeType; }
|
||||
get creationTime() { return this._creationTime; }
|
||||
get user() { return this._user; }
|
||||
get safety() { return this._safety; }
|
||||
get contentUrl() { return this._contentUrl; }
|
||||
get thumbnailUrl() { return this._thumbnailUrl; }
|
||||
get canvasWidth() { return this._canvasWidth || 800; }
|
||||
get canvasHeight() { return this._canvasHeight || 450; }
|
||||
get fileSize() { return this._fileSize || 0; }
|
||||
|
||||
get tags() { return this._tags; }
|
||||
get notes() { return this._notes; }
|
||||
get comments() { return this._comments; }
|
||||
get relations() { return this._relations; }
|
||||
|
||||
get score() { return this._score; }
|
||||
get favoriteCount() { return this._favoriteCount; }
|
||||
get ownFavorite() { return this._ownFavorite; }
|
||||
get ownScore() { return this._ownScore; }
|
||||
|
||||
static fromResponse(response) {
|
||||
const post = new Post();
|
||||
post._updateFromResponse(response);
|
||||
return post;
|
||||
const ret = new Post();
|
||||
ret._updateFromResponse(response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static get(id) {
|
||||
return api.get('/post/' + id)
|
||||
.then(response => {
|
||||
const post = Post.fromResponse(response);
|
||||
return Promise.resolve(post);
|
||||
return Promise.resolve(Post.fromResponse(response));
|
||||
}, response => {
|
||||
return Promise.reject(response);
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
get id() { return this._id; }
|
||||
get type() { return this._type; }
|
||||
get mimeType() { return this._mimeType; }
|
||||
get creationTime() { return this._creationTime; }
|
||||
get user() { return this._user; }
|
||||
get safety() { return this._safety; }
|
||||
get contentUrl() { return this._contentUrl; }
|
||||
get thumbnailUrl() { return this._thumbnailUrl; }
|
||||
get canvasWidth() { return this._canvasWidth || 800; }
|
||||
get canvasHeight() { return this._canvasHeight || 450; }
|
||||
get fileSize() { return this._fileSize || 0; }
|
||||
|
||||
get tags() { return this._tags; }
|
||||
get notes() { return this._notes; }
|
||||
get comments() { return this._comments; }
|
||||
get relations() { return this._relations; }
|
||||
|
||||
get score() { return this._score; }
|
||||
get favoriteCount() { return this._favoriteCount; }
|
||||
get ownFavorite() { return this._ownFavorite; }
|
||||
get ownScore() { return this._ownScore; }
|
||||
|
||||
setScore(score) {
|
||||
return api.put('/post/' + this._id + '/score', {score: score})
|
||||
.then(response => {
|
||||
|
@ -75,13 +74,13 @@ class Post extends events.EventTarget {
|
|||
this._updateFromResponse(response);
|
||||
if (this._ownFavorite !== prevFavorite) {
|
||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||
details: {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
details: {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
|
@ -98,13 +97,13 @@ class Post extends events.EventTarget {
|
|||
this._updateFromResponse(response);
|
||||
if (this._ownScore !== prevScore) {
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
details: {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||
details: {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
|
@ -121,13 +120,13 @@ class Post extends events.EventTarget {
|
|||
this._updateFromResponse(response);
|
||||
if (this._ownScore !== prevScore) {
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
details: {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||
details: {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
|
@ -138,27 +137,27 @@ class Post extends events.EventTarget {
|
|||
}
|
||||
|
||||
_updateFromResponse(response) {
|
||||
this._id = response.id;
|
||||
this._type = response.type;
|
||||
this._mimeType = response.mimeType;
|
||||
this._creationTime = response.creationTime;
|
||||
this._user = response.user;
|
||||
this._safety = response.safety;
|
||||
this._contentUrl = response.contentUrl;
|
||||
this._thumbnailUrl = response.thumbnailUrl;
|
||||
this._canvasWidth = response.canvasWidth;
|
||||
this._canvasHeight = response.canvasHeight;
|
||||
this._fileSize = response.fileSize;
|
||||
this._id = response.id;
|
||||
this._type = response.type;
|
||||
this._mimeType = response.mimeType;
|
||||
this._creationTime = response.creationTime;
|
||||
this._user = response.user;
|
||||
this._safety = response.safety;
|
||||
this._contentUrl = response.contentUrl;
|
||||
this._thumbnailUrl = response.thumbnailUrl;
|
||||
this._canvasWidth = response.canvasWidth;
|
||||
this._canvasHeight = response.canvasHeight;
|
||||
this._fileSize = response.fileSize;
|
||||
|
||||
this._tags = response.tags;
|
||||
this._notes = response.notes;
|
||||
this._comments = CommentList.fromResponse(response.comments);
|
||||
this._relations = response.relations;
|
||||
this._tags = response.tags;
|
||||
this._notes = response.notes;
|
||||
this._comments = CommentList.fromResponse(response.comments || []);
|
||||
this._relations = response.relations;
|
||||
|
||||
this._score = response.score;
|
||||
this._score = response.score;
|
||||
this._favoriteCount = response.favoriteCount;
|
||||
this._ownScore = response.ownScore;
|
||||
this._ownFavorite = response.ownFavorite;
|
||||
this._ownScore = response.ownScore;
|
||||
this._ownFavorite = response.ownFavorite;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
'use strict';
|
||||
|
||||
const events = require('../events.js');
|
||||
const api = require('../api.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Post = require('./post.js');
|
||||
|
||||
class PostList extends events.EventTarget {
|
||||
constructor(posts) {
|
||||
super();
|
||||
this._list = [];
|
||||
class PostList extends AbstractList {
|
||||
static getAround(id, searchQuery) {
|
||||
return api.get(`/post/${id}/around?fields=id&query=${searchQuery}`)
|
||||
.then(response => {
|
||||
return Promise.resolve(response);
|
||||
}).catch(response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
static fromResponse(postsResponse) {
|
||||
const postList = new PostList();
|
||||
for (let postResponse of postsResponse) {
|
||||
postList._list.push(Post.fromResponse(postResponse));
|
||||
}
|
||||
return postList;
|
||||
}
|
||||
|
||||
get posts() {
|
||||
return [...this._list];
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._list.length;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._list[Symbol.iterator]();
|
||||
static search(text, page, pageSize, fields) {
|
||||
const url =
|
||||
`/posts/?query=${text}` +
|
||||
`&page=${page}` +
|
||||
`&pageSize=${pageSize}` +
|
||||
`&fields=${fields.join(',')}`;
|
||||
return api.get(url).then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: PostList.fromResponse(response.results)}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PostList._itemClass = Post;
|
||||
PostList._itemName = 'post';
|
||||
|
||||
module.exports = PostList;
|
||||
|
|
114
client/js/models/tag.js
Normal file
114
client/js/models/tag.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const events = require('../events.js');
|
||||
|
||||
class Tag extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._origName = null;
|
||||
this._names = null;
|
||||
this._category = null;
|
||||
this._suggestions = null;
|
||||
this._implications = null;
|
||||
this._postCount = null;
|
||||
this._creationTime = null;
|
||||
this._lastEditTime = null;
|
||||
}
|
||||
|
||||
get names() { return this._names; }
|
||||
get category() { return this._category; }
|
||||
get suggestions() { return this._suggestions; }
|
||||
get implications() { return this._implications; }
|
||||
get postCount() { return this._postCount; }
|
||||
get creationTime() { return this._creationTime; }
|
||||
get lastEditTime() { return this._lastEditTime; }
|
||||
|
||||
set names(value) { this._names = value; }
|
||||
set category(value) { this._category = value; }
|
||||
set implications(value) { this._implications = value; }
|
||||
set suggestions(value) { this._suggestions = value; }
|
||||
|
||||
static fromResponse(response) {
|
||||
const ret = new Tag();
|
||||
ret._updateFromResponse(response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static get(id) {
|
||||
return api.get('/tag/' + id)
|
||||
.then(response => {
|
||||
return Promise.resolve(Tag.fromResponse(response));
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
const detail = {
|
||||
names: this.names,
|
||||
category: this.category,
|
||||
implications: this.implications,
|
||||
suggestions: this.suggestions,
|
||||
};
|
||||
let promise = this._origName ?
|
||||
api.put('/tag/' + this._origName, detail) :
|
||||
api.post('/tags', detail);
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
merge(targetName) {
|
||||
return api.post('/tag-merge/', {
|
||||
remove: this._origName,
|
||||
mergeTo: targetName,
|
||||
}).then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete('/tag/' + this._origName)
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
_updateFromResponse(response) {
|
||||
this._origName = response.names ? response.names[0] : null;
|
||||
this._names = response.names;
|
||||
this._category = response.category;
|
||||
this._implications = response.implications;
|
||||
this._suggestions = response.suggestions;
|
||||
this._creationTime = response.creationTime;
|
||||
this._lastEditTime = response.lastEditTime;
|
||||
this._postCount = response.usages;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Tag;
|
87
client/js/models/tag_category.js
Normal file
87
client/js/models/tag_category.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const events = require('../events.js');
|
||||
|
||||
class TagCategory extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._name = '';
|
||||
this._color = '#000000';
|
||||
this._tagCount = 0;
|
||||
this._isDefault = false;
|
||||
this._origName = null;
|
||||
this._origColor = null;
|
||||
}
|
||||
|
||||
get name() { return this._name; }
|
||||
get color() { return this._color; }
|
||||
get tagCount() { return this._tagCount; }
|
||||
get isDefault() { return this._isDefault; }
|
||||
get isTransient() { return !this._origName; }
|
||||
|
||||
set name(value) { this._name = value; }
|
||||
set color(value) { this._color = value; }
|
||||
|
||||
static fromResponse(response) {
|
||||
const ret = new TagCategory();
|
||||
ret._updateFromResponse(response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
save() {
|
||||
const data = {};
|
||||
if (this.name !== this._origName) {
|
||||
data.name = this.name;
|
||||
}
|
||||
if (this.color !== this._origColor) {
|
||||
data.color = this.color;
|
||||
}
|
||||
|
||||
if (!Object.keys(data).length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let promise = this._origName ?
|
||||
api.put('/tag-category/' + this._origName, data) :
|
||||
api.post('/tag-categories', data);
|
||||
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
tagCategory: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete('/tag-category/' + this._origName)
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
tagCategory: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
_updateFromResponse(response) {
|
||||
this._name = response.name;
|
||||
this._color = response.color;
|
||||
this._isDefault = response.default;
|
||||
this._tagCount = response.usages;
|
||||
this._origName = this.name;
|
||||
this._origColor = this.color;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TagCategory;
|
80
client/js/models/tag_category_list.js
Normal file
80
client/js/models/tag_category_list.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const TagCategory = require('./tag_category.js');
|
||||
|
||||
class TagCategoryList extends AbstractList {
|
||||
constructor() {
|
||||
super();
|
||||
this._defaultCategory = null;
|
||||
this._origDefaultCategory = null;
|
||||
this._deletedCategories = [];
|
||||
this.addEventListener('remove', e => this._evtCategoryDeleted(e));
|
||||
}
|
||||
|
||||
static fromResponse(response) {
|
||||
const ret = super.fromResponse(response);
|
||||
ret._defaultCategory = null;
|
||||
for (let tagCategory of ret) {
|
||||
if (tagCategory.isDefault) {
|
||||
ret._defaultCategory = tagCategory;
|
||||
}
|
||||
}
|
||||
ret._origDefaultCategory = ret._defaultCategory;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static get() {
|
||||
return api.get('/tag-categories/').then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: TagCategoryList.fromResponse(response.results)}));
|
||||
});
|
||||
}
|
||||
|
||||
get defaultCategory() {
|
||||
return this._defaultCategory;
|
||||
}
|
||||
|
||||
set defaultCategory(tagCategory) {
|
||||
this._defaultCategory = tagCategory;
|
||||
}
|
||||
|
||||
save() {
|
||||
let promises = [];
|
||||
for (let tagCategory of this) {
|
||||
promises.push(tagCategory.save());
|
||||
}
|
||||
for (let tagCategory of this._deletedCategories) {
|
||||
promises.push(tagCategory.delete());
|
||||
}
|
||||
|
||||
if (this._defaultCategory !== this._origDefaultCategory) {
|
||||
promises.push(
|
||||
api.put(
|
||||
`/tag-category/${this._defaultCategory.name}/default`));
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(response => {
|
||||
this._deletedCategories = [];
|
||||
return Promise.resolve();
|
||||
}, errorMessage => {
|
||||
return Promise.reject(
|
||||
errorMessage.description || errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
_evtCategoryDeleted(e) {
|
||||
if (!e.detail.tagCategory.isTransient) {
|
||||
this._deletedCategories.push(e.detail.tagCategory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TagCategoryList._itemClass = TagCategory;
|
||||
TagCategoryList._itemName = 'tagCategory';
|
||||
|
||||
module.exports = TagCategoryList;
|
24
client/js/models/tag_list.js
Normal file
24
client/js/models/tag_list.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Tag = require('./tag.js');
|
||||
|
||||
class TagList extends AbstractList {
|
||||
static search(text, page, pageSize, fields) {
|
||||
const url =
|
||||
`/tags/?query=${text}` +
|
||||
`&page=${page}` +
|
||||
`&pageSize=${pageSize}` +
|
||||
`&fields=${fields.join(',')}`;
|
||||
return api.get(url).then(response => {
|
||||
response.results = TagList.fromResponse(response.results);
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TagList._itemClass = Tag;
|
||||
TagList._itemName = 'tag';
|
||||
|
||||
module.exports = TagList;
|
149
client/js/models/user.js
Normal file
149
client/js/models/user.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const events = require('../events.js');
|
||||
|
||||
class User extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._name = null;
|
||||
this._rank = null;
|
||||
this._email = null;
|
||||
this._avatarStyle = null;
|
||||
this._avatarUrl = null;
|
||||
this._creationTime = null;
|
||||
this._lastLoginTime = null;
|
||||
this._commentCount = null;
|
||||
this._favoritePostCount = null;
|
||||
this._uploadedPostCount = null;
|
||||
this._likedPostCount = null;
|
||||
this._dislikedPostCount = null;
|
||||
|
||||
this._origName = null;
|
||||
this._origEmail = null;
|
||||
this._origRank = null;
|
||||
this._origAvatarStyle = null;
|
||||
|
||||
this._password = null;
|
||||
this._avatarContent = null;
|
||||
}
|
||||
|
||||
get name() { return this._name; }
|
||||
get rank() { return this._rank; }
|
||||
get email() { return this._email; }
|
||||
get avatarStyle() { return this._avatarStyle; }
|
||||
get avatarUrl() { return this._avatarUrl; }
|
||||
get creationTime() { return this._creationTime; }
|
||||
get lastLoginTime() { return this._lastLoginTime; }
|
||||
get commentCount() { return this._commentCount; }
|
||||
get favoritePostCount() { return this._favoritePostCount; }
|
||||
get uploadedPostCount() { return this._uploadedPostCount; }
|
||||
get likedPostCount() { return this._likedPostCount; }
|
||||
get dislikedPostCount() { return this._dislikedPostCount; }
|
||||
get rankName() { return api.rankNames.get(this.rank); }
|
||||
get avatarContent() { throw 'Invalid operation'; }
|
||||
get password() { throw 'Invalid operation'; }
|
||||
|
||||
set name(value) { this._name = value; }
|
||||
set rank(value) { this._rank = value; }
|
||||
set email(value) { this._email = value || null; }
|
||||
set avatarStyle(value) { this._avatarStyle = value; }
|
||||
set avatarContent(value) { this._avatarContent = value; }
|
||||
set password(value) { this._password = value; }
|
||||
|
||||
static fromResponse(response) {
|
||||
const ret = new User();
|
||||
ret._updateFromResponse(response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static get(name) {
|
||||
return api.get('/user/' + name)
|
||||
.then(response => {
|
||||
return Promise.resolve(User.fromResponse(response));
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
const files = [];
|
||||
const data = {};
|
||||
if (this.name !== this._origName) {
|
||||
data.name = this.name;
|
||||
}
|
||||
if (this._password) {
|
||||
data.password = this._password;
|
||||
}
|
||||
|
||||
if (this.email !== this._origEmail) {
|
||||
data.email = this.email;
|
||||
}
|
||||
|
||||
if (this.rank !== this._origRank) {
|
||||
data.rank = this.rank;
|
||||
}
|
||||
if (this.avatarStyle !== this._origAvatarStyle) {
|
||||
data.avatarStyle = this.avatarStyle;
|
||||
}
|
||||
if (this._avatarContent) {
|
||||
files.avatar = this._avatarContent;
|
||||
}
|
||||
|
||||
let promise = this._origName ?
|
||||
api.put('/user/' + this._origName, data, files) :
|
||||
api.post('/users', data, files);
|
||||
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
user: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete('/user/' + this._origName)
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
user: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
}, response => {
|
||||
return Promise.reject(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
_updateFromResponse(response) {
|
||||
this._name = response.name;
|
||||
this._rank = response.rank;
|
||||
this._email = response.email;
|
||||
this._avatarStyle = response.avatarStyle;
|
||||
this._avatarUrl = response.avatarUrl;
|
||||
this._creationTime = response.creationTime;
|
||||
this._lastLoginTime = response.lastLoginTime;
|
||||
this._commentCount = response.commentCount;
|
||||
this._favoritePostCount = response.favoritePostCount;
|
||||
this._uploadedPostCount = response.uploadedPostCount;
|
||||
this._likedPostCount = response.likedPostCount;
|
||||
this._dislikedPostCount = response.dislikedPostCount;
|
||||
|
||||
this._origName = this.name;
|
||||
this._origRank = this.rank;
|
||||
this._origEmail = this.email;
|
||||
this._origAvatarStyle = this.avatarStyle;
|
||||
|
||||
this._password = null;
|
||||
this._avatarContent = null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = User;
|
22
client/js/models/user_list.js
Normal file
22
client/js/models/user_list.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const User = require('./user.js');
|
||||
|
||||
class UserList extends AbstractList {
|
||||
static search(text, page) {
|
||||
const url = `/users/?query=${text}&page=${page}&pageSize=30`;
|
||||
return api.get(url).then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: UserList.fromResponse(response.results)}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
UserList._itemClass = User;
|
||||
UserList._itemName = 'user';
|
||||
|
||||
module.exports = UserList;
|
|
@ -1,40 +1,59 @@
|
|||
'use strict';
|
||||
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const TagCategory = require('../models/tag_category.js');
|
||||
|
||||
const template = views.getTemplate('tag-categories');
|
||||
const rowTemplate = views.getTemplate('tag-category-row');
|
||||
|
||||
class TagCategoriesView {
|
||||
class TagCategoriesView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
super();
|
||||
this._ctx = ctx;
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
const sourceNode = template(ctx);
|
||||
|
||||
const formNode = sourceNode.querySelector('form');
|
||||
const newRowTemplate = sourceNode.querySelector('.add-template');
|
||||
const tableBodyNode = sourceNode.querySelector('tbody');
|
||||
const addLinkNode = sourceNode.querySelector('a.add');
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
views.decorateValidator(this._formNode);
|
||||
|
||||
newRowTemplate.parentNode.removeChild(newRowTemplate);
|
||||
views.decorateValidator(formNode);
|
||||
|
||||
for (let row of tableBodyNode.querySelectorAll('tr')) {
|
||||
this._addRowHandlers(row);
|
||||
}
|
||||
|
||||
if (addLinkNode) {
|
||||
addLinkNode.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
let newRow = newRowTemplate.cloneNode(true);
|
||||
tableBody.appendChild(newRow);
|
||||
this._addRowHandlers(row);
|
||||
});
|
||||
}
|
||||
|
||||
formNode.addEventListener('submit', e => {
|
||||
this._evtSaveButtonClick(e, ctx);
|
||||
const categoriesToAdd = Array.from(ctx.tagCategories);
|
||||
categoriesToAdd.sort((a, b) => {
|
||||
if (b.isDefault) {
|
||||
return 1;
|
||||
} else if (a.isDefault) {
|
||||
return -1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
for (let tagCategory of categoriesToAdd) {
|
||||
this._addTagCategoryRowNode(tagCategory);
|
||||
}
|
||||
|
||||
views.replaceContent(this._hostNode, sourceNode);
|
||||
if (this._addLinkNode) {
|
||||
this._addLinkNode.addEventListener(
|
||||
'click', e => this._evtAddButtonClick(e));
|
||||
}
|
||||
|
||||
ctx.tagCategories.addEventListener(
|
||||
'add', e => this._evtTagCategoryAdded(e));
|
||||
|
||||
ctx.tagCategories.addEventListener(
|
||||
'remove', e => this._evtTagCategoryDeleted(e));
|
||||
|
||||
this._formNode.addEventListener(
|
||||
'submit', e => this._evtSaveButtonClick(e, ctx));
|
||||
}
|
||||
|
||||
enableForm() {
|
||||
views.enableForm(this._formNode);
|
||||
}
|
||||
|
||||
disableForm() {
|
||||
views.disableForm(this._formNode);
|
||||
}
|
||||
|
||||
clearMessages() {
|
||||
views.clearMessages(this._hostNode);
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
|
@ -45,88 +64,100 @@ class TagCategoriesView {
|
|||
views.showError(this._hostNode, message);
|
||||
}
|
||||
|
||||
_evtSaveButtonClick(e, ctx) {
|
||||
e.preventDefault();
|
||||
|
||||
views.clearMessages(this._hostNode);
|
||||
const tableBodyNode = this._hostNode.querySelector('tbody');
|
||||
|
||||
ctx.getCategories().then(categories => {
|
||||
let existingCategories = {};
|
||||
for (let category of categories) {
|
||||
existingCategories[category.name] = category;
|
||||
}
|
||||
|
||||
let defaultCategory = null;
|
||||
let addedCategories = [];
|
||||
let removedCategories = [];
|
||||
let changedCategories = [];
|
||||
let allNames = [];
|
||||
for (let row of tableBodyNode.querySelectorAll('tr')) {
|
||||
let name = row.getAttribute('data-category');
|
||||
let category = {
|
||||
originalName: name,
|
||||
name: row.querySelector('.name input').value,
|
||||
color: row.querySelector('.color input').value,
|
||||
};
|
||||
if (row.classList.contains('default')) {
|
||||
defaultCategory = category.name;
|
||||
}
|
||||
if (!name) {
|
||||
if (category.name) {
|
||||
addedCategories.push(category);
|
||||
}
|
||||
} else {
|
||||
const existingCategory = existingCategories[name];
|
||||
if (existingCategory.color !== category.color ||
|
||||
existingCategory.name !== category.name) {
|
||||
changedCategories.push(category);
|
||||
}
|
||||
}
|
||||
allNames.push(name);
|
||||
}
|
||||
for (let name of Object.keys(existingCategories)) {
|
||||
if (allNames.indexOf(name) === -1) {
|
||||
removedCategories.push(name);
|
||||
}
|
||||
}
|
||||
ctx.saveChanges(
|
||||
addedCategories,
|
||||
changedCategories,
|
||||
removedCategories,
|
||||
defaultCategory);
|
||||
});
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
}
|
||||
|
||||
_evtRemoveButtonClick(e, row, link) {
|
||||
get _tableBodyNode() {
|
||||
return this._hostNode.querySelector('tbody');
|
||||
}
|
||||
|
||||
get _addLinkNode() {
|
||||
return this._hostNode.querySelector('a.add');
|
||||
}
|
||||
|
||||
_addTagCategoryRowNode(tagCategory) {
|
||||
const rowNode = rowTemplate(
|
||||
Object.assign(
|
||||
{}, this._ctx, {tagCategory: tagCategory}));
|
||||
|
||||
const nameInput = rowNode.querySelector('.name input');
|
||||
if (nameInput) {
|
||||
nameInput.addEventListener(
|
||||
'change', e => this._evtNameChange(e, rowNode));
|
||||
}
|
||||
|
||||
const colorInput = rowNode.querySelector('.color input');
|
||||
if (colorInput) {
|
||||
colorInput.addEventListener(
|
||||
'change', e => this._evtColorChange(e, rowNode));
|
||||
}
|
||||
|
||||
const removeLinkNode = rowNode.querySelector('.remove a');
|
||||
if (removeLinkNode) {
|
||||
removeLinkNode.addEventListener(
|
||||
'click', e => this._evtDeleteButtonClick(e, rowNode));
|
||||
}
|
||||
|
||||
const defaultLinkNode = rowNode.querySelector('.set-default a');
|
||||
if (defaultLinkNode) {
|
||||
defaultLinkNode.addEventListener(
|
||||
'click', e => this._evtSetDefaultButtonClick(e, rowNode));
|
||||
}
|
||||
|
||||
this._tableBodyNode.appendChild(rowNode);
|
||||
|
||||
rowNode._tagCategory = tagCategory;
|
||||
tagCategory._rowNode = rowNode;
|
||||
}
|
||||
|
||||
_removeTagCategoryRowNode(tagCategory) {
|
||||
const rowNode = tagCategory._rowNode;
|
||||
rowNode.parentNode.removeChild(rowNode);
|
||||
}
|
||||
|
||||
_evtTagCategoryAdded(e) {
|
||||
this._addTagCategoryRowNode(e.detail.tagCategory);
|
||||
}
|
||||
|
||||
_evtTagCategoryDeleted(e) {
|
||||
this._removeTagCategoryRowNode(e.detail.tagCategory);
|
||||
}
|
||||
|
||||
_evtAddButtonClick(e) {
|
||||
e.preventDefault();
|
||||
if (link.classList.contains('inactive')) {
|
||||
this._ctx.tagCategories.add(new TagCategory());
|
||||
}
|
||||
|
||||
_evtNameChange(e, rowNode) {
|
||||
rowNode._tagCategory.name = e.target.value;
|
||||
}
|
||||
|
||||
_evtColorChange(e, rowNode) {
|
||||
rowNode._tagCategory.color = e.target.value;
|
||||
}
|
||||
|
||||
_evtDeleteButtonClick(e, rowNode, link) {
|
||||
e.preventDefault();
|
||||
if (e.target.classList.contains('inactive')) {
|
||||
return;
|
||||
}
|
||||
row.parentNode.removeChild(row);
|
||||
this._ctx.tagCategories.remove(rowNode._tagCategory);
|
||||
}
|
||||
|
||||
_evtSetDefaultButtonClick(e, row) {
|
||||
_evtSetDefaultButtonClick(e, rowNode) {
|
||||
e.preventDefault();
|
||||
const oldRowNode = row.parentNode.querySelector('tr.default');
|
||||
this._ctx.tagCategories.defaultCategory = rowNode._tagCategory;
|
||||
const oldRowNode = rowNode.parentNode.querySelector('tr.default');
|
||||
if (oldRowNode) {
|
||||
oldRowNode.classList.remove('default');
|
||||
}
|
||||
row.classList.add('default');
|
||||
rowNode.classList.add('default');
|
||||
}
|
||||
|
||||
_addRowHandlers(row) {
|
||||
const removeLink = row.querySelector('.remove a');
|
||||
if (removeLink) {
|
||||
removeLink.addEventListener(
|
||||
'click', e => this._evtRemoveButtonClick(e, row, removeLink));
|
||||
}
|
||||
|
||||
const defaultLink = row.querySelector('.set-default a');
|
||||
if (defaultLink) {
|
||||
defaultLink.addEventListener(
|
||||
'click', e => this._evtSetDefaultButtonClick(e, row));
|
||||
}
|
||||
_evtSaveButtonClick(e, ctx) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,16 +12,21 @@ class TagView extends events.EventTarget {
|
|||
constructor(ctx) {
|
||||
super();
|
||||
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._ctx = ctx;
|
||||
ctx.tag.addEventListener('change', e => this._evtChange(e));
|
||||
ctx.section = ctx.section || 'summary';
|
||||
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._install();
|
||||
}
|
||||
|
||||
_install() {
|
||||
const ctx = this._ctx;
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
|
||||
for (let item of this._hostNode.querySelectorAll('[data-name]')) {
|
||||
if (item.getAttribute('data-name') === ctx.section) {
|
||||
item.className = 'active';
|
||||
} else {
|
||||
item.className = '';
|
||||
}
|
||||
item.classList.toggle(
|
||||
'active', item.getAttribute('data-name') === ctx.section);
|
||||
}
|
||||
|
||||
ctx.hostNode = this._hostNode.querySelector('.tag-content-holder');
|
||||
|
@ -65,6 +70,11 @@ class TagView extends events.EventTarget {
|
|||
showError(message) {
|
||||
this._view.showError(message);
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
this._ctx.tag = e.detail.tag;
|
||||
this._install(this._ctx);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TagView;
|
||||
|
|
|
@ -60,12 +60,12 @@ class UserEditView extends events.EventTarget {
|
|||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
user: this._user,
|
||||
name: this._userNameFieldNode.value,
|
||||
password: this._passwordFieldNode.value,
|
||||
email: this._emailFieldNode.value,
|
||||
rank: this._rankFieldNode.value,
|
||||
avatarStyle: this._avatarStyleFieldNode.value,
|
||||
user: this._user,
|
||||
name: (this._userNameFieldNode || {}).value,
|
||||
email: (this._emailFieldNode || {}).value,
|
||||
rank: (this._rankFieldNode || {}).value,
|
||||
avatarStyle: (this._avatarStyleFieldNode || {}).value,
|
||||
password: (this._passwordFieldNode || {}).value,
|
||||
avatarContent: this._avatarContent,
|
||||
},
|
||||
}));
|
||||
|
|
|
@ -12,10 +12,17 @@ class UserView extends events.EventTarget {
|
|||
constructor(ctx) {
|
||||
super();
|
||||
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._ctx = ctx;
|
||||
ctx.user.addEventListener('change', e => this._evtChange(e));
|
||||
ctx.section = ctx.section || 'summary';
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._install();
|
||||
}
|
||||
|
||||
_install() {
|
||||
const ctx = this._ctx;
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
for (let item of this._hostNode.querySelectorAll('[data-name]')) {
|
||||
if (item.getAttribute('data-name') === ctx.section) {
|
||||
item.className = 'active';
|
||||
|
@ -61,6 +68,11 @@ class UserView extends events.EventTarget {
|
|||
disableForm() {
|
||||
this._view.disableForm();
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
this._ctx.user = e.detail.user;
|
||||
this._install(this._ctx);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserView;
|
||||
|
|
Loading…
Reference in a new issue