client/general: replace direct API with models
This commit is contained in:
parent
5f4b67a2bc
commit
eb09677bf8
|
@ -10,67 +10,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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>
|
</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>
|
</table>
|
||||||
|
|
||||||
<% if (ctx.canCreate) { %>
|
<% if (ctx.canCreate) { %>
|
||||||
|
|
|
@ -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'>
|
<div class='tag-delete'>
|
||||||
<form>
|
<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>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>
|
<p>Check <a href='/posts/text=<%= ctx.tag.names[0] %>'>which posts</a> are tagged with <%= ctx.tag.names[0] %>.</p>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
<% } %>
|
<% } %>
|
||||||
</td>
|
</td>
|
||||||
<td class='usages'>
|
<td class='usages'>
|
||||||
<%= tag.usages %>
|
<%= tag.postCount %>
|
||||||
</td>
|
</td>
|
||||||
<td class='edit-time'>
|
<td class='edit-time'>
|
||||||
<%= ctx.makeRelativeTime(tag.lastEditTime) %>
|
<%= ctx.makeRelativeTime(tag.lastEditTime) %>
|
||||||
|
|
|
@ -22,6 +22,15 @@ class Api extends events.EventTarget {
|
||||||
'administrator',
|
'administrator',
|
||||||
'nobody',
|
'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) {
|
get(url, options) {
|
||||||
|
|
|
@ -7,29 +7,18 @@ const topNavigation = require('../models/top_navigation.js');
|
||||||
const PageController = require('../controllers/page_controller.js');
|
const PageController = require('../controllers/page_controller.js');
|
||||||
const CommentsPageView = require('../views/comments_page_view.js');
|
const CommentsPageView = require('../views/comments_page_view.js');
|
||||||
|
|
||||||
|
const fields = ['id', 'comments', 'commentCount', 'thumbnailUrl'];
|
||||||
|
|
||||||
class CommentsController {
|
class CommentsController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
topNavigation.activate('comments');
|
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({
|
this._pageController = new PageController({
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
clientUrl: '/comments/' + misc.formatSearchQuery({page: '{page}'}),
|
clientUrl: '/comments/' + misc.formatSearchQuery({page: '{page}'}),
|
||||||
requestPage: page => {
|
requestPage: page => {
|
||||||
return proxy(page).then(response => {
|
return PostList.search(
|
||||||
return Promise.resolve(Object.assign(
|
'sort:comment-date+comment-count-min:1', page, 10, fields);
|
||||||
{},
|
|
||||||
response,
|
|
||||||
{results: PostList.fromResponse(response.results)}));
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
pageRenderer: pageCtx => {
|
pageRenderer: pageCtx => {
|
||||||
Object.assign(pageCtx, {
|
Object.assign(pageCtx, {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const config = require('../config.js');
|
const config = require('../config.js');
|
||||||
|
const Info = require('../models/info.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const HomeView = require('../views/home_view.js');
|
const HomeView = require('../views/home_view.js');
|
||||||
|
|
||||||
|
@ -16,20 +17,20 @@ class HomeController {
|
||||||
canListPosts: api.hasPrivilege('posts:list'),
|
canListPosts: api.hasPrivilege('posts:list'),
|
||||||
});
|
});
|
||||||
|
|
||||||
api.get('/info')
|
Info.get()
|
||||||
.then(response => {
|
.then(info => {
|
||||||
this._homeView.setStats({
|
this._homeView.setStats({
|
||||||
diskUsage: response.diskUsage,
|
diskUsage: info.diskUsage,
|
||||||
postCount: response.postCount,
|
postCount: info.postCount,
|
||||||
});
|
});
|
||||||
this._homeView.setFeaturedPost({
|
this._homeView.setFeaturedPost({
|
||||||
featuredPost: response.featuredPost,
|
featuredPost: info.featuredPost,
|
||||||
featuringUser: response.featuringUser,
|
featuringUser: info.featuringUser,
|
||||||
featuringTime: response.featuringTime,
|
featuringTime: info.featuringTime,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
response => {
|
errorMessage => {
|
||||||
this._homeView.showError(response.description);
|
this._homeView.showError(errorMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,22 +28,6 @@ class PageController {
|
||||||
showError(message) {
|
showError(message) {
|
||||||
this._view.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;
|
module.exports = PageController;
|
||||||
|
|
|
@ -5,6 +5,7 @@ const misc = require('../util/misc.js');
|
||||||
const settings = require('../models/settings.js');
|
const settings = require('../models/settings.js');
|
||||||
const Comment = require('../models/comment.js');
|
const Comment = require('../models/comment.js');
|
||||||
const Post = require('../models/post.js');
|
const Post = require('../models/post.js');
|
||||||
|
const PostList = require('../models/post_list.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const PostView = require('../views/post_view.js');
|
const PostView = require('../views/post_view.js');
|
||||||
const EmptyView = require('../views/empty_view.js');
|
const EmptyView = require('../views/empty_view.js');
|
||||||
|
@ -15,8 +16,8 @@ class PostController {
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
Post.get(id),
|
Post.get(id),
|
||||||
api.get(`/post/${id}/around?fields=id&query=` +
|
PostList.getAround(
|
||||||
this._decorateSearchQuery(
|
id, this._decorateSearchQuery(
|
||||||
searchQuery ? searchQuery.text : '')),
|
searchQuery ? searchQuery.text : '')),
|
||||||
]).then(responses => {
|
]).then(responses => {
|
||||||
const [post, aroundResponse] = responses;
|
const [post, aroundResponse] = responses;
|
||||||
|
@ -53,9 +54,9 @@ class PostController {
|
||||||
this._view.commentListControl.addEventListener(
|
this._view.commentListControl.addEventListener(
|
||||||
'delete', e => this._evtDeleteComment(e));
|
'delete', e => this._evtDeleteComment(e));
|
||||||
}
|
}
|
||||||
}, response => {
|
}, errorMessage => {
|
||||||
this._view = new EmptyView();
|
this._view = new EmptyView();
|
||||||
this._view.showError(response.description);
|
this._view.showError(errorMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,16 @@
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const settings = require('../models/settings.js');
|
const settings = require('../models/settings.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
|
const PostList = require('../models/post_list.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const PageController = require('../controllers/page_controller.js');
|
const PageController = require('../controllers/page_controller.js');
|
||||||
const PostsHeaderView = require('../views/posts_header_view.js');
|
const PostsHeaderView = require('../views/posts_header_view.js');
|
||||||
const PostsPageView = require('../views/posts_page_view.js');
|
const PostsPageView = require('../views/posts_page_view.js');
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
'id', 'thumbnailUrl', 'type',
|
||||||
|
'score', 'favoriteCount', 'commentCount', 'tags'];
|
||||||
|
|
||||||
class PostListController {
|
class PostListController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
topNavigation.activate('posts');
|
topNavigation.activate('posts');
|
||||||
|
@ -16,16 +21,11 @@ class PostListController {
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
clientUrl: '/posts/' + misc.formatSearchQuery({
|
clientUrl: '/posts/' + misc.formatSearchQuery({
|
||||||
text: ctx.searchQuery.text, page: '{page}'}),
|
text: ctx.searchQuery.text, page: '{page}'}),
|
||||||
requestPage: PageController.createHistoryCacheProxy(
|
requestPage: page => {
|
||||||
ctx,
|
return PostList.search(
|
||||||
page => {
|
this._decorateSearchQuery(ctx.searchQuery.text),
|
||||||
const text
|
page, 40, fields);
|
||||||
= this._decorateSearchQuery(ctx.searchQuery.text);
|
},
|
||||||
return api.get(
|
|
||||||
`/posts/?query=${text}&page=${page}&pageSize=40` +
|
|
||||||
'&fields=id,type,tags,score,favoriteCount,' +
|
|
||||||
'commentCount,thumbnailUrl');
|
|
||||||
}),
|
|
||||||
headerRenderer: headerCtx => {
|
headerRenderer: headerCtx => {
|
||||||
return new PostsHeaderView(headerCtx);
|
return new PostsHeaderView(headerCtx);
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const tags = require('../tags.js');
|
const tags = require('../tags.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
|
const TagCategoryList = require('../models/tag_category_list.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const TagCategoriesView = require('../views/tag_categories_view.js');
|
const TagCategoriesView = require('../views/tag_categories_view.js');
|
||||||
const EmptyView = require('../views/empty_view.js');
|
const EmptyView = require('../views/empty_view.js');
|
||||||
|
@ -10,65 +11,34 @@ const EmptyView = require('../views/empty_view.js');
|
||||||
class TagCategoriesController {
|
class TagCategoriesController {
|
||||||
constructor() {
|
constructor() {
|
||||||
topNavigation.activate('tags');
|
topNavigation.activate('tags');
|
||||||
api.get('/tag-categories/').then(response => {
|
TagCategoryList.get().then(response => {
|
||||||
|
this._tagCategories = response.results;
|
||||||
this._view = new TagCategoriesView({
|
this._view = new TagCategoriesView({
|
||||||
tagCategories: response.results,
|
tagCategories: this._tagCategories,
|
||||||
canEditName: api.hasPrivilege('tagCategories:edit:name'),
|
canEditName: api.hasPrivilege('tagCategories:edit:name'),
|
||||||
canEditColor: api.hasPrivilege('tagCategories:edit:color'),
|
canEditColor: api.hasPrivilege('tagCategories:edit:color'),
|
||||||
canDelete: api.hasPrivilege('tagCategories:delete'),
|
canDelete: api.hasPrivilege('tagCategories:delete'),
|
||||||
canCreate: api.hasPrivilege('tagCategories:create'),
|
canCreate: api.hasPrivilege('tagCategories:create'),
|
||||||
canSetDefault: api.hasPrivilege('tagCategories:setDefault'),
|
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 => {
|
}, response => {
|
||||||
this._view = new EmptyView();
|
this._view = new EmptyView();
|
||||||
this._view.showError(response.description);
|
this._view.showError(response.description);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveTagCategories(
|
_evtSubmit(e) {
|
||||||
addedCategories,
|
this._view.clearMessages();
|
||||||
changedCategories,
|
this._view.disableForm();
|
||||||
removedCategories,
|
this._tagCategories.save()
|
||||||
defaultCategory) {
|
.then(() => {
|
||||||
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();
|
tags.refreshExport();
|
||||||
|
this._view.enableForm();
|
||||||
this._view.showSuccess('Changes saved.');
|
this._view.showSuccess('Changes saved.');
|
||||||
},
|
}, errorMessage => {
|
||||||
response => {
|
this._view.enableForm();
|
||||||
this._view.showError(response.description);
|
this._view.showError(errorMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,27 +3,19 @@
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const tags = require('../tags.js');
|
const tags = require('../tags.js');
|
||||||
|
const Tag = require('../models/tag.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const TagView = require('../views/tag_view.js');
|
const TagView = require('../views/tag_view.js');
|
||||||
const EmptyView = require('../views/empty_view.js');
|
const EmptyView = require('../views/empty_view.js');
|
||||||
|
|
||||||
class TagController {
|
class TagController {
|
||||||
constructor(ctx, section) {
|
constructor(ctx, section) {
|
||||||
new Promise((resolve, reject) => {
|
Tag.get(ctx.params.name).then(tag => {
|
||||||
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 => {
|
|
||||||
topNavigation.activate('tags');
|
topNavigation.activate('tags');
|
||||||
|
|
||||||
|
this._name = ctx.params.name;
|
||||||
|
tag.addEventListener('change', e => this._evtSaved(e));
|
||||||
|
|
||||||
const categories = {};
|
const categories = {};
|
||||||
for (let category of tags.getAllCategories()) {
|
for (let category of tags.getAllCategories()) {
|
||||||
categories[category.name] = category.name;
|
categories[category.name] = category.name;
|
||||||
|
@ -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) {
|
_evtChange(e) {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
return api.put('/tag/' + e.detail.tag.names[0], {
|
e.detail.tag.names = e.detail.names;
|
||||||
names: e.detail.names,
|
e.detail.tag.category = e.detail.category;
|
||||||
category: e.detail.category,
|
e.detail.tag.implications = e.detail.implications;
|
||||||
implications: e.detail.implications,
|
e.detail.tag.suggestions = e.detail.suggestions;
|
||||||
suggestions: e.detail.suggestions,
|
e.detail.tag.save().then(() => {
|
||||||
}).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);
|
|
||||||
}
|
|
||||||
this._view.showSuccess('Tag saved.');
|
this._view.showSuccess('Tag saved.');
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
}, response => {
|
}, response => {
|
||||||
|
@ -74,17 +67,11 @@ class TagController {
|
||||||
_evtMerge(e) {
|
_evtMerge(e) {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
return api.post(
|
e.detail.tag.merge(e.detail.targetTagName).then(() => {
|
||||||
'/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);
|
|
||||||
this._view.showSuccess('Tag merged.');
|
this._view.showSuccess('Tag merged.');
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
}, response => {
|
}, errorMessage => {
|
||||||
this._view.showError(response.description);
|
this._view.showError(errorMessage);
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -92,11 +79,12 @@ class TagController {
|
||||||
_evtDelete(e) {
|
_evtDelete(e) {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
return api.delete('/tag/' + e.detail.tag.names[0]).then(response => {
|
e.detail.tag.delete()
|
||||||
|
.then(() => {
|
||||||
const ctx = router.show('/tags/');
|
const ctx = router.show('/tags/');
|
||||||
ctx.controller.showSuccess('Tag deleted.');
|
ctx.controller.showSuccess('Tag deleted.');
|
||||||
}, response => {
|
}, errorMessage => {
|
||||||
this._view.showError(response.description);
|
this._view.showError(errorMessage);
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
|
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
|
const TagList = require('../models/tag_list.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const PageController = require('../controllers/page_controller.js');
|
const PageController = require('../controllers/page_controller.js');
|
||||||
const TagsHeaderView = require('../views/tags_header_view.js');
|
const TagsHeaderView = require('../views/tags_header_view.js');
|
||||||
const TagsPageView = require('../views/tags_page_view.js');
|
const TagsPageView = require('../views/tags_page_view.js');
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
'names', 'suggestions', 'implications', 'lastEditTime', 'usages'];
|
||||||
|
|
||||||
class TagListController {
|
class TagListController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
topNavigation.activate('tags');
|
topNavigation.activate('tags');
|
||||||
|
@ -15,15 +19,9 @@ class TagListController {
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
clientUrl: '/tags/' + misc.formatSearchQuery({
|
clientUrl: '/tags/' + misc.formatSearchQuery({
|
||||||
text: ctx.searchQuery.text, page: '{page}'}),
|
text: ctx.searchQuery.text, page: '{page}'}),
|
||||||
requestPage: PageController.createHistoryCacheProxy(
|
requestPage: page => {
|
||||||
ctx,
|
return TagList.search(ctx.searchQuery.text, page, 50, fields);
|
||||||
page => {
|
},
|
||||||
const text = ctx.searchQuery.text;
|
|
||||||
return api.get(
|
|
||||||
`/tags/?query=${text}&page=${page}&pageSize=50` +
|
|
||||||
'&fields=names,suggestions,implications,' +
|
|
||||||
'lastEditTime,usages');
|
|
||||||
}),
|
|
||||||
headerRenderer: headerCtx => {
|
headerRenderer: headerCtx => {
|
||||||
Object.assign(headerCtx, {
|
Object.assign(headerCtx, {
|
||||||
canEditTagCategories:
|
canEditTagCategories:
|
||||||
|
|
|
@ -4,39 +4,20 @@ const router = require('../router.js');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const config = require('../config.js');
|
const config = require('../config.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
|
const User = require('../models/user.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const UserView = require('../views/user_view.js');
|
const UserView = require('../views/user_view.js');
|
||||||
const EmptyView = require('../views/empty_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 {
|
class UserController {
|
||||||
constructor(ctx, section) {
|
constructor(ctx, section) {
|
||||||
new Promise((resolve, reject) => {
|
User.get(ctx.params.name).then(user => {
|
||||||
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 => {
|
|
||||||
const isLoggedIn = api.isLoggedIn(user);
|
const isLoggedIn = api.isLoggedIn(user);
|
||||||
const infix = isLoggedIn ? 'self' : 'any';
|
const infix = isLoggedIn ? 'self' : 'any';
|
||||||
|
|
||||||
|
this._name = ctx.params.name;
|
||||||
|
user.addEventListener('change', e => this._evtSaved(e));
|
||||||
|
|
||||||
const myRankIndex = api.user ?
|
const myRankIndex = api.user ?
|
||||||
api.allRanks.indexOf(api.user.rank) :
|
api.allRanks.indexOf(api.user.rank) :
|
||||||
0;
|
0;
|
||||||
|
@ -48,7 +29,7 @@ class UserController {
|
||||||
if (rankIdx > myRankIndex) {
|
if (rankIdx > myRankIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ranks[rankIdentifier] = rankNames.get(rankIdentifier);
|
ranks[rankIdentifier] = api.rankNames.get(rankIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
|
@ -77,50 +58,50 @@ class UserController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_evtSaved(e) {
|
||||||
|
if (this._name !== e.detail.user.name) {
|
||||||
|
router.replace(
|
||||||
|
'/user/' + e.detail.user.name + '/edit', null, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_evtChange(e) {
|
_evtChange(e) {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
||||||
const infix = isLoggedIn ? 'self' : 'any';
|
const infix = isLoggedIn ? 'self' : 'any';
|
||||||
|
|
||||||
const files = [];
|
if (e.detail.name !== undefined) {
|
||||||
const data = {};
|
e.detail.user.name = e.detail.name;
|
||||||
if (e.detail.name) {
|
|
||||||
data.name = e.detail.name;
|
|
||||||
}
|
}
|
||||||
if (e.detail.password) {
|
if (e.detail.email !== undefined) {
|
||||||
data.password = e.detail.password;
|
e.detail.user.email = e.detail.email;
|
||||||
}
|
}
|
||||||
if (api.hasPrivilege('users:edit:' + infix + ':email')) {
|
if (e.detail.rank !== undefined) {
|
||||||
data.email = e.detail.email;
|
e.detail.user.rank = e.detail.rank;
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api.put('/user/' + e.detail.user.name, data, files)
|
if (e.detail.password !== undefined) {
|
||||||
.then(response => {
|
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 ?
|
return isLoggedIn ?
|
||||||
api.login(
|
api.login(
|
||||||
data.name || api.userName,
|
e.detail.name || api.userName,
|
||||||
data.password || api.userPassword,
|
e.detail.password || api.userPassword,
|
||||||
false) :
|
false) :
|
||||||
Promise.resolve();
|
Promise.resolve();
|
||||||
}, response => {
|
}, errorMessage => {
|
||||||
return Promise.reject(response.description);
|
return Promise.reject(errorMessage);
|
||||||
}).then(() => {
|
}).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.showSuccess('Settings updated.');
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
}, errorMessage => {
|
}, errorMessage => {
|
||||||
|
@ -133,8 +114,8 @@ class UserController {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
||||||
api.delete('/user/' + e.detail.user.name)
|
e.detail.user.delete()
|
||||||
.then(response => {
|
.then(() => {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
api.forget();
|
api.forget();
|
||||||
api.logout();
|
api.logout();
|
||||||
|
@ -146,7 +127,7 @@ class UserController {
|
||||||
const ctx = router.show('/');
|
const ctx = router.show('/');
|
||||||
ctx.controller.showSuccess('Account deleted.');
|
ctx.controller.showSuccess('Account deleted.');
|
||||||
}
|
}
|
||||||
}, response => {
|
}, errorMessage => {
|
||||||
this._view.showError(response.description);
|
this._view.showError(response.description);
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
|
const UserList = require('../models/user_list.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const PageController = require('../controllers/page_controller.js');
|
const PageController = require('../controllers/page_controller.js');
|
||||||
const UsersHeaderView = require('../views/users_header_view.js');
|
const UsersHeaderView = require('../views/users_header_view.js');
|
||||||
|
@ -15,13 +16,9 @@ class UserListController {
|
||||||
searchQuery: ctx.searchQuery,
|
searchQuery: ctx.searchQuery,
|
||||||
clientUrl: '/users/' + misc.formatSearchQuery({
|
clientUrl: '/users/' + misc.formatSearchQuery({
|
||||||
text: ctx.searchQuery.text, page: '{page}'}),
|
text: ctx.searchQuery.text, page: '{page}'}),
|
||||||
requestPage: PageController.createHistoryCacheProxy(
|
requestPage: page => {
|
||||||
ctx,
|
return UserList.search(ctx.searchQuery.text, page);
|
||||||
page => {
|
},
|
||||||
const text = ctx.searchQuery.text;
|
|
||||||
return api.get(
|
|
||||||
`/users/?query=${text}&page=${page}&pageSize=30`);
|
|
||||||
}),
|
|
||||||
headerRenderer: headerCtx => {
|
headerRenderer: headerCtx => {
|
||||||
return new UsersHeaderView(headerCtx);
|
return new UsersHeaderView(headerCtx);
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const router = require('../router.js');
|
const router = require('../router.js');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
|
const User = require('../models/user.js');
|
||||||
const topNavigation = require('../models/top_navigation.js');
|
const topNavigation = require('../models/top_navigation.js');
|
||||||
const RegistrationView = require('../views/registration_view.js');
|
const RegistrationView = require('../views/registration_view.js');
|
||||||
|
|
||||||
|
@ -15,11 +16,11 @@ class UserRegistrationController {
|
||||||
_evtRegister(e) {
|
_evtRegister(e) {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
api.post('/users/', {
|
const user = new User();
|
||||||
name: e.detail.name,
|
user.name = e.detail.name;
|
||||||
password: e.detail.password,
|
user.email = e.detail.email;
|
||||||
email: e.detail.email
|
user.password = e.detail.password;
|
||||||
}).then(() => {
|
user.save().then(() => {
|
||||||
api.forget();
|
api.forget();
|
||||||
return api.login(e.detail.name, e.detail.password, false);
|
return api.login(e.detail.name, e.detail.password, false);
|
||||||
}, response => {
|
}, response => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ class TagAutoCompleteControl extends AutoCompleteControl {
|
||||||
return Array.from(allTags.entries())
|
return Array.from(allTags.entries())
|
||||||
.filter(kv => match(transform(kv[0]), text))
|
.filter(kv => match(transform(kv[0]), text))
|
||||||
.sort((kv1, kv2) => {
|
.sort((kv1, kv2) => {
|
||||||
return kv2[1].usages - kv1[1].usages;
|
return kv2[1].postCount - kv1[1].postCount;
|
||||||
})
|
})
|
||||||
.map(kv => {
|
.map(kv => {
|
||||||
const category = kv[1].category;
|
const category = kv[1].category;
|
||||||
|
|
|
@ -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,8 +6,6 @@ const events = require('../events.js');
|
||||||
class Comment extends events.EventTarget {
|
class Comment extends events.EventTarget {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.commentList = null;
|
|
||||||
|
|
||||||
this._id = null;
|
this._id = null;
|
||||||
this._postId = null;
|
this._postId = null;
|
||||||
this._text = null;
|
this._text = null;
|
||||||
|
@ -61,7 +59,7 @@ class Comment extends events.EventTarget {
|
||||||
return promise.then(response => {
|
return promise.then(response => {
|
||||||
this._updateFromResponse(response);
|
this._updateFromResponse(response);
|
||||||
this.dispatchEvent(new CustomEvent('change', {
|
this.dispatchEvent(new CustomEvent('change', {
|
||||||
details: {
|
detail: {
|
||||||
comment: this,
|
comment: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -74,11 +72,8 @@ class Comment extends events.EventTarget {
|
||||||
delete() {
|
delete() {
|
||||||
return api.delete('/comment/' + this._id)
|
return api.delete('/comment/' + this._id)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (this.commentList) {
|
|
||||||
this.commentList.remove(this);
|
|
||||||
}
|
|
||||||
this.dispatchEvent(new CustomEvent('delete', {
|
this.dispatchEvent(new CustomEvent('delete', {
|
||||||
details: {
|
detail: {
|
||||||
comment: this,
|
comment: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -93,7 +88,7 @@ class Comment extends events.EventTarget {
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this._updateFromResponse(response);
|
this._updateFromResponse(response);
|
||||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||||
details: {
|
detail: {
|
||||||
comment: this,
|
comment: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,59 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const events = require('../events.js');
|
const AbstractList = require('./abstract_list.js');
|
||||||
const Comment = require('./comment.js');
|
const Comment = require('./comment.js');
|
||||||
|
|
||||||
class CommentList extends events.EventTarget {
|
class CommentList extends AbstractList {
|
||||||
constructor(comments) {
|
|
||||||
super();
|
|
||||||
this._list = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromResponse(commentsResponse) {
|
CommentList._itemClass = Comment;
|
||||||
const commentList = new CommentList();
|
CommentList._itemName = 'comment';
|
||||||
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]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CommentList;
|
module.exports = CommentList;
|
||||||
|
|
|
@ -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;
|
|
@ -30,22 +30,6 @@ class Post extends events.EventTarget {
|
||||||
this._ownFavorite = null;
|
this._ownFavorite = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromResponse(response) {
|
|
||||||
const post = new Post();
|
|
||||||
post._updateFromResponse(response);
|
|
||||||
return post;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(id) {
|
|
||||||
return api.get('/post/' + id)
|
|
||||||
.then(response => {
|
|
||||||
const post = Post.fromResponse(response);
|
|
||||||
return Promise.resolve(post);
|
|
||||||
}, response => {
|
|
||||||
return Promise.reject(response);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get id() { return this._id; }
|
get id() { return this._id; }
|
||||||
get type() { return this._type; }
|
get type() { return this._type; }
|
||||||
get mimeType() { return this._mimeType; }
|
get mimeType() { return this._mimeType; }
|
||||||
|
@ -68,6 +52,21 @@ class Post extends events.EventTarget {
|
||||||
get ownFavorite() { return this._ownFavorite; }
|
get ownFavorite() { return this._ownFavorite; }
|
||||||
get ownScore() { return this._ownScore; }
|
get ownScore() { return this._ownScore; }
|
||||||
|
|
||||||
|
static fromResponse(response) {
|
||||||
|
const ret = new Post();
|
||||||
|
ret._updateFromResponse(response);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get(id) {
|
||||||
|
return api.get('/post/' + id)
|
||||||
|
.then(response => {
|
||||||
|
return Promise.resolve(Post.fromResponse(response));
|
||||||
|
}, response => {
|
||||||
|
return Promise.reject(response.description);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setScore(score) {
|
setScore(score) {
|
||||||
return api.put('/post/' + this._id + '/score', {score: score})
|
return api.put('/post/' + this._id + '/score', {score: score})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -75,13 +74,13 @@ class Post extends events.EventTarget {
|
||||||
this._updateFromResponse(response);
|
this._updateFromResponse(response);
|
||||||
if (this._ownFavorite !== prevFavorite) {
|
if (this._ownFavorite !== prevFavorite) {
|
||||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||||
details: {
|
detail: {
|
||||||
post: this,
|
post: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||||
details: {
|
detail: {
|
||||||
post: this,
|
post: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -98,13 +97,13 @@ class Post extends events.EventTarget {
|
||||||
this._updateFromResponse(response);
|
this._updateFromResponse(response);
|
||||||
if (this._ownScore !== prevScore) {
|
if (this._ownScore !== prevScore) {
|
||||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||||
details: {
|
detail: {
|
||||||
post: this,
|
post: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||||
details: {
|
detail: {
|
||||||
post: this,
|
post: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -121,13 +120,13 @@ class Post extends events.EventTarget {
|
||||||
this._updateFromResponse(response);
|
this._updateFromResponse(response);
|
||||||
if (this._ownScore !== prevScore) {
|
if (this._ownScore !== prevScore) {
|
||||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||||
details: {
|
detail: {
|
||||||
post: this,
|
post: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||||
details: {
|
detail: {
|
||||||
post: this,
|
post: this,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -152,7 +151,7 @@ class Post extends events.EventTarget {
|
||||||
|
|
||||||
this._tags = response.tags;
|
this._tags = response.tags;
|
||||||
this._notes = response.notes;
|
this._notes = response.notes;
|
||||||
this._comments = CommentList.fromResponse(response.comments);
|
this._comments = CommentList.fromResponse(response.comments || []);
|
||||||
this._relations = response.relations;
|
this._relations = response.relations;
|
||||||
|
|
||||||
this._score = response.score;
|
this._score = response.score;
|
||||||
|
|
|
@ -1,33 +1,35 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const events = require('../events.js');
|
const api = require('../api.js');
|
||||||
|
const AbstractList = require('./abstract_list.js');
|
||||||
const Post = require('./post.js');
|
const Post = require('./post.js');
|
||||||
|
|
||||||
class PostList extends events.EventTarget {
|
class PostList extends AbstractList {
|
||||||
constructor(posts) {
|
static getAround(id, searchQuery) {
|
||||||
super();
|
return api.get(`/post/${id}/around?fields=id&query=${searchQuery}`)
|
||||||
this._list = [];
|
.then(response => {
|
||||||
|
return Promise.resolve(response);
|
||||||
|
}).catch(response => {
|
||||||
|
return Promise.reject(response.description);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromResponse(postsResponse) {
|
static search(text, page, pageSize, fields) {
|
||||||
const postList = new PostList();
|
const url =
|
||||||
for (let postResponse of postsResponse) {
|
`/posts/?query=${text}` +
|
||||||
postList._list.push(Post.fromResponse(postResponse));
|
`&page=${page}` +
|
||||||
|
`&pageSize=${pageSize}` +
|
||||||
|
`&fields=${fields.join(',')}`;
|
||||||
|
return api.get(url).then(response => {
|
||||||
|
return Promise.resolve(Object.assign(
|
||||||
|
{},
|
||||||
|
response,
|
||||||
|
{results: PostList.fromResponse(response.results)}));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return postList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get posts() {
|
PostList._itemClass = Post;
|
||||||
return [...this._list];
|
PostList._itemName = 'post';
|
||||||
}
|
|
||||||
|
|
||||||
get length() {
|
|
||||||
return this._list.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this._list[Symbol.iterator]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = PostList;
|
module.exports = PostList;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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';
|
'use strict';
|
||||||
|
|
||||||
|
const events = require('../events.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
|
const TagCategory = require('../models/tag_category.js');
|
||||||
|
|
||||||
const template = views.getTemplate('tag-categories');
|
const template = views.getTemplate('tag-categories');
|
||||||
|
const rowTemplate = views.getTemplate('tag-category-row');
|
||||||
|
|
||||||
class TagCategoriesView {
|
class TagCategoriesView extends events.EventTarget {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
super();
|
||||||
|
this._ctx = ctx;
|
||||||
this._hostNode = document.getElementById('content-holder');
|
this._hostNode = document.getElementById('content-holder');
|
||||||
const sourceNode = template(ctx);
|
|
||||||
|
|
||||||
const formNode = sourceNode.querySelector('form');
|
views.replaceContent(this._hostNode, template(ctx));
|
||||||
const newRowTemplate = sourceNode.querySelector('.add-template');
|
views.decorateValidator(this._formNode);
|
||||||
const tableBodyNode = sourceNode.querySelector('tbody');
|
|
||||||
const addLinkNode = sourceNode.querySelector('a.add');
|
|
||||||
|
|
||||||
newRowTemplate.parentNode.removeChild(newRowTemplate);
|
const categoriesToAdd = Array.from(ctx.tagCategories);
|
||||||
views.decorateValidator(formNode);
|
categoriesToAdd.sort((a, b) => {
|
||||||
|
if (b.isDefault) {
|
||||||
for (let row of tableBodyNode.querySelectorAll('tr')) {
|
return 1;
|
||||||
this._addRowHandlers(row);
|
} else if (a.isDefault) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
for (let tagCategory of categoriesToAdd) {
|
||||||
|
this._addTagCategoryRowNode(tagCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addLinkNode) {
|
if (this._addLinkNode) {
|
||||||
addLinkNode.addEventListener('click', e => {
|
this._addLinkNode.addEventListener(
|
||||||
e.preventDefault();
|
'click', e => this._evtAddButtonClick(e));
|
||||||
let newRow = newRowTemplate.cloneNode(true);
|
|
||||||
tableBody.appendChild(newRow);
|
|
||||||
this._addRowHandlers(row);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formNode.addEventListener('submit', e => {
|
ctx.tagCategories.addEventListener(
|
||||||
this._evtSaveButtonClick(e, ctx);
|
'add', e => this._evtTagCategoryAdded(e));
|
||||||
});
|
|
||||||
|
|
||||||
views.replaceContent(this._hostNode, sourceNode);
|
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) {
|
showSuccess(message) {
|
||||||
|
@ -45,88 +64,100 @@ class TagCategoriesView {
|
||||||
views.showError(this._hostNode, message);
|
views.showError(this._hostNode, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtSaveButtonClick(e, ctx) {
|
get _formNode() {
|
||||||
|
return this._hostNode.querySelector('form');
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
e.preventDefault();
|
||||||
|
this._ctx.tagCategories.add(new TagCategory());
|
||||||
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;
|
_evtNameChange(e, rowNode) {
|
||||||
let addedCategories = [];
|
rowNode._tagCategory.name = e.target.value;
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtRemoveButtonClick(e, row, link) {
|
_evtColorChange(e, rowNode) {
|
||||||
|
rowNode._tagCategory.color = e.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_evtDeleteButtonClick(e, rowNode, link) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (link.classList.contains('inactive')) {
|
if (e.target.classList.contains('inactive')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
row.parentNode.removeChild(row);
|
this._ctx.tagCategories.remove(rowNode._tagCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtSetDefaultButtonClick(e, row) {
|
_evtSetDefaultButtonClick(e, rowNode) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const oldRowNode = row.parentNode.querySelector('tr.default');
|
this._ctx.tagCategories.defaultCategory = rowNode._tagCategory;
|
||||||
|
const oldRowNode = rowNode.parentNode.querySelector('tr.default');
|
||||||
if (oldRowNode) {
|
if (oldRowNode) {
|
||||||
oldRowNode.classList.remove('default');
|
oldRowNode.classList.remove('default');
|
||||||
}
|
}
|
||||||
row.classList.add('default');
|
rowNode.classList.add('default');
|
||||||
}
|
}
|
||||||
|
|
||||||
_addRowHandlers(row) {
|
_evtSaveButtonClick(e, ctx) {
|
||||||
const removeLink = row.querySelector('.remove a');
|
e.preventDefault();
|
||||||
if (removeLink) {
|
this.dispatchEvent(new CustomEvent('submit'));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,21 @@ class TagView extends events.EventTarget {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._hostNode = document.getElementById('content-holder');
|
this._ctx = ctx;
|
||||||
|
ctx.tag.addEventListener('change', e => this._evtChange(e));
|
||||||
ctx.section = ctx.section || 'summary';
|
ctx.section = ctx.section || 'summary';
|
||||||
|
|
||||||
|
this._hostNode = document.getElementById('content-holder');
|
||||||
|
this._install();
|
||||||
|
}
|
||||||
|
|
||||||
|
_install() {
|
||||||
|
const ctx = this._ctx;
|
||||||
views.replaceContent(this._hostNode, template(ctx));
|
views.replaceContent(this._hostNode, template(ctx));
|
||||||
|
|
||||||
for (let item of this._hostNode.querySelectorAll('[data-name]')) {
|
for (let item of this._hostNode.querySelectorAll('[data-name]')) {
|
||||||
if (item.getAttribute('data-name') === ctx.section) {
|
item.classList.toggle(
|
||||||
item.className = 'active';
|
'active', item.getAttribute('data-name') === ctx.section);
|
||||||
} else {
|
|
||||||
item.className = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.hostNode = this._hostNode.querySelector('.tag-content-holder');
|
ctx.hostNode = this._hostNode.querySelector('.tag-content-holder');
|
||||||
|
@ -65,6 +70,11 @@ class TagView extends events.EventTarget {
|
||||||
showError(message) {
|
showError(message) {
|
||||||
this._view.showError(message);
|
this._view.showError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_evtChange(e) {
|
||||||
|
this._ctx.tag = e.detail.tag;
|
||||||
|
this._install(this._ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TagView;
|
module.exports = TagView;
|
||||||
|
|
|
@ -61,11 +61,11 @@ class UserEditView extends events.EventTarget {
|
||||||
this.dispatchEvent(new CustomEvent('submit', {
|
this.dispatchEvent(new CustomEvent('submit', {
|
||||||
detail: {
|
detail: {
|
||||||
user: this._user,
|
user: this._user,
|
||||||
name: this._userNameFieldNode.value,
|
name: (this._userNameFieldNode || {}).value,
|
||||||
password: this._passwordFieldNode.value,
|
email: (this._emailFieldNode || {}).value,
|
||||||
email: this._emailFieldNode.value,
|
rank: (this._rankFieldNode || {}).value,
|
||||||
rank: this._rankFieldNode.value,
|
avatarStyle: (this._avatarStyleFieldNode || {}).value,
|
||||||
avatarStyle: this._avatarStyleFieldNode.value,
|
password: (this._passwordFieldNode || {}).value,
|
||||||
avatarContent: this._avatarContent,
|
avatarContent: this._avatarContent,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -12,10 +12,17 @@ class UserView extends events.EventTarget {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._hostNode = document.getElementById('content-holder');
|
this._ctx = ctx;
|
||||||
|
ctx.user.addEventListener('change', e => this._evtChange(e));
|
||||||
ctx.section = ctx.section || 'summary';
|
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]')) {
|
for (let item of this._hostNode.querySelectorAll('[data-name]')) {
|
||||||
if (item.getAttribute('data-name') === ctx.section) {
|
if (item.getAttribute('data-name') === ctx.section) {
|
||||||
item.className = 'active';
|
item.className = 'active';
|
||||||
|
@ -61,6 +68,11 @@ class UserView extends events.EventTarget {
|
||||||
disableForm() {
|
disableForm() {
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_evtChange(e) {
|
||||||
|
this._ctx.user = e.detail.user;
|
||||||
|
this._install(this._ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = UserView;
|
module.exports = UserView;
|
||||||
|
|
Loading…
Reference in New Issue