client/js: format code to ESLint
This commit is contained in:
		
							parent
							
								
									48c9001194
								
							
						
					
					
						commit
						4329b1620f
					
				
							
								
								
									
										293
									
								
								client/.eslintrc.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								client/.eslintrc.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,293 @@
 | 
			
		||||
env:
 | 
			
		||||
  browser: true
 | 
			
		||||
  commonjs: true
 | 
			
		||||
  es6: true
 | 
			
		||||
extends: 'eslint:recommended'
 | 
			
		||||
globals:
 | 
			
		||||
  Atomics: readonly
 | 
			
		||||
  SharedArrayBuffer: readonly
 | 
			
		||||
ignorePatterns:
 | 
			
		||||
  - build.js
 | 
			
		||||
parserOptions:
 | 
			
		||||
  ecmaVersion: 11
 | 
			
		||||
rules:
 | 
			
		||||
  accessor-pairs: error
 | 
			
		||||
  array-bracket-newline: error
 | 
			
		||||
  array-bracket-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - never
 | 
			
		||||
  array-callback-return: error
 | 
			
		||||
  array-element-newline: 'off'
 | 
			
		||||
  arrow-body-style: 'off'
 | 
			
		||||
  arrow-parens:
 | 
			
		||||
    - error
 | 
			
		||||
    - as-needed
 | 
			
		||||
  arrow-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - after: true
 | 
			
		||||
      before: true
 | 
			
		||||
  block-scoped-var: error
 | 
			
		||||
  block-spacing: error
 | 
			
		||||
  brace-style:
 | 
			
		||||
    - error
 | 
			
		||||
    - 1tbs
 | 
			
		||||
  callback-return: 'off'
 | 
			
		||||
  camelcase: error
 | 
			
		||||
  class-methods-use-this: 'off'
 | 
			
		||||
  comma-dangle: 'off'
 | 
			
		||||
  comma-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - after: true
 | 
			
		||||
      before: false
 | 
			
		||||
  comma-style:
 | 
			
		||||
    - error
 | 
			
		||||
    - last
 | 
			
		||||
  complexity: 'off'
 | 
			
		||||
  computed-property-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - never
 | 
			
		||||
  consistent-return: 'off'
 | 
			
		||||
  consistent-this: 'off'
 | 
			
		||||
  curly: error
 | 
			
		||||
  default-case: error
 | 
			
		||||
  default-case-last: error
 | 
			
		||||
  default-param-last: error
 | 
			
		||||
  dot-location:
 | 
			
		||||
    - error
 | 
			
		||||
    - property
 | 
			
		||||
  dot-notation:
 | 
			
		||||
    - error
 | 
			
		||||
    - allowKeywords: true
 | 
			
		||||
  eol-last: error
 | 
			
		||||
  eqeqeq: error
 | 
			
		||||
  func-call-spacing: error
 | 
			
		||||
  func-name-matching: error
 | 
			
		||||
  func-names: error
 | 
			
		||||
  func-style:
 | 
			
		||||
    - error
 | 
			
		||||
    - declaration
 | 
			
		||||
    - allowArrowFunctions: true
 | 
			
		||||
  function-call-argument-newline:
 | 
			
		||||
    - error
 | 
			
		||||
    - consistent
 | 
			
		||||
  function-paren-newline: 'off'
 | 
			
		||||
  generator-star-spacing: error
 | 
			
		||||
  global-require: 'off'
 | 
			
		||||
  grouped-accessor-pairs: 'off'
 | 
			
		||||
  guard-for-in: error
 | 
			
		||||
  handle-callback-err: error
 | 
			
		||||
  id-blacklist: error
 | 
			
		||||
  id-length: 'off'
 | 
			
		||||
  id-match: error
 | 
			
		||||
  implicit-arrow-linebreak:
 | 
			
		||||
    - error
 | 
			
		||||
    - beside
 | 
			
		||||
  indent:
 | 
			
		||||
    - error
 | 
			
		||||
    - 4
 | 
			
		||||
  indent-legacy: 'off'
 | 
			
		||||
  init-declarations: error
 | 
			
		||||
  jsx-quotes: error
 | 
			
		||||
  key-spacing: error
 | 
			
		||||
  keyword-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - after: true
 | 
			
		||||
      before: true
 | 
			
		||||
  line-comment-position: 'off'
 | 
			
		||||
  linebreak-style:
 | 
			
		||||
    - error
 | 
			
		||||
    - unix
 | 
			
		||||
  lines-around-comment: error
 | 
			
		||||
  lines-around-directive: error
 | 
			
		||||
  lines-between-class-members:
 | 
			
		||||
    - error
 | 
			
		||||
    - always
 | 
			
		||||
  max-classes-per-file: 'off'
 | 
			
		||||
  max-depth: error
 | 
			
		||||
  max-len: 'off'
 | 
			
		||||
  max-lines: 'off'
 | 
			
		||||
  max-lines-per-function: 'off'
 | 
			
		||||
  max-nested-callbacks: error
 | 
			
		||||
  max-params: 'off'
 | 
			
		||||
  max-statements: 'off'
 | 
			
		||||
  max-statements-per-line: error
 | 
			
		||||
  multiline-comment-style:
 | 
			
		||||
    - error
 | 
			
		||||
    - separate-lines
 | 
			
		||||
  multiline-ternary: 'off'
 | 
			
		||||
  new-cap: error
 | 
			
		||||
  new-parens: error
 | 
			
		||||
  newline-after-var: 'off'
 | 
			
		||||
  newline-before-return: 'off'
 | 
			
		||||
  newline-per-chained-call: 'off'
 | 
			
		||||
  no-alert: 'off'
 | 
			
		||||
  no-array-constructor: error
 | 
			
		||||
  no-await-in-loop: error
 | 
			
		||||
  no-bitwise: 'off'
 | 
			
		||||
  no-buffer-constructor: 'off'
 | 
			
		||||
  no-caller: error
 | 
			
		||||
  no-catch-shadow: error
 | 
			
		||||
  no-confusing-arrow: error
 | 
			
		||||
  no-console: error
 | 
			
		||||
  no-constructor-return: error
 | 
			
		||||
  no-continue: 'off'
 | 
			
		||||
  no-div-regex: 'off'
 | 
			
		||||
  no-duplicate-imports: error
 | 
			
		||||
  no-else-return: 'off'
 | 
			
		||||
  no-empty-function: 'off'
 | 
			
		||||
  no-eq-null: error
 | 
			
		||||
  no-eval: error
 | 
			
		||||
  no-extend-native: error
 | 
			
		||||
  no-extra-bind: error
 | 
			
		||||
  no-extra-label: error
 | 
			
		||||
  no-extra-parens: 'off'
 | 
			
		||||
  no-floating-decimal: error
 | 
			
		||||
  no-implicit-globals: error
 | 
			
		||||
  no-implied-eval: error
 | 
			
		||||
  no-inline-comments: 'off'
 | 
			
		||||
  no-invalid-this: error
 | 
			
		||||
  no-iterator: error
 | 
			
		||||
  no-label-var: error
 | 
			
		||||
  no-labels: error
 | 
			
		||||
  no-lone-blocks: error
 | 
			
		||||
  no-lonely-if: error
 | 
			
		||||
  no-loop-func: 'off'
 | 
			
		||||
  no-loss-of-precision: error
 | 
			
		||||
  no-magic-numbers: 'off'
 | 
			
		||||
  no-mixed-operators: error
 | 
			
		||||
  no-mixed-requires: error
 | 
			
		||||
  no-multi-assign: error
 | 
			
		||||
  no-multi-spaces:
 | 
			
		||||
    - error
 | 
			
		||||
    - ignoreEOLComments: true
 | 
			
		||||
  no-multi-str: error
 | 
			
		||||
  no-multiple-empty-lines: error
 | 
			
		||||
  no-native-reassign: error
 | 
			
		||||
  no-negated-condition: 'off'
 | 
			
		||||
  no-negated-in-lhs: error
 | 
			
		||||
  no-nested-ternary: error
 | 
			
		||||
  no-new: 'off'
 | 
			
		||||
  no-new-func: error
 | 
			
		||||
  no-new-object: error
 | 
			
		||||
  no-new-require: error
 | 
			
		||||
  no-new-wrappers: error
 | 
			
		||||
  no-octal-escape: error
 | 
			
		||||
  no-param-reassign: 'off'
 | 
			
		||||
  no-path-concat: error
 | 
			
		||||
  no-plusplus: 'off'
 | 
			
		||||
  no-process-env: error
 | 
			
		||||
  no-process-exit: error
 | 
			
		||||
  no-proto: error
 | 
			
		||||
  no-restricted-exports: error
 | 
			
		||||
  no-restricted-globals: error
 | 
			
		||||
  no-restricted-imports: error
 | 
			
		||||
  no-restricted-modules: error
 | 
			
		||||
  no-restricted-properties: error
 | 
			
		||||
  no-restricted-syntax: error
 | 
			
		||||
  no-return-assign: error
 | 
			
		||||
  no-return-await: error
 | 
			
		||||
  no-script-url: error
 | 
			
		||||
  no-self-compare: error
 | 
			
		||||
  no-sequences: error
 | 
			
		||||
  no-shadow: 'off'
 | 
			
		||||
  no-spaced-func: error
 | 
			
		||||
  no-sync: error
 | 
			
		||||
  no-tabs: error
 | 
			
		||||
  no-template-curly-in-string: error
 | 
			
		||||
  no-ternary: 'off'
 | 
			
		||||
  no-throw-literal: 'off'
 | 
			
		||||
  no-trailing-spaces: error
 | 
			
		||||
  no-undef-init: error
 | 
			
		||||
  no-undefined: 'off'
 | 
			
		||||
  no-underscore-dangle: 'off'
 | 
			
		||||
  no-unmodified-loop-condition: error
 | 
			
		||||
  no-unneeded-ternary: error
 | 
			
		||||
  no-unused-expressions: error
 | 
			
		||||
  no-unused-vars: 'off'
 | 
			
		||||
  no-use-before-define: 'off'
 | 
			
		||||
  no-useless-backreference: error
 | 
			
		||||
  no-useless-call: error
 | 
			
		||||
  no-useless-computed-key: error
 | 
			
		||||
  no-useless-concat: error
 | 
			
		||||
  no-useless-constructor: error
 | 
			
		||||
  no-useless-escape: 'off'
 | 
			
		||||
  no-useless-rename: error
 | 
			
		||||
  no-useless-return: error
 | 
			
		||||
  no-var: 'off'
 | 
			
		||||
  no-void: error
 | 
			
		||||
  no-warning-comments: warn
 | 
			
		||||
  no-whitespace-before-property: error
 | 
			
		||||
  nonblock-statement-body-position: error
 | 
			
		||||
  object-curly-newline: error
 | 
			
		||||
  object-curly-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - never
 | 
			
		||||
  object-shorthand: 'off'
 | 
			
		||||
  one-var: 'off'
 | 
			
		||||
  one-var-declaration-per-line: error
 | 
			
		||||
  operator-assignment:
 | 
			
		||||
    - error
 | 
			
		||||
    - always
 | 
			
		||||
  operator-linebreak: 'off'
 | 
			
		||||
  padded-blocks: 'off'
 | 
			
		||||
  padding-line-between-statements: error
 | 
			
		||||
  prefer-arrow-callback: error
 | 
			
		||||
  prefer-const: 'off'
 | 
			
		||||
  prefer-destructuring: 'off'
 | 
			
		||||
  prefer-exponentiation-operator: 'off'
 | 
			
		||||
  prefer-named-capture-group: 'off'
 | 
			
		||||
  prefer-numeric-literals: error
 | 
			
		||||
  prefer-object-spread: 'off'
 | 
			
		||||
  prefer-promise-reject-errors: 'off'
 | 
			
		||||
  prefer-reflect: 'off'
 | 
			
		||||
  prefer-regex-literals: warn
 | 
			
		||||
  prefer-rest-params: 'off'
 | 
			
		||||
  prefer-spread: 'off'
 | 
			
		||||
  prefer-template: 'off'
 | 
			
		||||
  quote-props: 'off'
 | 
			
		||||
  quotes: 'off'
 | 
			
		||||
  radix:
 | 
			
		||||
    - error
 | 
			
		||||
    - as-needed
 | 
			
		||||
  require-atomic-updates: error
 | 
			
		||||
  require-await: error
 | 
			
		||||
  require-jsdoc: 'off'
 | 
			
		||||
  require-unicode-regexp: 'off'
 | 
			
		||||
  rest-spread-spacing: error
 | 
			
		||||
  semi: 'off'
 | 
			
		||||
  semi-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - after: true
 | 
			
		||||
      before: false
 | 
			
		||||
  semi-style:
 | 
			
		||||
    - error
 | 
			
		||||
    - last
 | 
			
		||||
  sort-imports: error
 | 
			
		||||
  sort-keys: 'off'
 | 
			
		||||
  sort-vars: error
 | 
			
		||||
  space-before-blocks: error
 | 
			
		||||
  space-before-function-paren: 'off'
 | 
			
		||||
  space-in-parens:
 | 
			
		||||
    - error
 | 
			
		||||
    - never
 | 
			
		||||
  space-infix-ops: error
 | 
			
		||||
  space-unary-ops: error
 | 
			
		||||
  spaced-comment:
 | 
			
		||||
    - error
 | 
			
		||||
    - always
 | 
			
		||||
  strict: error
 | 
			
		||||
  switch-colon-spacing: error
 | 
			
		||||
  symbol-description: error
 | 
			
		||||
  template-curly-spacing:
 | 
			
		||||
    - error
 | 
			
		||||
    - never
 | 
			
		||||
  template-tag-spacing: error
 | 
			
		||||
  unicode-bom:
 | 
			
		||||
    - error
 | 
			
		||||
    - never
 | 
			
		||||
  valid-jsdoc: error
 | 
			
		||||
  vars-on-top: error
 | 
			
		||||
  wrap-iife: error
 | 
			
		||||
  wrap-regex: error
 | 
			
		||||
  yield-star-spacing: error
 | 
			
		||||
  yoda: 'off'
 | 
			
		||||
@ -377,8 +377,9 @@ class Api extends events.EventTarget {
 | 
			
		||||
            try {
 | 
			
		||||
                if (this.userName && this.token) {
 | 
			
		||||
                    req.auth = null;
 | 
			
		||||
                    req.set('Authorization', 'Token '
 | 
			
		||||
                        + new Buffer(this.userName + ":" + this.token).toString('base64'))
 | 
			
		||||
                    // eslint-disable-next-line no-undef
 | 
			
		||||
                    req.set('Authorization', 'Token ' + new Buffer(
 | 
			
		||||
                        this.userName + ":" + this.token).toString('base64'))
 | 
			
		||||
                } else if (this.userName && this.userPassword) {
 | 
			
		||||
                    req.auth(
 | 
			
		||||
                        this.userName,
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,9 @@ class CommentsController {
 | 
			
		||||
            requestPage: (offset, limit) => {
 | 
			
		||||
                return PostList.search(
 | 
			
		||||
                    'sort:comment-date comment-count-min:1',
 | 
			
		||||
                    offset, limit, fields);
 | 
			
		||||
                    offset,
 | 
			
		||||
                    limit,
 | 
			
		||||
                    fields);
 | 
			
		||||
            },
 | 
			
		||||
            pageRenderer: pageCtx => {
 | 
			
		||||
                Object.assign(pageCtx, {
 | 
			
		||||
@ -68,9 +70,10 @@ class CommentsController {
 | 
			
		||||
        e.detail.comment.delete()
 | 
			
		||||
            .catch(error => window.alert(error.message));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter(['comments'],
 | 
			
		||||
        (ctx, next) => { new CommentsController(ctx); });
 | 
			
		||||
};
 | 
			
		||||
    router.enter(['comments'], (ctx, next) => {
 | 
			
		||||
        new CommentsController(ctx);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -41,10 +41,10 @@ class HomeController {
 | 
			
		||||
    showError(message) {
 | 
			
		||||
        this._homeView.showError(message);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter([], (ctx, next) => {
 | 
			
		||||
        ctx.controller = new HomeController();
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9,10 +9,10 @@ class NotFoundController {
 | 
			
		||||
        topNavigation.setTitle('Not found');
 | 
			
		||||
        this._notFoundView = new NotFoundView(path);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter(null, (ctx, next) => {
 | 
			
		||||
        ctx.controller = new NotFoundController(ctx.canonicalPath);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -57,7 +57,8 @@ class PostDetailController extends BasePostController {
 | 
			
		||||
        if (this._id !== e.detail.post.id) {
 | 
			
		||||
            router.replace(
 | 
			
		||||
                uri.formatClientLink('post', e.detail.post.id, section),
 | 
			
		||||
                null, false);
 | 
			
		||||
                null,
 | 
			
		||||
                false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -71,7 +72,8 @@ class PostDetailController extends BasePostController {
 | 
			
		||||
                router.replace(
 | 
			
		||||
                    uri.formatClientLink(
 | 
			
		||||
                        'post', e.detail.targetPost.id, 'merge'),
 | 
			
		||||
                    null, false);
 | 
			
		||||
                    null,
 | 
			
		||||
                    false);
 | 
			
		||||
            }, error => {
 | 
			
		||||
                this._view.showError(error.message);
 | 
			
		||||
                this._view.enableForm();
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,8 @@ const EmptyView = require('../views/empty_view.js');
 | 
			
		||||
 | 
			
		||||
const fields = [
 | 
			
		||||
    'id', 'thumbnailUrl', 'type', 'safety',
 | 
			
		||||
    'score', 'favoriteCount', 'commentCount', 'tags', 'version'];
 | 
			
		||||
    'score', 'favoriteCount', 'commentCount', 'tags', 'version'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
class PostListController {
 | 
			
		||||
    constructor(ctx) {
 | 
			
		||||
@ -62,8 +63,7 @@ class PostListController {
 | 
			
		||||
 | 
			
		||||
    _evtTag(e) {
 | 
			
		||||
        Promise.all(
 | 
			
		||||
            this._bulkEditTags.map(tag =>
 | 
			
		||||
                e.detail.post.tags.addByName(tag)))
 | 
			
		||||
            this._bulkEditTags.map(tag => e.detail.post.tags.addByName(tag)))
 | 
			
		||||
            .then(e.detail.post.save())
 | 
			
		||||
            .catch(error => window.alert(error.message));
 | 
			
		||||
    }
 | 
			
		||||
@ -117,5 +117,7 @@ class PostListController {
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter(
 | 
			
		||||
        ['posts'],
 | 
			
		||||
        (ctx, next) => { ctx.controller = new PostListController(ctx); });
 | 
			
		||||
        (ctx, next) => {
 | 
			
		||||
            ctx.controller = new PostListController(ctx);
 | 
			
		||||
        });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -18,10 +18,10 @@ class PostMainController extends BasePostController {
 | 
			
		||||
 | 
			
		||||
        let parameters = ctx.parameters;
 | 
			
		||||
        Promise.all([
 | 
			
		||||
                Post.get(ctx.parameters.id),
 | 
			
		||||
                PostList.getAround(
 | 
			
		||||
                    ctx.parameters.id,
 | 
			
		||||
                    parameters ? parameters.query : null),
 | 
			
		||||
            Post.get(ctx.parameters.id),
 | 
			
		||||
            PostList.getAround(
 | 
			
		||||
                ctx.parameters.id,
 | 
			
		||||
                parameters ? parameters.query : null),
 | 
			
		||||
        ]).then(responses => {
 | 
			
		||||
            const [post, aroundResponse] = responses;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -57,33 +57,32 @@ class PostUploadController {
 | 
			
		||||
        this._view.clearMessages();
 | 
			
		||||
 | 
			
		||||
        e.detail.uploadables.reduce(
 | 
			
		||||
            (promise, uploadable) =>
 | 
			
		||||
                promise.then(() => this._uploadSinglePost(
 | 
			
		||||
                    uploadable, e.detail.skipDuplicates)),
 | 
			
		||||
            (promise, uploadable) => promise.then(() => this._uploadSinglePost(
 | 
			
		||||
                uploadable, e.detail.skipDuplicates)),
 | 
			
		||||
            Promise.resolve())
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    this._view.clearMessages();
 | 
			
		||||
                    misc.disableExitConfirmation();
 | 
			
		||||
                    const ctx = router.show(uri.formatClientLink('posts'));
 | 
			
		||||
                    ctx.controller.showSuccess('Posts uploaded.');
 | 
			
		||||
                }, error => {
 | 
			
		||||
                    if (error.uploadable) {
 | 
			
		||||
                        if (error.similarPosts) {
 | 
			
		||||
                            error.uploadable.lookalikes = error.similarPosts;
 | 
			
		||||
                            this._view.updateUploadable(error.uploadable);
 | 
			
		||||
                            this._view.showInfo(genericErrorMessage);
 | 
			
		||||
                            this._view.showInfo(
 | 
			
		||||
                                error.message, error.uploadable);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this._view.showError(genericErrorMessage);
 | 
			
		||||
                            this._view.showError(
 | 
			
		||||
                                error.message, error.uploadable);
 | 
			
		||||
                        }
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                this._view.clearMessages();
 | 
			
		||||
                misc.disableExitConfirmation();
 | 
			
		||||
                const ctx = router.show(uri.formatClientLink('posts'));
 | 
			
		||||
                ctx.controller.showSuccess('Posts uploaded.');
 | 
			
		||||
            }, error => {
 | 
			
		||||
                if (error.uploadable) {
 | 
			
		||||
                    if (error.similarPosts) {
 | 
			
		||||
                        error.uploadable.lookalikes = error.similarPosts;
 | 
			
		||||
                        this._view.updateUploadable(error.uploadable);
 | 
			
		||||
                        this._view.showInfo(genericErrorMessage);
 | 
			
		||||
                        this._view.showInfo(
 | 
			
		||||
                            error.message, error.uploadable);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this._view.showError(error.message);
 | 
			
		||||
                        this._view.showError(genericErrorMessage);
 | 
			
		||||
                        this._view.showError(
 | 
			
		||||
                            error.message, error.uploadable);
 | 
			
		||||
                    }
 | 
			
		||||
                    this._view.enableForm();
 | 
			
		||||
                });
 | 
			
		||||
                } else {
 | 
			
		||||
                    this._view.showError(error.message);
 | 
			
		||||
                }
 | 
			
		||||
                this._view.enableForm();
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _uploadSinglePost(uploadable, skipDuplicates) {
 | 
			
		||||
@ -153,7 +152,9 @@ class PostUploadController {
 | 
			
		||||
        post.newContent = uploadable.url || uploadable.file;
 | 
			
		||||
        // if uploadable.source is ever going to be a valid field (e.g when setting source directly in the upload window)
 | 
			
		||||
        // you'll need to change the line below to `post.source = uploadable.source || uploadable.url;`
 | 
			
		||||
        if (uploadable.url) post.source = uploadable.url;
 | 
			
		||||
        if (uploadable.url) {
 | 
			
		||||
            post.source = uploadable.url;
 | 
			
		||||
        }
 | 
			
		||||
        return post;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ class SettingsController {
 | 
			
		||||
        settings.save(e.detail);
 | 
			
		||||
        this._view.showSuccess('Settings saved.');
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter(['settings'], (ctx, next) => {
 | 
			
		||||
 | 
			
		||||
@ -45,5 +45,7 @@ class SnapshotsController {
 | 
			
		||||
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter(['history'],
 | 
			
		||||
        (ctx, next) => { ctx.controller = new SnapshotsController(ctx); });
 | 
			
		||||
        (ctx, next) => {
 | 
			
		||||
            ctx.controller = new SnapshotsController(ctx);
 | 
			
		||||
        });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -69,7 +69,8 @@ class TagController {
 | 
			
		||||
        if (this._name !== e.detail.tag.names[0]) {
 | 
			
		||||
            router.replace(
 | 
			
		||||
                uri.formatClientLink('tag', e.detail.tag.names[0], section),
 | 
			
		||||
                null, false);
 | 
			
		||||
                null,
 | 
			
		||||
                false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -105,7 +106,8 @@ class TagController {
 | 
			
		||||
                router.replace(
 | 
			
		||||
                    uri.formatClientLink(
 | 
			
		||||
                        'tag', e.detail.targetTagName, 'merge'),
 | 
			
		||||
                    null, false);
 | 
			
		||||
                    null,
 | 
			
		||||
                    false);
 | 
			
		||||
            }, error => {
 | 
			
		||||
                this._view.showError(error.message);
 | 
			
		||||
                this._view.enableForm();
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,8 @@ const fields = [
 | 
			
		||||
    'implications',
 | 
			
		||||
    'creationTime',
 | 
			
		||||
    'usages',
 | 
			
		||||
    'category'];
 | 
			
		||||
    'category'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
class TagListController {
 | 
			
		||||
    constructor(ctx) {
 | 
			
		||||
@ -81,5 +82,7 @@ class TagListController {
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter(
 | 
			
		||||
        ['tags'],
 | 
			
		||||
        (ctx, next) => { ctx.controller = new TagListController(ctx); });
 | 
			
		||||
        (ctx, next) => {
 | 
			
		||||
            ctx.controller = new TagListController(ctx);
 | 
			
		||||
        });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ class UserController {
 | 
			
		||||
            userTokenPromise,
 | 
			
		||||
            User.get(userName)
 | 
			
		||||
        ]).then(responses => {
 | 
			
		||||
            const [userTokens, user]  = responses;
 | 
			
		||||
            const [userTokens, user] = responses;
 | 
			
		||||
            const isLoggedIn = api.isLoggedIn(user);
 | 
			
		||||
            const infix = isLoggedIn ? 'self' : 'any';
 | 
			
		||||
 | 
			
		||||
@ -133,7 +133,8 @@ class UserController {
 | 
			
		||||
        if (this._name !== e.detail.user.name) {
 | 
			
		||||
            router.replace(
 | 
			
		||||
                uri.formatClientLink('user', e.detail.user.name, section),
 | 
			
		||||
                null, false);
 | 
			
		||||
                null,
 | 
			
		||||
                false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ class UserListController {
 | 
			
		||||
            defaultLimit: 30,
 | 
			
		||||
            getClientUrlForPage: (offset, limit) => {
 | 
			
		||||
                const parameters = Object.assign(
 | 
			
		||||
                    {}, this._ctx.parameters, {offset, offset, limit: limit});
 | 
			
		||||
                    {}, this._ctx.parameters, {offset: offset, limit: limit});
 | 
			
		||||
                return uri.formatClientLink('users', parameters);
 | 
			
		||||
            },
 | 
			
		||||
            requestPage: (offset, limit) => {
 | 
			
		||||
@ -71,5 +71,7 @@ class UserListController {
 | 
			
		||||
module.exports = router => {
 | 
			
		||||
    router.enter(
 | 
			
		||||
        ['users'],
 | 
			
		||||
        (ctx, next) => { ctx.controller = new UserListController(ctx); });
 | 
			
		||||
        (ctx, next) => {
 | 
			
		||||
            ctx.controller = new UserListController(ctx);
 | 
			
		||||
        });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -121,7 +121,9 @@ class AutoCompleteControl {
 | 
			
		||||
        document.body.appendChild(this._suggestionDiv);
 | 
			
		||||
 | 
			
		||||
        views.monitorNodeRemoval(
 | 
			
		||||
            this._sourceInputNode, () => { this._uninstall(); });
 | 
			
		||||
            this._sourceInputNode, () => {
 | 
			
		||||
                this._uninstall();
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _uninstall() {
 | 
			
		||||
@ -137,13 +139,21 @@ class AutoCompleteControl {
 | 
			
		||||
            if (key === KEY_ESCAPE) {
 | 
			
		||||
                func = this.hide;
 | 
			
		||||
            } else if (key === KEY_TAB && shift) {
 | 
			
		||||
                func = () => { this._selectPrevious(); };
 | 
			
		||||
                func = () => {
 | 
			
		||||
                    this._selectPrevious();
 | 
			
		||||
                };
 | 
			
		||||
            } else if (key === KEY_TAB && !shift) {
 | 
			
		||||
                func = () => { this._selectNext(); };
 | 
			
		||||
                func = () => {
 | 
			
		||||
                    this._selectNext();
 | 
			
		||||
                };
 | 
			
		||||
            } else if (key === KEY_UP) {
 | 
			
		||||
                func = () => { this._selectPrevious(); };
 | 
			
		||||
                func = () => {
 | 
			
		||||
                    this._selectPrevious();
 | 
			
		||||
                };
 | 
			
		||||
            } else if (key === KEY_DOWN) {
 | 
			
		||||
                func = () => { this._selectNext(); };
 | 
			
		||||
                func = () => {
 | 
			
		||||
                    this._selectNext();
 | 
			
		||||
                };
 | 
			
		||||
            } else if (key === KEY_RETURN && this._activeResult >= 0) {
 | 
			
		||||
                func = () => {
 | 
			
		||||
                    this._confirm(this._getActiveSuggestion());
 | 
			
		||||
@ -165,13 +175,17 @@ class AutoCompleteControl {
 | 
			
		||||
        } else {
 | 
			
		||||
            window.clearTimeout(this._showTimeout);
 | 
			
		||||
            this._showTimeout = window.setTimeout(
 | 
			
		||||
                () => { this._showOrHide(); }, 250);
 | 
			
		||||
                () => {
 | 
			
		||||
                    this._showOrHide();
 | 
			
		||||
                }, 250);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _evtBlur(e) {
 | 
			
		||||
        window.clearTimeout(this._showTimeout);
 | 
			
		||||
        window.setTimeout(() => { this.hide(); }, 50);
 | 
			
		||||
        window.setTimeout(() => {
 | 
			
		||||
            this.hide();
 | 
			
		||||
        }, 50);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _getActiveSuggestion() {
 | 
			
		||||
@ -261,10 +275,10 @@ class AutoCompleteControl {
 | 
			
		||||
        // 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;
 | 
			
		||||
            inputRect.top + (inputRect.height / 2) < viewPortHeight / 2 ? 1 : -1;
 | 
			
		||||
 | 
			
		||||
        let x = inputRect.left - bodyRect.left;
 | 
			
		||||
        let y = direction == 1 ?
 | 
			
		||||
        let y = direction === 1 ?
 | 
			
		||||
            inputRect.bottom - bodyRect.top - verticalShift :
 | 
			
		||||
            inputRect.top - bodyRect.top - listRect.height + verticalShift;
 | 
			
		||||
 | 
			
		||||
@ -276,7 +290,7 @@ class AutoCompleteControl {
 | 
			
		||||
            const prevHeight = listRect.height;
 | 
			
		||||
            listRect = this._suggestionDiv.getBoundingClientRect();
 | 
			
		||||
            const heightDelta = prevHeight - listRect.height;
 | 
			
		||||
            if (direction == -1) {
 | 
			
		||||
            if (direction === -1) {
 | 
			
		||||
                y += heightDelta;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -296,6 +310,6 @@ class AutoCompleteControl {
 | 
			
		||||
            activeItem.classList.add('active');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = AutoCompleteControl;
 | 
			
		||||
 | 
			
		||||
@ -212,11 +212,6 @@ class CommentControl extends events.EventTarget {
 | 
			
		||||
        this._selectTab('preview');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _evtEditClick(e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.enterEditMode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _evtSaveChangesClick(e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.dispatchEvent(new CustomEvent('submit', {
 | 
			
		||||
@ -260,6 +255,6 @@ class CommentControl extends events.EventTarget {
 | 
			
		||||
    _forgetHeight() {
 | 
			
		||||
        this._heightKeeperNode.style.minHeight = null;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = CommentControl;
 | 
			
		||||
 | 
			
		||||
@ -54,6 +54,6 @@ class CommentListControl extends events.EventTarget {
 | 
			
		||||
    _evtRemove(e) {
 | 
			
		||||
        this._uninstallCommentNode(e.detail.comment);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = CommentListControl;
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,7 @@ class ExpanderControl {
 | 
			
		||||
        this._syncIcon();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line accessor-pairs
 | 
			
		||||
    set title(newTitle) {
 | 
			
		||||
        if (this._expanderNode) {
 | 
			
		||||
            this._expanderNode
 | 
			
		||||
 | 
			
		||||
@ -107,7 +107,9 @@ class PostContentControl {
 | 
			
		||||
        this._reinstall();
 | 
			
		||||
        optimizedResize.add(() => this._refreshSize());
 | 
			
		||||
        views.monitorNodeRemoval(
 | 
			
		||||
            this._hostNode, () => { this._uninstall(); });
 | 
			
		||||
            this._hostNode, () => {
 | 
			
		||||
                this._uninstall();
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _reinstall() {
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,6 @@ class PostEditSidebarControl extends events.EventTarget {
 | 
			
		||||
            canEditPostFlags: api.hasPrivilege('posts:edit:flags'),
 | 
			
		||||
            canEditPostContent: api.hasPrivilege('posts:edit:content'),
 | 
			
		||||
            canEditPostThumbnail: api.hasPrivilege('posts:edit:thumbnail'),
 | 
			
		||||
            canEditPostSource : api.hasPrivilege('posts:edit:source'),
 | 
			
		||||
            canCreateAnonymousPosts: api.hasPrivilege('posts:create:anonymous'),
 | 
			
		||||
            canDeletePosts: api.hasPrivilege('posts:delete'),
 | 
			
		||||
            canFeaturePosts: api.hasPrivilege('posts:feature'),
 | 
			
		||||
@ -78,8 +77,7 @@ class PostEditSidebarControl extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
        if (this._contentInputNode) {
 | 
			
		||||
            this._contentFileDropper = new FileDropperControl(
 | 
			
		||||
                this._contentInputNode, {
 | 
			
		||||
                    allowUrls: true,
 | 
			
		||||
                this._contentInputNode, {allowUrls: true,
 | 
			
		||||
                    lock: true,
 | 
			
		||||
                    urlPlaceholder: '...or paste an URL here.'});
 | 
			
		||||
            this._contentFileDropper.addEventListener('fileadd', e => {
 | 
			
		||||
@ -284,6 +282,7 @@ class PostEditSidebarControl extends events.EventTarget {
 | 
			
		||||
        try {
 | 
			
		||||
            success = document.execCommand('copy');
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            // continue regardless of error
 | 
			
		||||
        }
 | 
			
		||||
        textarea.blur();
 | 
			
		||||
        document.body.removeChild(textarea);
 | 
			
		||||
@ -383,10 +382,16 @@ class PostEditSidebarControl extends events.EventTarget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get _videoFlags() {
 | 
			
		||||
        if (!this._loopVideoInputNode) return undefined;
 | 
			
		||||
        if (!this._loopVideoInputNode) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
        let ret = [];
 | 
			
		||||
        if (this._loopVideoInputNode.checked) ret.push('loop');
 | 
			
		||||
        if (this._soundVideoInputNode.checked) ret.push('sound');
 | 
			
		||||
        if (this._loopVideoInputNode.checked) {
 | 
			
		||||
            ret.push('loop');
 | 
			
		||||
        }
 | 
			
		||||
        if (this._soundVideoInputNode.checked) {
 | 
			
		||||
            ret.push('sound');
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -462,6 +467,6 @@ class PostEditSidebarControl extends events.EventTarget {
 | 
			
		||||
    showError(message) {
 | 
			
		||||
        views.showError(this._hostNode, message);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = PostEditSidebarControl;
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ function _getNoteCentroid(note) {
 | 
			
		||||
        const y0 = note.polygon.at(i).y;
 | 
			
		||||
        const x1 = note.polygon.at((i + 1) % vertexCount).x;
 | 
			
		||||
        const y1 = note.polygon.at((i + 1) % vertexCount).y;
 | 
			
		||||
        const a = x0 * y1 - x1 * y0;
 | 
			
		||||
        const a = (x0 * y1) - (x1 * y0);
 | 
			
		||||
        signedArea += a;
 | 
			
		||||
        centroid.x += (x0 + x1) * a;
 | 
			
		||||
        centroid.y += (y0 + y1) * a;
 | 
			
		||||
@ -188,12 +188,12 @@ class SelectedState extends ActiveState {
 | 
			
		||||
    evtCanvasKeyDown(e) {
 | 
			
		||||
        const delta = e.ctrlKey ? 10 : 1;
 | 
			
		||||
        const offsetMap = {
 | 
			
		||||
            [KEY_LEFT]:  [-delta, 0],
 | 
			
		||||
            [KEY_UP]:    [0, -delta],
 | 
			
		||||
            [KEY_DOWN]:  [0, delta],
 | 
			
		||||
            [KEY_LEFT]: [-delta, 0],
 | 
			
		||||
            [KEY_UP]: [0, -delta],
 | 
			
		||||
            [KEY_DOWN]: [0, delta],
 | 
			
		||||
            [KEY_RIGHT]: [delta, 0],
 | 
			
		||||
        };
 | 
			
		||||
        if (offsetMap.hasOwnProperty(e.which)) {
 | 
			
		||||
        if (Object.prototype.hasOwnProperty.call(offsetMap, e.witch)) {
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            e.stopImmediatePropagation();
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
@ -283,8 +283,8 @@ class SelectedState extends ActiveState {
 | 
			
		||||
        const origin = _getNoteCentroid(this._note);
 | 
			
		||||
        const originalSize = _getNoteSize(this._note);
 | 
			
		||||
        const targetSize = new Point(
 | 
			
		||||
            originalSize.x + x / this._control.boundingBox.width,
 | 
			
		||||
            originalSize.y + y / this._control.boundingBox.height);
 | 
			
		||||
            originalSize.x + (x / this._control.boundingBox.width),
 | 
			
		||||
            originalSize.y + (y / this._control.boundingBox.height));
 | 
			
		||||
        const scale = new Point(
 | 
			
		||||
            targetSize.x / originalSize.x,
 | 
			
		||||
            targetSize.y / originalSize.y);
 | 
			
		||||
@ -305,7 +305,7 @@ class MovingPointState extends ActiveState {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    evtCanvasKeyDown(e) {
 | 
			
		||||
        if (e.which == KEY_ESCAPE) {
 | 
			
		||||
        if (e.which === KEY_ESCAPE) {
 | 
			
		||||
            this._notePoint.x = this._originalNotePoint.x;
 | 
			
		||||
            this._notePoint.y = this._originalNotePoint.y;
 | 
			
		||||
            this._control._state = new SelectedState(this._control, this._note);
 | 
			
		||||
@ -333,7 +333,7 @@ class MovingNoteState extends ActiveState {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    evtCanvasKeyDown(e) {
 | 
			
		||||
        if (e.which == KEY_ESCAPE) {
 | 
			
		||||
        if (e.which === KEY_ESCAPE) {
 | 
			
		||||
            for (let i of misc.range(this._note.polygon.length)) {
 | 
			
		||||
                this._note.polygon.at(i).x = this._originalPolygon[i].x;
 | 
			
		||||
                this._note.polygon.at(i).y = this._originalPolygon[i].y;
 | 
			
		||||
@ -366,7 +366,7 @@ class ScalingNoteState extends ActiveState {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    evtCanvasKeyDown(e) {
 | 
			
		||||
        if (e.which == KEY_ESCAPE) {
 | 
			
		||||
        if (e.which === KEY_ESCAPE) {
 | 
			
		||||
            for (let i of misc.range(this._note.polygon.length)) {
 | 
			
		||||
                this._note.polygon.at(i).x = this._originalPolygon[i].x;
 | 
			
		||||
                this._note.polygon.at(i).y = this._originalPolygon[i].y;
 | 
			
		||||
@ -384,12 +384,12 @@ class ScalingNoteState extends ActiveState {
 | 
			
		||||
            const originalPolygonPoint = this._originalPolygon[i];
 | 
			
		||||
            polygonPoint.x =
 | 
			
		||||
                originalMousePoint.x +
 | 
			
		||||
                (originalPolygonPoint.x - originalMousePoint.x) *
 | 
			
		||||
                (1 + (mousePoint.x - originalMousePoint.x) / originalSize.x);
 | 
			
		||||
                ((originalPolygonPoint.x - originalMousePoint.x) *
 | 
			
		||||
                (1 + ((mousePoint.x - originalMousePoint.x) / originalSize.x)));
 | 
			
		||||
            polygonPoint.y =
 | 
			
		||||
                originalMousePoint.y +
 | 
			
		||||
                (originalPolygonPoint.y - originalMousePoint.y) *
 | 
			
		||||
                (1 + (mousePoint.y - originalMousePoint.y) / originalSize.y);
 | 
			
		||||
                ((originalPolygonPoint.y - originalMousePoint.y) *
 | 
			
		||||
                (1 + ((mousePoint.y - originalMousePoint.y) / originalSize.y)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -466,12 +466,12 @@ class DrawingPolygonState extends ActiveState {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    evtCanvasKeyDown(e) {
 | 
			
		||||
        if (e.which == KEY_ESCAPE) {
 | 
			
		||||
        if (e.which === KEY_ESCAPE) {
 | 
			
		||||
            this._note.polygon.remove(this._note.polygon.secondLastPoint);
 | 
			
		||||
            if (this._note.polygon.length === 1) {
 | 
			
		||||
                this._cancel();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (e.which == KEY_RETURN) {
 | 
			
		||||
        } else if (e.which === KEY_RETURN) {
 | 
			
		||||
            this._finish();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -674,13 +674,13 @@ class PostNotesOverlayControl extends events.EventTarget {
 | 
			
		||||
        const x = (
 | 
			
		||||
            -bodyRect.left +
 | 
			
		||||
            svgRect.left +
 | 
			
		||||
            svgRect.width * centroid.x -
 | 
			
		||||
            noteRect.width / 2);
 | 
			
		||||
            (svgRect.width * centroid.x) -
 | 
			
		||||
            (noteRect.width / 2));
 | 
			
		||||
        const y = (
 | 
			
		||||
            -bodyRect.top +
 | 
			
		||||
            svgRect.top +
 | 
			
		||||
            svgRect.height * centroid.y -
 | 
			
		||||
            noteRect.height / 2);
 | 
			
		||||
            (svgRect.height * centroid.y) -
 | 
			
		||||
            (noteRect.height / 2));
 | 
			
		||||
        this._textNode.style.left = x + 'px';
 | 
			
		||||
        this._textNode.style.top = y + 'px';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -197,6 +197,6 @@ class PostReadonlySidebarControl extends events.EventTarget {
 | 
			
		||||
    _evtChangeScore(e) {
 | 
			
		||||
        this._installScore();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = PostReadonlySidebarControl;
 | 
			
		||||
 | 
			
		||||
@ -36,22 +36,21 @@ class TagAutoCompleteControl extends AutoCompleteControl {
 | 
			
		||||
            const term = misc.escapeSearchTerm(text);
 | 
			
		||||
            const query = (
 | 
			
		||||
                text.length < minLengthForPartialSearch
 | 
			
		||||
                ? term + '*'
 | 
			
		||||
                : '*' + term + '*') + ' sort:usages';
 | 
			
		||||
                    ? term + '*'
 | 
			
		||||
                    : '*' + term + '*') + ' sort:usages';
 | 
			
		||||
 | 
			
		||||
            return new Promise((resolve, reject) => {
 | 
			
		||||
                TagList.search(
 | 
			
		||||
                    query, 0, this._options.maxResults,
 | 
			
		||||
                    ['names', 'category', 'usages'])
 | 
			
		||||
                .then(
 | 
			
		||||
                    response => resolve(
 | 
			
		||||
                        _tagListToMatches(response.results, this._options)),
 | 
			
		||||
                    reject);
 | 
			
		||||
                    query, 0, this._options.maxResults, ['names', 'category', 'usages'])
 | 
			
		||||
                    .then(
 | 
			
		||||
                        response => resolve(
 | 
			
		||||
                            _tagListToMatches(response.results, this._options)),
 | 
			
		||||
                        reject);
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        super(input, options);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = TagAutoCompleteControl;
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ class SuggestionList {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set(suggestion, weight) {
 | 
			
		||||
        if (this._suggestions.hasOwnProperty(suggestion)) {
 | 
			
		||||
        if (Object.prototype.hasOwnProperty.call(this._suggestions, suggestion)) {
 | 
			
		||||
            weight = Math.max(weight, this._suggestions[suggestion]);
 | 
			
		||||
        }
 | 
			
		||||
        this._suggestions[suggestion] = weight;
 | 
			
		||||
@ -72,7 +72,7 @@ class SuggestionList {
 | 
			
		||||
        tuples.sort((a, b) => {
 | 
			
		||||
            let weightDiff = b[1] - a[1];
 | 
			
		||||
            let nameDiff = a[0].localeCompare(b[0]);
 | 
			
		||||
            return weightDiff == 0 ? nameDiff : weightDiff;
 | 
			
		||||
            return weightDiff === 0 ? nameDiff : weightDiff;
 | 
			
		||||
        });
 | 
			
		||||
        return tuples.map(tuple => {
 | 
			
		||||
            return {tagName: tuple[0], weight: tuple[1]};
 | 
			
		||||
@ -102,7 +102,7 @@ class TagInputControl extends events.EventTarget {
 | 
			
		||||
                },
 | 
			
		||||
                confirm: tag => {
 | 
			
		||||
                    this._tagInputNode.value = '';
 | 
			
		||||
                    // XXX: tags from autocomplete don't contain implications
 | 
			
		||||
                    // note: tags from autocomplete don't contain implications
 | 
			
		||||
                    // so they need to be looked up in API
 | 
			
		||||
                    this.addTagByName(tag.names[0], SOURCE_USER_INPUT);
 | 
			
		||||
                },
 | 
			
		||||
@ -160,7 +160,7 @@ class TagInputControl extends events.EventTarget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addTag(tag, source) {
 | 
			
		||||
        if (source != SOURCE_INIT && this.tags.isTaggedWith(tag.names[0])) {
 | 
			
		||||
        if (source !== SOURCE_INIT && this.tags.isTaggedWith(tag.names[0])) {
 | 
			
		||||
            const listItemNode = this._getListItemNode(tag);
 | 
			
		||||
            if (source !== SOURCE_IMPLICATION) {
 | 
			
		||||
                listItemNode.classList.add('duplicate');
 | 
			
		||||
@ -240,7 +240,7 @@ class TagInputControl extends events.EventTarget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _evtInputKeyDown(e) {
 | 
			
		||||
        if (e.which == KEY_RETURN || e.which == KEY_SPACE) {
 | 
			
		||||
        if (e.which === KEY_RETURN || e.which === KEY_SPACE) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            this._hideAutoComplete();
 | 
			
		||||
            this.addTagByText(this._tagInputNode.value, SOURCE_USER_INPUT);
 | 
			
		||||
@ -328,8 +328,8 @@ class TagInputControl extends events.EventTarget {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        api.get(
 | 
			
		||||
                uri.formatApiLink('tag-siblings', tag.names[0]),
 | 
			
		||||
                {noProgress: true})
 | 
			
		||||
            uri.formatApiLink('tag-siblings', tag.names[0]),
 | 
			
		||||
            {noProgress: true})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                return Promise.resolve(response.results);
 | 
			
		||||
            }, response => {
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ class EventTarget {
 | 
			
		||||
            this[method] = this.eventTarget[method].bind(this.eventTarget);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function proxyEvent(source, target, sourceEventType, targetEventType) {
 | 
			
		||||
    if (!source.addEventListener) {
 | 
			
		||||
 | 
			
		||||
@ -60,17 +60,17 @@ api.fetchConfig().then(() => {
 | 
			
		||||
    window.alert('Could not fetch basic configuration from server');
 | 
			
		||||
}).then(() => {
 | 
			
		||||
    api.loginFromCookies().then(() => {
 | 
			
		||||
            tags.refreshCategoryColorMap();
 | 
			
		||||
        tags.refreshCategoryColorMap();
 | 
			
		||||
        router.start();
 | 
			
		||||
    }, error => {
 | 
			
		||||
        if (window.location.href.indexOf('login') !== -1) {
 | 
			
		||||
            api.forget();
 | 
			
		||||
            router.start();
 | 
			
		||||
        }, error => {
 | 
			
		||||
            if (window.location.href.indexOf('login') !== -1) {
 | 
			
		||||
                api.forget();
 | 
			
		||||
                router.start();
 | 
			
		||||
            } else {
 | 
			
		||||
                const ctx = router.start('/');
 | 
			
		||||
                ctx.controller.showError(
 | 
			
		||||
                    'An error happened while trying to log you in: ' +
 | 
			
		||||
        } else {
 | 
			
		||||
            const ctx = router.start('/');
 | 
			
		||||
            ctx.controller.showError(
 | 
			
		||||
                'An error happened while trying to log you in: ' +
 | 
			
		||||
                        error.message);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -22,16 +22,41 @@ class Comment extends events.EventTarget {
 | 
			
		||||
        return comment;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get id()           { return this._id; }
 | 
			
		||||
    get postId()       { return this._postId; }
 | 
			
		||||
    get text()         { return this._text || ''; }
 | 
			
		||||
    get user()         { return this._user; }
 | 
			
		||||
    get creationTime() { return this._creationTime; }
 | 
			
		||||
    get lastEditTime() { return this._lastEditTime; }
 | 
			
		||||
    get score()        { return this._score; }
 | 
			
		||||
    get ownScore()     { return this._ownScore; }
 | 
			
		||||
    get id() {
 | 
			
		||||
        return this._id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set text(value)    { this._text = value; }
 | 
			
		||||
    get postId() {
 | 
			
		||||
        return this._postId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get text() {
 | 
			
		||||
        return this._text || '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get user() {
 | 
			
		||||
        return this._user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get creationTime() {
 | 
			
		||||
        return this._creationTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get lastEditTime() {
 | 
			
		||||
        return this._lastEditTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get score() {
 | 
			
		||||
        return this._score;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get ownScore() {
 | 
			
		||||
        return this._ownScore;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set text(value) {
 | 
			
		||||
        this._text = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    save() {
 | 
			
		||||
        const detail = {
 | 
			
		||||
@ -56,8 +81,8 @@ class Comment extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    delete() {
 | 
			
		||||
        return api.delete(
 | 
			
		||||
                uri.formatApiLink('comment', this.id),
 | 
			
		||||
                {version: this._version})
 | 
			
		||||
            uri.formatApiLink('comment', this.id),
 | 
			
		||||
            {version: this._version})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                this.dispatchEvent(new CustomEvent('delete', {
 | 
			
		||||
                    detail: {
 | 
			
		||||
@ -70,8 +95,8 @@ class Comment extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    setScore(score) {
 | 
			
		||||
        return api.put(
 | 
			
		||||
                uri.formatApiLink('comment', this.id, 'score'),
 | 
			
		||||
                {score: score})
 | 
			
		||||
            uri.formatApiLink('comment', this.id, 'score'),
 | 
			
		||||
            {score: score})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                this._updateFromResponse(response);
 | 
			
		||||
                this.dispatchEvent(new CustomEvent('changeScore', {
 | 
			
		||||
@ -84,15 +109,15 @@ class Comment extends events.EventTarget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _updateFromResponse(response) {
 | 
			
		||||
        this._version      = response.version;
 | 
			
		||||
        this._id           = response.id;
 | 
			
		||||
        this._postId       = response.postId;
 | 
			
		||||
        this._text         = response.text;
 | 
			
		||||
        this._user         = response.user;
 | 
			
		||||
        this._version = response.version;
 | 
			
		||||
        this._id = response.id;
 | 
			
		||||
        this._postId = response.postId;
 | 
			
		||||
        this._text = response.text;
 | 
			
		||||
        this._user = response.user;
 | 
			
		||||
        this._creationTime = response.creationTime;
 | 
			
		||||
        this._lastEditTime = response.lastEditTime;
 | 
			
		||||
        this._score        = parseInt(response.score);
 | 
			
		||||
        this._ownScore     = parseInt(response.ownScore);
 | 
			
		||||
        this._score = parseInt(response.score);
 | 
			
		||||
        this._ownScore = parseInt(response.ownScore);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,10 +11,17 @@ class Note extends events.EventTarget {
 | 
			
		||||
        this._polygon = new PointList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get text() { return this._text; }
 | 
			
		||||
    get polygon() { return this._polygon; }
 | 
			
		||||
    get text() {
 | 
			
		||||
        return this._text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set text(value) { this._text = value; }
 | 
			
		||||
    get polygon() {
 | 
			
		||||
        return this._polygon;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set text(value) {
 | 
			
		||||
        this._text = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fromResponse(response) {
 | 
			
		||||
        const note = new Note();
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,13 @@ class Point extends events.EventTarget {
 | 
			
		||||
        this._y = y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get x() { return this._x; }
 | 
			
		||||
    get y() { return this._y; }
 | 
			
		||||
    get x() {
 | 
			
		||||
        return this._x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get y() {
 | 
			
		||||
        return this._y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set x(value) {
 | 
			
		||||
        this._x = value;
 | 
			
		||||
@ -21,6 +26,6 @@ class Point extends events.EventTarget {
 | 
			
		||||
        this._y = value;
 | 
			
		||||
        this.dispatchEvent(new CustomEvent('change', {detail: {point: this}}));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Point;
 | 
			
		||||
 | 
			
		||||
@ -23,43 +23,141 @@ class Post extends events.EventTarget {
 | 
			
		||||
        this._updateFromResponse({});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get id()                 { return this._id; }
 | 
			
		||||
    get type()               { return this._type; }
 | 
			
		||||
    get mimeType()           { return this._mimeType; }
 | 
			
		||||
    get creationTime()       { return this._creationTime; }
 | 
			
		||||
    get user()               { return this._user; }
 | 
			
		||||
    get safety()             { return this._safety; }
 | 
			
		||||
    get contentUrl()         { return this._contentUrl; }
 | 
			
		||||
    get fullContentUrl()     { return this._fullContentUrl; }
 | 
			
		||||
    get thumbnailUrl()       { return this._thumbnailUrl; }
 | 
			
		||||
    get source()             { return this._source; }
 | 
			
		||||
    get sourceSplit()        { return this._source.split('\n'); }
 | 
			
		||||
    get canvasWidth()        { return this._canvasWidth || 800; }
 | 
			
		||||
    get canvasHeight()       { return this._canvasHeight || 450; }
 | 
			
		||||
    get fileSize()           { return this._fileSize || 0; }
 | 
			
		||||
    get newContent()         { throw 'Invalid operation'; }
 | 
			
		||||
    get newThumbnail()       { throw 'Invalid operation'; }
 | 
			
		||||
    get id() {
 | 
			
		||||
        return this._id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get flags()              { return this._flags; }
 | 
			
		||||
    get tags()               { return this._tags; }
 | 
			
		||||
    get tagNames()           { return this._tags.map(tag => tag.names[0]); }
 | 
			
		||||
    get notes()              { return this._notes; }
 | 
			
		||||
    get comments()           { return this._comments; }
 | 
			
		||||
    get relations()          { return this._relations; }
 | 
			
		||||
    get type() {
 | 
			
		||||
        return this._type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get score()              { return this._score; }
 | 
			
		||||
    get commentCount()       { return this._commentCount; }
 | 
			
		||||
    get favoriteCount()      { return this._favoriteCount; }
 | 
			
		||||
    get ownFavorite()        { return this._ownFavorite; }
 | 
			
		||||
    get ownScore()           { return this._ownScore; }
 | 
			
		||||
    get hasCustomThumbnail() { return this._hasCustomThumbnail; }
 | 
			
		||||
    get mimeType() {
 | 
			
		||||
        return this._mimeType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set flags(value)         { this._flags = value; }
 | 
			
		||||
    set safety(value)        { this._safety = value; }
 | 
			
		||||
    set relations(value)     { this._relations = value; }
 | 
			
		||||
    set newContent(value)    { this._newContent = value; }
 | 
			
		||||
    set newThumbnail(value)  { this._newThumbnail = value; }
 | 
			
		||||
    set source(value)        { this._source = value; }
 | 
			
		||||
    get creationTime() {
 | 
			
		||||
        return this._creationTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get user() {
 | 
			
		||||
        return this._user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get safety() {
 | 
			
		||||
        return this._safety;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get contentUrl() {
 | 
			
		||||
        return this._contentUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get fullContentUrl() {
 | 
			
		||||
        return this._fullContentUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get thumbnailUrl() {
 | 
			
		||||
        return this._thumbnailUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get source() {
 | 
			
		||||
        return this._source;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get sourceSplit() {
 | 
			
		||||
        return this._source.split('\n');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get canvasWidth() {
 | 
			
		||||
        return this._canvasWidth || 800;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get canvasHeight() {
 | 
			
		||||
        return this._canvasHeight || 450;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get fileSize() {
 | 
			
		||||
        return this._fileSize || 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get newContent() {
 | 
			
		||||
        throw 'Invalid operation';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get newThumbnail() {
 | 
			
		||||
        throw 'Invalid operation';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get flags() {
 | 
			
		||||
        return this._flags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get tags() {
 | 
			
		||||
        return this._tags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get tagNames() {
 | 
			
		||||
        return this._tags.map(tag => tag.names[0]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get notes() {
 | 
			
		||||
        return this._notes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get comments() {
 | 
			
		||||
        return this._comments;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get relations() {
 | 
			
		||||
        return this._relations;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get score() {
 | 
			
		||||
        return this._score;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get commentCount() {
 | 
			
		||||
        return this._commentCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get favoriteCount() {
 | 
			
		||||
        return this._favoriteCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get ownFavorite() {
 | 
			
		||||
        return this._ownFavorite;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get ownScore() {
 | 
			
		||||
        return this._ownScore;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get hasCustomThumbnail() {
 | 
			
		||||
        return this._hasCustomThumbnail;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set flags(value) {
 | 
			
		||||
        this._flags = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set safety(value) {
 | 
			
		||||
        this._safety = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set relations(value) {
 | 
			
		||||
        this._relations = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set newContent(value) {
 | 
			
		||||
        this._newContent = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set newThumbnail(value) {
 | 
			
		||||
        this._newThumbnail = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set source(value) {
 | 
			
		||||
        this._source = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fromResponse(response) {
 | 
			
		||||
        const ret = new Post();
 | 
			
		||||
@ -158,8 +256,8 @@ class Post extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    feature() {
 | 
			
		||||
        return api.post(
 | 
			
		||||
                uri.formatApiLink('featured-post'),
 | 
			
		||||
                {id: this._id})
 | 
			
		||||
            uri.formatApiLink('featured-post'),
 | 
			
		||||
            {id: this._id})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                return Promise.resolve();
 | 
			
		||||
            });
 | 
			
		||||
@ -167,8 +265,8 @@ class Post extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    delete() {
 | 
			
		||||
        return api.delete(
 | 
			
		||||
                uri.formatApiLink('post', this.id),
 | 
			
		||||
                {version: this._version})
 | 
			
		||||
            uri.formatApiLink('post', this.id),
 | 
			
		||||
            {version: this._version})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                this.dispatchEvent(new CustomEvent('delete', {
 | 
			
		||||
                    detail: {
 | 
			
		||||
@ -202,8 +300,8 @@ class Post extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    setScore(score) {
 | 
			
		||||
        return api.put(
 | 
			
		||||
                uri.formatApiLink('post', this.id, 'score'),
 | 
			
		||||
                {score: score})
 | 
			
		||||
            uri.formatApiLink('post', this.id, 'score'),
 | 
			
		||||
            {score: score})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                const prevFavorite = this._ownFavorite;
 | 
			
		||||
                this._updateFromResponse(response);
 | 
			
		||||
@ -274,29 +372,29 @@ class Post extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    _updateFromResponse(response) {
 | 
			
		||||
        const map = () => ({
 | 
			
		||||
            _version:       response.version,
 | 
			
		||||
            _id:            response.id,
 | 
			
		||||
            _type:          response.type,
 | 
			
		||||
            _mimeType:      response.mimeType,
 | 
			
		||||
            _creationTime:  response.creationTime,
 | 
			
		||||
            _user:          response.user,
 | 
			
		||||
            _safety:        response.safety,
 | 
			
		||||
            _contentUrl:    response.contentUrl,
 | 
			
		||||
            _version: response.version,
 | 
			
		||||
            _id: response.id,
 | 
			
		||||
            _type: response.type,
 | 
			
		||||
            _mimeType: response.mimeType,
 | 
			
		||||
            _creationTime: response.creationTime,
 | 
			
		||||
            _user: response.user,
 | 
			
		||||
            _safety: response.safety,
 | 
			
		||||
            _contentUrl: response.contentUrl,
 | 
			
		||||
            _fullContentUrl: new URL(response.contentUrl, document.getElementsByTagName('base')[0].href).href,
 | 
			
		||||
            _thumbnailUrl:  response.thumbnailUrl,
 | 
			
		||||
            _source:        response.source,
 | 
			
		||||
            _canvasWidth:   response.canvasWidth,
 | 
			
		||||
            _canvasHeight:  response.canvasHeight,
 | 
			
		||||
            _fileSize:      response.fileSize,
 | 
			
		||||
            _thumbnailUrl: response.thumbnailUrl,
 | 
			
		||||
            _source: response.source,
 | 
			
		||||
            _canvasWidth: response.canvasWidth,
 | 
			
		||||
            _canvasHeight: response.canvasHeight,
 | 
			
		||||
            _fileSize: response.fileSize,
 | 
			
		||||
 | 
			
		||||
            _flags:         [...response.flags || []],
 | 
			
		||||
            _relations:     [...response.relations || []],
 | 
			
		||||
            _flags: [...response.flags || []],
 | 
			
		||||
            _relations: [...response.relations || []],
 | 
			
		||||
 | 
			
		||||
            _score:         response.score,
 | 
			
		||||
            _commentCount:  response.commentCount,
 | 
			
		||||
            _score: response.score,
 | 
			
		||||
            _commentCount: response.commentCount,
 | 
			
		||||
            _favoriteCount: response.favoriteCount,
 | 
			
		||||
            _ownScore:      response.ownScore,
 | 
			
		||||
            _ownFavorite:   response.ownFavorite,
 | 
			
		||||
            _ownScore: response.ownScore,
 | 
			
		||||
            _ownFavorite: response.ownFavorite,
 | 
			
		||||
            _hasCustomThumbnail: response.hasCustomThumbnail,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -309,6 +407,6 @@ class Post extends events.EventTarget {
 | 
			
		||||
        Object.assign(this, map());
 | 
			
		||||
        Object.assign(this._orig, map());
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Post;
 | 
			
		||||
 | 
			
		||||
@ -18,13 +18,13 @@ class PostList extends AbstractList {
 | 
			
		||||
 | 
			
		||||
    static search(text, offset, limit, fields) {
 | 
			
		||||
        return api.get(
 | 
			
		||||
                uri.formatApiLink(
 | 
			
		||||
                    'posts', {
 | 
			
		||||
                        query: PostList._decorateSearchQuery(text || ''),
 | 
			
		||||
                        offset: offset,
 | 
			
		||||
                        limit: limit,
 | 
			
		||||
                        fields: fields.join(','),
 | 
			
		||||
                    }))
 | 
			
		||||
            uri.formatApiLink(
 | 
			
		||||
                'posts', {
 | 
			
		||||
                    query: PostList._decorateSearchQuery(text || ''),
 | 
			
		||||
                    offset: offset,
 | 
			
		||||
                    limit: limit,
 | 
			
		||||
                    fields: fields.join(','),
 | 
			
		||||
                }))
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                return Promise.resolve(Object.assign(
 | 
			
		||||
                    {},
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ class Settings extends events.EventTarget {
 | 
			
		||||
        try {
 | 
			
		||||
            Object.assign(ret, JSON.parse(localStorage.getItem('settings')));
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            // continue regardless of error
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
@ -50,6 +51,6 @@ class Settings extends events.EventTarget {
 | 
			
		||||
    get() {
 | 
			
		||||
        return this.cache;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = new Settings();
 | 
			
		||||
 | 
			
		||||
@ -10,12 +10,29 @@ class Snapshot extends events.EventTarget {
 | 
			
		||||
        this._updateFromResponse({});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get operation() { return this._operation; }
 | 
			
		||||
    get type()      { return this._type; }
 | 
			
		||||
    get id()        { return this._id; }
 | 
			
		||||
    get user()      { return this._user; }
 | 
			
		||||
    get data()      { return this._data; }
 | 
			
		||||
    get time()      { return this._time; }
 | 
			
		||||
    get operation() {
 | 
			
		||||
        return this._operation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get type() {
 | 
			
		||||
        return this._type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get id() {
 | 
			
		||||
        return this._id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get user() {
 | 
			
		||||
        return this._user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get data() {
 | 
			
		||||
        return this._data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get time() {
 | 
			
		||||
        return this._time;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fromResponse(response) {
 | 
			
		||||
        const ret = new Snapshot();
 | 
			
		||||
@ -26,11 +43,11 @@ class Snapshot extends events.EventTarget {
 | 
			
		||||
    _updateFromResponse(response) {
 | 
			
		||||
        const map = {
 | 
			
		||||
            _operation: response.operation,
 | 
			
		||||
            _type:      response.type,
 | 
			
		||||
            _id:        response.id,
 | 
			
		||||
            _user:      response.user,
 | 
			
		||||
            _data:      response.data,
 | 
			
		||||
            _time:      response.time,
 | 
			
		||||
            _type: response.type,
 | 
			
		||||
            _id: response.id,
 | 
			
		||||
            _user: response.user,
 | 
			
		||||
            _data: response.data,
 | 
			
		||||
            _time: response.time,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Object.assign(this, map);
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ const Snapshot = require('./snapshot.js');
 | 
			
		||||
class SnapshotList extends AbstractList {
 | 
			
		||||
    static search(text, offset, limit) {
 | 
			
		||||
        return api.get(uri.formatApiLink(
 | 
			
		||||
                'snapshots', {query: text, offset: offset, limit: limit}))
 | 
			
		||||
            'snapshots', {query: text, offset: offset, limit: limit}))
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                return Promise.resolve(Object.assign(
 | 
			
		||||
                    {},
 | 
			
		||||
 | 
			
		||||
@ -20,18 +20,49 @@ class Tag extends events.EventTarget {
 | 
			
		||||
        this._updateFromResponse({});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get names()             { return this._names; }
 | 
			
		||||
    get category()          { return this._category; }
 | 
			
		||||
    get description()       { return this._description; }
 | 
			
		||||
    get suggestions()       { return this._suggestions; }
 | 
			
		||||
    get implications()      { return this._implications; }
 | 
			
		||||
    get postCount()         { return this._postCount; }
 | 
			
		||||
    get creationTime()      { return this._creationTime; }
 | 
			
		||||
    get lastEditTime()      { return this._lastEditTime; }
 | 
			
		||||
    get names() {
 | 
			
		||||
        return this._names;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set names(value)        { this._names = value; }
 | 
			
		||||
    set category(value)     { this._category = value; }
 | 
			
		||||
    set description(value)  { this._description = value; }
 | 
			
		||||
    get category() {
 | 
			
		||||
        return this._category;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get description() {
 | 
			
		||||
        return this._description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get suggestions() {
 | 
			
		||||
        return this._suggestions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get implications() {
 | 
			
		||||
        return this._implications;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get postCount() {
 | 
			
		||||
        return this._postCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get creationTime() {
 | 
			
		||||
        return this._creationTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get lastEditTime() {
 | 
			
		||||
        return this._lastEditTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set names(value) {
 | 
			
		||||
        this._names = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set category(value) {
 | 
			
		||||
        this._category = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set description(value) {
 | 
			
		||||
        this._description = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fromResponse(response) {
 | 
			
		||||
        const ret = new Tag();
 | 
			
		||||
@ -113,8 +144,8 @@ class Tag extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    delete() {
 | 
			
		||||
        return api.delete(
 | 
			
		||||
                uri.formatApiLink('tag', this._origName),
 | 
			
		||||
                {version: this._version})
 | 
			
		||||
            uri.formatApiLink('tag', this._origName),
 | 
			
		||||
            {version: this._version})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                this.dispatchEvent(new CustomEvent('delete', {
 | 
			
		||||
                    detail: {
 | 
			
		||||
@ -127,14 +158,14 @@ class Tag extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    _updateFromResponse(response) {
 | 
			
		||||
        const map = {
 | 
			
		||||
            _version:      response.version,
 | 
			
		||||
            _origName:     response.names ? response.names[0] : null,
 | 
			
		||||
            _names:        response.names,
 | 
			
		||||
            _category:     response.category,
 | 
			
		||||
            _description:  response.description,
 | 
			
		||||
            _version: response.version,
 | 
			
		||||
            _origName: response.names ? response.names[0] : null,
 | 
			
		||||
            _names: response.names,
 | 
			
		||||
            _category: response.category,
 | 
			
		||||
            _description: response.description,
 | 
			
		||||
            _creationTime: response.creationTime,
 | 
			
		||||
            _lastEditTime: response.lastEditTime,
 | 
			
		||||
            _postCount:    response.usages || 0,
 | 
			
		||||
            _postCount: response.usages || 0,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for (let obj of [this, this._orig]) {
 | 
			
		||||
@ -145,6 +176,6 @@ class Tag extends events.EventTarget {
 | 
			
		||||
        Object.assign(this, map);
 | 
			
		||||
        Object.assign(this._orig, map);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Tag;
 | 
			
		||||
 | 
			
		||||
@ -7,22 +7,41 @@ const events = require('../events.js');
 | 
			
		||||
class TagCategory extends events.EventTarget {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        this._name      = '';
 | 
			
		||||
        this._color     = '#000000';
 | 
			
		||||
        this._tagCount  = 0;
 | 
			
		||||
        this._name = '';
 | 
			
		||||
        this._color = '#000000';
 | 
			
		||||
        this._tagCount = 0;
 | 
			
		||||
        this._isDefault = false;
 | 
			
		||||
        this._origName  = null;
 | 
			
		||||
        this._origName = null;
 | 
			
		||||
        this._origColor = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get name()        { return this._name; }
 | 
			
		||||
    get color()       { return this._color; }
 | 
			
		||||
    get tagCount()    { return this._tagCount; }
 | 
			
		||||
    get isDefault()   { return this._isDefault; }
 | 
			
		||||
    get isTransient() { return !this._origName; }
 | 
			
		||||
    get name() {
 | 
			
		||||
        return this._name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set name(value)   { this._name = value; }
 | 
			
		||||
    set color(value)  { this._color = value; }
 | 
			
		||||
    get color() {
 | 
			
		||||
        return this._color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get tagCount() {
 | 
			
		||||
        return this._tagCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get isDefault() {
 | 
			
		||||
        return this._isDefault;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get isTransient() {
 | 
			
		||||
        return !this._origName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set name(value) {
 | 
			
		||||
        this._name = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set color(value) {
 | 
			
		||||
        this._color = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fromResponse(response) {
 | 
			
		||||
        const ret = new TagCategory();
 | 
			
		||||
@ -64,8 +83,8 @@ class TagCategory extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    delete() {
 | 
			
		||||
        return api.delete(
 | 
			
		||||
                uri.formatApiLink('tag-category', this._origName),
 | 
			
		||||
                {version: this._version})
 | 
			
		||||
            uri.formatApiLink('tag-category', this._origName),
 | 
			
		||||
            {version: this._version})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                this.dispatchEvent(new CustomEvent('delete', {
 | 
			
		||||
                    detail: {
 | 
			
		||||
@ -77,12 +96,12 @@ class TagCategory extends events.EventTarget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _updateFromResponse(response) {
 | 
			
		||||
        this._version   = response.version;
 | 
			
		||||
        this._name      = response.name;
 | 
			
		||||
        this._color     = response.color;
 | 
			
		||||
        this._version = response.version;
 | 
			
		||||
        this._name = response.name;
 | 
			
		||||
        this._color = response.color;
 | 
			
		||||
        this._isDefault = response.default;
 | 
			
		||||
        this._tagCount  = response.usages;
 | 
			
		||||
        this._origName  = this.name;
 | 
			
		||||
        this._tagCount = response.usages;
 | 
			
		||||
        this._origName = this.name;
 | 
			
		||||
        this._origColor = this.color;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,13 +8,13 @@ const Tag = require('./tag.js');
 | 
			
		||||
class TagList extends AbstractList {
 | 
			
		||||
    static search(text, offset, limit, fields) {
 | 
			
		||||
        return api.get(
 | 
			
		||||
                uri.formatApiLink(
 | 
			
		||||
                    'tags', {
 | 
			
		||||
                        query: text,
 | 
			
		||||
                        offset: offset,
 | 
			
		||||
                        limit: limit,
 | 
			
		||||
                        fields: fields.join(','),
 | 
			
		||||
                    }))
 | 
			
		||||
            uri.formatApiLink(
 | 
			
		||||
                'tags', {
 | 
			
		||||
                    query: text,
 | 
			
		||||
                    offset: offset,
 | 
			
		||||
                    limit: limit,
 | 
			
		||||
                    fields: fields.join(','),
 | 
			
		||||
                }))
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                return Promise.resolve(Object.assign(
 | 
			
		||||
                    {},
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ class TopNavigationItem {
 | 
			
		||||
        this.imageUrl = imageUrl === undefined ? null : imageUrl;
 | 
			
		||||
        this.key = null;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TopNavigation extends events.EventTarget {
 | 
			
		||||
    constructor() {
 | 
			
		||||
@ -72,7 +72,7 @@ class TopNavigation extends events.EventTarget {
 | 
			
		||||
    hide(key) {
 | 
			
		||||
        this.get(key).available = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _makeTopNavigation() {
 | 
			
		||||
    const ret = new TopNavigation();
 | 
			
		||||
 | 
			
		||||
@ -11,28 +11,89 @@ class User extends events.EventTarget {
 | 
			
		||||
        this._updateFromResponse({});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get name()               { return this._name; }
 | 
			
		||||
    get rank()               { return this._rank; }
 | 
			
		||||
    get email()              { return this._email; }
 | 
			
		||||
    get avatarStyle()        { return this._avatarStyle; }
 | 
			
		||||
    get avatarUrl()          { return this._avatarUrl; }
 | 
			
		||||
    get creationTime()       { return this._creationTime; }
 | 
			
		||||
    get lastLoginTime()      { return this._lastLoginTime; }
 | 
			
		||||
    get commentCount()       { return this._commentCount; }
 | 
			
		||||
    get favoritePostCount()  { return this._favoritePostCount; }
 | 
			
		||||
    get uploadedPostCount()  { return this._uploadedPostCount; }
 | 
			
		||||
    get likedPostCount()     { return this._likedPostCount; }
 | 
			
		||||
    get dislikedPostCount()  { return this._dislikedPostCount; }
 | 
			
		||||
    get rankName()           { return api.rankNames.get(this.rank); }
 | 
			
		||||
    get avatarContent()      { throw 'Invalid operation'; }
 | 
			
		||||
    get password()           { throw 'Invalid operation'; }
 | 
			
		||||
    get name() {
 | 
			
		||||
        return this._name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set name(value)          { this._name = value; }
 | 
			
		||||
    set rank(value)          { this._rank = value; }
 | 
			
		||||
    set email(value)         { this._email = value || null; }
 | 
			
		||||
    set avatarStyle(value)   { this._avatarStyle = value; }
 | 
			
		||||
    set avatarContent(value) { this._avatarContent = value; }
 | 
			
		||||
    set password(value)      { this._password = value; }
 | 
			
		||||
    get rank() {
 | 
			
		||||
        return this._rank;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get email() {
 | 
			
		||||
        return this._email;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get avatarStyle() {
 | 
			
		||||
        return this._avatarStyle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get avatarUrl() {
 | 
			
		||||
        return this._avatarUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get creationTime() {
 | 
			
		||||
        return this._creationTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get lastLoginTime() {
 | 
			
		||||
        return this._lastLoginTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get commentCount() {
 | 
			
		||||
        return this._commentCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get favoritePostCount() {
 | 
			
		||||
        return this._favoritePostCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get uploadedPostCount() {
 | 
			
		||||
        return this._uploadedPostCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get likedPostCount() {
 | 
			
		||||
        return this._likedPostCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get dislikedPostCount() {
 | 
			
		||||
        return this._dislikedPostCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get rankName() {
 | 
			
		||||
        return api.rankNames.get(this.rank);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get avatarContent() {
 | 
			
		||||
        throw 'Invalid operation';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get password() {
 | 
			
		||||
        throw 'Invalid operation';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set name(value) {
 | 
			
		||||
        this._name = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set rank(value) {
 | 
			
		||||
        this._rank = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set email(value) {
 | 
			
		||||
        this._email = value || null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set avatarStyle(value) {
 | 
			
		||||
        this._avatarStyle = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set avatarContent(value) {
 | 
			
		||||
        this._avatarContent = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set password(value) {
 | 
			
		||||
        this._password = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fromResponse(response) {
 | 
			
		||||
        const ret = new User();
 | 
			
		||||
@ -91,8 +152,8 @@ class User extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    delete() {
 | 
			
		||||
        return api.delete(
 | 
			
		||||
                uri.formatApiLink('user', this._orig._name),
 | 
			
		||||
                {version: this._version})
 | 
			
		||||
            uri.formatApiLink('user', this._orig._name),
 | 
			
		||||
            {version: this._version})
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                this.dispatchEvent(new CustomEvent('delete', {
 | 
			
		||||
                    detail: {
 | 
			
		||||
@ -105,27 +166,27 @@ class User extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    _updateFromResponse(response) {
 | 
			
		||||
        const map = {
 | 
			
		||||
            _version:           response.version,
 | 
			
		||||
            _name:              response.name,
 | 
			
		||||
            _rank:              response.rank,
 | 
			
		||||
            _email:             response.email,
 | 
			
		||||
            _avatarStyle:       response.avatarStyle,
 | 
			
		||||
            _avatarUrl:         response.avatarUrl,
 | 
			
		||||
            _creationTime:      response.creationTime,
 | 
			
		||||
            _lastLoginTime:     response.lastLoginTime,
 | 
			
		||||
            _commentCount:      response.commentCount,
 | 
			
		||||
            _version: response.version,
 | 
			
		||||
            _name: response.name,
 | 
			
		||||
            _rank: response.rank,
 | 
			
		||||
            _email: response.email,
 | 
			
		||||
            _avatarStyle: response.avatarStyle,
 | 
			
		||||
            _avatarUrl: response.avatarUrl,
 | 
			
		||||
            _creationTime: response.creationTime,
 | 
			
		||||
            _lastLoginTime: response.lastLoginTime,
 | 
			
		||||
            _commentCount: response.commentCount,
 | 
			
		||||
            _favoritePostCount: response.favoritePostCount,
 | 
			
		||||
            _uploadedPostCount: response.uploadedPostCount,
 | 
			
		||||
            _likedPostCount:    response.likedPostCount,
 | 
			
		||||
            _likedPostCount: response.likedPostCount,
 | 
			
		||||
            _dislikedPostCount: response.dislikedPostCount,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Object.assign(this, map);
 | 
			
		||||
        Object.assign(this._orig, map);
 | 
			
		||||
 | 
			
		||||
        this._password          = null;
 | 
			
		||||
        this._avatarContent     = null;
 | 
			
		||||
        this._password = null;
 | 
			
		||||
        this._avatarContent = null;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = User;
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,8 @@ const User = require('./user.js');
 | 
			
		||||
class UserList extends AbstractList {
 | 
			
		||||
    static search(text, offset, limit) {
 | 
			
		||||
        return api.get(
 | 
			
		||||
                uri.formatApiLink(
 | 
			
		||||
                    'users', {query: text, offset: offset, limit: limit}))
 | 
			
		||||
            uri.formatApiLink(
 | 
			
		||||
                'users', {query: text, offset: offset, limit: limit}))
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                return Promise.resolve(Object.assign(
 | 
			
		||||
                    {},
 | 
			
		||||
 | 
			
		||||
@ -11,16 +11,41 @@ class UserToken extends events.EventTarget {
 | 
			
		||||
        this._updateFromResponse({});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get token()          { return this._token; }
 | 
			
		||||
    get note()           { return this._note; }
 | 
			
		||||
    get enabled()        { return this._enabled; }
 | 
			
		||||
    get version()        { return this._version; }
 | 
			
		||||
    get expirationTime() { return this._expirationTime; }
 | 
			
		||||
    get creationTime()   { return this._creationTime; }
 | 
			
		||||
    get lastEditTime()   { return this._lastEditTime; }
 | 
			
		||||
    get lastUsageTime()  { return this._lastUsageTime; }
 | 
			
		||||
    get token() {
 | 
			
		||||
        return this._token;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set note(value)      { this._note = value; }
 | 
			
		||||
    get note() {
 | 
			
		||||
        return this._note;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get enabled() {
 | 
			
		||||
        return this._enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get version() {
 | 
			
		||||
        return this._version;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get expirationTime() {
 | 
			
		||||
        return this._expirationTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get creationTime() {
 | 
			
		||||
        return this._creationTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get lastEditTime() {
 | 
			
		||||
        return this._lastEditTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get lastUsageTime() {
 | 
			
		||||
        return this._lastUsageTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set note(value) {
 | 
			
		||||
        this._note = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fromResponse(response) {
 | 
			
		||||
        if (typeof response.results !== 'undefined') {
 | 
			
		||||
@ -98,14 +123,14 @@ class UserToken extends events.EventTarget {
 | 
			
		||||
 | 
			
		||||
    _updateFromResponse(response) {
 | 
			
		||||
        const map = {
 | 
			
		||||
            _token:           response.token,
 | 
			
		||||
            _note:            response.note,
 | 
			
		||||
            _enabled:         response.enabled,
 | 
			
		||||
            _expirationTime:  response.expirationTime,
 | 
			
		||||
            _version:         response.version,
 | 
			
		||||
            _creationTime:    response.creationTime,
 | 
			
		||||
            _lastEditTime:    response.lastEditTime,
 | 
			
		||||
            _lastUsageTime:   response.lastUsageTime,
 | 
			
		||||
            _token: response.token,
 | 
			
		||||
            _note: response.note,
 | 
			
		||||
            _enabled: response.enabled,
 | 
			
		||||
            _expirationTime: response.expirationTime,
 | 
			
		||||
            _version: response.version,
 | 
			
		||||
            _creationTime: response.creationTime,
 | 
			
		||||
            _lastEditTime: response.lastEditTime,
 | 
			
		||||
            _lastUsageTime: response.lastUsageTime,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Object.assign(this, map);
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ class Context {
 | 
			
		||||
    replaceState() {
 | 
			
		||||
        history.replaceState(this.state, this.title, this.canonicalPath);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Route {
 | 
			
		||||
    constructor(path) {
 | 
			
		||||
@ -119,7 +119,7 @@ class Route {
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Router {
 | 
			
		||||
    constructor() {
 | 
			
		||||
@ -217,14 +217,14 @@ class Router {
 | 
			
		||||
        if (current === ctx.canonicalPath) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        router.stop();
 | 
			
		||||
        this.stop();
 | 
			
		||||
        location.href = ctx.canonicalPath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get url() {
 | 
			
		||||
        return location.pathname + location.search + location.hash;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const _onPopState = router => {
 | 
			
		||||
    let loaded = false;
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ const settings = require('../models/settings.js');
 | 
			
		||||
 | 
			
		||||
let paused = false;
 | 
			
		||||
const _originalStopCallback = mousetrap.prototype.stopCallback;
 | 
			
		||||
// eslint-disable-next-line func-names
 | 
			
		||||
mousetrap.prototype.stopCallback = function(...args) {
 | 
			
		||||
    var self = this;
 | 
			
		||||
    if (paused) {
 | 
			
		||||
@ -28,6 +29,10 @@ function unbind(hotkey) {
 | 
			
		||||
module.exports = {
 | 
			
		||||
    bind: bind,
 | 
			
		||||
    unbind: unbind,
 | 
			
		||||
    pause: () => { paused = true; },
 | 
			
		||||
    unpause: () => { paused = false; },
 | 
			
		||||
    pause: () => {
 | 
			
		||||
        paused = true;
 | 
			
		||||
    },
 | 
			
		||||
    unpause: () => {
 | 
			
		||||
        paused = false;
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ class TildeWrapper extends BaseMarkdownWrapper {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//prevent ^#... from being treated as headers, due to tag permalinks
 | 
			
		||||
// prevent ^#... from being treated as headers, due to tag permalinks
 | 
			
		||||
class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
 | 
			
		||||
    preprocess(text) {
 | 
			
		||||
        return text.replace(/^#/g, '%%%#');
 | 
			
		||||
@ -59,7 +59,7 @@ class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//post, user and tags permalinks
 | 
			
		||||
// post, user and tags permalinks
 | 
			
		||||
class EntityPermalinkWrapper extends BaseMarkdownWrapper {
 | 
			
		||||
    preprocess(text) {
 | 
			
		||||
        // URL-based permalinks
 | 
			
		||||
@ -127,7 +127,7 @@ function createRenderer() {
 | 
			
		||||
    const renderer = new marked.Renderer();
 | 
			
		||||
    renderer.image = (href, title, alt) => {
 | 
			
		||||
        let [_, url, width, height] =
 | 
			
		||||
            /^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/.exec(href);
 | 
			
		||||
            (/^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/).exec(href);
 | 
			
		||||
        let res = '<img src="' + sanitize(url) + '" alt="' + sanitize(alt);
 | 
			
		||||
        if (width) {
 | 
			
		||||
            res += '" width="' + width;
 | 
			
		||||
 | 
			
		||||
@ -12,8 +12,8 @@ function decamelize(str, sep) {
 | 
			
		||||
        .toLowerCase();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function* range(start=0, end=null, step=1) {
 | 
			
		||||
    if (end == null) {
 | 
			
		||||
function *range(start = 0, end = null, step = 1) {
 | 
			
		||||
    if (end === null) {
 | 
			
		||||
        end = start;
 | 
			
		||||
        start = 0;
 | 
			
		||||
    }
 | 
			
		||||
@ -63,17 +63,17 @@ function formatRelativeTime(timeString) {
 | 
			
		||||
    const future = now < then;
 | 
			
		||||
 | 
			
		||||
    const descriptions = [
 | 
			
		||||
        [60,                            'a few seconds', null],
 | 
			
		||||
        [60 * 2,                        'a minute',      null],
 | 
			
		||||
        [60 * 60,                       '% minutes',     60],
 | 
			
		||||
        [60 * 60 * 2,                   'an hour',       null],
 | 
			
		||||
        [60 * 60 * 24,                  '% hours',       60 * 60],
 | 
			
		||||
        [60 * 60 * 24 * 2,              'a day',         null],
 | 
			
		||||
        [60 * 60 * 24 * 30.42,          '% days',        60 * 60 * 24],
 | 
			
		||||
        [60 * 60 * 24 * 30.42 * 2,      'a month',       null],
 | 
			
		||||
        [60 * 60 * 24 * 30.42 * 12,     '% months',      60 * 60 * 24 * 30.42],
 | 
			
		||||
        [60 * 60 * 24 * 30.42 * 12 * 2, 'a year',        null],
 | 
			
		||||
        [8640000000000000 /*max*/, '% years', 60 * 60 * 24 * 30.42 * 12],
 | 
			
		||||
        [60, 'a few seconds', null],
 | 
			
		||||
        [60 * 2, 'a minute', null],
 | 
			
		||||
        [60 * 60, '% minutes', 60],
 | 
			
		||||
        [60 * 60 * 2, 'an hour', null],
 | 
			
		||||
        [60 * 60 * 24, '% hours', 60 * 60],
 | 
			
		||||
        [60 * 60 * 24 * 2, 'a day', null],
 | 
			
		||||
        [60 * 60 * 24 * 30.42, '% days', 60 * 60 * 24],
 | 
			
		||||
        [60 * 60 * 24 * 30.42 * 2, 'a month', null],
 | 
			
		||||
        [60 * 60 * 24 * 30.42 * 12, '% months', 60 * 60 * 24 * 30.42],
 | 
			
		||||
        [60 * 60 * 24 * 30.42 * 12 * 2, 'a year', null],
 | 
			
		||||
        [8640000000000000 /* max*/, '% years', 60 * 60 * 24 * 30.42 * 12],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    let text = null;
 | 
			
		||||
@ -192,7 +192,7 @@ function dataURItoBlob(dataURI) {
 | 
			
		||||
        unescape(chunks[1]);
 | 
			
		||||
    const mimeString = chunks[0].split(':')[1].split(';')[0];
 | 
			
		||||
    const data = new Uint8Array(byteString.length);
 | 
			
		||||
    for (var i = 0; i < byteString.length; i++) {
 | 
			
		||||
    for (let i = 0; i < byteString.length; i++) {
 | 
			
		||||
        data[i] = byteString.charCodeAt(i);
 | 
			
		||||
    }
 | 
			
		||||
    return new Blob([data], {type: mimeString});
 | 
			
		||||
@ -206,21 +206,21 @@ function getPrettyTagName(tag) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    range:                   range,
 | 
			
		||||
    formatRelativeTime:      formatRelativeTime,
 | 
			
		||||
    formatFileSize:          formatFileSize,
 | 
			
		||||
    formatMarkdown:          formatMarkdown,
 | 
			
		||||
    formatInlineMarkdown:    formatInlineMarkdown,
 | 
			
		||||
    unindent:                unindent,
 | 
			
		||||
    enableExitConfirmation:  enableExitConfirmation,
 | 
			
		||||
    range: range,
 | 
			
		||||
    formatRelativeTime: formatRelativeTime,
 | 
			
		||||
    formatFileSize: formatFileSize,
 | 
			
		||||
    formatMarkdown: formatMarkdown,
 | 
			
		||||
    formatInlineMarkdown: formatInlineMarkdown,
 | 
			
		||||
    unindent: unindent,
 | 
			
		||||
    enableExitConfirmation: enableExitConfirmation,
 | 
			
		||||
    disableExitConfirmation: disableExitConfirmation,
 | 
			
		||||
    confirmPageExit:         confirmPageExit,
 | 
			
		||||
    escapeHtml:              escapeHtml,
 | 
			
		||||
    makeCssName:             makeCssName,
 | 
			
		||||
    splitByWhitespace:       splitByWhitespace,
 | 
			
		||||
    arraysDiffer:            arraysDiffer,
 | 
			
		||||
    decamelize:              decamelize,
 | 
			
		||||
    escapeSearchTerm:        escapeSearchTerm,
 | 
			
		||||
    dataURItoBlob:           dataURItoBlob,
 | 
			
		||||
    getPrettyTagName:        getPrettyTagName,
 | 
			
		||||
    confirmPageExit: confirmPageExit,
 | 
			
		||||
    escapeHtml: escapeHtml,
 | 
			
		||||
    makeCssName: makeCssName,
 | 
			
		||||
    splitByWhitespace: splitByWhitespace,
 | 
			
		||||
    arraysDiffer: arraysDiffer,
 | 
			
		||||
    decamelize: decamelize,
 | 
			
		||||
    escapeSearchTerm: escapeSearchTerm,
 | 
			
		||||
    dataURItoBlob: dataURItoBlob,
 | 
			
		||||
    getPrettyTagName: getPrettyTagName,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ function resize() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function runCallbacks() {
 | 
			
		||||
    callbacks.forEach(function(callback) {
 | 
			
		||||
    callbacks.forEach(callback => {
 | 
			
		||||
        callback();
 | 
			
		||||
    });
 | 
			
		||||
    running = false;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
/* eslint-disable func-names, no-extend-native */
 | 
			
		||||
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
// fix iterating over NodeList in Chrome and Opera
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const nprogress = require('nprogress');
 | 
			
		||||
 | 
			
		||||
let nesting = 0;
 | 
			
		||||
@ -20,5 +22,5 @@ function done() {
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    start: start,
 | 
			
		||||
    done:  done,
 | 
			
		||||
    done: done,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const direction = {
 | 
			
		||||
    NONE: null,
 | 
			
		||||
    LEFT: 'left',
 | 
			
		||||
@ -26,30 +28,29 @@ function handleTouchMove(handler, evt) {
 | 
			
		||||
        } else {
 | 
			
		||||
            handler._direction = direction.RIGHT;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (yDirection > 0) {
 | 
			
		||||
        handler._direction = direction.DOWN;
 | 
			
		||||
    } else {
 | 
			
		||||
        if (yDirection > 0) {
 | 
			
		||||
            handler._direction = direction.DOWN;
 | 
			
		||||
        } else {
 | 
			
		||||
            handler._direction = direction.UP;
 | 
			
		||||
        }
 | 
			
		||||
        handler._direction = direction.UP;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleTouchEnd(handler) {
 | 
			
		||||
    switch (handler._direction) {
 | 
			
		||||
        case direction.NONE:
 | 
			
		||||
            return;
 | 
			
		||||
        case direction.LEFT:
 | 
			
		||||
            handler._swipeLeftTask();
 | 
			
		||||
            break;
 | 
			
		||||
        case direction.RIGHT:
 | 
			
		||||
            handler._swipeRightTask();
 | 
			
		||||
            break;
 | 
			
		||||
        case direction.DOWN:
 | 
			
		||||
            handler._swipeDownTask();
 | 
			
		||||
            break;
 | 
			
		||||
        case direction.UP:
 | 
			
		||||
            handler._swipeUpTask();
 | 
			
		||||
    case direction.NONE:
 | 
			
		||||
        return;
 | 
			
		||||
    case direction.LEFT:
 | 
			
		||||
        handler._swipeLeftTask();
 | 
			
		||||
        break;
 | 
			
		||||
    case direction.RIGHT:
 | 
			
		||||
        handler._swipeRightTask();
 | 
			
		||||
        break;
 | 
			
		||||
    case direction.DOWN:
 | 
			
		||||
        handler._swipeDownTask();
 | 
			
		||||
        break;
 | 
			
		||||
    case direction.UP:
 | 
			
		||||
        handler._swipeUpTask();
 | 
			
		||||
    // no default
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handler._xStart = null;
 | 
			
		||||
@ -58,10 +59,10 @@ function handleTouchEnd(handler) {
 | 
			
		||||
 | 
			
		||||
class Touch {
 | 
			
		||||
    constructor(target,
 | 
			
		||||
                swipeLeft = () => {},
 | 
			
		||||
                swipeRight = () => {},
 | 
			
		||||
                swipeUp = () => {},
 | 
			
		||||
                swipeDown = () => {}) {
 | 
			
		||||
        swipeLeft = () => {},
 | 
			
		||||
        swipeRight = () => {},
 | 
			
		||||
        swipeUp = () => {},
 | 
			
		||||
        swipeDown = () => {}) {
 | 
			
		||||
        this._target = target;
 | 
			
		||||
 | 
			
		||||
        this._swipeLeftTask = swipeLeft;
 | 
			
		||||
@ -74,12 +75,18 @@ class Touch {
 | 
			
		||||
        this._direction = direction.NONE;
 | 
			
		||||
 | 
			
		||||
        this._target.addEventListener('touchstart',
 | 
			
		||||
            (evt) => { handleTouchStart(this, evt); });
 | 
			
		||||
            evt => {
 | 
			
		||||
                handleTouchStart(this, evt);
 | 
			
		||||
            });
 | 
			
		||||
        this._target.addEventListener('touchmove',
 | 
			
		||||
            (evt) => { handleTouchMove(this, evt); });
 | 
			
		||||
            evt => {
 | 
			
		||||
                handleTouchMove(this, evt);
 | 
			
		||||
            });
 | 
			
		||||
        this._target.addEventListener('touchend',
 | 
			
		||||
            () => { handleTouchEnd(this); });
 | 
			
		||||
            () => {
 | 
			
		||||
                handleTouchEnd(this);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Touch;
 | 
			
		||||
module.exports = Touch;
 | 
			
		||||
 | 
			
		||||
@ -72,7 +72,7 @@ function extractRootDomain(url) {
 | 
			
		||||
    if (arrLen > 2) {
 | 
			
		||||
        domain = splitArr[arrLen - 2] + '.' + splitArr[arrLen - 1];
 | 
			
		||||
        // check to see if it's using a Country Code Top Level Domain (ccTLD) (i.e. ".me.uk")
 | 
			
		||||
        if (splitArr[arrLen - 2].length == 2 && splitArr[arrLen - 1].length == 2) {
 | 
			
		||||
        if (splitArr[arrLen - 2].length === 2 && splitArr[arrLen - 1].length === 2) {
 | 
			
		||||
            // this is using a ccTLD
 | 
			
		||||
            domain = splitArr[arrLen - 3] + '.' + domain;
 | 
			
		||||
        }
 | 
			
		||||
@ -85,11 +85,11 @@ function escapeColons(text) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    formatClientLink:  formatClientLink,
 | 
			
		||||
    formatApiLink:     formatApiLink,
 | 
			
		||||
    escapeColons:      escapeColons,
 | 
			
		||||
    escapeParam:       escapeParam,
 | 
			
		||||
    unescapeParam:     unescapeParam,
 | 
			
		||||
    extractHostname:   extractHostname,
 | 
			
		||||
    formatClientLink: formatClientLink,
 | 
			
		||||
    formatApiLink: formatApiLink,
 | 
			
		||||
    escapeColons: escapeColons,
 | 
			
		||||
    escapeParam: escapeParam,
 | 
			
		||||
    unescapeParam: unescapeParam,
 | 
			
		||||
    extractHostname: extractHostname,
 | 
			
		||||
    extractRootDomain: extractRootDomain,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -94,11 +94,10 @@ function makeSelect(options) {
 | 
			
		||||
                name: options.name,
 | 
			
		||||
                disabled: options.readonly,
 | 
			
		||||
            },
 | 
			
		||||
            ...Object.keys(options.keyValues).map(key =>
 | 
			
		||||
                makeElement(
 | 
			
		||||
                    'option',
 | 
			
		||||
                    {value: key, selected: key === options.selectedKey},
 | 
			
		||||
                    options.keyValues[key])));
 | 
			
		||||
            ...Object.keys(options.keyValues).map(key => makeElement(
 | 
			
		||||
                'option',
 | 
			
		||||
                {value: key, selected: key === options.selectedKey},
 | 
			
		||||
                options.keyValues[key])));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeInput(options) {
 | 
			
		||||
@ -157,10 +156,7 @@ function makeColorInput(options) {
 | 
			
		||||
                color: ${options.value}`,
 | 
			
		||||
        });
 | 
			
		||||
    return makeElement(
 | 
			
		||||
        'label', {class: 'color'},
 | 
			
		||||
        textInput,
 | 
			
		||||
        backgroundPreviewNode,
 | 
			
		||||
        textPreviewNode);
 | 
			
		||||
        'label', {class: 'color'}, textInput, backgroundPreviewNode, textPreviewNode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeNumericInput(options) {
 | 
			
		||||
@ -175,14 +171,12 @@ function makeDateInput(options) {
 | 
			
		||||
 | 
			
		||||
function getPostUrl(id, parameters) {
 | 
			
		||||
    return uri.formatClientLink(
 | 
			
		||||
        'post', id,
 | 
			
		||||
        parameters ? {query: parameters.query} : {});
 | 
			
		||||
        'post', id, parameters ? {query: parameters.query} : {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getPostEditUrl(id, parameters) {
 | 
			
		||||
    return uri.formatClientLink(
 | 
			
		||||
        'post', id, 'edit',
 | 
			
		||||
        parameters ? {query: parameters.query} : {});
 | 
			
		||||
        'post', id, 'edit', parameters ? {query: parameters.query} : {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makePostLink(id, includeHash) {
 | 
			
		||||
@ -335,26 +329,25 @@ function clearMessages(target) {
 | 
			
		||||
function htmlToDom(html) {
 | 
			
		||||
    // code taken from jQuery + Krasimir Tsonev's blog
 | 
			
		||||
    const wrapMap = {
 | 
			
		||||
        _:      [1, '<div>', '</div>'],
 | 
			
		||||
        _: [1, '<div>', '</div>'],
 | 
			
		||||
        option: [1, '<select multiple>', '</select>'],
 | 
			
		||||
        legend: [1, '<fieldset>', '</fieldset>'],
 | 
			
		||||
        area:   [1, '<map>', '</map>'],
 | 
			
		||||
        param:  [1, '<object>', '</object>'],
 | 
			
		||||
        thead:  [1, '<table>', '</table>'],
 | 
			
		||||
        tr:     [2, '<table><tbody>', '</tbody></table>'],
 | 
			
		||||
        td:     [3, '<table><tbody><tr>', '</tr></tbody></table>'],
 | 
			
		||||
        col:    [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
 | 
			
		||||
        area: [1, '<map>', '</map>'],
 | 
			
		||||
        param: [1, '<object>', '</object>'],
 | 
			
		||||
        thead: [1, '<table>', '</table>'],
 | 
			
		||||
        tr: [2, '<table><tbody>', '</tbody></table>'],
 | 
			
		||||
        td: [3, '<table><tbody><tr>', '</tr></tbody></table>'],
 | 
			
		||||
        col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
 | 
			
		||||
    };
 | 
			
		||||
    wrapMap.optgroup = wrapMap.option;
 | 
			
		||||
    wrapMap.tbody =
 | 
			
		||||
        wrapMap.tfoot =
 | 
			
		||||
        wrapMap.colgroup =
 | 
			
		||||
        wrapMap.caption =
 | 
			
		||||
        wrapMap.thead;
 | 
			
		||||
    wrapMap.tbody = wrapMap.thead;
 | 
			
		||||
    wrapMap.tfoot = wrapMap.thead;
 | 
			
		||||
    wrapMap.colgroup = wrapMap.thead;
 | 
			
		||||
    wrapMap.caption = wrapMap.thead;
 | 
			
		||||
    wrapMap.th = wrapMap.td;
 | 
			
		||||
 | 
			
		||||
    let element = document.createElement('div');
 | 
			
		||||
    const match = /<\s*(\w+)[^>]*?>/g.exec(html);
 | 
			
		||||
    const match = (/<\s*(\w+)[^>]*?>/g).exec(html);
 | 
			
		||||
 | 
			
		||||
    if (match) {
 | 
			
		||||
        const tag = match[1];
 | 
			
		||||
@ -381,32 +374,32 @@ function getTemplate(templatePath) {
 | 
			
		||||
            ctx = {};
 | 
			
		||||
        }
 | 
			
		||||
        Object.assign(ctx, {
 | 
			
		||||
            getPostUrl:        getPostUrl,
 | 
			
		||||
            getPostEditUrl:    getPostEditUrl,
 | 
			
		||||
            makeRelativeTime:  makeRelativeTime,
 | 
			
		||||
            makeFileSize:      makeFileSize,
 | 
			
		||||
            makeMarkdown:      makeMarkdown,
 | 
			
		||||
            makeThumbnail:     makeThumbnail,
 | 
			
		||||
            makeRadio:         makeRadio,
 | 
			
		||||
            makeCheckbox:      makeCheckbox,
 | 
			
		||||
            makeSelect:        makeSelect,
 | 
			
		||||
            makeInput:         makeInput,
 | 
			
		||||
            makeButton:        makeButton,
 | 
			
		||||
            makeTextarea:      makeTextarea,
 | 
			
		||||
            makeTextInput:     makeTextInput,
 | 
			
		||||
            getPostUrl: getPostUrl,
 | 
			
		||||
            getPostEditUrl: getPostEditUrl,
 | 
			
		||||
            makeRelativeTime: makeRelativeTime,
 | 
			
		||||
            makeFileSize: makeFileSize,
 | 
			
		||||
            makeMarkdown: makeMarkdown,
 | 
			
		||||
            makeThumbnail: makeThumbnail,
 | 
			
		||||
            makeRadio: makeRadio,
 | 
			
		||||
            makeCheckbox: makeCheckbox,
 | 
			
		||||
            makeSelect: makeSelect,
 | 
			
		||||
            makeInput: makeInput,
 | 
			
		||||
            makeButton: makeButton,
 | 
			
		||||
            makeTextarea: makeTextarea,
 | 
			
		||||
            makeTextInput: makeTextInput,
 | 
			
		||||
            makePasswordInput: makePasswordInput,
 | 
			
		||||
            makeEmailInput:    makeEmailInput,
 | 
			
		||||
            makeColorInput:    makeColorInput,
 | 
			
		||||
            makeDateInput:     makeDateInput,
 | 
			
		||||
            makePostLink:      makePostLink,
 | 
			
		||||
            makeTagLink:       makeTagLink,
 | 
			
		||||
            makeUserLink:      makeUserLink,
 | 
			
		||||
            makeFlexboxAlign:  makeFlexboxAlign,
 | 
			
		||||
            makeAccessKey:     makeAccessKey,
 | 
			
		||||
            makeElement:       makeElement,
 | 
			
		||||
            makeCssName:       misc.makeCssName,
 | 
			
		||||
            makeNumericInput:  makeNumericInput,
 | 
			
		||||
            formatClientLink:  uri.formatClientLink
 | 
			
		||||
            makeEmailInput: makeEmailInput,
 | 
			
		||||
            makeColorInput: makeColorInput,
 | 
			
		||||
            makeDateInput: makeDateInput,
 | 
			
		||||
            makePostLink: makePostLink,
 | 
			
		||||
            makeTagLink: makeTagLink,
 | 
			
		||||
            makeUserLink: makeUserLink,
 | 
			
		||||
            makeFlexboxAlign: makeFlexboxAlign,
 | 
			
		||||
            makeAccessKey: makeAccessKey,
 | 
			
		||||
            makeElement: makeElement,
 | 
			
		||||
            makeCssName: misc.makeCssName,
 | 
			
		||||
            makeNumericInput: makeNumericInput,
 | 
			
		||||
            formatClientLink: uri.formatClientLink
 | 
			
		||||
        });
 | 
			
		||||
        return htmlToDom(templateFactory(ctx));
 | 
			
		||||
    };
 | 
			
		||||
@ -444,7 +437,7 @@ function enableForm(form) {
 | 
			
		||||
function syncScrollPosition() {
 | 
			
		||||
    window.requestAnimationFrame(
 | 
			
		||||
        () => {
 | 
			
		||||
            if (history.state && history.state.hasOwnProperty('scrollX')) {
 | 
			
		||||
            if (history.state && Object.prototype.hasOwnProperty.call(history.state, 'scrollX')) {
 | 
			
		||||
                window.scrollTo(history.state.scrollX, history.state.scrollY);
 | 
			
		||||
            } else {
 | 
			
		||||
                window.scrollTo(0, 0);
 | 
			
		||||
@ -520,24 +513,24 @@ document.addEventListener('click', e => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    htmlToDom:             htmlToDom,
 | 
			
		||||
    getTemplate:           getTemplate,
 | 
			
		||||
    emptyContent:          emptyContent,
 | 
			
		||||
    replaceContent:        replaceContent,
 | 
			
		||||
    enableForm:            enableForm,
 | 
			
		||||
    disableForm:           disableForm,
 | 
			
		||||
    decorateValidator:     decorateValidator,
 | 
			
		||||
    makeTagLink:           makeTagLink,
 | 
			
		||||
    makePostLink:          makePostLink,
 | 
			
		||||
    makeCheckbox:          makeCheckbox,
 | 
			
		||||
    makeRadio:             makeRadio,
 | 
			
		||||
    syncScrollPosition:    syncScrollPosition,
 | 
			
		||||
    slideDown:             slideDown,
 | 
			
		||||
    slideUp:               slideUp,
 | 
			
		||||
    monitorNodeRemoval:    monitorNodeRemoval,
 | 
			
		||||
    clearMessages:         clearMessages,
 | 
			
		||||
    htmlToDom: htmlToDom,
 | 
			
		||||
    getTemplate: getTemplate,
 | 
			
		||||
    emptyContent: emptyContent,
 | 
			
		||||
    replaceContent: replaceContent,
 | 
			
		||||
    enableForm: enableForm,
 | 
			
		||||
    disableForm: disableForm,
 | 
			
		||||
    decorateValidator: decorateValidator,
 | 
			
		||||
    makeTagLink: makeTagLink,
 | 
			
		||||
    makePostLink: makePostLink,
 | 
			
		||||
    makeCheckbox: makeCheckbox,
 | 
			
		||||
    makeRadio: makeRadio,
 | 
			
		||||
    syncScrollPosition: syncScrollPosition,
 | 
			
		||||
    slideDown: slideDown,
 | 
			
		||||
    slideUp: slideUp,
 | 
			
		||||
    monitorNodeRemoval: monitorNodeRemoval,
 | 
			
		||||
    clearMessages: clearMessages,
 | 
			
		||||
    appendExclamationMark: appendExclamationMark,
 | 
			
		||||
    showError:             showError,
 | 
			
		||||
    showSuccess:           showSuccess,
 | 
			
		||||
    showInfo:              showInfo,
 | 
			
		||||
    showError: showError,
 | 
			
		||||
    showSuccess: showSuccess,
 | 
			
		||||
    showInfo: showInfo,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ function isScrolledIntoView(element) {
 | 
			
		||||
    do {
 | 
			
		||||
        top += element.offsetTop || 0;
 | 
			
		||||
        element = element.offsetParent;
 | 
			
		||||
    } while(element);
 | 
			
		||||
    } while (element);
 | 
			
		||||
    return (
 | 
			
		||||
        (top >= window.scrollY) &&
 | 
			
		||||
        (top <= window.scrollY + window.innerHeight));
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ class HelpView {
 | 
			
		||||
        views.replaceContent(this._hostNode, sourceNode);
 | 
			
		||||
 | 
			
		||||
        for (let itemNode of
 | 
			
		||||
                sourceNode.querySelectorAll('.primary [data-name]')) {
 | 
			
		||||
            sourceNode.querySelectorAll('.primary [data-name]')) {
 | 
			
		||||
            itemNode.classList.toggle(
 | 
			
		||||
                'active',
 | 
			
		||||
                itemNode.getAttribute('data-name') === section);
 | 
			
		||||
@ -59,7 +59,7 @@ class HelpView {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (let itemNode of
 | 
			
		||||
                sourceNode.querySelectorAll('.secondary [data-name]')) {
 | 
			
		||||
            sourceNode.querySelectorAll('.secondary [data-name]')) {
 | 
			
		||||
            itemNode.classList.toggle(
 | 
			
		||||
                'active',
 | 
			
		||||
                itemNode.getAttribute('data-name') === subsection);
 | 
			
		||||
 | 
			
		||||
@ -27,9 +27,8 @@ class HomeView {
 | 
			
		||||
            this._autoCompleteControl = new TagAutoCompleteControl(
 | 
			
		||||
                this._searchInputNode,
 | 
			
		||||
                {
 | 
			
		||||
                    confirm: tag =>
 | 
			
		||||
                        this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                            misc.escapeSearchTerm(tag.names[0]), true),
 | 
			
		||||
                    confirm: tag => this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                        misc.escapeSearchTerm(tag.names[0]), true),
 | 
			
		||||
                });
 | 
			
		||||
            this._formNode.addEventListener(
 | 
			
		||||
                'submit', e => this._evtFormSubmit(e));
 | 
			
		||||
@ -99,8 +98,7 @@ class HomeView {
 | 
			
		||||
    _evtFormSubmit(e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this._searchInputNode.blur();
 | 
			
		||||
        router.show(uri.formatClientLink('posts', {
 | 
			
		||||
            query: this._searchInputNode.value}));
 | 
			
		||||
        router.show(uri.formatClientLink('posts', {query: this._searchInputNode.value}));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ const navTemplate = views.getTemplate('manual-pager-nav');
 | 
			
		||||
 | 
			
		||||
function _removeConsecutiveDuplicates(a) {
 | 
			
		||||
    return a.filter((item, pos, ary) => {
 | 
			
		||||
        return !pos || item != ary[pos - 1];
 | 
			
		||||
        return !pos || item !== ary[pos - 1];
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,20 +23,22 @@ function _getVisiblePageNumbers(currentPage, totalPages) {
 | 
			
		||||
        pagesVisible.push(i);
 | 
			
		||||
    }
 | 
			
		||||
    for (let i = currentPage - threshold;
 | 
			
		||||
            i <= currentPage + threshold;
 | 
			
		||||
            i++) {
 | 
			
		||||
        i <= currentPage + threshold;
 | 
			
		||||
        i++) {
 | 
			
		||||
        pagesVisible.push(i);
 | 
			
		||||
    }
 | 
			
		||||
    pagesVisible = pagesVisible.filter((item, pos, ary) => {
 | 
			
		||||
        return item >= 1 && item <= totalPages;
 | 
			
		||||
    });
 | 
			
		||||
    pagesVisible = pagesVisible.sort((a, b) => { return a - b; });
 | 
			
		||||
    pagesVisible = pagesVisible.sort((a, b) => {
 | 
			
		||||
        return a - b;
 | 
			
		||||
    });
 | 
			
		||||
    pagesVisible = _removeConsecutiveDuplicates(pagesVisible);
 | 
			
		||||
    return pagesVisible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _getPages(
 | 
			
		||||
        currentPage, pageNumbers, limit, defaultLimit, removedItems) {
 | 
			
		||||
    currentPage, pageNumbers, limit, defaultLimit, removedItems) {
 | 
			
		||||
    const pages = new Map();
 | 
			
		||||
    let prevPage = 0;
 | 
			
		||||
    for (let page of pageNumbers) {
 | 
			
		||||
@ -46,7 +48,7 @@ function _getPages(
 | 
			
		||||
        pages.set(page, {
 | 
			
		||||
            number: page,
 | 
			
		||||
            offset:
 | 
			
		||||
                (page - 1) * limit -
 | 
			
		||||
                ((page - 1) * limit) -
 | 
			
		||||
                (page > currentPage ? removedItems : 0),
 | 
			
		||||
            limit: limit === defaultLimit ? null : limit,
 | 
			
		||||
            active: currentPage === page,
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,7 @@ class PostMainView {
 | 
			
		||||
                        margin,
 | 
			
		||||
                    iosCorrectedInnerHeight() -
 | 
			
		||||
                        topNavigationNode.getBoundingClientRect().height -
 | 
			
		||||
                        margin * 2,
 | 
			
		||||
                        (margin * 2),
 | 
			
		||||
                ];
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -80,7 +80,7 @@ class PostMainView {
 | 
			
		||||
        });
 | 
			
		||||
        keyboard.bind(['a', 'left'], showPreviousImage);
 | 
			
		||||
        keyboard.bind(['d', 'right'], showNextImage);
 | 
			
		||||
        keyboard.bind('del', (e) => {
 | 
			
		||||
        keyboard.bind('del', e => {
 | 
			
		||||
            if (ctx.editMode) {
 | 
			
		||||
                this.sidebarControl._evtDeleteClick(e);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -62,8 +62,7 @@ class PostMergeView extends events.EventTarget {
 | 
			
		||||
    _refreshSide(post, sideNode, sideName, isEditable) {
 | 
			
		||||
        views.replaceContent(
 | 
			
		||||
            sideNode,
 | 
			
		||||
            sideTemplate(Object.assign({}, this._ctx, {
 | 
			
		||||
                post: post,
 | 
			
		||||
            sideTemplate(Object.assign({}, this._ctx, {post: post,
 | 
			
		||||
                name: sideName,
 | 
			
		||||
                editable: isEditable})));
 | 
			
		||||
 | 
			
		||||
@ -85,10 +84,10 @@ class PostMergeView extends events.EventTarget {
 | 
			
		||||
            '.target-post-content :checked').value;
 | 
			
		||||
        this.dispatchEvent(new CustomEvent('submit', {
 | 
			
		||||
            detail: {
 | 
			
		||||
                post: checkedTargetPost == 'left' ?
 | 
			
		||||
                post: checkedTargetPost === 'left' ?
 | 
			
		||||
                    this._rightPost :
 | 
			
		||||
                    this._leftPost,
 | 
			
		||||
                targetPost: checkedTargetPost == 'left' ?
 | 
			
		||||
                targetPost: checkedTargetPost === 'left' ?
 | 
			
		||||
                    this._leftPost :
 | 
			
		||||
                    this._rightPost,
 | 
			
		||||
                useOldContent: checkedTargetPostContent !== checkedTargetPost,
 | 
			
		||||
 | 
			
		||||
@ -52,10 +52,6 @@ class BulkEditor extends events.EventTarget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BulkSafetyEditor extends BulkEditor {
 | 
			
		||||
    constructor(hostNode) {
 | 
			
		||||
        super(hostNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _evtOpenLinkClick(e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.toggleOpen(true);
 | 
			
		||||
@ -75,9 +71,8 @@ class BulkTagEditor extends BulkEditor {
 | 
			
		||||
        this._autoCompleteControl = new TagAutoCompleteControl(
 | 
			
		||||
            this._inputNode,
 | 
			
		||||
            {
 | 
			
		||||
                confirm: tag =>
 | 
			
		||||
                    this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                        tag.names[0], false),
 | 
			
		||||
                confirm: tag => this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                    tag.names[0], false),
 | 
			
		||||
            });
 | 
			
		||||
        this._hostNode.addEventListener('submit', e => this._evtFormSubmit(e));
 | 
			
		||||
    }
 | 
			
		||||
@ -132,9 +127,8 @@ class PostsHeaderView extends events.EventTarget {
 | 
			
		||||
        this._autoCompleteControl = new TagAutoCompleteControl(
 | 
			
		||||
            this._queryInputNode,
 | 
			
		||||
            {
 | 
			
		||||
                confirm: tag =>
 | 
			
		||||
                    this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                        misc.escapeSearchTerm(tag.names[0]), true),
 | 
			
		||||
                confirm: tag => this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                    misc.escapeSearchTerm(tag.names[0]), true),
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        keyboard.bind('p', () => this._focusFirstPostNode());
 | 
			
		||||
@ -246,7 +240,7 @@ class PostsHeaderView extends events.EventTarget {
 | 
			
		||||
    _navigate() {
 | 
			
		||||
        this._autoCompleteControl.hide();
 | 
			
		||||
        let parameters = {query: this._queryInputNode.value};
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // convert falsy values to an empty string "" so that we can correctly compare with the current query
 | 
			
		||||
        const prevQuery = this._ctx.parameters.query ? this._ctx.parameters.query : "";
 | 
			
		||||
        parameters.offset = parameters.query === prevQuery ? this._ctx.parameters.offset : 0;
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@ class PostsPageView extends events.EventTarget {
 | 
			
		||||
            if (tagFlipperNode) {
 | 
			
		||||
                let tagged = true;
 | 
			
		||||
                for (let tag of this._ctx.bulkEdit.tags) {
 | 
			
		||||
                    tagged = tagged & post.tags.isTaggedWith(tag);
 | 
			
		||||
                    tagged &= post.tags.isTaggedWith(tag);
 | 
			
		||||
                }
 | 
			
		||||
                tagFlipperNode.classList.toggle('tagged', tagged);
 | 
			
		||||
            }
 | 
			
		||||
@ -109,7 +109,7 @@ class PostsPageView extends events.EventTarget {
 | 
			
		||||
            if (safetyFlipperNode) {
 | 
			
		||||
                for (let linkNode of safetyFlipperNode.querySelectorAll('a')) {
 | 
			
		||||
                    const safety = linkNode.getAttribute('data-safety');
 | 
			
		||||
                    linkNode.classList.toggle('active', post.safety == safety);
 | 
			
		||||
                    linkNode.classList.toggle('active', post.safety === safety);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ class RegistrationView extends events.EventTarget {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.dispatchEvent(new CustomEvent('submit', {
 | 
			
		||||
            detail: {
 | 
			
		||||
                name:  this._userNameFieldNode.value,
 | 
			
		||||
                name: this._userNameFieldNode.value,
 | 
			
		||||
                password: this._passwordFieldNode.value,
 | 
			
		||||
                email: this._emailFieldNode.value,
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ class TagEditView extends events.EventTarget {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (let node of this._formNode.querySelectorAll(
 | 
			
		||||
                'input, select, textarea')) {
 | 
			
		||||
            'input, select, textarea')) {
 | 
			
		||||
            node.addEventListener(
 | 
			
		||||
                'change', e => {
 | 
			
		||||
                    this.dispatchEvent(new CustomEvent('change'));
 | 
			
		||||
 | 
			
		||||
@ -22,9 +22,8 @@ class TagMergeView extends events.EventTarget {
 | 
			
		||||
            this._autoCompleteControl = new TagAutoCompleteControl(
 | 
			
		||||
                this._targetTagFieldNode,
 | 
			
		||||
                {
 | 
			
		||||
                    confirm: tag =>
 | 
			
		||||
                        this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                            tag.names[0], false),
 | 
			
		||||
                    confirm: tag => this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                        tag.names[0], false),
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,9 +20,8 @@ class TagsHeaderView extends events.EventTarget {
 | 
			
		||||
            this._autoCompleteControl = new TagAutoCompleteControl(
 | 
			
		||||
                this._queryInputNode,
 | 
			
		||||
                {
 | 
			
		||||
                    confirm: tag =>
 | 
			
		||||
                        this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                            misc.escapeSearchTerm(tag.names[0]), true),
 | 
			
		||||
                    confirm: tag => this._autoCompleteControl.replaceSelectedText(
 | 
			
		||||
                        misc.escapeSearchTerm(tag.names[0]), true),
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,8 +11,8 @@ class UserEditView extends events.EventTarget {
 | 
			
		||||
    constructor(ctx) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        ctx.userNamePattern = api.getUserNameRegex() + /|^$/.source;
 | 
			
		||||
        ctx.passwordPattern = api.getPasswordRegex() + /|^$/.source;
 | 
			
		||||
        ctx.userNamePattern = api.getUserNameRegex() + (/|^$/).source;
 | 
			
		||||
        ctx.passwordPattern = api.getPasswordRegex() + (/|^$/).source;
 | 
			
		||||
 | 
			
		||||
        this._user = ctx.user;
 | 
			
		||||
        this._hostNode = ctx.hostNode;
 | 
			
		||||
 | 
			
		||||
@ -92,9 +92,9 @@ class UserTokenView extends events.EventTarget {
 | 
			
		||||
                expirationTime:
 | 
			
		||||
                    (this._userTokenExpirationTimeInputNode
 | 
			
		||||
                        && this._userTokenExpirationTimeInputNode.value) ?
 | 
			
		||||
                    new Date(this._userTokenExpirationTimeInputNode.value)
 | 
			
		||||
                        .toISOString() :
 | 
			
		||||
                    undefined,
 | 
			
		||||
                        new Date(this._userTokenExpirationTimeInputNode.value)
 | 
			
		||||
                            .toISOString() :
 | 
			
		||||
                        undefined,
 | 
			
		||||
            },
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,7 @@ class UserView extends events.EventTarget {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ctx.hostNode = this._hostNode.querySelector('#user-content-holder');
 | 
			
		||||
        if (ctx.section == 'edit') {
 | 
			
		||||
        if (ctx.section === 'edit') {
 | 
			
		||||
            if (!this._ctx.canEditAnything) {
 | 
			
		||||
                this._view = new EmptyView();
 | 
			
		||||
                this._view.showError(
 | 
			
		||||
@ -46,7 +46,7 @@ class UserView extends events.EventTarget {
 | 
			
		||||
                this._view = new UserEditView(ctx);
 | 
			
		||||
                events.proxyEvent(this._view, this, 'submit');
 | 
			
		||||
            }
 | 
			
		||||
        } else if (ctx.section == 'list-tokens') {
 | 
			
		||||
        } else if (ctx.section === 'list-tokens') {
 | 
			
		||||
            if (!this._ctx.canListTokens) {
 | 
			
		||||
                this._view = new EmptyView();
 | 
			
		||||
                this._view.showError(
 | 
			
		||||
@ -57,7 +57,7 @@ class UserView extends events.EventTarget {
 | 
			
		||||
                events.proxyEvent(this._view, this, 'submit', 'create-token');
 | 
			
		||||
                events.proxyEvent(this._view, this, 'update', 'update-token');
 | 
			
		||||
            }
 | 
			
		||||
        } else if (ctx.section == 'delete') {
 | 
			
		||||
        } else if (ctx.section === 'delete') {
 | 
			
		||||
            if (!this._ctx.canDelete) {
 | 
			
		||||
                this._view = new EmptyView();
 | 
			
		||||
                this._view.showError(
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user