client/router: introduce own router
I'm tired of page.js lack of documentation around finer quirks, and being forced to read its crap code. Refactored into classes, removed unused cruft.
This commit is contained in:
		
							parent
							
								
									4295e1c827
								
							
						
					
					
						commit
						76882b59ef
					
				@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const api = require('../api.js');
 | 
			
		||||
const events = require('../events.js');
 | 
			
		||||
const topNavController = require('../controllers/top_nav_controller.js');
 | 
			
		||||
@ -14,13 +14,20 @@ class AuthController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page(/\/password-reset\/([^:]+):([^:]+)$/,
 | 
			
		||||
        router.enter(
 | 
			
		||||
            /\/password-reset\/([^:]+):([^:]+)$/,
 | 
			
		||||
            (ctx, next) => {
 | 
			
		||||
                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(); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/password-reset',
 | 
			
		||||
            (ctx, next) => { this._passwordResetRoute(); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/login',
 | 
			
		||||
            (ctx, next) => { this._loginRoute(); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/logout',
 | 
			
		||||
            (ctx, next) => { this._logoutRoute(); });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _loginRoute() {
 | 
			
		||||
@ -33,7 +40,7 @@ class AuthController {
 | 
			
		||||
                    api.login(name, password, doRemember)
 | 
			
		||||
                        .then(() => {
 | 
			
		||||
                            resolve();
 | 
			
		||||
                            page('/');
 | 
			
		||||
                            router.show('/');
 | 
			
		||||
                            events.notify(events.Success, 'Logged in');
 | 
			
		||||
                        }, errorMessage => {
 | 
			
		||||
                            reject(errorMessage);
 | 
			
		||||
@ -46,7 +53,7 @@ class AuthController {
 | 
			
		||||
    _logoutRoute() {
 | 
			
		||||
        api.forget();
 | 
			
		||||
        api.logout();
 | 
			
		||||
        page('/');
 | 
			
		||||
        router.show('/');
 | 
			
		||||
        events.notify(events.Success, 'Logged out');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -68,10 +75,10 @@ class AuthController {
 | 
			
		||||
            }, response => {
 | 
			
		||||
                return Promise.reject(response.description);
 | 
			
		||||
            }).then(() => {
 | 
			
		||||
                page('/');
 | 
			
		||||
                router.show('/');
 | 
			
		||||
                events.notify(events.Success, 'New password: ' + password);
 | 
			
		||||
            }, errorMessage => {
 | 
			
		||||
                page('/');
 | 
			
		||||
                router.show('/');
 | 
			
		||||
                events.notify(events.Error, errorMessage);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const api = require('../api.js');
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const misc = require('../util/misc.js');
 | 
			
		||||
const topNavController = require('../controllers/top_nav_controller.js');
 | 
			
		||||
const pageController = require('../controllers/page_controller.js');
 | 
			
		||||
@ -10,7 +10,7 @@ const EmptyView = require('../views/empty_view.js');
 | 
			
		||||
 | 
			
		||||
class CommentsController {
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/comments/:query?',
 | 
			
		||||
        router.enter('/comments/:query?',
 | 
			
		||||
            (ctx, next) => { misc.parseSearchQueryRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._listCommentsRoute(ctx); });
 | 
			
		||||
        this._commentsPageView = new CommentsPageView();
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const topNavController = require('../controllers/top_nav_controller.js');
 | 
			
		||||
const HelpView = require('../views/help_view.js');
 | 
			
		||||
 | 
			
		||||
@ -10,11 +10,13 @@ class HelpController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/help', () => { this._showHelpRoute(); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/help',
 | 
			
		||||
            (ctx, next) => { this._showHelpRoute(); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/help/:section',
 | 
			
		||||
            (ctx, next) => { this._showHelpRoute(ctx.params.section); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/help/:section/:subsection',
 | 
			
		||||
            (ctx, next) => {
 | 
			
		||||
                this._showHelpRoute(ctx.params.section, ctx.params.subsection);
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,13 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const topNavController = require('../controllers/top_nav_controller.js');
 | 
			
		||||
 | 
			
		||||
class HistoryController {
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/history', (ctx, next) => { this._listHistoryRoute(); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/history',
 | 
			
		||||
            (ctx, next) => { this._listHistoryRoute(); });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _listHistoryRoute() {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const api = require('../api.js');
 | 
			
		||||
const events = require('../events.js');
 | 
			
		||||
const topNavController = require('../controllers/top_nav_controller.js');
 | 
			
		||||
@ -14,8 +14,12 @@ class HomeController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/', (ctx, next) => { this._indexRoute(); });
 | 
			
		||||
        page('*', (ctx, next) => { this._notFoundRoute(ctx); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/',
 | 
			
		||||
            (ctx, next) => { this._indexRoute(); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '*',
 | 
			
		||||
            (ctx, next) => { this._notFoundRoute(ctx); });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _indexRoute() {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const api = require('../api.js');
 | 
			
		||||
const settings = require('../settings.js');
 | 
			
		||||
const events = require('../events.js');
 | 
			
		||||
@ -20,14 +20,17 @@ class PostsController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/upload', (ctx, next) => { this._uploadPostsRoute(); });
 | 
			
		||||
        page('/posts/:query?',
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/upload',
 | 
			
		||||
            (ctx, next) => { this._uploadPostsRoute(); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/posts/:query?',
 | 
			
		||||
            (ctx, next) => { misc.parseSearchQueryRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._listPostsRoute(ctx); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/post/:id',
 | 
			
		||||
            (ctx, next) => { this._showPostRoute(ctx.params.id, false); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/post/:id/edit',
 | 
			
		||||
            (ctx, next) => { this._showPostRoute(ctx.params.id, true); });
 | 
			
		||||
        this._emptyView = new EmptyView();
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const settings = require('../settings.js');
 | 
			
		||||
const topNavController = require('../controllers/top_nav_controller.js');
 | 
			
		||||
const SettingsView = require('../views/settings_view.js');
 | 
			
		||||
@ -11,7 +11,7 @@ class SettingsController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/settings', (ctx, next) => { this._settingsRoute(); });
 | 
			
		||||
        router.enter('/settings', (ctx, next) => { this._settingsRoute(); });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _settingsRoute() {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const api = require('../api.js');
 | 
			
		||||
const tags = require('../tags.js');
 | 
			
		||||
const events = require('../events.js');
 | 
			
		||||
@ -23,20 +23,22 @@ class TagsController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/tag-categories', () => { this._tagCategoriesRoute(); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/tag-categories',
 | 
			
		||||
            (ctx, next) => { this._tagCategoriesRoute(ctx, next); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/tag/:name',
 | 
			
		||||
            (ctx, next) => { this._loadTagRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._showTagRoute(ctx, next); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/tag/:name/merge',
 | 
			
		||||
            (ctx, next) => { this._loadTagRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._mergeTagRoute(ctx, next); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/tag/:name/delete',
 | 
			
		||||
            (ctx, next) => { this._loadTagRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._deleteTagRoute(ctx, next); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/tags/:query?',
 | 
			
		||||
            (ctx, next) => { misc.parseSearchQueryRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._listTagsRoute(ctx, next); });
 | 
			
		||||
@ -136,7 +138,7 @@ class TagsController {
 | 
			
		||||
    _saveTag(tag, input) {
 | 
			
		||||
        return api.put('/tag/' + tag.names[0], input).then(response => {
 | 
			
		||||
            if (input.names && input.names[0] !== tag.names[0]) {
 | 
			
		||||
                page('/tag/' + input.names[0]);
 | 
			
		||||
                router.show('/tag/' + input.names[0]);
 | 
			
		||||
            }
 | 
			
		||||
            events.notify(events.Success, 'Tag saved.');
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
@ -151,7 +153,7 @@ class TagsController {
 | 
			
		||||
            '/tag-merge/',
 | 
			
		||||
            {remove: tag.names[0], mergeTo: targetTagName}
 | 
			
		||||
        ).then(response => {
 | 
			
		||||
            page('/tag/' + targetTagName + '/merge');
 | 
			
		||||
            router.show('/tag/' + targetTagName + '/merge');
 | 
			
		||||
            events.notify(events.Success, 'Tag merged.');
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        }, response => {
 | 
			
		||||
@ -162,7 +164,7 @@ class TagsController {
 | 
			
		||||
 | 
			
		||||
    _deleteTag(tag) {
 | 
			
		||||
        return api.delete('/tag/' + tag.names[0]).then(response => {
 | 
			
		||||
            page('/tags/');
 | 
			
		||||
            router.show('/tags/');
 | 
			
		||||
            events.notify(events.Success, 'Tag deleted.');
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        }, response => {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const api = require('../api.js');
 | 
			
		||||
const config = require('../config.js');
 | 
			
		||||
const events = require('../events.js');
 | 
			
		||||
@ -34,28 +34,31 @@ class UsersController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerRoutes() {
 | 
			
		||||
        page('/register', () => { this._createUserRoute(); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/register',
 | 
			
		||||
            (ctx, next) => { this._createUserRoute(ctx, next); });
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/users/:query?',
 | 
			
		||||
            (ctx, next) => { misc.parseSearchQueryRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._listUsersRoute(ctx, next); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/user/:name',
 | 
			
		||||
            (ctx, next) => { this._loadUserRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._showUserRoute(ctx, next); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/user/:name/edit',
 | 
			
		||||
            (ctx, next) => { this._loadUserRoute(ctx, next); },
 | 
			
		||||
            (ctx, next) => { this._editUserRoute(ctx, next); });
 | 
			
		||||
        page(
 | 
			
		||||
        router.enter(
 | 
			
		||||
            '/user/:name/delete',
 | 
			
		||||
            (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) => {
 | 
			
		||||
        router.exit(
 | 
			
		||||
            /\/users\/.*/, (ctx, next) => {
 | 
			
		||||
                pageController.stop();
 | 
			
		||||
                next();
 | 
			
		||||
            });
 | 
			
		||||
        router.exit(/\/user\/.*/, (ctx, next) => {
 | 
			
		||||
            this._cachedUser = null;
 | 
			
		||||
            next();
 | 
			
		||||
        });
 | 
			
		||||
@ -81,7 +84,7 @@ class UsersController {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _createUserRoute() {
 | 
			
		||||
    _createUserRoute(ctx, next) {
 | 
			
		||||
        topNavController.activate('register');
 | 
			
		||||
        this._registrationView.render({
 | 
			
		||||
            register: (...args) => {
 | 
			
		||||
@ -135,7 +138,7 @@ class UsersController {
 | 
			
		||||
                return Promise.reject(response.description);
 | 
			
		||||
            }).then(() => {
 | 
			
		||||
                resolve();
 | 
			
		||||
                page('/');
 | 
			
		||||
                router.show('/');
 | 
			
		||||
                events.notify(events.Success, 'Welcome aboard!');
 | 
			
		||||
            }, errorMessage => {
 | 
			
		||||
                reject();
 | 
			
		||||
@ -184,7 +187,7 @@ class UsersController {
 | 
			
		||||
                }).then(() => {
 | 
			
		||||
                    resolve();
 | 
			
		||||
                    if (data.name && data.name !== user.name) {
 | 
			
		||||
                        page('/user/' + data.name + '/edit');
 | 
			
		||||
                        router.show('/user/' + data.name + '/edit');
 | 
			
		||||
                    }
 | 
			
		||||
                    events.notify(events.Success, 'Settings updated.');
 | 
			
		||||
                }, errorMessage => {
 | 
			
		||||
@ -203,9 +206,9 @@ class UsersController {
 | 
			
		||||
                    api.logout();
 | 
			
		||||
                }
 | 
			
		||||
                if (api.hasPrivilege('users:list')) {
 | 
			
		||||
                    page('/users');
 | 
			
		||||
                    router.show('/users');
 | 
			
		||||
                } else {
 | 
			
		||||
                    page('/');
 | 
			
		||||
                    router.show('/');
 | 
			
		||||
                }
 | 
			
		||||
                events.notify(events.Success, 'Account deleted.');
 | 
			
		||||
                return Promise.resolve();
 | 
			
		||||
 | 
			
		||||
@ -3,32 +3,37 @@
 | 
			
		||||
require('./util/polyfill.js');
 | 
			
		||||
const misc = require('./util/misc.js');
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const origPushState = page.Context.prototype.pushState;
 | 
			
		||||
page.Context.prototype.pushState = function() {
 | 
			
		||||
const router = require('./router.js');
 | 
			
		||||
 | 
			
		||||
const origPushState = router.Context.prototype.pushState;
 | 
			
		||||
router.Context.prototype.pushState = function() {
 | 
			
		||||
    window.scrollTo(0, 0);
 | 
			
		||||
    origPushState.call(this);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
page.cancel = function(ctx) {
 | 
			
		||||
router.cancel = function(ctx) {
 | 
			
		||||
    prevContext = ctx;
 | 
			
		||||
    ctx.pushState();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
page.exit((ctx, next) => {
 | 
			
		||||
    views.unlistenToMessages();
 | 
			
		||||
    if (misc.confirmPageExit()) {
 | 
			
		||||
        next();
 | 
			
		||||
    } else {
 | 
			
		||||
        page.cancel(ctx);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
router.exit(
 | 
			
		||||
    /.*/,
 | 
			
		||||
    (ctx, next) => {
 | 
			
		||||
        views.unlistenToMessages();
 | 
			
		||||
        if (misc.confirmPageExit()) {
 | 
			
		||||
            next();
 | 
			
		||||
        } else {
 | 
			
		||||
            router.cancel(ctx);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
const mousetrap = require('mousetrap');
 | 
			
		||||
page(/.*/, (ctx, next) => {
 | 
			
		||||
    mousetrap.reset();
 | 
			
		||||
    next();
 | 
			
		||||
});
 | 
			
		||||
router.enter(
 | 
			
		||||
    /.*/,
 | 
			
		||||
    (ctx, next) => {
 | 
			
		||||
        mousetrap.reset();
 | 
			
		||||
        next();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
let controllers = [];
 | 
			
		||||
controllers.push(require('./controllers/auth_controller.js'));
 | 
			
		||||
@ -40,6 +45,7 @@ controllers.push(require('./controllers/history_controller.js'));
 | 
			
		||||
controllers.push(require('./controllers/tags_controller.js'));
 | 
			
		||||
controllers.push(require('./controllers/settings_controller.js'));
 | 
			
		||||
 | 
			
		||||
// home defines 404 routes, need to be registered as last
 | 
			
		||||
controllers.push(require('./controllers/home_controller.js'));
 | 
			
		||||
 | 
			
		||||
const tags = require('./tags.js');
 | 
			
		||||
@ -52,13 +58,13 @@ for (let controller of controllers) {
 | 
			
		||||
const api = require('./api.js');
 | 
			
		||||
Promise.all([tags.refreshExport(), api.loginFromCookies()])
 | 
			
		||||
    .then(() => {
 | 
			
		||||
        page();
 | 
			
		||||
        router.start();
 | 
			
		||||
    }).catch(errorMessage => {
 | 
			
		||||
        if (window.location.href.indexOf('login') !== -1) {
 | 
			
		||||
            api.forget();
 | 
			
		||||
            page();
 | 
			
		||||
            router.start();
 | 
			
		||||
        } else {
 | 
			
		||||
            page('/');
 | 
			
		||||
            router.start('/');
 | 
			
		||||
            events.notify(
 | 
			
		||||
                events.Error,
 | 
			
		||||
                'An error happened while trying to log you in: ' +
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										293
									
								
								client/js/router.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								client/js/router.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,293 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
// modified page.js by visionmedia
 | 
			
		||||
// - removed unused crap
 | 
			
		||||
// - refactored to classes
 | 
			
		||||
 | 
			
		||||
const pathToRegexp = require('path-to-regexp');
 | 
			
		||||
const clickEvent = document.ontouchstart ? 'touchstart' : 'click';
 | 
			
		||||
let location = window.history.location || window.location;
 | 
			
		||||
 | 
			
		||||
const base = '';
 | 
			
		||||
let prevContext = null;
 | 
			
		||||
 | 
			
		||||
function _decodeURLEncodedURIComponent(val) {
 | 
			
		||||
    if (typeof val !== 'string') {
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
    return decodeURIComponent(val.replace(/\+/g, ' '));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _isSameOrigin(href) {
 | 
			
		||||
    let origin = location.protocol + '//' + location.hostname;
 | 
			
		||||
    if (location.port) {
 | 
			
		||||
        origin += ':' + location.port;
 | 
			
		||||
    }
 | 
			
		||||
    return href && href.indexOf(origin) === 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Context {
 | 
			
		||||
    constructor(path, state) {
 | 
			
		||||
        if (path[0] === '/' && path.indexOf(base) !== 0) {
 | 
			
		||||
            path = base + path;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.canonicalPath = path;
 | 
			
		||||
        this.path = path.replace(base, '') || '/';
 | 
			
		||||
 | 
			
		||||
        this.title = document.title;
 | 
			
		||||
        this.state = state || {};
 | 
			
		||||
        this.state.path = path;
 | 
			
		||||
        this.params = {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pushState() {
 | 
			
		||||
        history.pushState(this.state, this.title, this.canonicalPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    save() {
 | 
			
		||||
        history.replaceState(this.state, this.title, this.canonicalPath);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Route {
 | 
			
		||||
    constructor(path, options) {
 | 
			
		||||
        options = options || {};
 | 
			
		||||
        this.path = (path === '*') ? '(.*)' : path;
 | 
			
		||||
        this.method = 'GET';
 | 
			
		||||
        this.regexp = pathToRegexp(this.path, this.keys = [], options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    middleware(fn) {
 | 
			
		||||
        return (ctx, next) => {
 | 
			
		||||
            if (this.match(ctx.path, ctx.params)) {
 | 
			
		||||
                return fn(ctx, next);
 | 
			
		||||
            }
 | 
			
		||||
            next();
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    match(path, params) {
 | 
			
		||||
        const keys = this.keys;
 | 
			
		||||
        const qsIndex = path.indexOf('?');
 | 
			
		||||
        const pathname = ~qsIndex ? path.slice(0, qsIndex) : path;
 | 
			
		||||
        const m = this.regexp.exec(decodeURIComponent(pathname));
 | 
			
		||||
 | 
			
		||||
        if (!m) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (let i = 1, len = m.length; i < len; ++i) {
 | 
			
		||||
            const key = keys[i - 1];
 | 
			
		||||
            const val = _decodeURLEncodedURIComponent(m[i]);
 | 
			
		||||
            if (val !== undefined || !(hasOwnProperty.call(params, key.name))) {
 | 
			
		||||
                params[key.name] = val;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Router {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this._callbacks = [];
 | 
			
		||||
        this._exits = [];
 | 
			
		||||
        this._current = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enter(path) {
 | 
			
		||||
        const route = new Route(path);
 | 
			
		||||
        for (let i = 1; i < arguments.length; ++i) {
 | 
			
		||||
            this._callbacks.push(route.middleware(arguments[i]));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exit(path, fn) {
 | 
			
		||||
        const route = new Route(path);
 | 
			
		||||
        for (let i = 1; i < arguments.length; ++i) {
 | 
			
		||||
            this._exits.push(route.middleware(arguments[i]));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    start() {
 | 
			
		||||
        if (this._running) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this._running = true;
 | 
			
		||||
        this._onPopState = _onPopState(this);
 | 
			
		||||
        this._onClick = _onClick(this);
 | 
			
		||||
        window.addEventListener('popstate', this._onPopState, false);
 | 
			
		||||
        document.addEventListener(clickEvent, this._onClick, false);
 | 
			
		||||
        const url = location.pathname + location.search + location.hash;
 | 
			
		||||
        this.replace(url, null, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stop() {
 | 
			
		||||
        if (!this._running) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this._current = '';
 | 
			
		||||
        this._running = false;
 | 
			
		||||
        document.removeEventListener(clickEvent, this._onClick, false);
 | 
			
		||||
        window.removeEventListener('popstate', this._onPopState, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show(path, state, push) {
 | 
			
		||||
        const ctx = new Context(path, state);
 | 
			
		||||
        this._current = ctx.path;
 | 
			
		||||
        this.dispatch(ctx);
 | 
			
		||||
        if (ctx.handled !== false && push !== false) {
 | 
			
		||||
            ctx.pushState();
 | 
			
		||||
        }
 | 
			
		||||
        return ctx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    replace(path, state, dispatch) {
 | 
			
		||||
        var ctx = new Context(path, state);
 | 
			
		||||
        this._current = ctx.path;
 | 
			
		||||
        ctx.save();
 | 
			
		||||
        if (dispatch) {
 | 
			
		||||
            this.dispatch(ctx);
 | 
			
		||||
        }
 | 
			
		||||
        return ctx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispatch(ctx) {
 | 
			
		||||
        const prev = prevContext;
 | 
			
		||||
        let i = 0;
 | 
			
		||||
        let j = 0;
 | 
			
		||||
 | 
			
		||||
        prevContext = ctx;
 | 
			
		||||
 | 
			
		||||
        const nextExit = () => {
 | 
			
		||||
            const fn = this._exits[j++];
 | 
			
		||||
            if (!fn) {
 | 
			
		||||
                return nextEnter();
 | 
			
		||||
            }
 | 
			
		||||
            fn(prev, nextExit);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const nextEnter = () => {
 | 
			
		||||
            const fn = this._callbacks[i++];
 | 
			
		||||
            if (ctx.path !== this._current) {
 | 
			
		||||
                ctx.handled = false;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!fn) {
 | 
			
		||||
                return this._unhandled(ctx);
 | 
			
		||||
            }
 | 
			
		||||
            fn(ctx, nextEnter);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (prev) {
 | 
			
		||||
            nextExit();
 | 
			
		||||
        } else {
 | 
			
		||||
            nextEnter();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _unhandled(ctx) {
 | 
			
		||||
        if (ctx.handled) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        let current = location.pathname + location.search;
 | 
			
		||||
        if (current === ctx.canonicalPath) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        router.stop();
 | 
			
		||||
        ctx.handled = false;
 | 
			
		||||
        location.href = ctx.canonicalPath;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const _onPopState = router => {
 | 
			
		||||
    let loaded = false;
 | 
			
		||||
    if (document.readyState === 'complete') {
 | 
			
		||||
        loaded = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        window.addEventListener(
 | 
			
		||||
            'load',
 | 
			
		||||
            () => {
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    loaded = true;
 | 
			
		||||
                }, 0);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
    return e => {
 | 
			
		||||
        if (!loaded) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (e.state) {
 | 
			
		||||
            const path = e.state.path;
 | 
			
		||||
            router.replace(path, e.state, true);
 | 
			
		||||
        } else {
 | 
			
		||||
            router.show(
 | 
			
		||||
                location.pathname + location.hash,
 | 
			
		||||
                undefined,
 | 
			
		||||
                false);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const _onClick = router => {
 | 
			
		||||
    return e => {
 | 
			
		||||
        if (1 !== _which(e)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (e.metaKey || e.ctrlKey || e.shiftKey) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (e.defaultPrevented) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let el = e.path ? e.path[0] : e.target;
 | 
			
		||||
        while (el && el.nodeName !== 'A') {
 | 
			
		||||
            el = el.parentNode;
 | 
			
		||||
        }
 | 
			
		||||
        if (!el || el.nodeName !== 'A') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (el.hasAttribute('download') ||
 | 
			
		||||
                el.getAttribute('rel') === 'external') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const link = el.getAttribute('href');
 | 
			
		||||
        if (el.pathname === location.pathname && (el.hash || '#' === link)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (link && link.indexOf('mailto:') > -1) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (el.target) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!_isSameOrigin(el.href)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let path = el.pathname + el.search + (el.hash || '');
 | 
			
		||||
 | 
			
		||||
        const orig = path;
 | 
			
		||||
        if (path.indexOf(base) === 0) {
 | 
			
		||||
            path = path.substr(base.length);
 | 
			
		||||
        }
 | 
			
		||||
        if (base && orig === path) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        router.show(orig);
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function _which(e) {
 | 
			
		||||
    e = e || window.event;
 | 
			
		||||
    return e.which === null ? e.button : e.which;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Router.prototype.Context = Context;
 | 
			
		||||
Router.prototype.Route = Route;
 | 
			
		||||
module.exports = new Router();
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const events = require('../events.js');
 | 
			
		||||
const views = require('../util/views.js');
 | 
			
		||||
 | 
			
		||||
@ -55,10 +55,9 @@ class EndlessPageView {
 | 
			
		||||
            }
 | 
			
		||||
            let topPageNumber = parseInt(topPageNode.getAttribute('data-page'));
 | 
			
		||||
            if (topPageNumber !== this.currentPage) {
 | 
			
		||||
                page.replace(
 | 
			
		||||
                router.replace(
 | 
			
		||||
                    _formatUrl(ctx.clientUrl, topPageNumber),
 | 
			
		||||
                    null,
 | 
			
		||||
                    false,
 | 
			
		||||
                    {},
 | 
			
		||||
                    false);
 | 
			
		||||
                this.currentPage = topPageNumber;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const config = require('../config.js');
 | 
			
		||||
const misc = require('../util/misc.js');
 | 
			
		||||
const views = require('../util/views.js');
 | 
			
		||||
@ -32,7 +32,7 @@ class HomeView {
 | 
			
		||||
            form.querySelector('input[name=all-posts')
 | 
			
		||||
                .addEventListener('click', e => {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                    page('/posts/');
 | 
			
		||||
                    router.show('/posts/');
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            const searchTextInput = form.querySelector(
 | 
			
		||||
@ -42,7 +42,7 @@ class HomeView {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                const text = searchTextInput.value;
 | 
			
		||||
                searchTextInput.blur();
 | 
			
		||||
                page('/posts/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
                router.show('/posts/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const events = require('../events.js');
 | 
			
		||||
const keyboard = require('../util/keyboard.js');
 | 
			
		||||
const misc = require('../util/misc.js');
 | 
			
		||||
@ -85,12 +85,12 @@ class ManualPageView {
 | 
			
		||||
 | 
			
		||||
            keyboard.bind(['a', 'left'], () => {
 | 
			
		||||
                if (currentPage > 1) {
 | 
			
		||||
                    page.show(_formatUrl(ctx.clientUrl, currentPage - 1));
 | 
			
		||||
                    router.show(_formatUrl(ctx.clientUrl, currentPage - 1));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            keyboard.bind(['d', 'right'], () => {
 | 
			
		||||
                if (currentPage < totalPages) {
 | 
			
		||||
                    page.show(_formatUrl(ctx.clientUrl, currentPage + 1));
 | 
			
		||||
                    router.show(_formatUrl(ctx.clientUrl, currentPage + 1));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const api = require('../api.js');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const views = require('../util/views.js');
 | 
			
		||||
const keyboard = require('../util/keyboard.js');
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const PostContentControl = require('../controls/post_content_control.js');
 | 
			
		||||
const PostNotesOverlayControl
 | 
			
		||||
    = require('../controls/post_notes_overlay_control.js');
 | 
			
		||||
@ -60,19 +60,19 @@ class PostView {
 | 
			
		||||
 | 
			
		||||
        keyboard.bind('e', () => {
 | 
			
		||||
            if (ctx.editMode) {
 | 
			
		||||
                page.show('/post/' + ctx.post.id);
 | 
			
		||||
                router.show('/post/' + ctx.post.id);
 | 
			
		||||
            } else {
 | 
			
		||||
                page.show('/post/' + ctx.post.id + '/edit');
 | 
			
		||||
                router.show('/post/' + ctx.post.id + '/edit');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        keyboard.bind(['a', 'left'], () => {
 | 
			
		||||
            if (ctx.nextPostId) {
 | 
			
		||||
                page.show('/post/' + ctx.nextPostId);
 | 
			
		||||
                router.show('/post/' + ctx.nextPostId);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        keyboard.bind(['d', 'right'], () => {
 | 
			
		||||
            if (ctx.prevPostId) {
 | 
			
		||||
                page.show('/post/' + ctx.prevPostId);
 | 
			
		||||
                router.show('/post/' + ctx.prevPostId);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const settings = require('../settings.js');
 | 
			
		||||
const keyboard = require('../util/keyboard.js');
 | 
			
		||||
const misc = require('../util/misc.js');
 | 
			
		||||
@ -56,14 +56,14 @@ class PostsHeaderView {
 | 
			
		||||
        browsingSettings.listPosts[safety]
 | 
			
		||||
            = !browsingSettings.listPosts[safety];
 | 
			
		||||
        settings.saveSettings(browsingSettings, true);
 | 
			
		||||
        page(url.replace(/{page}/, 1));
 | 
			
		||||
        router.show(url.replace(/{page}/, 1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _evtFormSubmit(e, searchTextInput) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        const text = searchTextInput.value;
 | 
			
		||||
        searchTextInput.blur();
 | 
			
		||||
        page('/posts/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
        router.show('/posts/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const keyboard = require('../util/keyboard.js');
 | 
			
		||||
const misc = require('../util/misc.js');
 | 
			
		||||
const views = require('../util/views.js');
 | 
			
		||||
@ -31,7 +31,7 @@ class TagsHeaderView {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            const text = searchTextInput.value;
 | 
			
		||||
            searchTextInput.blur();
 | 
			
		||||
            page('/tags/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
            router.show('/tags/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        views.showView(target, source);
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const page = require('page');
 | 
			
		||||
const router = require('../router.js');
 | 
			
		||||
const keyboard = require('../util/keyboard.js');
 | 
			
		||||
const misc = require('../util/misc.js');
 | 
			
		||||
const views = require('../util/views.js');
 | 
			
		||||
@ -25,7 +25,7 @@ class UsersHeaderView {
 | 
			
		||||
            const searchTextInput = form.querySelector('[name=search-text]');
 | 
			
		||||
            const text = searchTextInput.value;
 | 
			
		||||
            searchTextInput.blur();
 | 
			
		||||
            page('/users/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
            router.show('/users/' + misc.formatSearchQuery({text: text}));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        views.showView(target, source);
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@
 | 
			
		||||
    "merge": "^1.2.0",
 | 
			
		||||
    "mousetrap": "^1.5.3",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "page": "^1.7.1",
 | 
			
		||||
    "path-to-regexp": "^1.5.1",
 | 
			
		||||
    "stylus": "^0.54.2",
 | 
			
		||||
    "superagent": "^1.8.3",
 | 
			
		||||
    "uglify-js": "git://github.com/mishoo/UglifyJS2.git#harmony",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user