client/general: refactor all the things
- Move controls to the "controls/" directory - Make controls interface look similar to each other - Prefix "private" methods and attributes with underscore
This commit is contained in:
parent
c88dfd228a
commit
69fe8ec31a
41 changed files with 633 additions and 617 deletions
|
@ -51,7 +51,7 @@ class Api {
|
|||
}
|
||||
|
||||
_process(url, requestFactory, data, files) {
|
||||
const fullUrl = this.getFullUrl(url);
|
||||
const fullUrl = this._getFullUrl(url);
|
||||
return new Promise((resolve, reject) => {
|
||||
nprogress.start();
|
||||
let req = requestFactory(fullUrl);
|
||||
|
@ -161,7 +161,7 @@ class Api {
|
|||
}
|
||||
}
|
||||
|
||||
getFullUrl(url) {
|
||||
_getFullUrl(url) {
|
||||
return (config.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,24 +9,24 @@ const PasswordResetView = require('../views/password_reset_view.js');
|
|||
|
||||
class AuthController {
|
||||
constructor() {
|
||||
this.loginView = new LoginView();
|
||||
this.passwordResetView = new PasswordResetView();
|
||||
this._loginView = new LoginView();
|
||||
this._passwordResetView = new PasswordResetView();
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
page(/\/password-reset\/([^:]+):([^:]+)$/,
|
||||
(ctx, next) => {
|
||||
this.passwordResetFinishRoute(ctx.params[0], ctx.params[1]);
|
||||
this._passwordResetFinishRoute(ctx.params[0], ctx.params[1]);
|
||||
});
|
||||
page('/password-reset', (ctx, next) => { this.passwordResetRoute(); });
|
||||
page('/login', (ctx, next) => { this.loginRoute(); });
|
||||
page('/logout', (ctx, next) => { this.logoutRoute(); });
|
||||
page('/password-reset', (ctx, next) => { this._passwordResetRoute(); });
|
||||
page('/login', (ctx, next) => { this._loginRoute(); });
|
||||
page('/logout', (ctx, next) => { this._logoutRoute(); });
|
||||
}
|
||||
|
||||
loginRoute() {
|
||||
_loginRoute() {
|
||||
api.forget();
|
||||
topNavController.activate('login');
|
||||
this.loginView.render({
|
||||
this._loginView.render({
|
||||
login: (name, password, doRemember) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.forget();
|
||||
|
@ -43,22 +43,22 @@ class AuthController {
|
|||
}});
|
||||
}
|
||||
|
||||
logoutRoute() {
|
||||
_logoutRoute() {
|
||||
api.forget();
|
||||
api.logout();
|
||||
page('/');
|
||||
events.notify(events.Success, 'Logged out');
|
||||
}
|
||||
|
||||
passwordResetRoute() {
|
||||
_passwordResetRoute() {
|
||||
topNavController.activate('login');
|
||||
this.passwordResetView.render({
|
||||
this._passwordResetView.render({
|
||||
proceed: (...args) => {
|
||||
return this._passwordReset(...args);
|
||||
}});
|
||||
}
|
||||
|
||||
passwordResetFinishRoute(name, token) {
|
||||
_passwordResetFinishRoute(name, token) {
|
||||
api.forget();
|
||||
api.logout();
|
||||
api.post('/password-reset/' + name, {token: token})
|
||||
|
|
|
@ -5,10 +5,10 @@ const topNavController = require('../controllers/top_nav_controller.js');
|
|||
|
||||
class CommentsController {
|
||||
registerRoutes() {
|
||||
page('/comments', (ctx, next) => { this.listCommentsRoute(); });
|
||||
page('/comments', (ctx, next) => { this._listCommentsRoute(); });
|
||||
}
|
||||
|
||||
listCommentsRoute() {
|
||||
_listCommentsRoute() {
|
||||
topNavController.activate('comments');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,24 +6,24 @@ const HelpView = require('../views/help_view.js');
|
|||
|
||||
class HelpController {
|
||||
constructor() {
|
||||
this.helpView = new HelpView();
|
||||
this._helpView = new HelpView();
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
page('/help', () => { this.showHelpRoute(); });
|
||||
page('/help', () => { this._showHelpRoute(); });
|
||||
page(
|
||||
'/help/:section',
|
||||
(ctx, next) => { this.showHelpRoute(ctx.params.section); });
|
||||
(ctx, next) => { this._showHelpRoute(ctx.params.section); });
|
||||
page(
|
||||
'/help/:section/:subsection',
|
||||
(ctx, next) => {
|
||||
this.showHelpRoute(ctx.params.section, ctx.params.subsection);
|
||||
this._showHelpRoute(ctx.params.section, ctx.params.subsection);
|
||||
});
|
||||
}
|
||||
|
||||
showHelpRoute(section, subsection) {
|
||||
_showHelpRoute(section, subsection) {
|
||||
topNavController.activate('help');
|
||||
this.helpView.render({
|
||||
this._helpView.render({
|
||||
section: section,
|
||||
subsection: subsection,
|
||||
});
|
||||
|
|
|
@ -5,10 +5,10 @@ const topNavController = require('../controllers/top_nav_controller.js');
|
|||
|
||||
class HistoryController {
|
||||
registerRoutes() {
|
||||
page('/history', (ctx, next) => { this.showHistoryRoute(); });
|
||||
page('/history', (ctx, next) => { this._listHistoryRoute(); });
|
||||
}
|
||||
|
||||
listHistoryRoute() {
|
||||
_listHistoryRoute() {
|
||||
topNavController.activate('');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,20 +6,20 @@ const HomeView = require('../views/home_view.js');
|
|||
|
||||
class HomeController {
|
||||
constructor() {
|
||||
this.homeView = new HomeView();
|
||||
this._homeView = new HomeView();
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
page('/', (ctx, next) => { this.indexRoute(); });
|
||||
page('*', (ctx, next) => { this.notFoundRoute(); });
|
||||
page('/', (ctx, next) => { this._indexRoute(); });
|
||||
page('*', (ctx, next) => { this._notFoundRoute(); });
|
||||
}
|
||||
|
||||
indexRoute() {
|
||||
_indexRoute() {
|
||||
topNavController.activate('home');
|
||||
this.homeView.render({});
|
||||
this._homeView.render({});
|
||||
}
|
||||
|
||||
notFoundRoute() {
|
||||
_notFoundRoute() {
|
||||
topNavController.activate('');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,27 +8,27 @@ const ManualPageView = require('../views/manual_page_view.js');
|
|||
class PageController {
|
||||
constructor() {
|
||||
events.listen(events.SettingsChange, () => {
|
||||
this.update();
|
||||
this._update();
|
||||
return true;
|
||||
});
|
||||
this.update();
|
||||
this._update();
|
||||
}
|
||||
|
||||
update() {
|
||||
_update() {
|
||||
if (settings.getSettings().endlessScroll) {
|
||||
this.pageView = new EndlessPageView();
|
||||
this._pageView = new EndlessPageView();
|
||||
} else {
|
||||
this.pageView = new ManualPageView();
|
||||
this._pageView = new ManualPageView();
|
||||
}
|
||||
}
|
||||
|
||||
run(ctx) {
|
||||
this.pageView.unrender();
|
||||
this.pageView.render(ctx);
|
||||
this._pageView.unrender();
|
||||
this._pageView.render(ctx);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.pageView.unrender();
|
||||
this._pageView.unrender();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,29 +5,29 @@ const topNavController = require('../controllers/top_nav_controller.js');
|
|||
|
||||
class PostsController {
|
||||
registerRoutes() {
|
||||
page('/upload', (ctx, next) => { this.uploadPostsRoute(); });
|
||||
page('/posts', (ctx, next) => { this.listPostsRoute(); });
|
||||
page('/upload', (ctx, next) => { this._uploadPostsRoute(); });
|
||||
page('/posts', (ctx, next) => { this._listPostsRoute(); });
|
||||
page(
|
||||
'/post/:id',
|
||||
(ctx, next) => { this.showPostRoute(ctx.params.id); });
|
||||
(ctx, next) => { this._showPostRoute(ctx.params.id); });
|
||||
page(
|
||||
'/post/:id/edit',
|
||||
(ctx, next) => { this.editPostRoute(ctx.params.id); });
|
||||
(ctx, next) => { this._editPostRoute(ctx.params.id); });
|
||||
}
|
||||
|
||||
uploadPostsRoute() {
|
||||
_uploadPostsRoute() {
|
||||
topNavController.activate('upload');
|
||||
}
|
||||
|
||||
listPostsRoute() {
|
||||
_listPostsRoute() {
|
||||
topNavController.activate('posts');
|
||||
}
|
||||
|
||||
showPostRoute(id) {
|
||||
_showPostRoute(id) {
|
||||
topNavController.activate('posts');
|
||||
}
|
||||
|
||||
editPostRoute(id) {
|
||||
_editPostRoute(id) {
|
||||
topNavController.activate('posts');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,16 +7,16 @@ const SettingsView = require('../views/settings_view.js');
|
|||
|
||||
class SettingsController {
|
||||
constructor() {
|
||||
this.settingsView = new SettingsView();
|
||||
this._settingsView = new SettingsView();
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
page('/settings', (ctx, next) => { this.settingsRoute(); });
|
||||
page('/settings', (ctx, next) => { this._settingsRoute(); });
|
||||
}
|
||||
|
||||
settingsRoute() {
|
||||
_settingsRoute() {
|
||||
topNavController.activate('settings');
|
||||
this.settingsView.render({
|
||||
this._settingsView.render({
|
||||
getSettings: () => settings.getSettings(),
|
||||
saveSettings: newSettings => settings.saveSettings(newSettings),
|
||||
});
|
||||
|
|
|
@ -15,31 +15,31 @@ const EmptyView = require('../views/empty_view.js');
|
|||
|
||||
class TagsController {
|
||||
constructor() {
|
||||
this.tagView = new TagView();
|
||||
this.tagsHeaderView = new TagsHeaderView();
|
||||
this.tagsPageView = new TagsPageView();
|
||||
this.tagCategoriesView = new TagCategoriesView();
|
||||
this.emptyView = new EmptyView();
|
||||
this._tagView = new TagView();
|
||||
this._tagsHeaderView = new TagsHeaderView();
|
||||
this._tagsPageView = new TagsPageView();
|
||||
this._tagCategoriesView = new TagCategoriesView();
|
||||
this._emptyView = new EmptyView();
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
page('/tag-categories', () => { this.tagCategoriesRoute(); });
|
||||
page('/tag-categories', () => { this._tagCategoriesRoute(); });
|
||||
page(
|
||||
'/tag/:name',
|
||||
(ctx, next) => { this.loadTagRoute(ctx, next); },
|
||||
(ctx, next) => { this.showTagRoute(ctx, next); });
|
||||
(ctx, next) => { this._loadTagRoute(ctx, next); },
|
||||
(ctx, next) => { this._showTagRoute(ctx, next); });
|
||||
page(
|
||||
'/tag/:name/merge',
|
||||
(ctx, next) => { this.loadTagRoute(ctx, next); },
|
||||
(ctx, next) => { this.mergeTagRoute(ctx, next); });
|
||||
(ctx, next) => { this._loadTagRoute(ctx, next); },
|
||||
(ctx, next) => { this._mergeTagRoute(ctx, next); });
|
||||
page(
|
||||
'/tag/:name/delete',
|
||||
(ctx, next) => { this.loadTagRoute(ctx, next); },
|
||||
(ctx, next) => { this.deleteTagRoute(ctx, next); });
|
||||
(ctx, next) => { this._loadTagRoute(ctx, next); },
|
||||
(ctx, next) => { this._deleteTagRoute(ctx, next); });
|
||||
page(
|
||||
'/tags/:query?',
|
||||
(ctx, next) => { misc.parseSearchQueryRoute(ctx, next); },
|
||||
(ctx, next) => { this.listTagsRoute(ctx, next); });
|
||||
(ctx, next) => { this._listTagsRoute(ctx, next); });
|
||||
}
|
||||
|
||||
_saveTagCategories(addedCategories, changedCategories, removedCategories) {
|
||||
|
@ -64,34 +64,35 @@ class TagsController {
|
|||
});
|
||||
}
|
||||
|
||||
loadTagRoute(ctx, next) {
|
||||
_loadTagRoute(ctx, next) {
|
||||
if (ctx.state.tag) {
|
||||
next();
|
||||
} else if (this.tag && this.tag.names == ctx.params.names) {
|
||||
ctx.state.tag = this.tag;
|
||||
} else if (this._cachedTag &&
|
||||
this._cachedTag.names == ctx.params.names) {
|
||||
ctx.state.tag = this._cachedTag;
|
||||
next();
|
||||
} else {
|
||||
api.get('/tag/' + ctx.params.name).then(response => {
|
||||
ctx.state.tag = response.tag;
|
||||
ctx.save();
|
||||
this.tag = response.tag;
|
||||
this._cachedTag = response.tag;
|
||||
next();
|
||||
}, response => {
|
||||
this.emptyView.render();
|
||||
this._emptyView.render();
|
||||
events.notify(events.Error, response.description);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showTagRoute(ctx, next) {
|
||||
_showTagRoute(ctx, next) {
|
||||
this._show(ctx.state.tag, 'summary');
|
||||
}
|
||||
|
||||
mergeTagRoute(ctx, next) {
|
||||
_mergeTagRoute(ctx, next) {
|
||||
this._show(ctx.state.tag, 'merge');
|
||||
}
|
||||
|
||||
deleteTagRoute(ctx, next) {
|
||||
_deleteTagRoute(ctx, next) {
|
||||
this._show(ctx.state.tag, 'delete');
|
||||
}
|
||||
|
||||
|
@ -101,7 +102,7 @@ class TagsController {
|
|||
for (let category of tags.getAllCategories()) {
|
||||
categories[category.name] = category.name;
|
||||
}
|
||||
this.tagView.render({
|
||||
this._tagView.render({
|
||||
tag: tag,
|
||||
section: section,
|
||||
canEditNames: api.hasPrivilege('tags:edit:names'),
|
||||
|
@ -152,10 +153,10 @@ class TagsController {
|
|||
});
|
||||
}
|
||||
|
||||
tagCategoriesRoute(ctx, next) {
|
||||
_tagCategoriesRoute(ctx, next) {
|
||||
topNavController.activate('tags');
|
||||
api.get('/tag-categories/').then(response => {
|
||||
this.tagCategoriesView.render({
|
||||
this._tagCategoriesView.render({
|
||||
tagCategories: response.results,
|
||||
canEditName: api.hasPrivilege('tagCategories:edit:name'),
|
||||
canEditColor: api.hasPrivilege('tagCategories:edit:color'),
|
||||
|
@ -173,12 +174,12 @@ class TagsController {
|
|||
}
|
||||
});
|
||||
}, response => {
|
||||
this.emptyView.render();
|
||||
this._emptyView.render();
|
||||
events.notify(events.Error, response.description);
|
||||
});
|
||||
}
|
||||
|
||||
listTagsRoute(ctx, next) {
|
||||
_listTagsRoute(ctx, next) {
|
||||
topNavController.activate('tags');
|
||||
|
||||
pageController.run({
|
||||
|
@ -192,8 +193,8 @@ class TagsController {
|
|||
clientUrl: '/tags/' + misc.formatSearchQuery({
|
||||
text: ctx.searchQuery.text, page: '{page}'}),
|
||||
searchQuery: ctx.searchQuery,
|
||||
headerRenderer: this.tagsHeaderView,
|
||||
pageRenderer: this.tagsPageView,
|
||||
headerRenderer: this._tagsHeaderView,
|
||||
pageRenderer: this._tagsPageView,
|
||||
canEditTagCategories: api.hasPrivilege('tagCategories:edit'),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,10 +16,10 @@ class NavigationItem {
|
|||
|
||||
class TopNavController {
|
||||
constructor() {
|
||||
this.topNavView = new TopNavView();
|
||||
this.activeItem = null;
|
||||
this._topNavView = new TopNavView();
|
||||
this._activeItem = null;
|
||||
|
||||
this.items = {
|
||||
this._items = {
|
||||
'home': new NavigationItem('H', 'Home', '/'),
|
||||
'posts': new NavigationItem('P', 'Posts', '/posts'),
|
||||
'upload': new NavigationItem('U', 'Upload', '/upload'),
|
||||
|
@ -36,11 +36,11 @@ class TopNavController {
|
|||
};
|
||||
|
||||
const rerender = () => {
|
||||
this.updateVisibility();
|
||||
this.topNavView.render({
|
||||
items: this.items,
|
||||
activeItem: this.activeItem});
|
||||
this.topNavView.activate(this.activeItem);
|
||||
this._updateVisibility();
|
||||
this._topNavView.render({
|
||||
items: this._items,
|
||||
activeItem: this._activeItem});
|
||||
this._topNavView.activate(this._activeItem);
|
||||
};
|
||||
|
||||
events.listen(
|
||||
|
@ -49,41 +49,41 @@ class TopNavController {
|
|||
rerender();
|
||||
}
|
||||
|
||||
updateVisibility() {
|
||||
this.items.account.url = '/user/' + api.userName;
|
||||
this.items.account.imageUrl = api.user ? api.user.avatarUrl : null;
|
||||
_updateVisibility() {
|
||||
this._items.account.url = '/user/' + api.userName;
|
||||
this._items.account.imageUrl = api.user ? api.user.avatarUrl : null;
|
||||
|
||||
const b = Object.keys(this.items);
|
||||
const b = Object.keys(this._items);
|
||||
for (let key of b) {
|
||||
this.items[key].available = true;
|
||||
this._items[key].available = true;
|
||||
}
|
||||
if (!api.hasPrivilege('posts:list')) {
|
||||
this.items.posts.available = false;
|
||||
this._items.posts.available = false;
|
||||
}
|
||||
if (!api.hasPrivilege('posts:create')) {
|
||||
this.items.upload.available = false;
|
||||
this._items.upload.available = false;
|
||||
}
|
||||
if (!api.hasPrivilege('comments:list')) {
|
||||
this.items.comments.available = false;
|
||||
this._items.comments.available = false;
|
||||
}
|
||||
if (!api.hasPrivilege('tags:list')) {
|
||||
this.items.tags.available = false;
|
||||
this._items.tags.available = false;
|
||||
}
|
||||
if (!api.hasPrivilege('users:list')) {
|
||||
this.items.users.available = false;
|
||||
this._items.users.available = false;
|
||||
}
|
||||
if (api.isLoggedIn()) {
|
||||
this.items.register.available = false;
|
||||
this.items.login.available = false;
|
||||
this._items.register.available = false;
|
||||
this._items.login.available = false;
|
||||
} else {
|
||||
this.items.account.available = false;
|
||||
this.items.logout.available = false;
|
||||
this._items.account.available = false;
|
||||
this._items.logout.available = false;
|
||||
}
|
||||
}
|
||||
|
||||
activate(itemName) {
|
||||
this.activeItem = itemName;
|
||||
this.topNavView.activate(this.activeItem);
|
||||
this._activeItem = itemName;
|
||||
this._topNavView.activate(this._activeItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,42 +26,42 @@ const rankNames = {
|
|||
|
||||
class UsersController {
|
||||
constructor() {
|
||||
this.registrationView = new RegistrationView();
|
||||
this.userView = new UserView();
|
||||
this.usersHeaderView = new UsersHeaderView();
|
||||
this.usersPageView = new UsersPageView();
|
||||
this.emptyView = new EmptyView();
|
||||
this._registrationView = new RegistrationView();
|
||||
this._userView = new UserView();
|
||||
this._usersHeaderView = new UsersHeaderView();
|
||||
this._usersPageView = new UsersPageView();
|
||||
this._emptyView = new EmptyView();
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
page('/register', () => { this.createUserRoute(); });
|
||||
page('/register', () => { this._createUserRoute(); });
|
||||
page(
|
||||
'/users/:query?',
|
||||
(ctx, next) => { misc.parseSearchQueryRoute(ctx, next); },
|
||||
(ctx, next) => { this.listUsersRoute(ctx, next); });
|
||||
(ctx, next) => { this._listUsersRoute(ctx, next); });
|
||||
page(
|
||||
'/user/:name',
|
||||
(ctx, next) => { this.loadUserRoute(ctx, next); },
|
||||
(ctx, next) => { this.showUserRoute(ctx, next); });
|
||||
(ctx, next) => { this._loadUserRoute(ctx, next); },
|
||||
(ctx, next) => { this._showUserRoute(ctx, next); });
|
||||
page(
|
||||
'/user/:name/edit',
|
||||
(ctx, next) => { this.loadUserRoute(ctx, next); },
|
||||
(ctx, next) => { this.editUserRoute(ctx, next); });
|
||||
(ctx, next) => { this._loadUserRoute(ctx, next); },
|
||||
(ctx, next) => { this._editUserRoute(ctx, next); });
|
||||
page(
|
||||
'/user/:name/delete',
|
||||
(ctx, next) => { this.loadUserRoute(ctx, next); },
|
||||
(ctx, next) => { this.deleteUserRoute(ctx, next); });
|
||||
(ctx, next) => { this._loadUserRoute(ctx, next); },
|
||||
(ctx, next) => { this._deleteUserRoute(ctx, next); });
|
||||
page.exit(/\/users\/.*/, (ctx, next) => {
|
||||
pageController.stop();
|
||||
next();
|
||||
});
|
||||
page.exit(/\/user\/.*/, (ctx, next) => {
|
||||
this.user = null;
|
||||
this._cachedUser = null;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
listUsersRoute(ctx, next) {
|
||||
_listUsersRoute(ctx, next) {
|
||||
topNavController.activate('users');
|
||||
|
||||
pageController.run({
|
||||
|
@ -75,48 +75,48 @@ class UsersController {
|
|||
clientUrl: '/users/' + misc.formatSearchQuery({
|
||||
text: ctx.searchQuery.text, page: '{page}'}),
|
||||
searchQuery: ctx.searchQuery,
|
||||
headerRenderer: this.usersHeaderView,
|
||||
pageRenderer: this.usersPageView,
|
||||
headerRenderer: this._usersHeaderView,
|
||||
pageRenderer: this._usersPageView,
|
||||
});
|
||||
}
|
||||
|
||||
createUserRoute() {
|
||||
_createUserRoute() {
|
||||
topNavController.activate('register');
|
||||
this.registrationView.render({
|
||||
this._registrationView.render({
|
||||
register: (...args) => {
|
||||
return this._register(...args);
|
||||
}});
|
||||
}
|
||||
|
||||
loadUserRoute(ctx, next) {
|
||||
_loadUserRoute(ctx, next) {
|
||||
if (ctx.state.user) {
|
||||
next();
|
||||
} else if (this.user && this.user.name == ctx.params.name) {
|
||||
ctx.state.user = this.user;
|
||||
} else if (this._cachedUser && this._cachedUser == ctx.params.name) {
|
||||
ctx.state.user = this._cachedUser;
|
||||
next();
|
||||
} else {
|
||||
api.get('/user/' + ctx.params.name).then(response => {
|
||||
response.user.rankName = rankNames[response.user.rank];
|
||||
ctx.state.user = response.user;
|
||||
ctx.save();
|
||||
this.user = response.user;
|
||||
this._cachedUser = response.user;
|
||||
next();
|
||||
}, response => {
|
||||
this.emptyView.render();
|
||||
this._emptyView.render();
|
||||
events.notify(events.Error, response.description);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showUserRoute(ctx, next) {
|
||||
_showUserRoute(ctx, next) {
|
||||
this._show(ctx.state.user, 'summary');
|
||||
}
|
||||
|
||||
editUserRoute(ctx, next) {
|
||||
_editUserRoute(ctx, next) {
|
||||
this._show(ctx.state.user, 'edit');
|
||||
}
|
||||
|
||||
deleteUserRoute(ctx, next) {
|
||||
_deleteUserRoute(ctx, next) {
|
||||
this._show(ctx.state.user, 'delete');
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ class UsersController {
|
|||
return new Promise((resolve, reject) => {
|
||||
api.put('/user/' + user.name, data, files)
|
||||
.then(response => {
|
||||
this.user = response.user;
|
||||
this._cachedUser = response.user;
|
||||
return isLoggedIn ?
|
||||
api.login(
|
||||
data.name || api.userName,
|
||||
|
@ -236,7 +236,7 @@ class UsersController {
|
|||
} else {
|
||||
topNavController.activate('users');
|
||||
}
|
||||
this.userView.render({
|
||||
this._userView.render({
|
||||
user: user,
|
||||
section: section,
|
||||
isLoggedIn: isLoggedIn,
|
||||
|
|
297
client/js/controls/auto_complete_control.js
Normal file
297
client/js/controls/auto_complete_control.js
Normal file
|
@ -0,0 +1,297 @@
|
|||
'use strict';
|
||||
|
||||
const lodash = require('lodash');
|
||||
const views = require('../util/views.js');
|
||||
|
||||
const KEY_TAB = 9;
|
||||
const KEY_RETURN = 13;
|
||||
const KEY_DELETE = 46;
|
||||
const KEY_ESCAPE = 27;
|
||||
const KEY_UP = 38;
|
||||
const KEY_DOWN = 40;
|
||||
|
||||
function _getSelectionStart(input) {
|
||||
if ('selectionStart' in input) {
|
||||
return input.selectionStart;
|
||||
}
|
||||
if (document.selection) {
|
||||
input.focus();
|
||||
const sel = document.selection.createRange();
|
||||
const selLen = document.selection.createRange().text.length;
|
||||
sel.moveStart('character', -input.value.length);
|
||||
return sel.text.length - selLen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class AutoCompleteControl {
|
||||
constructor(sourceInputNode, options) {
|
||||
this._sourceInputNode = sourceInputNode;
|
||||
this._options = lodash.extend({}, {
|
||||
verticalShift: 2,
|
||||
source: null,
|
||||
maxResults: 15,
|
||||
getTextToFind: () => {
|
||||
const value = sourceInputNode.value;
|
||||
const start = _getSelectionStart(sourceInputNode);
|
||||
return value.substring(0, start).replace(/.*\s+/, '');
|
||||
},
|
||||
confirm: text => {
|
||||
const start = _getSelectionStart(sourceInputNode);
|
||||
let prefix = '';
|
||||
let suffix = sourceInputNode.value.substring(start);
|
||||
let middle = sourceInputNode.value.substring(0, start);
|
||||
const index = middle.lastIndexOf(' ');
|
||||
if (index !== -1) {
|
||||
prefix = sourceInputNode.value.substring(0, index + 1);
|
||||
middle = sourceInputNode.value.substring(index + 1);
|
||||
}
|
||||
sourceInputNode.value = prefix +
|
||||
this._results[this._activeResult].value +
|
||||
' ' +
|
||||
suffix.trimLeft();
|
||||
sourceInputNode.focus();
|
||||
},
|
||||
delete: text => {
|
||||
},
|
||||
getMatches: null,
|
||||
}, options);
|
||||
|
||||
this._showTimeout = null;
|
||||
this._results = [];
|
||||
this._activeResult = -1;
|
||||
|
||||
this._mutationObserver = new MutationObserver(
|
||||
mutations => {
|
||||
for (let mutation of mutations) {
|
||||
for (let node of mutation.removedNodes) {
|
||||
if (node.contains(this._sourceInputNode)) {
|
||||
this._uninstall();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._install();
|
||||
}
|
||||
|
||||
hide() {
|
||||
window.clearTimeout(this._showTimeout);
|
||||
this._suggestionDiv.style.display = 'none';
|
||||
this._isVisible = false;
|
||||
}
|
||||
|
||||
_show() {
|
||||
this._suggestionDiv.style.display = 'block';
|
||||
this._isVisible = true;
|
||||
}
|
||||
|
||||
_showOrHide() {
|
||||
const textToFind = this._options.getTextToFind();
|
||||
if (!textToFind || !textToFind.length) {
|
||||
this.hide();
|
||||
} else {
|
||||
this._updateResults(textToFind);
|
||||
this._refreshList();
|
||||
}
|
||||
}
|
||||
|
||||
_install() {
|
||||
if (!this._sourceInputNode) {
|
||||
throw new Error('Input element was not found');
|
||||
}
|
||||
if (this._sourceInputNode.getAttribute('data-autocomplete')) {
|
||||
throw new Error(
|
||||
'Autocompletion was already added for this element');
|
||||
}
|
||||
this._sourceInputNode.setAttribute('data-autocomplete', true);
|
||||
this._sourceInputNode.setAttribute('autocomplete', 'off');
|
||||
|
||||
this._mutationObserver.observe(
|
||||
document.body, {childList: true, subtree: true});
|
||||
this._sourceInputNode.addEventListener(
|
||||
'keydown', e => this._evtKeyDown(e));
|
||||
this._sourceInputNode.addEventListener(
|
||||
'blur', e => this._evtBlur(e));
|
||||
|
||||
this._suggestionDiv = views.htmlToDom(
|
||||
'<div class="autocomplete"><ul></ul></div>');
|
||||
this._suggestionList = this._suggestionDiv.querySelector('ul');
|
||||
document.body.appendChild(this._suggestionDiv);
|
||||
}
|
||||
|
||||
_uninstall() {
|
||||
window.clearTimeout(this._showTimeout);
|
||||
this._mutationObserver.disconnect();
|
||||
document.body.removeChild(this._suggestionDiv);
|
||||
}
|
||||
|
||||
_evtKeyDown(e) {
|
||||
const key = e.which;
|
||||
const shift = e.shiftKey;
|
||||
let func = null;
|
||||
if (this._isVisible) {
|
||||
if (key === KEY_ESCAPE) {
|
||||
func = this.hide;
|
||||
} else if (key === KEY_TAB && shift) {
|
||||
func = () => { this._selectPrevious(); };
|
||||
} else if (key === KEY_TAB && !shift) {
|
||||
func = () => { this._selectNext(); };
|
||||
} else if (key === KEY_UP) {
|
||||
func = () => { this._selectPrevious(); };
|
||||
} else if (key === KEY_DOWN) {
|
||||
func = () => { this._selectNext(); };
|
||||
} else if (key === KEY_RETURN && this._activeResult >= 0) {
|
||||
func = () => {
|
||||
this._options.confirm(this._getActiveSuggestion());
|
||||
this.hide();
|
||||
};
|
||||
} else if (key === KEY_DELETE && this._activeResult >= 0) {
|
||||
func = () => {
|
||||
this._options.delete(this._getActiveSuggestion());
|
||||
this.hide();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func !== null) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
func();
|
||||
} else {
|
||||
window.clearTimeout(this._showTimeout);
|
||||
this._showTimeout = window.setTimeout(
|
||||
() => { this._showOrHide(); }, 250);
|
||||
}
|
||||
}
|
||||
|
||||
_evtBlur(e) {
|
||||
window.clearTimeout(this._showTimeout);
|
||||
window.setTimeout(() => { this.hide(); }, 50);
|
||||
}
|
||||
|
||||
_getActiveSuggestion() {
|
||||
if (this._activeResult === -1) {
|
||||
return null;
|
||||
}
|
||||
return this._results[this._activeResult].value;
|
||||
}
|
||||
|
||||
_selectPrevious() {
|
||||
this._select(this._activeResult === -1 ?
|
||||
this._results.length - 1 :
|
||||
this._activeResult - 1);
|
||||
}
|
||||
|
||||
_selectNext() {
|
||||
this._select(this._activeResult === -1 ? 0 : this._activeResult + 1);
|
||||
}
|
||||
|
||||
_select(newActiveResult) {
|
||||
this._activeResult =
|
||||
newActiveResult.between(0, this._results.length - 1, true) ?
|
||||
newActiveResult :
|
||||
-1;
|
||||
this._refreshActiveResult();
|
||||
}
|
||||
|
||||
_updateResults(textToFind) {
|
||||
const oldResults = this._results.slice();
|
||||
this._results =
|
||||
this._options.getMatches(textToFind)
|
||||
.slice(0, this._options.maxResults);
|
||||
if (!lodash.isEqual(oldResults, this._results)) {
|
||||
this._activeResult = -1;
|
||||
}
|
||||
}
|
||||
|
||||
_refreshList() {
|
||||
if (this._results.length === 0) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
while (this._suggestionList.firstChild) {
|
||||
this._suggestionList.removeChild(this._suggestionList.firstChild);
|
||||
}
|
||||
lodash.each(
|
||||
this._results,
|
||||
(resultItem, resultIndex) => {
|
||||
const listItem = document.createElement('li');
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.innerHTML = resultItem.caption;
|
||||
link.setAttribute('data-key', resultItem.value);
|
||||
link.addEventListener(
|
||||
'mouseenter',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
this._activeResult = resultIndex;
|
||||
this._refreshActiveResult();
|
||||
});
|
||||
link.addEventListener(
|
||||
'mousedown',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
this._activeResult = resultIndex;
|
||||
this._options.confirm(this._getActiveSuggestion());
|
||||
this.hide();
|
||||
});
|
||||
listItem.appendChild(link);
|
||||
this._suggestionList.appendChild(listItem);
|
||||
});
|
||||
this._refreshActiveResult();
|
||||
|
||||
// display the suggestions offscreen to get the height
|
||||
this._suggestionDiv.style.left = '-9999px';
|
||||
this._suggestionDiv.style.top = '-9999px';
|
||||
this._show();
|
||||
const verticalShift = this._options.verticalShift;
|
||||
const inputRect = this._sourceInputNode.getBoundingClientRect();
|
||||
const bodyRect = document.body.getBoundingClientRect();
|
||||
const viewPortHeight = bodyRect.bottom - bodyRect.top;
|
||||
let listRect = this._suggestionDiv.getBoundingClientRect();
|
||||
|
||||
// choose where to view the suggestions: if there's more space above
|
||||
// the input - draw the suggestions above it, otherwise below
|
||||
const direction =
|
||||
inputRect.top + inputRect.height / 2 < viewPortHeight / 2 ? 1 : -1;
|
||||
|
||||
let x = inputRect.left - bodyRect.left;
|
||||
let y = direction == 1 ?
|
||||
inputRect.bottom - bodyRect.top - verticalShift :
|
||||
inputRect.top - bodyRect.top - listRect.height + verticalShift;
|
||||
|
||||
// remove offscreen items until whole suggestion list can fit on the
|
||||
// screen
|
||||
while ((y < 0 || y + listRect.height > viewPortHeight) &&
|
||||
this._suggestionList.childNodes.length) {
|
||||
this._suggestionList.removeChild(this._suggestionList.lastChild);
|
||||
const prevHeight = listRect.height;
|
||||
listRect = this._suggestionDiv.getBoundingClientRect();
|
||||
const heightDelta = prevHeight - listRect.height;
|
||||
if (direction == -1) {
|
||||
y += heightDelta;
|
||||
}
|
||||
}
|
||||
|
||||
this._suggestionDiv.style.left = x + 'px';
|
||||
this._suggestionDiv.style.top = y + 'px';
|
||||
}
|
||||
|
||||
_refreshActiveResult() {
|
||||
let activeItem = this._suggestionList.querySelector('li.active');
|
||||
if (activeItem) {
|
||||
activeItem.classList.remove('active');
|
||||
}
|
||||
if (this._activeResult >= 0) {
|
||||
const allItems = this._suggestionList.querySelectorAll('li');
|
||||
activeItem = allItems[this._activeResult];
|
||||
activeItem.classList.add('active');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = AutoCompleteControl;
|
76
client/js/controls/file_dropper_control.js
Normal file
76
client/js/controls/file_dropper_control.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
'use strict';
|
||||
|
||||
const views = require('../util/views.js');
|
||||
|
||||
class FileDropperControl {
|
||||
constructor(target, options) {
|
||||
this._options = options;
|
||||
this._template = views.getTemplate('file-dropper');
|
||||
const source = this._template({
|
||||
allowMultiple: this._options.allowMultiple,
|
||||
id: 'file-' + Math.random().toString(36).substring(7),
|
||||
});
|
||||
|
||||
this._dropperNode = source.querySelector('.file-dropper');
|
||||
this._fileInputNode = source.querySelector('input');
|
||||
this._fileInputNode.style.display = 'none';
|
||||
this._fileInputNode.multiple = this._options._allowMultiple || false;
|
||||
|
||||
this._counter = 0;
|
||||
this._dropperNode.addEventListener(
|
||||
'dragenter', e => this._evtDragEnter(e));
|
||||
this._dropperNode.addEventListener(
|
||||
'dragleave', e => this._evtDragLeave(e));
|
||||
this._dropperNode.addEventListener(
|
||||
'dragover', e => this._evtDragOver(e));
|
||||
this._dropperNode.addEventListener(
|
||||
'drop', e => this._evtDrop(e));
|
||||
this._fileInputNode.addEventListener(
|
||||
'change', e => this._evtFileChange(e));
|
||||
|
||||
views.showView(target, source);
|
||||
}
|
||||
|
||||
_resolve(files) {
|
||||
files = Array.from(files);
|
||||
if (this._options.lock) {
|
||||
this._dropperNode.innerText =
|
||||
files.map(file => file.name).join(', ');
|
||||
}
|
||||
this._options.resolve(files);
|
||||
};
|
||||
|
||||
_evtFileChange(e) {
|
||||
this._resolve(e.target.files);
|
||||
}
|
||||
|
||||
_evtDragEnter(e) {
|
||||
this._dropperNode.classList.add('active');
|
||||
counter++;
|
||||
}
|
||||
|
||||
_evtDragLeave(e) {
|
||||
this._counter--;
|
||||
if (this._counter === 0) {
|
||||
this._dropperNode.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
_evtDragOver(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
_evtDrop(e) {
|
||||
e.preventDefault();
|
||||
this._dropperNode.classList.remove('active');
|
||||
if (!e.dataTransfer.files.length) {
|
||||
window.alert('Only files are supported.');
|
||||
}
|
||||
if (!this._options.allowMultiple && e.dataTransfer.files.length > 1) {
|
||||
window.alert('Cannot select multiple files.');
|
||||
}
|
||||
this._resolve(e.dataTransfer.files);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileDropperControl;
|
|
@ -20,9 +20,12 @@ class TagInputControl {
|
|||
this.readOnly = sourceInputNode.readOnly;
|
||||
|
||||
this._autoCompleteControls = [];
|
||||
|
||||
this._sourceInputNode = sourceInputNode;
|
||||
|
||||
this._install();
|
||||
}
|
||||
|
||||
_install() {
|
||||
// set up main edit area
|
||||
this._editAreaNode = views.htmlToDom('<div class="tag-input"></div>');
|
||||
this._editAreaNode.autocorrect = false;
|
||||
|
@ -43,12 +46,12 @@ class TagInputControl {
|
|||
this._editAreaNode.appendChild(this._tailWrapperNode);
|
||||
|
||||
// add existing tags
|
||||
this.addMultipleTags(sourceInputNode.value);
|
||||
this.addMultipleTags(this._sourceInputNode.value);
|
||||
|
||||
// show
|
||||
sourceInputNode.style.display = 'none';
|
||||
sourceInputNode.parentNode.insertBefore(
|
||||
this._editAreaNode, sourceInputNode.nextSibling);
|
||||
this._sourceInputNode.style.display = 'none';
|
||||
this._sourceInputNode.parentNode.insertBefore(
|
||||
this._editAreaNode, this._sourceInputNode.nextSibling);
|
||||
}
|
||||
|
||||
addMultipleTags(text, sourceNode) {
|
|
@ -1,298 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const lodash = require('lodash');
|
||||
const views = require('../util/views.js');
|
||||
|
||||
const KEY_TAB = 9;
|
||||
const KEY_RETURN = 13;
|
||||
const KEY_DELETE = 46;
|
||||
const KEY_ESCAPE = 27;
|
||||
const KEY_UP = 38;
|
||||
const KEY_DOWN = 40;
|
||||
|
||||
function getSelectionStart(input) {
|
||||
if ('selectionStart' in input) {
|
||||
return input.selectionStart;
|
||||
}
|
||||
if (document.selection) {
|
||||
input.focus();
|
||||
const sel = document.selection.createRange();
|
||||
const selLen = document.selection.createRange().text.length;
|
||||
sel.moveStart('character', -input.value.length);
|
||||
return sel.text.length - selLen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class AutoCompleteControl {
|
||||
constructor(input, options) {
|
||||
this.input = input;
|
||||
this.options = lodash.extend({}, {
|
||||
verticalShift: 2,
|
||||
source: null,
|
||||
maxResults: 15,
|
||||
getTextToFind: () => {
|
||||
const value = this.input.value;
|
||||
const start = getSelectionStart(this.input);
|
||||
return value.substring(0, start).replace(/.*\s+/, '');
|
||||
},
|
||||
confirm: text => {
|
||||
const start = getSelectionStart(this.input);
|
||||
let prefix = '';
|
||||
let suffix = this.input.value.substring(start);
|
||||
let middle = this.input.value.substring(0, start);
|
||||
const index = middle.lastIndexOf(' ');
|
||||
if (index !== -1) {
|
||||
prefix = this.input.value.substring(0, index + 1);
|
||||
middle = this.input.value.substring(index + 1);
|
||||
}
|
||||
this.input.value = prefix +
|
||||
this.results[this.activeResult].value +
|
||||
' ' +
|
||||
suffix.trimLeft();
|
||||
this.input.focus();
|
||||
},
|
||||
delete: text => {
|
||||
},
|
||||
getMatches: null,
|
||||
}, options);
|
||||
|
||||
this.showTimeout = null;
|
||||
this.results = [];
|
||||
this.activeResult = -1;
|
||||
|
||||
this.mutationObserver = new MutationObserver(
|
||||
mutations => {
|
||||
for (let mutation of mutations) {
|
||||
for (let node of mutation.removedNodes) {
|
||||
if (node.contains(input)) {
|
||||
this.uninstall();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.install();
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
window.clearTimeout(this.showTimeout);
|
||||
this.mutationObserver.disconnect();
|
||||
document.body.removeChild(this.suggestionDiv);
|
||||
}
|
||||
|
||||
install() {
|
||||
if (!this.input) {
|
||||
throw new Error('Input element was not found');
|
||||
}
|
||||
if (this.input.getAttribute('data-autocomplete')) {
|
||||
throw new Error(
|
||||
'Autocompletion was already added for this element');
|
||||
}
|
||||
this.input.setAttribute('data-autocomplete', true);
|
||||
this.input.setAttribute('autocomplete', 'off');
|
||||
|
||||
this.mutationObserver.observe(
|
||||
document.body, {childList: true, subtree: true});
|
||||
|
||||
this.input.addEventListener(
|
||||
'keydown',
|
||||
e => {
|
||||
const key = e.which;
|
||||
const shift = e.shiftKey;
|
||||
let func = null;
|
||||
if (this.isVisible) {
|
||||
if (key === KEY_ESCAPE) {
|
||||
func = this.hide;
|
||||
} else if (key === KEY_TAB && shift) {
|
||||
func = () => { this.selectPrevious(); };
|
||||
} else if (key === KEY_TAB && !shift) {
|
||||
func = () => { this.selectNext(); };
|
||||
} else if (key === KEY_UP) {
|
||||
func = () => { this.selectPrevious(); };
|
||||
} else if (key === KEY_DOWN) {
|
||||
func = () => { this.selectNext(); };
|
||||
} else if (key === KEY_RETURN && this.activeResult >= 0) {
|
||||
func = () => {
|
||||
this.options.confirm(this.getActiveSuggestion());
|
||||
this.hide();
|
||||
};
|
||||
} else if (key === KEY_DELETE && this.activeResult >= 0) {
|
||||
func = () => {
|
||||
this.options.delete(this.getActiveSuggestion());
|
||||
this.hide();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func !== null) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
func();
|
||||
} else {
|
||||
window.clearTimeout(this.showTimeout);
|
||||
this.showTimeout = window.setTimeout(
|
||||
() => { this.showOrHide(); },
|
||||
250);
|
||||
}
|
||||
});
|
||||
|
||||
this.input.addEventListener(
|
||||
'blur',
|
||||
e => {
|
||||
window.clearTimeout(this.showTimeout);
|
||||
window.setTimeout(() => { this.hide(); }, 50);
|
||||
});
|
||||
|
||||
this.suggestionDiv = views.htmlToDom(
|
||||
'<div class="autocomplete"><ul></ul></div>');
|
||||
this.suggestionList = this.suggestionDiv.querySelector('ul');
|
||||
document.body.appendChild(this.suggestionDiv);
|
||||
}
|
||||
|
||||
getActiveSuggestion() {
|
||||
if (this.activeResult === -1) {
|
||||
return null;
|
||||
}
|
||||
return this.results[this.activeResult].value;
|
||||
}
|
||||
|
||||
showOrHide() {
|
||||
const textToFind = this.options.getTextToFind();
|
||||
if (!textToFind || !textToFind.length) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.updateResults(textToFind);
|
||||
this.refreshList();
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
this.suggestionDiv.style.display = 'block';
|
||||
this.isVisible = true;
|
||||
}
|
||||
|
||||
hide() {
|
||||
window.clearTimeout(this.showTimeout);
|
||||
this.suggestionDiv.style.display = 'none';
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
selectPrevious() {
|
||||
this.select(this.activeResult === -1 ?
|
||||
this.results.length - 1 :
|
||||
this.activeResult - 1);
|
||||
}
|
||||
|
||||
selectNext() {
|
||||
this.select(this.activeResult === -1 ? 0 : this.activeResult + 1);
|
||||
}
|
||||
|
||||
select(newActiveResult) {
|
||||
this.activeResult =
|
||||
newActiveResult.between(0, this.results.length - 1, true) ?
|
||||
newActiveResult :
|
||||
-1;
|
||||
this.refreshActiveResult();
|
||||
}
|
||||
|
||||
updateResults(textToFind) {
|
||||
const oldResults = this.results.slice();
|
||||
this.results =
|
||||
this.options.getMatches(textToFind)
|
||||
.slice(0, this.options.maxResults);
|
||||
if (!lodash.isEqual(oldResults, this.results)) {
|
||||
this.activeResult = -1;
|
||||
}
|
||||
}
|
||||
|
||||
refreshList() {
|
||||
if (this.results.length === 0) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
while (this.suggestionList.firstChild) {
|
||||
this.suggestionList.removeChild(this.suggestionList.firstChild);
|
||||
}
|
||||
lodash.each(
|
||||
this.results,
|
||||
(resultItem, resultIndex) => {
|
||||
const listItem = document.createElement('li');
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.innerHTML = resultItem.caption;
|
||||
link.setAttribute('data-key', resultItem.value);
|
||||
link.addEventListener(
|
||||
'mouseenter',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
this.activeResult = resultIndex;
|
||||
this.refreshActiveResult();
|
||||
});
|
||||
link.addEventListener(
|
||||
'mousedown',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
this.activeResult = resultIndex;
|
||||
this.options.confirm(this.getActiveSuggestion());
|
||||
this.hide();
|
||||
});
|
||||
listItem.appendChild(link);
|
||||
this.suggestionList.appendChild(listItem);
|
||||
});
|
||||
this.refreshActiveResult();
|
||||
|
||||
// display the suggestions offscreen to get the height
|
||||
this.suggestionDiv.style.left = '-9999px';
|
||||
this.suggestionDiv.style.top = '-9999px';
|
||||
this.show();
|
||||
const verticalShift = this.options.verticalShift;
|
||||
const inputRect = this.input.getBoundingClientRect();
|
||||
const bodyRect = document.body.getBoundingClientRect();
|
||||
const viewPortHeight = bodyRect.bottom - bodyRect.top;
|
||||
let listRect = this.suggestionDiv.getBoundingClientRect();
|
||||
|
||||
// choose where to view the suggestions: if there's more space above
|
||||
// the input - draw the suggestions above it, otherwise below
|
||||
const direction =
|
||||
inputRect.top + inputRect.height / 2 < viewPortHeight / 2 ? 1 : -1;
|
||||
|
||||
let x = inputRect.left - bodyRect.left;
|
||||
let y = direction == 1 ?
|
||||
inputRect.bottom - bodyRect.top - verticalShift :
|
||||
inputRect.top - bodyRect.top - listRect.height + verticalShift;
|
||||
|
||||
// remove offscreen items until whole suggestion list can fit on the
|
||||
// screen
|
||||
while ((y < 0 || y + listRect.height > viewPortHeight) &&
|
||||
this.suggestionList.childNodes.length) {
|
||||
this.suggestionList.removeChild(this.suggestionList.lastChild);
|
||||
const prevHeight = listRect.height;
|
||||
listRect = this.suggestionDiv.getBoundingClientRect();
|
||||
const heightDelta = prevHeight - listRect.height;
|
||||
if (direction == -1) {
|
||||
y += heightDelta;
|
||||
}
|
||||
}
|
||||
|
||||
this.suggestionDiv.style.left = x + 'px';
|
||||
this.suggestionDiv.style.top = y + 'px';
|
||||
}
|
||||
|
||||
refreshActiveResult() {
|
||||
let activeItem = this.suggestionList.querySelector('li.active');
|
||||
if (activeItem) {
|
||||
activeItem.classList.remove('active');
|
||||
}
|
||||
if (this.activeResult >= 0) {
|
||||
const allItems = this.suggestionList.querySelectorAll('li');
|
||||
activeItem = allItems[this.activeResult];
|
||||
activeItem.classList.add('active');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = AutoCompleteControl;
|
|
@ -4,7 +4,7 @@ const views = require('../util/views.js');
|
|||
|
||||
class EmptyView {
|
||||
constructor() {
|
||||
this.template = () => {
|
||||
this._template = () => {
|
||||
return views.htmlToDom(
|
||||
'<div class="wrapper"><div class="messages"></div></div>');
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ class EmptyView {
|
|||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template();
|
||||
const source = this._template();
|
||||
views.listenToMessages(source);
|
||||
views.showView(target, source);
|
||||
}
|
||||
|
|
|
@ -6,19 +6,19 @@ const views = require('../util/views.js');
|
|||
|
||||
class EndlessPageView {
|
||||
constructor() {
|
||||
this.holderTemplate = views.getTemplate('endless-pager');
|
||||
this.pageTemplate = views.getTemplate('endless-pager-page');
|
||||
this._holderTemplate = views.getTemplate('endless-pager');
|
||||
this._pageTemplate = views.getTemplate('endless-pager-page');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.holderTemplate();
|
||||
const source = this._holderTemplate();
|
||||
const pageHeaderHolder = source.querySelector('.page-header-holder');
|
||||
const pagesHolder = source.querySelector('.pages-holder');
|
||||
views.listenToMessages(source);
|
||||
views.showView(target, source);
|
||||
this.active = true;
|
||||
this.working = 0;
|
||||
this._active = true;
|
||||
this._working = 0;
|
||||
|
||||
let headerRendererCtx = ctx;
|
||||
headerRendererCtx.target = pageHeaderHolder;
|
||||
|
@ -31,8 +31,8 @@ class EndlessPageView {
|
|||
this.totalPages = null;
|
||||
this.currentPage = null;
|
||||
|
||||
this.updater = () => {
|
||||
if (this.working) {
|
||||
this._updater = () => {
|
||||
if (this._working) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -66,46 +66,46 @@ class EndlessPageView {
|
|||
document.documentElement.clientHeight;
|
||||
|
||||
if (this.minPageShown > 1 && window.scrollY - threshold < 0) {
|
||||
this.loadPage(pagesHolder, ctx, this.minPageShown - 1, false)
|
||||
.then(() => this.updater());
|
||||
this._loadPage(pagesHolder, ctx, this.minPageShown - 1, false)
|
||||
.then(() => this._updater());
|
||||
} else if (this.maxPageShown < this.totalPages &&
|
||||
window.scrollY + threshold > scrollHeight) {
|
||||
this.loadPage(pagesHolder, ctx, this.maxPageShown + 1, true)
|
||||
.then(() => this.updater());
|
||||
this._loadPage(pagesHolder, ctx, this.maxPageShown + 1, true)
|
||||
.then(() => this._updater());
|
||||
}
|
||||
};
|
||||
|
||||
this.loadPage(pagesHolder, ctx, ctx.searchQuery.page, true)
|
||||
this._loadPage(pagesHolder, ctx, ctx.searchQuery.page, true)
|
||||
.then(pageNode => {
|
||||
if (ctx.searchQuery.page > 1) {
|
||||
window.scroll(0, pageNode.getBoundingClientRect().top);
|
||||
}
|
||||
this.updater();
|
||||
this._updater();
|
||||
});
|
||||
window.addEventListener('scroll', this.updater, true);
|
||||
window.addEventListener('unload', this.scrollToTop, true);
|
||||
window.addEventListener('scroll', this._updater, true);
|
||||
window.addEventListener('unload', this._scrollToTop, true);
|
||||
}
|
||||
|
||||
unrender() {
|
||||
this.active = false;
|
||||
window.removeEventListener('scroll', this.updater, true);
|
||||
window.removeEventListener('unload', this.scrollToTop, true);
|
||||
this._active = false;
|
||||
window.removeEventListener('scroll', this._updater, true);
|
||||
window.removeEventListener('unload', this._scrollToTop, true);
|
||||
}
|
||||
|
||||
scrollToTop() {
|
||||
_scrollToTop() {
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
|
||||
loadPage(pagesHolder, ctx, pageNumber, append) {
|
||||
this.working++;
|
||||
_loadPage(pagesHolder, ctx, pageNumber, append) {
|
||||
this._working++;
|
||||
return ctx.requestPage(pageNumber).then(response => {
|
||||
if (!this.active) {
|
||||
this.working--;
|
||||
if (!this._active) {
|
||||
this._working--;
|
||||
return Promise.reject();
|
||||
}
|
||||
this.totalPages = Math.ceil(response.total / response.pageSize);
|
||||
if (response.total) {
|
||||
const pageNode = this.pageTemplate({
|
||||
const pageNode = this._pageTemplate({
|
||||
page: pageNumber,
|
||||
totalPages: this.totalPages,
|
||||
});
|
||||
|
@ -137,17 +137,17 @@ class EndlessPageView {
|
|||
window.scrollX,
|
||||
window.scrollY + pageNode.offsetHeight);
|
||||
}
|
||||
this.working--;
|
||||
this._working--;
|
||||
return Promise.resolve(pageNode);
|
||||
}
|
||||
if (response.total <= (pageNumber - 1) * response.pageSize) {
|
||||
events.notify(events.Info, 'No data to show');
|
||||
}
|
||||
this.working--;
|
||||
this._working--;
|
||||
return Promise.reject();
|
||||
}, response => {
|
||||
events.notify(events.Error, response.description);
|
||||
this.working--;
|
||||
this._working--;
|
||||
return Promise.reject();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const views = require('../util/views.js');
|
||||
|
||||
class FileDropperControl {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('file-dropper');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template({
|
||||
allowMultiple: ctx.allowMultiple,
|
||||
id: 'file-' + Math.random().toString(36).substring(7),
|
||||
});
|
||||
|
||||
const dropper = source.querySelector('.file-dropper');
|
||||
const fileInput = source.querySelector('input');
|
||||
fileInput.style.display = 'none';
|
||||
fileInput.multiple = ctx.allowMultiple || false;
|
||||
|
||||
const resolve = files => {
|
||||
files = Array.from(files);
|
||||
if (ctx.lock) {
|
||||
dropper.innerText = files.map(file => file.name).join(', ');
|
||||
}
|
||||
ctx.resolve(files);
|
||||
};
|
||||
|
||||
let counter = 0;
|
||||
dropper.addEventListener('dragenter', e => {
|
||||
dropper.classList.add('active');
|
||||
counter++;
|
||||
});
|
||||
|
||||
dropper.addEventListener('dragleave', e => {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
dropper.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
dropper.addEventListener('dragover', e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
dropper.addEventListener('drop', e => {
|
||||
dropper.classList.remove('active');
|
||||
e.preventDefault();
|
||||
if (!e.dataTransfer.files.length) {
|
||||
window.alert('Only files are supported.');
|
||||
}
|
||||
if (!ctx.allowMultiple && e.dataTransfer.files.length > 1) {
|
||||
window.alert('Cannot select multiple files.');
|
||||
}
|
||||
resolve(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', e => {
|
||||
resolve(e.target.files);
|
||||
});
|
||||
|
||||
views.showView(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileDropperControl;
|
|
@ -5,14 +5,14 @@ const views = require('../util/views.js');
|
|||
|
||||
class HelpView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('help');
|
||||
this.sectionTemplates = {};
|
||||
this._template = views.getTemplate('help');
|
||||
this._sectionTemplates = {};
|
||||
const sectionKeys = ['about', 'keyboard', 'search', 'comments', 'tos'];
|
||||
for (let section of sectionKeys) {
|
||||
const templateName = 'help-' + section;
|
||||
this.sectionTemplates[section] = views.getTemplate(templateName);
|
||||
this._sectionTemplates[section] = views.getTemplate(templateName);
|
||||
}
|
||||
this.subsectionTemplates = {
|
||||
this._subsectionTemplates = {
|
||||
'search': {
|
||||
'default': views.getTemplate('help-search-general'),
|
||||
'posts': views.getTemplate('help-search-posts'),
|
||||
|
@ -24,23 +24,23 @@ class HelpView {
|
|||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template();
|
||||
const source = this._template();
|
||||
|
||||
ctx.section = ctx.section || 'about';
|
||||
if (ctx.section in this.sectionTemplates) {
|
||||
if (ctx.section in this._sectionTemplates) {
|
||||
views.showView(
|
||||
source.querySelector('.content'),
|
||||
this.sectionTemplates[ctx.section]({
|
||||
this._sectionTemplates[ctx.section]({
|
||||
name: config.name,
|
||||
}));
|
||||
}
|
||||
|
||||
ctx.subsection = ctx.subsection || 'default';
|
||||
if (ctx.section in this.subsectionTemplates &&
|
||||
ctx.subsection in this.subsectionTemplates[ctx.section]) {
|
||||
if (ctx.section in this._subsectionTemplates &&
|
||||
ctx.subsection in this._subsectionTemplates[ctx.section]) {
|
||||
views.showView(
|
||||
source.querySelector('.subcontent'),
|
||||
this.subsectionTemplates[ctx.section][ctx.subsection]({
|
||||
this._subsectionTemplates[ctx.section][ctx.subsection]({
|
||||
name: config.name,
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class HomeView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('home');
|
||||
this._template = views.getTemplate('home');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template({
|
||||
const source = this._template({
|
||||
name: config.name,
|
||||
version: config.meta.version,
|
||||
buildDate: config.meta.buildDate,
|
||||
|
|
|
@ -5,12 +5,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class LoginView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('login');
|
||||
this._template = views.getTemplate('login');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template({
|
||||
const source = this._template({
|
||||
userNamePattern: config.userNameRegex,
|
||||
passwordPattern: config.passwordRegex,
|
||||
canSendMails: config.canSendMails,
|
||||
|
|
|
@ -6,13 +6,13 @@ const keyboard = require('../util/keyboard.js');
|
|||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
|
||||
function removeConsecutiveDuplicates(a) {
|
||||
function _removeConsecutiveDuplicates(a) {
|
||||
return a.filter((item, pos, ary) => {
|
||||
return !pos || item != ary[pos - 1];
|
||||
});
|
||||
}
|
||||
|
||||
function getVisiblePageNumbers(currentPage, totalPages) {
|
||||
function _getVisiblePageNumbers(currentPage, totalPages) {
|
||||
const threshold = 2;
|
||||
let pagesVisible = [];
|
||||
for (let i = 1; i <= threshold; i++) {
|
||||
|
@ -30,11 +30,11 @@ function getVisiblePageNumbers(currentPage, totalPages) {
|
|||
return item >= 1 && item <= totalPages;
|
||||
});
|
||||
pagesVisible = pagesVisible.sort((a, b) => { return a - b; });
|
||||
pagesVisible = removeConsecutiveDuplicates(pagesVisible);
|
||||
pagesVisible = _removeConsecutiveDuplicates(pagesVisible);
|
||||
return pagesVisible;
|
||||
}
|
||||
|
||||
function getPages(currentPage, pageNumbers, clientUrl) {
|
||||
function _getPages(currentPage, pageNumbers, clientUrl) {
|
||||
const pages = [];
|
||||
let lastPage = 0;
|
||||
for (let page of pageNumbers) {
|
||||
|
@ -53,13 +53,13 @@ function getPages(currentPage, pageNumbers, clientUrl) {
|
|||
|
||||
class ManualPageView {
|
||||
constructor() {
|
||||
this.holderTemplate = views.getTemplate('manual-pager');
|
||||
this.navTemplate = views.getTemplate('manual-pager-nav');
|
||||
this._holderTemplate = views.getTemplate('manual-pager');
|
||||
this._navTemplate = views.getTemplate('manual-pager-nav');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.holderTemplate();
|
||||
const source = this._holderTemplate();
|
||||
const pageContentHolder = source.querySelector('.page-content-holder');
|
||||
const pageHeaderHolder = source.querySelector('.page-header-holder');
|
||||
const pageNav = source.querySelector('.page-nav');
|
||||
|
@ -75,8 +75,8 @@ class ManualPageView {
|
|||
ctx.pageRenderer.render(pageRendererCtx);
|
||||
|
||||
const totalPages = Math.ceil(response.total / response.pageSize);
|
||||
const pageNumbers = getVisiblePageNumbers(currentPage, totalPages);
|
||||
const pages = getPages(currentPage, pageNumbers, ctx.clientUrl);
|
||||
const pageNumbers = _getVisiblePageNumbers(currentPage, totalPages);
|
||||
const pages = _getPages(currentPage, pageNumbers, ctx.clientUrl);
|
||||
|
||||
keyboard.bind(['a', 'left'], () => {
|
||||
if (currentPage > 1) {
|
||||
|
@ -90,7 +90,7 @@ class ManualPageView {
|
|||
});
|
||||
|
||||
if (response.total) {
|
||||
views.showView(pageNav, this.navTemplate({
|
||||
views.showView(pageNav, this._navTemplate({
|
||||
prevLink: ctx.clientUrl.format({page: currentPage - 1}),
|
||||
nextLink: ctx.clientUrl.format({page: currentPage + 1}),
|
||||
prevLinkActive: currentPage > 1,
|
||||
|
|
|
@ -4,12 +4,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class PasswordResetView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('password-reset');
|
||||
this._template = views.getTemplate('password-reset');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template();
|
||||
const source = this._template();
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const userNameOrEmailField = source.querySelector('#user-name');
|
||||
|
|
|
@ -5,7 +5,7 @@ const views = require('../util/views.js');
|
|||
|
||||
class RegistrationView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('user-registration');
|
||||
this._template = views.getTemplate('user-registration');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
|
@ -13,7 +13,7 @@ class RegistrationView {
|
|||
ctx.passwordPattern = config.passwordRegex;
|
||||
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const userNameField = source.querySelector('#user-name');
|
||||
|
|
|
@ -4,12 +4,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class SettingsView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('settings');
|
||||
this._template = views.getTemplate('settings');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template({browsingSettings: ctx.getSettings()});
|
||||
const source = this._template({browsingSettings: ctx.getSettings()});
|
||||
|
||||
const form = source.querySelector('form');
|
||||
views.decorateValidator(form);
|
||||
|
|
|
@ -5,7 +5,7 @@ const views = require('../util/views.js');
|
|||
|
||||
class TagListHeaderView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('tag-categories');
|
||||
this._template = views.getTemplate('tag-categories');
|
||||
}
|
||||
|
||||
_saveButtonClickHandler(e, ctx, target) {
|
||||
|
@ -73,7 +73,7 @@ class TagListHeaderView {
|
|||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const newRowTemplate = source.querySelector('.add-template');
|
||||
|
|
|
@ -4,12 +4,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class TagDeleteView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('tag-delete');
|
||||
this._template = views.getTemplate('tag-delete');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
|
||||
|
|
|
@ -2,18 +2,19 @@
|
|||
|
||||
const config = require('../config.js');
|
||||
const views = require('../util/views.js');
|
||||
const TagAutoCompleteControl = require('./tag_auto_complete_control.js');
|
||||
const TagAutoCompleteControl =
|
||||
require('../controls/tag_auto_complete_control.js');
|
||||
|
||||
class TagMergeView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('tag-merge');
|
||||
this._template = views.getTemplate('tag-merge');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
ctx.tagNamePattern = config.tagNameRegex;
|
||||
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const otherTagField = source.querySelector('.target input');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const config = require('../config.js');
|
||||
const views = require('../util/views.js');
|
||||
const TagInputControl = require('./tag_input_control.js');
|
||||
const TagInputControl = require('../controls/tag_input_control.js');
|
||||
|
||||
function split(str) {
|
||||
return str.split(/\s+/).filter(s => s);
|
||||
|
@ -10,7 +10,7 @@ function split(str) {
|
|||
|
||||
class TagSummaryView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('tag-summary');
|
||||
this._template = views.getTemplate('tag-summary');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
|
@ -18,7 +18,7 @@ class TagSummaryView {
|
|||
ctx.tagNamesPattern = '^((' + baseRegex + ')\\s+)*(' + baseRegex + ')$';
|
||||
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const namesField = source.querySelector('.names input');
|
||||
|
|
|
@ -7,15 +7,15 @@ const TagDeleteView = require('./tag_delete_view.js');
|
|||
|
||||
class TagView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('tag');
|
||||
this.summaryView = new TagSummaryView();
|
||||
this.mergeView = new TagMergeView();
|
||||
this.deleteView = new TagDeleteView();
|
||||
this._template = views.getTemplate('tag');
|
||||
this._summaryView = new TagSummaryView();
|
||||
this._mergeView = new TagMergeView();
|
||||
this._deleteView = new TagDeleteView();
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
ctx.section = ctx.section || 'summary';
|
||||
|
||||
|
@ -29,11 +29,11 @@ class TagView {
|
|||
|
||||
let view = null;
|
||||
if (ctx.section == 'merge') {
|
||||
view = this.mergeView;
|
||||
view = this._mergeView;
|
||||
} else if (ctx.section == 'delete') {
|
||||
view = this.deleteView;
|
||||
view = this._deleteView;
|
||||
} else {
|
||||
view = this.summaryView;
|
||||
view = this._summaryView;
|
||||
}
|
||||
ctx.target = source.querySelector('.tag-content-holder');
|
||||
view.render(ctx);
|
||||
|
|
|
@ -4,16 +4,17 @@ const page = require('page');
|
|||
const keyboard = require('../util/keyboard.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const TagAutoCompleteControl = require('./tag_auto_complete_control.js');
|
||||
const TagAutoCompleteControl =
|
||||
require('../controls/tag_auto_complete_control.js');
|
||||
|
||||
class TagsHeaderView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('tags-header');
|
||||
this._template = views.getTemplate('tags-header');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const searchTextInput = form.querySelector('[name=search-text]');
|
||||
|
|
|
@ -4,12 +4,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class TagsPageView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('tags-page');
|
||||
this._template = views.getTemplate('tags-page');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
views.showView(target, source);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ const views = require('../util/views.js');
|
|||
|
||||
class TopNavView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('top-nav');
|
||||
this.navHolder = document.getElementById('top-nav-holder');
|
||||
this._template = views.getTemplate('top-nav');
|
||||
this._navHolder = document.getElementById('top-nav-holder');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = this.navHolder;
|
||||
const source = this.template(ctx);
|
||||
const target = this._navHolder;
|
||||
const source = this._template(ctx);
|
||||
|
||||
for (let link of source.querySelectorAll('a')) {
|
||||
const regex = new RegExp(
|
||||
|
@ -21,7 +21,7 @@ class TopNavView {
|
|||
'<span class="access-key" data-accesskey="$1">$1</span>');
|
||||
}
|
||||
|
||||
views.showView(this.navHolder, source);
|
||||
views.showView(this._navHolder, source);
|
||||
}
|
||||
|
||||
activate(itemName) {
|
||||
|
|
|
@ -4,12 +4,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class UserDeleteView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('user-delete');
|
||||
this._template = views.getTemplate('user-delete');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
const config = require('../config.js');
|
||||
const views = require('../util/views.js');
|
||||
const FileDropperControl = require('./file_dropper_control.js');
|
||||
const FileDropperControl = require('../controls/file_dropper_control.js');
|
||||
|
||||
class UserEditView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('user-edit');
|
||||
this.fileDropperControl = new FileDropperControl();
|
||||
this._template = views.getTemplate('user-edit');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
|
@ -15,7 +14,7 @@ class UserEditView {
|
|||
ctx.passwordPattern = config.passwordRegex + /|^$/.source;
|
||||
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const avatarContentField = source.querySelector('#avatar-content');
|
||||
|
@ -23,8 +22,10 @@ class UserEditView {
|
|||
views.decorateValidator(form);
|
||||
|
||||
let avatarContent = null;
|
||||
this.fileDropperControl.render({
|
||||
target: avatarContentField,
|
||||
if (avatarContentField) {
|
||||
new FileDropperControl(
|
||||
avatarContentField,
|
||||
{
|
||||
lock: true,
|
||||
resolve: files => {
|
||||
source.querySelector(
|
||||
|
@ -32,6 +33,7 @@ class UserEditView {
|
|||
avatarContent = files[0];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', e => {
|
||||
const rankField = source.querySelector('#user-rank');
|
||||
|
|
|
@ -4,12 +4,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class UserSummaryView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('user-summary');
|
||||
this._template = views.getTemplate('user-summary');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
views.listenToMessages(source);
|
||||
views.showView(target, source);
|
||||
}
|
||||
|
|
|
@ -7,15 +7,15 @@ const UserEditView = require('./user_edit_view.js');
|
|||
|
||||
class UserView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('user');
|
||||
this.deleteView = new UserDeleteView();
|
||||
this.summaryView = new UserSummaryView();
|
||||
this.editView = new UserEditView();
|
||||
this._template = views.getTemplate('user');
|
||||
this._deleteView = new UserDeleteView();
|
||||
this._summaryView = new UserSummaryView();
|
||||
this._editView = new UserEditView();
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
ctx.section = ctx.section || 'summary';
|
||||
|
||||
|
@ -29,11 +29,11 @@ class UserView {
|
|||
|
||||
let view = null;
|
||||
if (ctx.section == 'edit') {
|
||||
view = this.editView;
|
||||
view = this._editView;
|
||||
} else if (ctx.section == 'delete') {
|
||||
view = this.deleteView;
|
||||
view = this._deleteView;
|
||||
} else {
|
||||
view = this.summaryView;
|
||||
view = this._summaryView;
|
||||
}
|
||||
ctx.target = source.querySelector('#user-content-holder');
|
||||
view.render(ctx);
|
||||
|
|
|
@ -7,12 +7,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class UsersHeaderView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('users-header');
|
||||
this._template = views.getTemplate('users-header');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ const views = require('../util/views.js');
|
|||
|
||||
class UsersPageView {
|
||||
constructor() {
|
||||
this.template = views.getTemplate('users-page');
|
||||
this._template = views.getTemplate('users-page');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this.template(ctx);
|
||||
const source = this._template(ctx);
|
||||
views.showView(target, source);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue