client+server: implement code autoformatting using prettier and black
This commit is contained in:
parent
c06aaa63af
commit
57193b5715
312 changed files with 15512 additions and 12825 deletions
|
@ -1,50 +1,91 @@
|
|||
repos:
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: stable
|
||||
hooks:
|
||||
- id: black
|
||||
files: server/szurubooru/
|
||||
language_version: python3.8
|
||||
|
||||
- repo: https://github.com/timothycrosley/isort
|
||||
rev: '4.3.21-2'
|
||||
hooks:
|
||||
- id: isort
|
||||
files: server/szurubooru/
|
||||
exclude: server/szurubooru/migrations/env.py
|
||||
additional_dependencies:
|
||||
- toml
|
||||
|
||||
- repo: https://github.com/prettier/prettier
|
||||
rev: '2.0.5'
|
||||
hooks:
|
||||
- id: prettier
|
||||
files: client/js/
|
||||
exclude: client/js/.gitignore
|
||||
args: ['--config', 'client/.prettierrc.yml']
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v7.1.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
files: client/js/
|
||||
args: ['--fix']
|
||||
additional_dependencies:
|
||||
- eslint-config-prettier
|
||||
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: '3.8.2'
|
||||
hooks:
|
||||
- id: flake8
|
||||
files: server/szurubooru/
|
||||
additional_dependencies:
|
||||
- flake8-print
|
||||
args: ['--config=server/setup.cfg']
|
||||
- flake8-print
|
||||
args: ['--config=server/.flake8']
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pytest
|
||||
name: pytest
|
||||
entry: >-
|
||||
bash -c
|
||||
'docker build -f server/Dockerfile.test -t $(git rev-parse --short HEAD)-test server/
|
||||
&& docker run --rm -t $(git rev-parse --short HEAD)-test szurubooru/
|
||||
&& docker rmi --no-prune $(git rev-parse --short HEAD)-test'
|
||||
language: system
|
||||
types: [python]
|
||||
files: server/szurubooru/
|
||||
pass_filenames: false
|
||||
- id: docker-build-client
|
||||
name: Test building the client in Docker
|
||||
entry: bash -c 'docker build -t szurubooru-client:$(git rev-parse --short HEAD) client/'
|
||||
name: Docker - build client
|
||||
entry: bash -c 'docker build client/'
|
||||
language: system
|
||||
types: [file]
|
||||
files: client/
|
||||
pass_filenames: false
|
||||
|
||||
- id: docker-build-server
|
||||
name: Test building the server in Docker
|
||||
entry: bash -c 'docker build -t szurubooru-server:$(git rev-parse --short HEAD) server/'
|
||||
name: Docker - build server
|
||||
entry: bash -c 'docker build server/'
|
||||
language: system
|
||||
types: [file]
|
||||
files: server/
|
||||
pass_filenames: false
|
||||
|
||||
- id: pytest
|
||||
name: pytest
|
||||
entry: bash -c 'docker run --rm -t $(docker build -f server/Dockerfile.test -q server/) szurubooru/'
|
||||
language: system
|
||||
types: [python]
|
||||
files: server/szurubooru/
|
||||
exclude: server/szurubooru/migrations/
|
||||
pass_filenames: false
|
||||
|
||||
- id: pytest-cov
|
||||
name: pytest
|
||||
entry: bash -c 'docker run --rm -t $(docker build -f server/Dockerfile.test -q server/) --cov-report=term-missing:skip-covered --cov=szurubooru szurubooru/'
|
||||
language: system
|
||||
types: [python]
|
||||
files: server/szurubooru/
|
||||
exclude: server/szurubooru/migrations/
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual]
|
||||
|
||||
fail_fast: true
|
||||
exclude: LICENSE.md
|
||||
|
|
|
@ -2,7 +2,7 @@ env:
|
|||
browser: true
|
||||
commonjs: true
|
||||
es6: true
|
||||
extends: 'eslint:recommended'
|
||||
extends: 'prettier'
|
||||
globals:
|
||||
Atomics: readonly
|
||||
SharedArrayBuffer: readonly
|
||||
|
@ -10,284 +10,3 @@ 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'
|
||||
|
|
4
client/.prettierrc.yml
Normal file
4
client/.prettierrc.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
parser: babel
|
||||
printWidth: 79
|
||||
tabWidth: 4
|
||||
quoteProps: consistent
|
250
client/js/api.js
250
client/js/api.js
|
@ -1,10 +1,10 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const cookies = require('js-cookie');
|
||||
const request = require('superagent');
|
||||
const events = require('./events.js');
|
||||
const progress = require('./util/progress.js');
|
||||
const uri = require('./util/uri.js');
|
||||
const cookies = require("js-cookie");
|
||||
const request = require("superagent");
|
||||
const events = require("./events.js");
|
||||
const progress = require("./util/progress.js");
|
||||
const uri = require("./util/uri.js");
|
||||
|
||||
let fileTokens = {};
|
||||
let remoteConfig = null;
|
||||
|
@ -18,22 +18,22 @@ class Api extends events.EventTarget {
|
|||
this.token = null;
|
||||
this.cache = {};
|
||||
this.allRanks = [
|
||||
'anonymous',
|
||||
'restricted',
|
||||
'regular',
|
||||
'power',
|
||||
'moderator',
|
||||
'administrator',
|
||||
'nobody',
|
||||
"anonymous",
|
||||
"restricted",
|
||||
"regular",
|
||||
"power",
|
||||
"moderator",
|
||||
"administrator",
|
||||
"nobody",
|
||||
];
|
||||
this.rankNames = new Map([
|
||||
['anonymous', 'Anonymous'],
|
||||
['restricted', 'Restricted user'],
|
||||
['regular', 'Regular user'],
|
||||
['power', 'Power user'],
|
||||
['moderator', 'Moderator'],
|
||||
['administrator', 'Administrator'],
|
||||
['nobody', 'Nobody'],
|
||||
["anonymous", "Anonymous"],
|
||||
["restricted", "Restricted user"],
|
||||
["regular", "Regular user"],
|
||||
["power", "Power user"],
|
||||
["moderator", "Moderator"],
|
||||
["administrator", "Administrator"],
|
||||
["nobody", "Nobody"],
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -43,11 +43,12 @@ class Api extends events.EventTarget {
|
|||
resolve(this.cache[url]);
|
||||
});
|
||||
}
|
||||
return this._wrappedRequest(url, request.get, {}, {}, options)
|
||||
.then(response => {
|
||||
return this._wrappedRequest(url, request.get, {}, {}, options).then(
|
||||
(response) => {
|
||||
this.cache[url] = response;
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
post(url, data, files, options) {
|
||||
|
@ -67,10 +68,9 @@ class Api extends events.EventTarget {
|
|||
|
||||
fetchConfig() {
|
||||
if (remoteConfig === null) {
|
||||
return this.get(uri.formatApiLink('info'))
|
||||
.then(response => {
|
||||
remoteConfig = response.config;
|
||||
});
|
||||
return this.get(uri.formatApiLink("info")).then((response) => {
|
||||
remoteConfig = response.config;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -115,7 +115,8 @@ class Api extends events.EventTarget {
|
|||
continue;
|
||||
}
|
||||
const rankIndex = this.allRanks.indexOf(
|
||||
remoteConfig.privileges[p]);
|
||||
remoteConfig.privileges[p]
|
||||
);
|
||||
if (minViableRank === null || rankIndex < minViableRank) {
|
||||
minViableRank = rankIndex;
|
||||
}
|
||||
|
@ -123,17 +124,16 @@ class Api extends events.EventTarget {
|
|||
if (minViableRank === null) {
|
||||
throw `Bad privilege name: ${lookup}`;
|
||||
}
|
||||
let myRank = this.user !== null ?
|
||||
this.allRanks.indexOf(this.user.rank) :
|
||||
0;
|
||||
let myRank =
|
||||
this.user !== null ? this.allRanks.indexOf(this.user.rank) : 0;
|
||||
return myRank >= minViableRank;
|
||||
}
|
||||
|
||||
loginFromCookies() {
|
||||
const auth = cookies.getJSON('auth');
|
||||
return auth && auth.user && auth.token ?
|
||||
this.loginWithToken(auth.user, auth.token, true) :
|
||||
Promise.resolve();
|
||||
const auth = cookies.getJSON("auth");
|
||||
return auth && auth.user && auth.token
|
||||
? this.loginWithToken(auth.user, auth.token, true)
|
||||
: Promise.resolve();
|
||||
}
|
||||
|
||||
loginWithToken(userName, token, doRemember) {
|
||||
|
@ -141,63 +141,74 @@ class Api extends events.EventTarget {
|
|||
return new Promise((resolve, reject) => {
|
||||
this.userName = userName;
|
||||
this.token = token;
|
||||
this.get('/user/' + userName + '?bump-login=true')
|
||||
.then(response => {
|
||||
this.get("/user/" + userName + "?bump-login=true").then(
|
||||
(response) => {
|
||||
const options = {};
|
||||
if (doRemember) {
|
||||
options.expires = 365;
|
||||
}
|
||||
cookies.set(
|
||||
'auth',
|
||||
{'user': userName, 'token': token},
|
||||
options);
|
||||
"auth",
|
||||
{ user: userName, token: token },
|
||||
options
|
||||
);
|
||||
this.user = response;
|
||||
resolve();
|
||||
this.dispatchEvent(new CustomEvent('login'));
|
||||
}, error => {
|
||||
this.dispatchEvent(new CustomEvent("login"));
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
createToken(userName, options) {
|
||||
let userTokenRequest = {
|
||||
enabled: true,
|
||||
note: 'Web Login Token'
|
||||
note: "Web Login Token",
|
||||
};
|
||||
if (typeof options.expires !== 'undefined') {
|
||||
userTokenRequest.expirationTime = new Date().addDays(options.expires).toISOString()
|
||||
if (typeof options.expires !== "undefined") {
|
||||
userTokenRequest.expirationTime = new Date()
|
||||
.addDays(options.expires)
|
||||
.toISOString();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.post('/user-token/' + userName, userTokenRequest)
|
||||
.then(response => {
|
||||
this.post("/user-token/" + userName, userTokenRequest).then(
|
||||
(response) => {
|
||||
cookies.set(
|
||||
'auth',
|
||||
{'user': userName, 'token': response.token},
|
||||
options);
|
||||
"auth",
|
||||
{ user: userName, token: response.token },
|
||||
options
|
||||
);
|
||||
this.userName = userName;
|
||||
this.token = response.token;
|
||||
this.userPassword = null;
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
deleteToken(userName, userToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.delete('/user-token/' + userName + '/' + userToken, {})
|
||||
.then(response => {
|
||||
this.delete("/user-token/" + userName + "/" + userToken, {}).then(
|
||||
(response) => {
|
||||
const options = {};
|
||||
cookies.set(
|
||||
'auth',
|
||||
{'user': userName, 'token': null},
|
||||
options);
|
||||
"auth",
|
||||
{ user: userName, token: null },
|
||||
options
|
||||
);
|
||||
resolve();
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -206,8 +217,8 @@ class Api extends events.EventTarget {
|
|||
return new Promise((resolve, reject) => {
|
||||
this.userName = userName;
|
||||
this.userPassword = userPassword;
|
||||
this.get('/user/' + userName + '?bump-login=true')
|
||||
.then(response => {
|
||||
this.get("/user/" + userName + "?bump-login=true").then(
|
||||
(response) => {
|
||||
const options = {};
|
||||
if (doRemember) {
|
||||
options.expires = 365;
|
||||
|
@ -215,22 +226,26 @@ class Api extends events.EventTarget {
|
|||
this.createToken(this.userName, options);
|
||||
this.user = response;
|
||||
resolve();
|
||||
this.dispatchEvent(new CustomEvent('login'));
|
||||
}, error => {
|
||||
this.dispatchEvent(new CustomEvent("login"));
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
let self = this;
|
||||
this.deleteToken(this.userName, this.token)
|
||||
.then(response => {
|
||||
this.deleteToken(this.userName, this.token).then(
|
||||
(response) => {
|
||||
self._logout();
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
self._logout();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_logout() {
|
||||
|
@ -238,17 +253,19 @@ class Api extends events.EventTarget {
|
|||
this.userName = null;
|
||||
this.userPassword = null;
|
||||
this.token = null;
|
||||
this.dispatchEvent(new CustomEvent('logout'));
|
||||
this.dispatchEvent(new CustomEvent("logout"));
|
||||
}
|
||||
|
||||
forget() {
|
||||
cookies.remove('auth');
|
||||
cookies.remove("auth");
|
||||
}
|
||||
|
||||
isLoggedIn(user) {
|
||||
if (user) {
|
||||
return this.userName !== null &&
|
||||
this.userName.toLowerCase() === user.name.toLowerCase();
|
||||
return (
|
||||
this.userName !== null &&
|
||||
this.userName.toLowerCase() === user.name.toLowerCase()
|
||||
);
|
||||
} else {
|
||||
return this.userName !== null;
|
||||
}
|
||||
|
@ -259,8 +276,7 @@ class Api extends events.EventTarget {
|
|||
}
|
||||
|
||||
_getFullUrl(url) {
|
||||
const fullUrl =
|
||||
('api/' + url).replace(/([^:])\/+/g, '$1/');
|
||||
const fullUrl = ("api/" + url).replace(/([^:])\/+/g, "$1/");
|
||||
const matches = fullUrl.match(/^([^?]*)\??(.*)$/);
|
||||
const baseUrl = matches[1];
|
||||
const request = matches[2];
|
||||
|
@ -285,7 +301,7 @@ class Api extends events.EventTarget {
|
|||
const file = files[key];
|
||||
const fileId = this._getFileId(file);
|
||||
if (fileTokens[fileId]) {
|
||||
data[key + 'Token'] = fileTokens[fileId];
|
||||
data[key + "Token"] = fileTokens[fileId];
|
||||
} else {
|
||||
promise = promise
|
||||
.then(() => {
|
||||
|
@ -293,33 +309,40 @@ class Api extends events.EventTarget {
|
|||
abortFunction = () => uploadPromise.abort();
|
||||
return uploadPromise;
|
||||
})
|
||||
.then(token => {
|
||||
.then((token) => {
|
||||
abortFunction = () => {};
|
||||
fileTokens[fileId] = token;
|
||||
data[key + 'Token'] = token;
|
||||
data[key + "Token"] = token;
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
promise = promise.then(
|
||||
() => {
|
||||
promise = promise
|
||||
.then(() => {
|
||||
let requestPromise = this._rawRequest(
|
||||
url, requestFactory, data, {}, options);
|
||||
url,
|
||||
requestFactory,
|
||||
data,
|
||||
{},
|
||||
options
|
||||
);
|
||||
abortFunction = () => requestPromise.abort();
|
||||
return requestPromise;
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response && error.response.name ===
|
||||
'MissingOrExpiredRequiredFileError') {
|
||||
.catch((error) => {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.name === "MissingOrExpiredRequiredFileError"
|
||||
) {
|
||||
for (let key of Object.keys(files)) {
|
||||
const file = files[key];
|
||||
const fileId = this._getFileId(file);
|
||||
fileTokens[fileId] = null;
|
||||
}
|
||||
error.message =
|
||||
'The uploaded file has expired; ' +
|
||||
'please resend the form to reupload.';
|
||||
"The uploaded file has expired; " +
|
||||
"please resend the form to reupload.";
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
@ -331,13 +354,17 @@ class Api extends events.EventTarget {
|
|||
let abortFunction = () => {};
|
||||
let returnedPromise = new Promise((resolve, reject) => {
|
||||
let uploadPromise = this._rawRequest(
|
||||
'uploads', request.post, {}, {content: file}, options);
|
||||
"uploads",
|
||||
request.post,
|
||||
{},
|
||||
{ content: file },
|
||||
options
|
||||
);
|
||||
abortFunction = () => uploadPromise.abort();
|
||||
return uploadPromise.then(
|
||||
response => {
|
||||
abortFunction = () => {};
|
||||
return resolve(response.token);
|
||||
}, reject);
|
||||
return uploadPromise.then((response) => {
|
||||
abortFunction = () => {};
|
||||
return resolve(response.token);
|
||||
}, reject);
|
||||
});
|
||||
returnedPromise.abort = () => abortFunction();
|
||||
return returnedPromise;
|
||||
|
@ -352,7 +379,7 @@ class Api extends events.EventTarget {
|
|||
let returnedPromise = new Promise((resolve, reject) => {
|
||||
let req = requestFactory(fullUrl);
|
||||
|
||||
req.set('Accept', 'application/json');
|
||||
req.set("Accept", "application/json");
|
||||
|
||||
if (query) {
|
||||
req.query(query);
|
||||
|
@ -362,7 +389,7 @@ class Api extends events.EventTarget {
|
|||
for (let key of Object.keys(files)) {
|
||||
const value = files[key];
|
||||
if (value.constructor === String) {
|
||||
data[key + 'Url'] = value;
|
||||
data[key + "Url"] = value;
|
||||
} else {
|
||||
req.attach(key, value || new Blob());
|
||||
}
|
||||
|
@ -371,9 +398,9 @@ class Api extends events.EventTarget {
|
|||
|
||||
if (data) {
|
||||
if (files && Object.keys(files).length) {
|
||||
req.attach('metadata', new Blob([JSON.stringify(data)]));
|
||||
req.attach("metadata", new Blob([JSON.stringify(data)]));
|
||||
} else {
|
||||
req.set('Content-Type', 'application/json');
|
||||
req.set("Content-Type", "application/json");
|
||||
req.send(data);
|
||||
}
|
||||
}
|
||||
|
@ -382,19 +409,28 @@ class Api extends events.EventTarget {
|
|||
if (this.userName && this.token) {
|
||||
req.auth = null;
|
||||
// eslint-disable-next-line no-undef
|
||||
req.set('Authorization', 'Token ' + new Buffer(
|
||||
this.userName + ":" + this.token).toString('base64'))
|
||||
req.set(
|
||||
"Authorization",
|
||||
"Token " +
|
||||
new Buffer(
|
||||
this.userName + ":" + this.token
|
||||
).toString("base64")
|
||||
);
|
||||
} else if (this.userName && this.userPassword) {
|
||||
req.auth(
|
||||
this.userName,
|
||||
encodeURIComponent(this.userPassword)
|
||||
.replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
}));
|
||||
encodeURIComponent(this.userPassword).replace(
|
||||
/%([0-9A-F]{2})/g,
|
||||
(match, p1) => {
|
||||
return String.fromCharCode("0x" + p1);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(
|
||||
new Error('Authentication error (malformed credentials)'));
|
||||
new Error("Authentication error (malformed credentials)")
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.noProgress) {
|
||||
|
@ -402,10 +438,11 @@ class Api extends events.EventTarget {
|
|||
}
|
||||
|
||||
abortFunction = () => {
|
||||
req.abort(); // does *NOT* call the callback passed in .end()
|
||||
req.abort(); // does *NOT* call the callback passed in .end()
|
||||
progress.done();
|
||||
reject(
|
||||
new Error('The request was aborted due to user cancel.'));
|
||||
new Error("The request was aborted due to user cancel.")
|
||||
);
|
||||
};
|
||||
|
||||
req.end((error, response) => {
|
||||
|
@ -414,7 +451,8 @@ class Api extends events.EventTarget {
|
|||
if (error) {
|
||||
if (response && response.body) {
|
||||
error = new Error(
|
||||
response.body.description || 'Unknown error');
|
||||
response.body.description || "Unknown error"
|
||||
);
|
||||
error.response = response.body;
|
||||
}
|
||||
reject(error);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const config = require('./.config.autogen.json');
|
||||
const config = require("./.config.autogen.json");
|
||||
module.exports = config;
|
||||
|
|
|
@ -1,38 +1,40 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const tags = require('../tags.js');
|
||||
const pools = require('../pools.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const LoginView = require('../views/login_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const tags = require("../tags.js");
|
||||
const pools = require("../pools.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const LoginView = require("../views/login_view.js");
|
||||
|
||||
class LoginController {
|
||||
constructor() {
|
||||
api.forget();
|
||||
topNavigation.activate('login');
|
||||
topNavigation.setTitle('Login');
|
||||
topNavigation.activate("login");
|
||||
topNavigation.setTitle("Login");
|
||||
|
||||
this._loginView = new LoginView();
|
||||
this._loginView.addEventListener('submit', e => this._evtLogin(e));
|
||||
this._loginView.addEventListener("submit", (e) => this._evtLogin(e));
|
||||
}
|
||||
|
||||
_evtLogin(e) {
|
||||
this._loginView.clearMessages();
|
||||
this._loginView.disableForm();
|
||||
api.forget();
|
||||
api.login(e.detail.name, e.detail.password, e.detail.remember)
|
||||
.then(() => {
|
||||
api.login(e.detail.name, e.detail.password, e.detail.remember).then(
|
||||
() => {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showSuccess('Logged in');
|
||||
ctx.controller.showSuccess("Logged in");
|
||||
// reload tag category color map, this is required when `tag_categories:list` has a permission other than anonymous
|
||||
tags.refreshCategoryColorMap();
|
||||
pools.refreshCategoryColorMap();
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
this._loginView.showError(error.message);
|
||||
this._loginView.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,15 +43,15 @@ class LogoutController {
|
|||
api.forget();
|
||||
api.logout();
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showSuccess('Logged out');
|
||||
ctx.controller.showSuccess("Logged out");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['login'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["login"], (ctx, next) => {
|
||||
ctx.controller = new LoginController();
|
||||
});
|
||||
router.enter(['logout'], (ctx, next) => {
|
||||
router.enter(["logout"], (ctx, next) => {
|
||||
ctx.controller = new LogoutController();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const api = require("../api.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class BasePostController {
|
||||
constructor(ctx) {
|
||||
if (!api.hasPrivilege('posts:view')) {
|
||||
if (!api.hasPrivilege("posts:view")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view posts.');
|
||||
this._view.showError("You don't have privileges to view posts.");
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('posts');
|
||||
topNavigation.setTitle('Post #' + ctx.parameters.id.toString());
|
||||
topNavigation.activate("posts");
|
||||
topNavigation.setTitle("Post #" + ctx.parameters.id.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +1,55 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const PostList = require('../models/post_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const CommentsPageView = require('../views/comments_page_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const PostList = require("../models/post_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PageController = require("../controllers/page_controller.js");
|
||||
const CommentsPageView = require("../views/comments_page_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
const fields = ['id', 'comments', 'commentCount', 'thumbnailUrl'];
|
||||
const fields = ["id", "comments", "commentCount", "thumbnailUrl"];
|
||||
|
||||
class CommentsController {
|
||||
constructor(ctx) {
|
||||
if (!api.hasPrivilege('comments:list')) {
|
||||
if (!api.hasPrivilege("comments:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(
|
||||
'You don\'t have privileges to view comments.');
|
||||
"You don't have privileges to view comments."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('comments');
|
||||
topNavigation.setTitle('Listing comments');
|
||||
topNavigation.activate("comments");
|
||||
topNavigation.setTitle("Listing comments");
|
||||
|
||||
this._pageController = new PageController();
|
||||
this._pageController.run({
|
||||
parameters: ctx.parameters,
|
||||
defaultLimit: 10,
|
||||
getClientUrlForPage: (offset, limit) => {
|
||||
const parameters = Object.assign(
|
||||
{}, ctx.parameters, {offset: offset, limit: limit});
|
||||
return uri.formatClientLink('comments', parameters);
|
||||
const parameters = Object.assign({}, ctx.parameters, {
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
return uri.formatClientLink("comments", parameters);
|
||||
},
|
||||
requestPage: (offset, limit) => {
|
||||
return PostList.search(
|
||||
'sort:comment-date comment-count-min:1',
|
||||
"sort:comment-date comment-count-min:1",
|
||||
offset,
|
||||
limit,
|
||||
fields);
|
||||
fields
|
||||
);
|
||||
},
|
||||
pageRenderer: pageCtx => {
|
||||
pageRenderer: (pageCtx) => {
|
||||
Object.assign(pageCtx, {
|
||||
canViewPosts: api.hasPrivilege('posts:view'),
|
||||
canViewPosts: api.hasPrivilege("posts:view"),
|
||||
});
|
||||
const view = new CommentsPageView(pageCtx);
|
||||
view.addEventListener('submit', e => this._evtUpdate(e));
|
||||
view.addEventListener('score', e => this._evtScore(e));
|
||||
view.addEventListener('delete', e => this._evtDelete(e));
|
||||
view.addEventListener("submit", (e) => this._evtUpdate(e));
|
||||
view.addEventListener("score", (e) => this._evtScore(e));
|
||||
view.addEventListener("delete", (e) => this._evtDelete(e));
|
||||
return view;
|
||||
},
|
||||
});
|
||||
|
@ -54,26 +58,27 @@ class CommentsController {
|
|||
_evtUpdate(e) {
|
||||
// TODO: disable form
|
||||
e.detail.comment.text = e.detail.text;
|
||||
e.detail.comment.save()
|
||||
.catch(error => {
|
||||
e.detail.target.showError(error.message);
|
||||
// TODO: enable form
|
||||
});
|
||||
e.detail.comment.save().catch((error) => {
|
||||
e.detail.target.showError(error.message);
|
||||
// TODO: enable form
|
||||
});
|
||||
}
|
||||
|
||||
_evtScore(e) {
|
||||
e.detail.comment.setScore(e.detail.score)
|
||||
.catch(error => window.alert(error.message));
|
||||
e.detail.comment
|
||||
.setScore(e.detail.score)
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_evtDelete(e) {
|
||||
e.detail.comment.delete()
|
||||
.catch(error => window.alert(error.message));
|
||||
e.detail.comment
|
||||
.delete()
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['comments'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["comments"], (ctx, next) => {
|
||||
new CommentsController(ctx);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const HelpView = require('../views/help_view.js');
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const HelpView = require("../views/help_view.js");
|
||||
|
||||
class HelpController {
|
||||
constructor(section, subsection) {
|
||||
topNavigation.activate('help');
|
||||
topNavigation.setTitle('Help');
|
||||
topNavigation.activate("help");
|
||||
topNavigation.setTitle("Help");
|
||||
this._helpView = new HelpView(section, subsection);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['help'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["help"], (ctx, next) => {
|
||||
new HelpController();
|
||||
});
|
||||
router.enter(['help', ':section'], (ctx, next) => {
|
||||
router.enter(["help", ":section"], (ctx, next) => {
|
||||
new HelpController(ctx.parameters.section);
|
||||
});
|
||||
router.enter(['help', ':section', ':subsection'], (ctx, next) => {
|
||||
router.enter(["help", ":section", ":subsection"], (ctx, next) => {
|
||||
new HelpController(ctx.parameters.section, ctx.parameters.subsection);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const config = require('../config.js');
|
||||
const Info = require('../models/info.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const HomeView = require('../views/home_view.js');
|
||||
const api = require("../api.js");
|
||||
const config = require("../config.js");
|
||||
const Info = require("../models/info.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const HomeView = require("../views/home_view.js");
|
||||
|
||||
class HomeController {
|
||||
constructor() {
|
||||
topNavigation.activate('home');
|
||||
topNavigation.setTitle('Home');
|
||||
topNavigation.activate("home");
|
||||
topNavigation.setTitle("Home");
|
||||
|
||||
this._homeView = new HomeView({
|
||||
name: api.getName(),
|
||||
version: config.meta.version,
|
||||
buildDate: config.meta.buildDate,
|
||||
canListSnapshots: api.hasPrivilege('snapshots:list'),
|
||||
canListPosts: api.hasPrivilege('posts:list'),
|
||||
canListSnapshots: api.hasPrivilege("snapshots:list"),
|
||||
canListPosts: api.hasPrivilege("posts:list"),
|
||||
});
|
||||
|
||||
Info.get()
|
||||
.then(info => {
|
||||
Info.get().then(
|
||||
(info) => {
|
||||
this._homeView.setStats({
|
||||
diskUsage: info.diskUsage,
|
||||
postCount: info.postCount,
|
||||
|
@ -31,7 +31,8 @@ class HomeController {
|
|||
featuringTime: info.featuringTime,
|
||||
});
|
||||
},
|
||||
error => this._homeView.showError(error.message));
|
||||
(error) => this._homeView.showError(error.message)
|
||||
);
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
|
@ -43,8 +44,8 @@ class HomeController {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
module.exports = (router) => {
|
||||
router.enter([], (ctx, next) => {
|
||||
ctx.controller = new HomeController();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const NotFoundView = require('../views/not_found_view.js');
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const NotFoundView = require("../views/not_found_view.js");
|
||||
|
||||
class NotFoundController {
|
||||
constructor(path) {
|
||||
topNavigation.activate('');
|
||||
topNavigation.setTitle('Not found');
|
||||
topNavigation.activate("");
|
||||
topNavigation.setTitle("Not found");
|
||||
this._notFoundView = new NotFoundView(path);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
module.exports = (router) => {
|
||||
router.enter(null, (ctx, next) => {
|
||||
ctx.controller = new NotFoundController(ctx.canonicalPath);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const settings = require('../models/settings.js');
|
||||
const EndlessPageView = require('../views/endless_page_view.js');
|
||||
const ManualPageView = require('../views/manual_page_view.js');
|
||||
const settings = require("../models/settings.js");
|
||||
const EndlessPageView = require("../views/endless_page_view.js");
|
||||
const ManualPageView = require("../views/manual_page_view.js");
|
||||
|
||||
class PageController {
|
||||
constructor(ctx) {
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PasswordResetView = require('../views/password_reset_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PasswordResetView = require("../views/password_reset_view.js");
|
||||
|
||||
class PasswordResetController {
|
||||
constructor() {
|
||||
topNavigation.activate('login');
|
||||
topNavigation.setTitle('Password reminder');
|
||||
topNavigation.activate("login");
|
||||
topNavigation.setTitle("Password reminder");
|
||||
|
||||
this._passwordResetView = new PasswordResetView();
|
||||
this._passwordResetView.addEventListener(
|
||||
'submit', e => this._evtReset(e));
|
||||
this._passwordResetView.addEventListener("submit", (e) =>
|
||||
this._evtReset(e)
|
||||
);
|
||||
}
|
||||
|
||||
_evtReset(e) {
|
||||
|
@ -21,15 +22,20 @@ class PasswordResetController {
|
|||
this._passwordResetView.disableForm();
|
||||
api.forget();
|
||||
api.logout();
|
||||
api.get(uri.formatApiLink('password-reset', e.detail.userNameOrEmail))
|
||||
.then(() => {
|
||||
api.get(
|
||||
uri.formatApiLink("password-reset", e.detail.userNameOrEmail)
|
||||
).then(
|
||||
() => {
|
||||
this._passwordResetView.showSuccess(
|
||||
'E-mail has been sent. To finish the procedure, ' +
|
||||
'please click the link it contains.');
|
||||
}, error => {
|
||||
"E-mail has been sent. To finish the procedure, " +
|
||||
"please click the link it contains."
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._passwordResetView.showError(error.message);
|
||||
this._passwordResetView.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,26 +44,30 @@ class PasswordResetFinishController {
|
|||
api.forget();
|
||||
api.logout();
|
||||
let password = null;
|
||||
api.post(uri.formatApiLink('password-reset', name), {token: token})
|
||||
.then(response => {
|
||||
api.post(uri.formatApiLink("password-reset", name), { token: token })
|
||||
.then((response) => {
|
||||
password = response.password;
|
||||
return api.login(name, password, false);
|
||||
}).then(() => {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showSuccess('New password: ' + password);
|
||||
}, error => {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showError(error.message);
|
||||
});
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showSuccess("New password: " + password);
|
||||
},
|
||||
(error) => {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showError(error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['password-reset'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["password-reset"], (ctx, next) => {
|
||||
ctx.controller = new PasswordResetController();
|
||||
});
|
||||
router.enter(['password-reset', ':descriptor'], (ctx, next) => {
|
||||
const [name, token] = ctx.parameters.descriptor.split(':', 2);
|
||||
router.enter(["password-reset", ":descriptor"], (ctx, next) => {
|
||||
const [name, token] = ctx.parameters.descriptor.split(":", 2);
|
||||
ctx.controller = new PasswordResetFinishController(name, token);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,57 +1,69 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const pools = require('../pools.js');
|
||||
const PoolCategoryList = require('../models/pool_category_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PoolCategoriesView = require('../views/pool_categories_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const api = require("../api.js");
|
||||
const pools = require("../pools.js");
|
||||
const PoolCategoryList = require("../models/pool_category_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PoolCategoriesView = require("../views/pool_categories_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class PoolCategoriesController {
|
||||
constructor() {
|
||||
if (!api.hasPrivilege('poolCategories:list')) {
|
||||
if (!api.hasPrivilege("poolCategories:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(
|
||||
'You don\'t have privileges to view pool categories.');
|
||||
"You don't have privileges to view pool categories."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('pools');
|
||||
topNavigation.setTitle('Listing pools');
|
||||
PoolCategoryList.get().then(response => {
|
||||
this._poolCategories = response.results;
|
||||
this._view = new PoolCategoriesView({
|
||||
poolCategories: this._poolCategories,
|
||||
canEditName: api.hasPrivilege('poolCategories:edit:name'),
|
||||
canEditColor: api.hasPrivilege('poolCategories:edit:color'),
|
||||
canDelete: api.hasPrivilege('poolCategories:delete'),
|
||||
canCreate: api.hasPrivilege('poolCategories:create'),
|
||||
canSetDefault: api.hasPrivilege('poolCategories:setDefault'),
|
||||
});
|
||||
this._view.addEventListener('submit', e => this._evtSubmit(e));
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
topNavigation.activate("pools");
|
||||
topNavigation.setTitle("Listing pools");
|
||||
PoolCategoryList.get().then(
|
||||
(response) => {
|
||||
this._poolCategories = response.results;
|
||||
this._view = new PoolCategoriesView({
|
||||
poolCategories: this._poolCategories,
|
||||
canEditName: api.hasPrivilege("poolCategories:edit:name"),
|
||||
canEditColor: api.hasPrivilege(
|
||||
"poolCategories:edit:color"
|
||||
),
|
||||
canDelete: api.hasPrivilege("poolCategories:delete"),
|
||||
canCreate: api.hasPrivilege("poolCategories:create"),
|
||||
canSetDefault: api.hasPrivilege(
|
||||
"poolCategories:setDefault"
|
||||
),
|
||||
});
|
||||
this._view.addEventListener("submit", (e) =>
|
||||
this._evtSubmit(e)
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtSubmit(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
this._poolCategories.save()
|
||||
.then(() => {
|
||||
this._poolCategories.save().then(
|
||||
() => {
|
||||
pools.refreshCategoryColorMap();
|
||||
this._view.enableForm();
|
||||
this._view.showSuccess('Changes saved.');
|
||||
}, error => {
|
||||
this._view.showSuccess("Changes saved.");
|
||||
},
|
||||
(error) => {
|
||||
this._view.enableForm();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['pool-categories'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["pool-categories"], (ctx, next) => {
|
||||
ctx.controller = new PoolCategoriesController(ctx, next);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,63 +1,76 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const Pool = require('../models/pool.js');
|
||||
const Post = require('../models/post.js');
|
||||
const PoolCategoryList = require('../models/pool_category_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PoolView = require('../views/pool_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const Pool = require("../models/pool.js");
|
||||
const Post = require("../models/post.js");
|
||||
const PoolCategoryList = require("../models/pool_category_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PoolView = require("../views/pool_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class PoolController {
|
||||
constructor(ctx, section) {
|
||||
if (!api.hasPrivilege('pools:view')) {
|
||||
if (!api.hasPrivilege("pools:view")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view pools.');
|
||||
this._view.showError("You don't have privileges to view pools.");
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
PoolCategoryList.get(),
|
||||
Pool.get(ctx.parameters.id)
|
||||
]).then(responses => {
|
||||
const [poolCategoriesResponse, pool] = responses;
|
||||
Pool.get(ctx.parameters.id),
|
||||
]).then(
|
||||
(responses) => {
|
||||
const [poolCategoriesResponse, pool] = responses;
|
||||
|
||||
topNavigation.activate('pools');
|
||||
topNavigation.setTitle('Pool #' + pool.names[0]);
|
||||
topNavigation.activate("pools");
|
||||
topNavigation.setTitle("Pool #" + pool.names[0]);
|
||||
|
||||
this._name = ctx.parameters.name;
|
||||
pool.addEventListener('change', e => this._evtSaved(e, section));
|
||||
this._name = ctx.parameters.name;
|
||||
pool.addEventListener("change", (e) =>
|
||||
this._evtSaved(e, section)
|
||||
);
|
||||
|
||||
const categories = {};
|
||||
for (let category of poolCategoriesResponse.results) {
|
||||
categories[category.name] = category.name;
|
||||
const categories = {};
|
||||
for (let category of poolCategoriesResponse.results) {
|
||||
categories[category.name] = category.name;
|
||||
}
|
||||
|
||||
this._view = new PoolView({
|
||||
pool: pool,
|
||||
section: section,
|
||||
canEditAnything: api.hasPrivilege("pools:edit"),
|
||||
canEditNames: api.hasPrivilege("pools:edit:names"),
|
||||
canEditCategory: api.hasPrivilege("pools:edit:category"),
|
||||
canEditDescription: api.hasPrivilege(
|
||||
"pools:edit:description"
|
||||
),
|
||||
canEditPosts: api.hasPrivilege("pools:edit:posts"),
|
||||
canMerge: api.hasPrivilege("pools:merge"),
|
||||
canDelete: api.hasPrivilege("pools:delete"),
|
||||
categories: categories,
|
||||
escapeColons: uri.escapeColons,
|
||||
});
|
||||
|
||||
this._view.addEventListener("change", (e) =>
|
||||
this._evtChange(e)
|
||||
);
|
||||
this._view.addEventListener("submit", (e) =>
|
||||
this._evtUpdate(e)
|
||||
);
|
||||
this._view.addEventListener("merge", (e) => this._evtMerge(e));
|
||||
this._view.addEventListener("delete", (e) =>
|
||||
this._evtDelete(e)
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
|
||||
this._view = new PoolView({
|
||||
pool: pool,
|
||||
section: section,
|
||||
canEditAnything: api.hasPrivilege('pools:edit'),
|
||||
canEditNames: api.hasPrivilege('pools:edit:names'),
|
||||
canEditCategory: api.hasPrivilege('pools:edit:category'),
|
||||
canEditDescription: api.hasPrivilege('pools:edit:description'),
|
||||
canEditPosts: api.hasPrivilege('pools:edit:posts'),
|
||||
canMerge: api.hasPrivilege('pools:merge'),
|
||||
canDelete: api.hasPrivilege('pools:delete'),
|
||||
categories: categories,
|
||||
escapeColons: uri.escapeColons,
|
||||
});
|
||||
|
||||
this._view.addEventListener('change', e => this._evtChange(e));
|
||||
this._view.addEventListener('submit', e => this._evtUpdate(e));
|
||||
this._view.addEventListener('merge', e => this._evtMerge(e));
|
||||
this._view.addEventListener('delete', e => this._evtDelete(e));
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
|
@ -67,7 +80,11 @@ class PoolController {
|
|||
_evtSaved(e, section) {
|
||||
misc.disableExitConfirmation();
|
||||
if (this._name !== e.detail.pool.names[0]) {
|
||||
router.replace(uri.formatClientLink('pool', e.detail.pool.id, section), null, false);
|
||||
router.replace(
|
||||
uri.formatClientLink("pool", e.detail.pool.id, section),
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,62 +103,74 @@ class PoolController {
|
|||
if (e.detail.posts !== undefined) {
|
||||
e.detail.pool.posts.clear();
|
||||
for (let postId of e.detail.posts) {
|
||||
e.detail.pool.posts.add(Post.fromResponse({id: parseInt(postId)}));
|
||||
e.detail.pool.posts.add(
|
||||
Post.fromResponse({ id: parseInt(postId) })
|
||||
);
|
||||
}
|
||||
}
|
||||
e.detail.pool.save().then(() => {
|
||||
this._view.showSuccess('Pool saved.');
|
||||
this._view.enableForm();
|
||||
}, error => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
e.detail.pool.save().then(
|
||||
() => {
|
||||
this._view.showSuccess("Pool saved.");
|
||||
this._view.enableForm();
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtMerge(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
e.detail.pool
|
||||
.merge(e.detail.targetPoolId, e.detail.addAlias)
|
||||
.then(() => {
|
||||
this._view.showSuccess('Pool merged.');
|
||||
e.detail.pool.merge(e.detail.targetPoolId, e.detail.addAlias).then(
|
||||
() => {
|
||||
this._view.showSuccess("Pool merged.");
|
||||
this._view.enableForm();
|
||||
router.replace(
|
||||
uri.formatClientLink(
|
||||
'pool', e.detail.targetPoolId, 'merge'),
|
||||
"pool",
|
||||
e.detail.targetPoolId,
|
||||
"merge"
|
||||
),
|
||||
null,
|
||||
false);
|
||||
}, error => {
|
||||
false
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtDelete(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
e.detail.pool.delete()
|
||||
.then(() => {
|
||||
const ctx = router.show(uri.formatClientLink('pools'));
|
||||
ctx.controller.showSuccess('Pool deleted.');
|
||||
}, error => {
|
||||
e.detail.pool.delete().then(
|
||||
() => {
|
||||
const ctx = router.show(uri.formatClientLink("pools"));
|
||||
ctx.controller.showSuccess("Pool deleted.");
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['pool', ':id', 'edit'], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, 'edit');
|
||||
module.exports = (router) => {
|
||||
router.enter(["pool", ":id", "edit"], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, "edit");
|
||||
});
|
||||
router.enter(['pool', ':id', 'merge'], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, 'merge');
|
||||
router.enter(["pool", ":id", "merge"], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, "merge");
|
||||
});
|
||||
router.enter(['pool', ':id', 'delete'], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, 'delete');
|
||||
router.enter(["pool", ":id", "delete"], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, "delete");
|
||||
});
|
||||
router.enter(['pool', ':id'], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, 'summary');
|
||||
router.enter(["pool", ":id"], (ctx, next) => {
|
||||
ctx.controller = new PoolController(ctx, "summary");
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,58 +1,65 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const PoolCategoryList = require('../models/pool_category_list.js');
|
||||
const PoolCreateView = require('../views/pool_create_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const PoolCategoryList = require("../models/pool_category_list.js");
|
||||
const PoolCreateView = require("../views/pool_create_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class PoolCreateController {
|
||||
constructor(ctx) {
|
||||
if (!api.hasPrivilege('pools:create')) {
|
||||
if (!api.hasPrivilege("pools:create")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to create pools.');
|
||||
this._view.showError("You don't have privileges to create pools.");
|
||||
return;
|
||||
}
|
||||
|
||||
PoolCategoryList.get().then(poolCategoriesResponse => {
|
||||
const categories = {};
|
||||
for (let category of poolCategoriesResponse.results) {
|
||||
categories[category.name] = category.name;
|
||||
PoolCategoryList.get().then(
|
||||
(poolCategoriesResponse) => {
|
||||
const categories = {};
|
||||
for (let category of poolCategoriesResponse.results) {
|
||||
categories[category.name] = category.name;
|
||||
}
|
||||
|
||||
this._view = new PoolCreateView({
|
||||
canCreate: api.hasPrivilege("pools:create"),
|
||||
categories: categories,
|
||||
escapeColons: uri.escapeColons,
|
||||
});
|
||||
|
||||
this._view.addEventListener("submit", (e) =>
|
||||
this._evtCreate(e)
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
|
||||
this._view = new PoolCreateView({
|
||||
canCreate: api.hasPrivilege('pools:create'),
|
||||
categories: categories,
|
||||
escapeColons: uri.escapeColons,
|
||||
});
|
||||
|
||||
this._view.addEventListener('submit', e => this._evtCreate(e));
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
_evtCreate(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
api.post(uri.formatApiLink('pool'), e.detail)
|
||||
.then(() => {
|
||||
api.post(uri.formatApiLink("pool"), e.detail).then(
|
||||
() => {
|
||||
this._view.clearMessages();
|
||||
misc.disableExitConfirmation();
|
||||
const ctx = router.show(uri.formatClientLink('pools'));
|
||||
ctx.controller.showSuccess('Pool created.');
|
||||
}, error => {
|
||||
const ctx = router.show(uri.formatClientLink("pools"));
|
||||
ctx.controller.showSuccess("Pool created.");
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['pool', 'create'], (ctx, next) => {
|
||||
ctx.controller = new PoolCreateController(ctx, 'create');
|
||||
module.exports = (router) => {
|
||||
router.enter(["pool", "create"], (ctx, next) => {
|
||||
ctx.controller = new PoolCreateController(ctx, "create");
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,47 +1,51 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const PoolList = require('../models/pool_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const PoolsHeaderView = require('../views/pools_header_view.js');
|
||||
const PoolsPageView = require('../views/pools_page_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const PoolList = require("../models/pool_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PageController = require("../controllers/page_controller.js");
|
||||
const PoolsHeaderView = require("../views/pools_header_view.js");
|
||||
const PoolsPageView = require("../views/pools_page_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
const fields = [
|
||||
'id',
|
||||
'names',
|
||||
'posts',
|
||||
'creationTime',
|
||||
'postCount',
|
||||
'category'
|
||||
"id",
|
||||
"names",
|
||||
"posts",
|
||||
"creationTime",
|
||||
"postCount",
|
||||
"category",
|
||||
];
|
||||
|
||||
class PoolListController {
|
||||
constructor(ctx) {
|
||||
this._pageController = new PageController();
|
||||
|
||||
if (!api.hasPrivilege('pools:list')) {
|
||||
if (!api.hasPrivilege("pools:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view pools.');
|
||||
this._view.showError("You don't have privileges to view pools.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._ctx = ctx;
|
||||
|
||||
topNavigation.activate('pools');
|
||||
topNavigation.setTitle('Listing pools');
|
||||
topNavigation.activate("pools");
|
||||
topNavigation.setTitle("Listing pools");
|
||||
|
||||
this._headerView = new PoolsHeaderView({
|
||||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||
parameters: ctx.parameters,
|
||||
canCreate: api.hasPrivilege('pools:create'),
|
||||
canEditPoolCategories: api.hasPrivilege('poolCategories:edit'),
|
||||
canCreate: api.hasPrivilege("pools:create"),
|
||||
canEditPoolCategories: api.hasPrivilege("poolCategories:edit"),
|
||||
});
|
||||
this._headerView.addEventListener(
|
||||
'submit', e => this._evtSubmit(e), 'navigate', e => this._evtNavigate(e));
|
||||
"submit",
|
||||
(e) => this._evtSubmit(e),
|
||||
"navigate",
|
||||
(e) => this._evtNavigate(e)
|
||||
);
|
||||
|
||||
this._syncPageController();
|
||||
}
|
||||
|
@ -57,24 +61,27 @@ class PoolListController {
|
|||
_evtSubmit(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
e.detail.pool.save()
|
||||
.then(() => {
|
||||
this._installView(e.detail.pool, 'edit');
|
||||
this._view.showSuccess('Pool created.');
|
||||
e.detail.pool.save().then(
|
||||
() => {
|
||||
this._installView(e.detail.pool, "edit");
|
||||
this._view.showSuccess("Pool created.");
|
||||
router.replace(
|
||||
uri.formatClientLink(
|
||||
'pool', e.detail.pool.id, 'edit'),
|
||||
uri.formatClientLink("pool", e.detail.pool.id, "edit"),
|
||||
null,
|
||||
false);
|
||||
}, error => {
|
||||
false
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtNavigate(e) {
|
||||
router.showNoDispatch(
|
||||
uri.formatClientLink('pools', e.detail.parameters));
|
||||
uri.formatClientLink("pools", e.detail.parameters)
|
||||
);
|
||||
Object.assign(this._ctx.parameters, e.detail.parameters);
|
||||
this._syncPageController();
|
||||
}
|
||||
|
@ -84,25 +91,29 @@ class PoolListController {
|
|||
parameters: this._ctx.parameters,
|
||||
defaultLimit: 50,
|
||||
getClientUrlForPage: (offset, limit) => {
|
||||
const parameters = Object.assign(
|
||||
{}, this._ctx.parameters, {offset: offset, limit: limit});
|
||||
return uri.formatClientLink('pools', parameters);
|
||||
const parameters = Object.assign({}, this._ctx.parameters, {
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
return uri.formatClientLink("pools", parameters);
|
||||
},
|
||||
requestPage: (offset, limit) => {
|
||||
return PoolList.search(
|
||||
this._ctx.parameters.query, offset, limit, fields);
|
||||
this._ctx.parameters.query,
|
||||
offset,
|
||||
limit,
|
||||
fields
|
||||
);
|
||||
},
|
||||
pageRenderer: pageCtx => {
|
||||
pageRenderer: (pageCtx) => {
|
||||
return new PoolsPageView(pageCtx);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(
|
||||
['pools'],
|
||||
(ctx, next) => {
|
||||
ctx.controller = new PoolListController(ctx);
|
||||
});
|
||||
module.exports = (router) => {
|
||||
router.enter(["pools"], (ctx, next) => {
|
||||
ctx.controller = new PoolListController(ctx);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,28 +1,33 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const settings = require('../models/settings.js');
|
||||
const Post = require('../models/post.js');
|
||||
const PostList = require('../models/post_list.js');
|
||||
const PostDetailView = require('../views/post_detail_view.js');
|
||||
const BasePostController = require('./base_post_controller.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const settings = require("../models/settings.js");
|
||||
const Post = require("../models/post.js");
|
||||
const PostList = require("../models/post_list.js");
|
||||
const PostDetailView = require("../views/post_detail_view.js");
|
||||
const BasePostController = require("./base_post_controller.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class PostDetailController extends BasePostController {
|
||||
constructor(ctx, section) {
|
||||
super(ctx);
|
||||
|
||||
Post.get(ctx.parameters.id).then(post => {
|
||||
this._id = ctx.parameters.id;
|
||||
post.addEventListener('change', e => this._evtSaved(e, section));
|
||||
this._installView(post, section);
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
Post.get(ctx.parameters.id).then(
|
||||
(post) => {
|
||||
this._id = ctx.parameters.id;
|
||||
post.addEventListener("change", (e) =>
|
||||
this._evtSaved(e, section)
|
||||
);
|
||||
this._installView(post, section);
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
|
@ -33,58 +38,68 @@ class PostDetailController extends BasePostController {
|
|||
this._view = new PostDetailView({
|
||||
post: post,
|
||||
section: section,
|
||||
canMerge: api.hasPrivilege('posts:merge'),
|
||||
canMerge: api.hasPrivilege("posts:merge"),
|
||||
});
|
||||
|
||||
this._view.addEventListener('select', e => this._evtSelect(e));
|
||||
this._view.addEventListener('merge', e => this._evtMerge(e));
|
||||
this._view.addEventListener("select", (e) => this._evtSelect(e));
|
||||
this._view.addEventListener("merge", (e) => this._evtMerge(e));
|
||||
}
|
||||
|
||||
_evtSelect(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
Post.get(e.detail.postId).then(post => {
|
||||
this._view.selectPost(post);
|
||||
this._view.enableForm();
|
||||
}, error => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
Post.get(e.detail.postId).then(
|
||||
(post) => {
|
||||
this._view.selectPost(post);
|
||||
this._view.enableForm();
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtSaved(e, section) {
|
||||
misc.disableExitConfirmation();
|
||||
if (this._id !== e.detail.post.id) {
|
||||
router.replace(
|
||||
uri.formatClientLink('post', e.detail.post.id, section),
|
||||
uri.formatClientLink("post", e.detail.post.id, section),
|
||||
null,
|
||||
false);
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_evtMerge(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
e.detail.post.merge(e.detail.targetPost.id, e.detail.useOldContent)
|
||||
.then(() => {
|
||||
this._installView(e.detail.post, 'merge');
|
||||
this._view.showSuccess('Post merged.');
|
||||
router.replace(
|
||||
uri.formatClientLink(
|
||||
'post', e.detail.targetPost.id, 'merge'),
|
||||
null,
|
||||
false);
|
||||
}, error => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
e.detail.post
|
||||
.merge(e.detail.targetPost.id, e.detail.useOldContent)
|
||||
.then(
|
||||
() => {
|
||||
this._installView(e.detail.post, "merge");
|
||||
this._view.showSuccess("Post merged.");
|
||||
router.replace(
|
||||
uri.formatClientLink(
|
||||
"post",
|
||||
e.detail.targetPost.id,
|
||||
"merge"
|
||||
),
|
||||
null,
|
||||
false
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(
|
||||
['post', ':id', 'merge'],
|
||||
(ctx, next) => {
|
||||
ctx.controller = new PostDetailController(ctx, 'merge');
|
||||
});
|
||||
module.exports = (router) => {
|
||||
router.enter(["post", ":id", "merge"], (ctx, next) => {
|
||||
ctx.controller = new PostDetailController(ctx, "merge");
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,48 +1,56 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const settings = require('../models/settings.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const PostList = require('../models/post_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const PostsHeaderView = require('../views/posts_header_view.js');
|
||||
const PostsPageView = require('../views/posts_page_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const settings = require("../models/settings.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const PostList = require("../models/post_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PageController = require("../controllers/page_controller.js");
|
||||
const PostsHeaderView = require("../views/posts_header_view.js");
|
||||
const PostsPageView = require("../views/posts_page_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
const fields = [
|
||||
'id', 'thumbnailUrl', 'type', 'safety',
|
||||
'score', 'favoriteCount', 'commentCount', 'tags', 'version'
|
||||
"id",
|
||||
"thumbnailUrl",
|
||||
"type",
|
||||
"safety",
|
||||
"score",
|
||||
"favoriteCount",
|
||||
"commentCount",
|
||||
"tags",
|
||||
"version",
|
||||
];
|
||||
|
||||
class PostListController {
|
||||
constructor(ctx) {
|
||||
this._pageController = new PageController();
|
||||
|
||||
if (!api.hasPrivilege('posts:list')) {
|
||||
if (!api.hasPrivilege("posts:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view posts.');
|
||||
this._view.showError("You don't have privileges to view posts.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._ctx = ctx;
|
||||
|
||||
topNavigation.activate('posts');
|
||||
topNavigation.setTitle('Listing posts');
|
||||
topNavigation.activate("posts");
|
||||
topNavigation.setTitle("Listing posts");
|
||||
|
||||
this._headerView = new PostsHeaderView({
|
||||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||
parameters: ctx.parameters,
|
||||
enableSafety: api.safetyEnabled(),
|
||||
canBulkEditTags: api.hasPrivilege('posts:bulk-edit:tags'),
|
||||
canBulkEditSafety: api.hasPrivilege('posts:bulk-edit:safety'),
|
||||
canBulkEditTags: api.hasPrivilege("posts:bulk-edit:tags"),
|
||||
canBulkEditSafety: api.hasPrivilege("posts:bulk-edit:safety"),
|
||||
bulkEdit: {
|
||||
tags: this._bulkEditTags
|
||||
tags: this._bulkEditTags,
|
||||
},
|
||||
});
|
||||
this._headerView.addEventListener(
|
||||
'navigate', e => this._evtNavigate(e));
|
||||
this._headerView.addEventListener("navigate", (e) =>
|
||||
this._evtNavigate(e)
|
||||
);
|
||||
|
||||
this._syncPageController();
|
||||
}
|
||||
|
@ -52,33 +60,35 @@ class PostListController {
|
|||
}
|
||||
|
||||
get _bulkEditTags() {
|
||||
return (this._ctx.parameters.tag || '').split(/\s+/).filter(s => s);
|
||||
return (this._ctx.parameters.tag || "").split(/\s+/).filter((s) => s);
|
||||
}
|
||||
|
||||
_evtNavigate(e) {
|
||||
router.showNoDispatch(
|
||||
uri.formatClientLink('posts', e.detail.parameters));
|
||||
uri.formatClientLink("posts", e.detail.parameters)
|
||||
);
|
||||
Object.assign(this._ctx.parameters, e.detail.parameters);
|
||||
this._syncPageController();
|
||||
}
|
||||
|
||||
_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));
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_evtUntag(e) {
|
||||
for (let tag of this._bulkEditTags) {
|
||||
e.detail.post.tags.removeByName(tag);
|
||||
}
|
||||
e.detail.post.save().catch(error => window.alert(error.message));
|
||||
e.detail.post.save().catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_evtChangeSafety(e) {
|
||||
e.detail.post.safety = e.detail.safety;
|
||||
e.detail.post.save().catch(error => window.alert(error.message));
|
||||
e.detail.post.save().catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_syncPageController() {
|
||||
|
@ -86,39 +96,45 @@ class PostListController {
|
|||
parameters: this._ctx.parameters,
|
||||
defaultLimit: parseInt(settings.get().postsPerPage),
|
||||
getClientUrlForPage: (offset, limit) => {
|
||||
const parameters = Object.assign(
|
||||
{}, this._ctx.parameters, {offset: offset, limit: limit});
|
||||
return uri.formatClientLink('posts', parameters);
|
||||
const parameters = Object.assign({}, this._ctx.parameters, {
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
return uri.formatClientLink("posts", parameters);
|
||||
},
|
||||
requestPage: (offset, limit) => {
|
||||
return PostList.search(
|
||||
this._ctx.parameters.query, offset, limit, fields);
|
||||
this._ctx.parameters.query,
|
||||
offset,
|
||||
limit,
|
||||
fields
|
||||
);
|
||||
},
|
||||
pageRenderer: pageCtx => {
|
||||
pageRenderer: (pageCtx) => {
|
||||
Object.assign(pageCtx, {
|
||||
canViewPosts: api.hasPrivilege('posts:view'),
|
||||
canBulkEditTags: api.hasPrivilege('posts:bulk-edit:tags'),
|
||||
canBulkEditSafety:
|
||||
api.hasPrivilege('posts:bulk-edit:safety'),
|
||||
canViewPosts: api.hasPrivilege("posts:view"),
|
||||
canBulkEditTags: api.hasPrivilege("posts:bulk-edit:tags"),
|
||||
canBulkEditSafety: api.hasPrivilege(
|
||||
"posts:bulk-edit:safety"
|
||||
),
|
||||
bulkEdit: {
|
||||
tags: this._bulkEditTags,
|
||||
},
|
||||
});
|
||||
const view = new PostsPageView(pageCtx);
|
||||
view.addEventListener('tag', e => this._evtTag(e));
|
||||
view.addEventListener('untag', e => this._evtUntag(e));
|
||||
view.addEventListener(
|
||||
'changeSafety', e => this._evtChangeSafety(e));
|
||||
view.addEventListener("tag", (e) => this._evtTag(e));
|
||||
view.addEventListener("untag", (e) => this._evtUntag(e));
|
||||
view.addEventListener("changeSafety", (e) =>
|
||||
this._evtChangeSafety(e)
|
||||
);
|
||||
return view;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(
|
||||
['posts'],
|
||||
(ctx, next) => {
|
||||
ctx.controller = new PostListController(ctx);
|
||||
});
|
||||
module.exports = (router) => {
|
||||
router.enter(["posts"], (ctx, next) => {
|
||||
ctx.controller = new PostListController(ctx);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const settings = require('../models/settings.js');
|
||||
const Comment = require('../models/comment.js');
|
||||
const Post = require('../models/post.js');
|
||||
const PostList = require('../models/post_list.js');
|
||||
const PostMainView = require('../views/post_main_view.js');
|
||||
const BasePostController = require('./base_post_controller.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const settings = require("../models/settings.js");
|
||||
const Comment = require("../models/comment.js");
|
||||
const Post = require("../models/post.js");
|
||||
const PostList = require("../models/post_list.js");
|
||||
const PostMainView = require("../views/post_main_view.js");
|
||||
const BasePostController = require("./base_post_controller.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class PostMainController extends BasePostController {
|
||||
constructor(ctx, editMode) {
|
||||
|
@ -21,74 +21,107 @@ class PostMainController extends BasePostController {
|
|||
Post.get(ctx.parameters.id),
|
||||
PostList.getAround(
|
||||
ctx.parameters.id,
|
||||
parameters ? parameters.query : null),
|
||||
]).then(responses => {
|
||||
const [post, aroundResponse] = responses;
|
||||
parameters ? parameters.query : null
|
||||
),
|
||||
]).then(
|
||||
(responses) => {
|
||||
const [post, aroundResponse] = responses;
|
||||
|
||||
// remove junk from query, but save it into history so that it can
|
||||
// be still accessed after history navigation / page refresh
|
||||
if (parameters.query) {
|
||||
ctx.state.parameters = parameters;
|
||||
const url = editMode ?
|
||||
uri.formatClientLink('post', ctx.parameters.id, 'edit') :
|
||||
uri.formatClientLink('post', ctx.parameters.id);
|
||||
router.replace(url, ctx.state, false);
|
||||
// remove junk from query, but save it into history so that it can
|
||||
// be still accessed after history navigation / page refresh
|
||||
if (parameters.query) {
|
||||
ctx.state.parameters = parameters;
|
||||
const url = editMode
|
||||
? uri.formatClientLink(
|
||||
"post",
|
||||
ctx.parameters.id,
|
||||
"edit"
|
||||
)
|
||||
: uri.formatClientLink("post", ctx.parameters.id);
|
||||
router.replace(url, ctx.state, false);
|
||||
}
|
||||
|
||||
this._post = post;
|
||||
this._view = new PostMainView({
|
||||
post: post,
|
||||
editMode: editMode,
|
||||
prevPostId: aroundResponse.prev
|
||||
? aroundResponse.prev.id
|
||||
: null,
|
||||
nextPostId: aroundResponse.next
|
||||
? aroundResponse.next.id
|
||||
: null,
|
||||
canEditPosts: api.hasPrivilege("posts:edit"),
|
||||
canDeletePosts: api.hasPrivilege("posts:delete"),
|
||||
canFeaturePosts: api.hasPrivilege("posts:feature"),
|
||||
canListComments: api.hasPrivilege("comments:list"),
|
||||
canCreateComments: api.hasPrivilege("comments:create"),
|
||||
parameters: parameters,
|
||||
});
|
||||
|
||||
if (this._view.sidebarControl) {
|
||||
this._view.sidebarControl.addEventListener(
|
||||
"favorite",
|
||||
(e) => this._evtFavoritePost(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener(
|
||||
"unfavorite",
|
||||
(e) => this._evtUnfavoritePost(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener("score", (e) =>
|
||||
this._evtScorePost(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener(
|
||||
"fitModeChange",
|
||||
(e) => this._evtFitModeChange(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener("change", (e) =>
|
||||
this._evtPostChange(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener("submit", (e) =>
|
||||
this._evtUpdatePost(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener(
|
||||
"feature",
|
||||
(e) => this._evtFeaturePost(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener("delete", (e) =>
|
||||
this._evtDeletePost(e)
|
||||
);
|
||||
this._view.sidebarControl.addEventListener("merge", (e) =>
|
||||
this._evtMergePost(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._view.commentControl) {
|
||||
this._view.commentControl.addEventListener("change", (e) =>
|
||||
this._evtCommentChange(e)
|
||||
);
|
||||
this._view.commentControl.addEventListener("submit", (e) =>
|
||||
this._evtCreateComment(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._view.commentListControl) {
|
||||
this._view.commentListControl.addEventListener(
|
||||
"submit",
|
||||
(e) => this._evtUpdateComment(e)
|
||||
);
|
||||
this._view.commentListControl.addEventListener(
|
||||
"score",
|
||||
(e) => this._evtScoreComment(e)
|
||||
);
|
||||
this._view.commentListControl.addEventListener(
|
||||
"delete",
|
||||
(e) => this._evtDeleteComment(e)
|
||||
);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
|
||||
this._post = post;
|
||||
this._view = new PostMainView({
|
||||
post: post,
|
||||
editMode: editMode,
|
||||
prevPostId: aroundResponse.prev ? aroundResponse.prev.id : null,
|
||||
nextPostId: aroundResponse.next ? aroundResponse.next.id : null,
|
||||
canEditPosts: api.hasPrivilege('posts:edit'),
|
||||
canDeletePosts: api.hasPrivilege('posts:delete'),
|
||||
canFeaturePosts: api.hasPrivilege('posts:feature'),
|
||||
canListComments: api.hasPrivilege('comments:list'),
|
||||
canCreateComments: api.hasPrivilege('comments:create'),
|
||||
parameters: parameters,
|
||||
});
|
||||
|
||||
if (this._view.sidebarControl) {
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'favorite', e => this._evtFavoritePost(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'unfavorite', e => this._evtUnfavoritePost(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'score', e => this._evtScorePost(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'fitModeChange', e => this._evtFitModeChange(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'change', e => this._evtPostChange(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'submit', e => this._evtUpdatePost(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'feature', e => this._evtFeaturePost(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'delete', e => this._evtDeletePost(e));
|
||||
this._view.sidebarControl.addEventListener(
|
||||
'merge', e => this._evtMergePost(e));
|
||||
}
|
||||
|
||||
if (this._view.commentControl) {
|
||||
this._view.commentControl.addEventListener(
|
||||
'change', e => this._evtCommentChange(e));
|
||||
this._view.commentControl.addEventListener(
|
||||
'submit', e => this._evtCreateComment(e));
|
||||
}
|
||||
|
||||
if (this._view.commentListControl) {
|
||||
this._view.commentListControl.addEventListener(
|
||||
'submit', e => this._evtUpdateComment(e));
|
||||
this._view.commentListControl.addEventListener(
|
||||
'score', e => this._evtScoreComment(e));
|
||||
this._view.commentListControl.addEventListener(
|
||||
'delete', e => this._evtDeleteComment(e));
|
||||
}
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
_evtFitModeChange(e) {
|
||||
|
@ -100,32 +133,36 @@ class PostMainController extends BasePostController {
|
|||
_evtFeaturePost(e) {
|
||||
this._view.sidebarControl.disableForm();
|
||||
this._view.sidebarControl.clearMessages();
|
||||
e.detail.post.feature()
|
||||
.then(() => {
|
||||
this._view.sidebarControl.showSuccess('Post featured.');
|
||||
e.detail.post.feature().then(
|
||||
() => {
|
||||
this._view.sidebarControl.showSuccess("Post featured.");
|
||||
this._view.sidebarControl.enableForm();
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
this._view.sidebarControl.showError(error.message);
|
||||
this._view.sidebarControl.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtMergePost(e) {
|
||||
router.show(uri.formatClientLink('post', e.detail.post.id, 'merge'));
|
||||
router.show(uri.formatClientLink("post", e.detail.post.id, "merge"));
|
||||
}
|
||||
|
||||
_evtDeletePost(e) {
|
||||
this._view.sidebarControl.disableForm();
|
||||
this._view.sidebarControl.clearMessages();
|
||||
e.detail.post.delete()
|
||||
.then(() => {
|
||||
e.detail.post.delete().then(
|
||||
() => {
|
||||
misc.disableExitConfirmation();
|
||||
const ctx = router.show(uri.formatClientLink('posts'));
|
||||
ctx.controller.showSuccess('Post deleted.');
|
||||
}, error => {
|
||||
const ctx = router.show(uri.formatClientLink("posts"));
|
||||
ctx.controller.showSuccess("Post deleted.");
|
||||
},
|
||||
(error) => {
|
||||
this._view.sidebarControl.showError(error.message);
|
||||
this._view.sidebarControl.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtUpdatePost(e) {
|
||||
|
@ -150,15 +187,17 @@ class PostMainController extends BasePostController {
|
|||
if (e.detail.source !== undefined) {
|
||||
post.source = e.detail.source;
|
||||
}
|
||||
post.save()
|
||||
.then(() => {
|
||||
this._view.sidebarControl.showSuccess('Post saved.');
|
||||
post.save().then(
|
||||
() => {
|
||||
this._view.sidebarControl.showSuccess("Post saved.");
|
||||
this._view.sidebarControl.enableForm();
|
||||
misc.disableExitConfirmation();
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
this._view.sidebarControl.showError(error.message);
|
||||
this._view.sidebarControl.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtPostChange(e) {
|
||||
|
@ -173,79 +212,82 @@ class PostMainController extends BasePostController {
|
|||
this._view.commentControl.disableForm();
|
||||
const comment = Comment.create(this._post.id);
|
||||
comment.text = e.detail.text;
|
||||
comment.save()
|
||||
.then(() => {
|
||||
comment.save().then(
|
||||
() => {
|
||||
this._post.comments.add(comment);
|
||||
this._view.commentControl.exitEditMode();
|
||||
this._view.commentControl.enableForm();
|
||||
misc.disableExitConfirmation();
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
this._view.commentControl.showError(error.message);
|
||||
this._view.commentControl.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtUpdateComment(e) {
|
||||
// TODO: disable form
|
||||
e.detail.comment.text = e.detail.text;
|
||||
e.detail.comment.save()
|
||||
.catch(error => {
|
||||
e.detail.target.showError(error.message);
|
||||
// TODO: enable form
|
||||
});
|
||||
e.detail.comment.save().catch((error) => {
|
||||
e.detail.target.showError(error.message);
|
||||
// TODO: enable form
|
||||
});
|
||||
}
|
||||
|
||||
_evtScoreComment(e) {
|
||||
e.detail.comment.setScore(e.detail.score)
|
||||
.catch(error => window.alert(error.message));
|
||||
e.detail.comment
|
||||
.setScore(e.detail.score)
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_evtDeleteComment(e) {
|
||||
e.detail.comment.delete()
|
||||
.catch(error => window.alert(error.message));
|
||||
e.detail.comment
|
||||
.delete()
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_evtScorePost(e) {
|
||||
if (!api.hasPrivilege('posts:score')) {
|
||||
if (!api.hasPrivilege("posts:score")) {
|
||||
return;
|
||||
}
|
||||
e.detail.post.setScore(e.detail.score)
|
||||
.catch(error => window.alert(error.message));
|
||||
e.detail.post
|
||||
.setScore(e.detail.score)
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_evtFavoritePost(e) {
|
||||
if (!api.hasPrivilege('posts:favorite')) {
|
||||
if (!api.hasPrivilege("posts:favorite")) {
|
||||
return;
|
||||
}
|
||||
e.detail.post.addToFavorites()
|
||||
.catch(error => window.alert(error.message));
|
||||
e.detail.post
|
||||
.addToFavorites()
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
|
||||
_evtUnfavoritePost(e) {
|
||||
if (!api.hasPrivilege('posts:favorite')) {
|
||||
if (!api.hasPrivilege("posts:favorite")) {
|
||||
return;
|
||||
}
|
||||
e.detail.post.removeFromFavorites()
|
||||
.catch(error => window.alert(error.message));
|
||||
e.detail.post
|
||||
.removeFromFavorites()
|
||||
.catch((error) => window.alert(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['post', ':id', 'edit'],
|
||||
(ctx, next) => {
|
||||
// restore parameters from history state
|
||||
if (ctx.state.parameters) {
|
||||
Object.assign(ctx.parameters, ctx.state.parameters);
|
||||
}
|
||||
ctx.controller = new PostMainController(ctx, true);
|
||||
});
|
||||
router.enter(
|
||||
['post', ':id'],
|
||||
(ctx, next) => {
|
||||
// restore parameters from history state
|
||||
if (ctx.state.parameters) {
|
||||
Object.assign(ctx.parameters, ctx.state.parameters);
|
||||
}
|
||||
ctx.controller = new PostMainController(ctx, false);
|
||||
});
|
||||
module.exports = (router) => {
|
||||
router.enter(["post", ":id", "edit"], (ctx, next) => {
|
||||
// restore parameters from history state
|
||||
if (ctx.state.parameters) {
|
||||
Object.assign(ctx.parameters, ctx.state.parameters);
|
||||
}
|
||||
ctx.controller = new PostMainController(ctx, true);
|
||||
});
|
||||
router.enter(["post", ":id"], (ctx, next) => {
|
||||
// restore parameters from history state
|
||||
if (ctx.state.parameters) {
|
||||
Object.assign(ctx.parameters, ctx.state.parameters);
|
||||
}
|
||||
ctx.controller = new PostMainController(ctx, false);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const router = require('../router.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const progress = require('../util/progress.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const Post = require('../models/post.js');
|
||||
const Tag = require('../models/tag.js');
|
||||
const PostUploadView = require('../views/post_upload_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const api = require("../api.js");
|
||||
const router = require("../router.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const progress = require("../util/progress.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const Post = require("../models/post.js");
|
||||
const Tag = require("../models/tag.js");
|
||||
const PostUploadView = require("../views/post_upload_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
const genericErrorMessage =
|
||||
'One of the posts needs your attention; ' +
|
||||
"One of the posts needs your attention; " +
|
||||
'click "resume upload" when you\'re ready.';
|
||||
|
||||
class PostUploadController {
|
||||
constructor() {
|
||||
this._lastCancellablePromise = null;
|
||||
|
||||
if (!api.hasPrivilege('posts:create')) {
|
||||
if (!api.hasPrivilege("posts:create")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to upload posts.');
|
||||
this._view.showError("You don't have privileges to upload posts.");
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('upload');
|
||||
topNavigation.setTitle('Upload');
|
||||
topNavigation.activate("upload");
|
||||
topNavigation.setTitle("Upload");
|
||||
this._view = new PostUploadView({
|
||||
canUploadAnonymously: api.hasPrivilege('posts:create:anonymous'),
|
||||
canViewPosts: api.hasPrivilege('posts:view'),
|
||||
canUploadAnonymously: api.hasPrivilege("posts:create:anonymous"),
|
||||
canViewPosts: api.hasPrivilege("posts:view"),
|
||||
enableSafety: api.safetyEnabled(),
|
||||
});
|
||||
this._view.addEventListener('change', e => this._evtChange(e));
|
||||
this._view.addEventListener('submit', e => this._evtSubmit(e));
|
||||
this._view.addEventListener('cancel', e => this._evtCancel(e));
|
||||
this._view.addEventListener("change", (e) => this._evtChange(e));
|
||||
this._view.addEventListener("submit", (e) => this._evtSubmit(e));
|
||||
this._view.addEventListener("cancel", (e) => this._evtCancel(e));
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
|
@ -56,87 +56,109 @@ class PostUploadController {
|
|||
this._view.disableForm();
|
||||
this._view.clearMessages();
|
||||
|
||||
e.detail.uploadables.reduce(
|
||||
(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);
|
||||
e.detail.uploadables
|
||||
.reduce(
|
||||
(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
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._view.showError(genericErrorMessage);
|
||||
this._view.showError(
|
||||
error.message, error.uploadable);
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
} else {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
this._view.enableForm();
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
_uploadSinglePost(uploadable, skipDuplicates) {
|
||||
progress.start();
|
||||
let reverseSearchPromise = Promise.resolve();
|
||||
if (!uploadable.lookalikesConfirmed) {
|
||||
reverseSearchPromise =
|
||||
Post.reverseSearch(uploadable.url || uploadable.file);
|
||||
reverseSearchPromise = Post.reverseSearch(
|
||||
uploadable.url || uploadable.file
|
||||
);
|
||||
}
|
||||
this._lastCancellablePromise = reverseSearchPromise;
|
||||
|
||||
return reverseSearchPromise.then(searchResult => {
|
||||
if (searchResult) {
|
||||
// notify about exact duplicate
|
||||
if (searchResult.exactPost) {
|
||||
if (skipDuplicates) {
|
||||
this._view.removeUploadable(uploadable);
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
let error = new Error('Post already uploaded ' +
|
||||
`(@${searchResult.exactPost.id})`);
|
||||
return reverseSearchPromise
|
||||
.then((searchResult) => {
|
||||
if (searchResult) {
|
||||
// notify about exact duplicate
|
||||
if (searchResult.exactPost) {
|
||||
if (skipDuplicates) {
|
||||
this._view.removeUploadable(uploadable);
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
let error = new Error(
|
||||
"Post already uploaded " +
|
||||
`(@${searchResult.exactPost.id})`
|
||||
);
|
||||
error.uploadable = uploadable;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
// notify about similar posts
|
||||
if (searchResult.similarPosts.length) {
|
||||
let error = new Error(
|
||||
`Found ${searchResult.similarPosts.length} similar ` +
|
||||
"posts.\nYou can resume or discard this upload."
|
||||
);
|
||||
error.uploadable = uploadable;
|
||||
error.similarPosts = searchResult.similarPosts;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
// notify about similar posts
|
||||
if (searchResult.similarPosts.length) {
|
||||
let error = new Error(
|
||||
`Found ${searchResult.similarPosts.length} similar ` +
|
||||
'posts.\nYou can resume or discard this upload.');
|
||||
error.uploadable = uploadable;
|
||||
error.similarPosts = searchResult.similarPosts;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
// no duplicates, proceed with saving
|
||||
let post = this._uploadableToPost(uploadable);
|
||||
let savePromise = post.save(uploadable.anonymous)
|
||||
.then(() => {
|
||||
// no duplicates, proceed with saving
|
||||
let post = this._uploadableToPost(uploadable);
|
||||
let savePromise = post.save(uploadable.anonymous).then(() => {
|
||||
this._view.removeUploadable(uploadable);
|
||||
return Promise.resolve();
|
||||
});
|
||||
this._lastCancellablePromise = savePromise;
|
||||
return savePromise;
|
||||
}).then(result => {
|
||||
progress.done();
|
||||
return Promise.resolve(result);
|
||||
}, error => {
|
||||
error.uploadable = uploadable;
|
||||
progress.done();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
this._lastCancellablePromise = savePromise;
|
||||
return savePromise;
|
||||
})
|
||||
.then(
|
||||
(result) => {
|
||||
progress.done();
|
||||
return Promise.resolve(result);
|
||||
},
|
||||
(error) => {
|
||||
error.uploadable = uploadable;
|
||||
progress.done();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_uploadableToPost(uploadable) {
|
||||
|
@ -159,8 +181,8 @@ class PostUploadController {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['upload'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["upload"], (ctx, next) => {
|
||||
ctx.controller = new PostUploadController();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const settings = require('../models/settings.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const SettingsView = require('../views/settings_view.js');
|
||||
const settings = require("../models/settings.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const SettingsView = require("../views/settings_view.js");
|
||||
|
||||
class SettingsController {
|
||||
constructor() {
|
||||
topNavigation.activate('settings');
|
||||
topNavigation.setTitle('Browsing settings');
|
||||
topNavigation.activate("settings");
|
||||
topNavigation.setTitle("Browsing settings");
|
||||
this._view = new SettingsView({
|
||||
settings: settings.get(),
|
||||
});
|
||||
this._view.addEventListener('submit', e => this._evtSubmit(e));
|
||||
this._view.addEventListener("submit", (e) => this._evtSubmit(e));
|
||||
}
|
||||
|
||||
_evtSubmit(e) {
|
||||
this._view.clearMessages();
|
||||
settings.save(e.detail);
|
||||
this._view.showSuccess('Settings saved.');
|
||||
this._view.showSuccess("Settings saved.");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['settings'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["settings"], (ctx, next) => {
|
||||
ctx.controller = new SettingsController();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,41 +1,43 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const SnapshotList = require('../models/snapshot_list.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const SnapshotsPageView = require('../views/snapshots_page_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const SnapshotList = require("../models/snapshot_list.js");
|
||||
const PageController = require("../controllers/page_controller.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const SnapshotsPageView = require("../views/snapshots_page_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class SnapshotsController {
|
||||
constructor(ctx) {
|
||||
if (!api.hasPrivilege('snapshots:list')) {
|
||||
if (!api.hasPrivilege("snapshots:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view history.');
|
||||
this._view.showError("You don't have privileges to view history.");
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('');
|
||||
topNavigation.setTitle('History');
|
||||
topNavigation.activate("");
|
||||
topNavigation.setTitle("History");
|
||||
|
||||
this._pageController = new PageController();
|
||||
this._pageController.run({
|
||||
parameters: ctx.parameters,
|
||||
defaultLimit: 25,
|
||||
getClientUrlForPage: (offset, limit) => {
|
||||
const parameters = Object.assign(
|
||||
{}, ctx.parameters, {offset: offset, limit: limit});
|
||||
return uri.formatClientLink('history', parameters);
|
||||
const parameters = Object.assign({}, ctx.parameters, {
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
return uri.formatClientLink("history", parameters);
|
||||
},
|
||||
requestPage: (offset, limit) => {
|
||||
return SnapshotList.search('', offset, limit);
|
||||
return SnapshotList.search("", offset, limit);
|
||||
},
|
||||
pageRenderer: pageCtx => {
|
||||
pageRenderer: (pageCtx) => {
|
||||
Object.assign(pageCtx, {
|
||||
canViewPosts: api.hasPrivilege('posts:view'),
|
||||
canViewUsers: api.hasPrivilege('users:view'),
|
||||
canViewTags: api.hasPrivilege('tags:view'),
|
||||
canViewPosts: api.hasPrivilege("posts:view"),
|
||||
canViewUsers: api.hasPrivilege("users:view"),
|
||||
canViewTags: api.hasPrivilege("tags:view"),
|
||||
});
|
||||
return new SnapshotsPageView(pageCtx);
|
||||
},
|
||||
|
@ -43,9 +45,8 @@ class SnapshotsController {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['history'],
|
||||
(ctx, next) => {
|
||||
ctx.controller = new SnapshotsController(ctx);
|
||||
});
|
||||
module.exports = (router) => {
|
||||
router.enter(["history"], (ctx, next) => {
|
||||
ctx.controller = new SnapshotsController(ctx);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,57 +1,67 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const tags = require('../tags.js');
|
||||
const TagCategoryList = require('../models/tag_category_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const TagCategoriesView = require('../views/tag_categories_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const api = require("../api.js");
|
||||
const tags = require("../tags.js");
|
||||
const TagCategoryList = require("../models/tag_category_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const TagCategoriesView = require("../views/tag_categories_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class TagCategoriesController {
|
||||
constructor() {
|
||||
if (!api.hasPrivilege('tagCategories:list')) {
|
||||
if (!api.hasPrivilege("tagCategories:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(
|
||||
'You don\'t have privileges to view tag categories.');
|
||||
"You don't have privileges to view tag categories."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('tags');
|
||||
topNavigation.setTitle('Listing tags');
|
||||
TagCategoryList.get().then(response => {
|
||||
this._tagCategories = response.results;
|
||||
this._view = new TagCategoriesView({
|
||||
tagCategories: this._tagCategories,
|
||||
canEditName: api.hasPrivilege('tagCategories:edit:name'),
|
||||
canEditColor: api.hasPrivilege('tagCategories:edit:color'),
|
||||
canDelete: api.hasPrivilege('tagCategories:delete'),
|
||||
canCreate: api.hasPrivilege('tagCategories:create'),
|
||||
canSetDefault: api.hasPrivilege('tagCategories:setDefault'),
|
||||
});
|
||||
this._view.addEventListener('submit', e => this._evtSubmit(e));
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
topNavigation.activate("tags");
|
||||
topNavigation.setTitle("Listing tags");
|
||||
TagCategoryList.get().then(
|
||||
(response) => {
|
||||
this._tagCategories = response.results;
|
||||
this._view = new TagCategoriesView({
|
||||
tagCategories: this._tagCategories,
|
||||
canEditName: api.hasPrivilege("tagCategories:edit:name"),
|
||||
canEditColor: api.hasPrivilege("tagCategories:edit:color"),
|
||||
canDelete: api.hasPrivilege("tagCategories:delete"),
|
||||
canCreate: api.hasPrivilege("tagCategories:create"),
|
||||
canSetDefault: api.hasPrivilege(
|
||||
"tagCategories:setDefault"
|
||||
),
|
||||
});
|
||||
this._view.addEventListener("submit", (e) =>
|
||||
this._evtSubmit(e)
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtSubmit(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
this._tagCategories.save()
|
||||
.then(() => {
|
||||
this._tagCategories.save().then(
|
||||
() => {
|
||||
tags.refreshCategoryColorMap();
|
||||
this._view.enableForm();
|
||||
this._view.showSuccess('Changes saved.');
|
||||
}, error => {
|
||||
this._view.showSuccess("Changes saved.");
|
||||
},
|
||||
(error) => {
|
||||
this._view.enableForm();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['tag-categories'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["tag-categories"], (ctx, next) => {
|
||||
ctx.controller = new TagCategoriesController(ctx, next);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,63 +1,80 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const Tag = require('../models/tag.js');
|
||||
const TagCategoryList = require('../models/tag_category_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const TagView = require('../views/tag_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const Tag = require("../models/tag.js");
|
||||
const TagCategoryList = require("../models/tag_category_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const TagView = require("../views/tag_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class TagController {
|
||||
constructor(ctx, section) {
|
||||
if (!api.hasPrivilege('tags:view')) {
|
||||
if (!api.hasPrivilege("tags:view")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view tags.');
|
||||
this._view.showError("You don't have privileges to view tags.");
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
TagCategoryList.get(),
|
||||
Tag.get(ctx.parameters.name),
|
||||
]).then(responses => {
|
||||
const [tagCategoriesResponse, tag] = responses;
|
||||
]).then(
|
||||
(responses) => {
|
||||
const [tagCategoriesResponse, tag] = responses;
|
||||
|
||||
topNavigation.activate('tags');
|
||||
topNavigation.setTitle('Tag #' + tag.names[0]);
|
||||
topNavigation.activate("tags");
|
||||
topNavigation.setTitle("Tag #" + tag.names[0]);
|
||||
|
||||
this._name = ctx.parameters.name;
|
||||
tag.addEventListener('change', e => this._evtSaved(e, section));
|
||||
this._name = ctx.parameters.name;
|
||||
tag.addEventListener("change", (e) =>
|
||||
this._evtSaved(e, section)
|
||||
);
|
||||
|
||||
const categories = {};
|
||||
for (let category of tagCategoriesResponse.results) {
|
||||
categories[category.name] = category.name;
|
||||
const categories = {};
|
||||
for (let category of tagCategoriesResponse.results) {
|
||||
categories[category.name] = category.name;
|
||||
}
|
||||
|
||||
this._view = new TagView({
|
||||
tag: tag,
|
||||
section: section,
|
||||
canEditAnything: api.hasPrivilege("tags:edit"),
|
||||
canEditNames: api.hasPrivilege("tags:edit:names"),
|
||||
canEditCategory: api.hasPrivilege("tags:edit:category"),
|
||||
canEditImplications: api.hasPrivilege(
|
||||
"tags:edit:implications"
|
||||
),
|
||||
canEditSuggestions: api.hasPrivilege(
|
||||
"tags:edit:suggestions"
|
||||
),
|
||||
canEditDescription: api.hasPrivilege(
|
||||
"tags:edit:description"
|
||||
),
|
||||
canMerge: api.hasPrivilege("tags:merge"),
|
||||
canDelete: api.hasPrivilege("tags:delete"),
|
||||
categories: categories,
|
||||
escapeColons: uri.escapeColons,
|
||||
});
|
||||
|
||||
this._view.addEventListener("change", (e) =>
|
||||
this._evtChange(e)
|
||||
);
|
||||
this._view.addEventListener("submit", (e) =>
|
||||
this._evtUpdate(e)
|
||||
);
|
||||
this._view.addEventListener("merge", (e) => this._evtMerge(e));
|
||||
this._view.addEventListener("delete", (e) =>
|
||||
this._evtDelete(e)
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
|
||||
this._view = new TagView({
|
||||
tag: tag,
|
||||
section: section,
|
||||
canEditAnything: api.hasPrivilege('tags:edit'),
|
||||
canEditNames: api.hasPrivilege('tags:edit:names'),
|
||||
canEditCategory: api.hasPrivilege('tags:edit:category'),
|
||||
canEditImplications: api.hasPrivilege('tags:edit:implications'),
|
||||
canEditSuggestions: api.hasPrivilege('tags:edit:suggestions'),
|
||||
canEditDescription: api.hasPrivilege('tags:edit:description'),
|
||||
canMerge: api.hasPrivilege('tags:merge'),
|
||||
canDelete: api.hasPrivilege('tags:delete'),
|
||||
categories: categories,
|
||||
escapeColons: uri.escapeColons,
|
||||
});
|
||||
|
||||
this._view.addEventListener('change', e => this._evtChange(e));
|
||||
this._view.addEventListener('submit', e => this._evtUpdate(e));
|
||||
this._view.addEventListener('merge', e => this._evtMerge(e));
|
||||
this._view.addEventListener('delete', e => this._evtDelete(e));
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
|
@ -68,9 +85,10 @@ class TagController {
|
|||
misc.disableExitConfirmation();
|
||||
if (this._name !== e.detail.tag.names[0]) {
|
||||
router.replace(
|
||||
uri.formatClientLink('tag', e.detail.tag.names[0], section),
|
||||
uri.formatClientLink("tag", e.detail.tag.names[0], section),
|
||||
null,
|
||||
false);
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,59 +104,69 @@ class TagController {
|
|||
if (e.detail.description !== undefined) {
|
||||
e.detail.tag.description = e.detail.description;
|
||||
}
|
||||
e.detail.tag.save().then(() => {
|
||||
this._view.showSuccess('Tag saved.');
|
||||
this._view.enableForm();
|
||||
}, error => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
e.detail.tag.save().then(
|
||||
() => {
|
||||
this._view.showSuccess("Tag saved.");
|
||||
this._view.enableForm();
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtMerge(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
e.detail.tag
|
||||
.merge(e.detail.targetTagName, e.detail.addAlias)
|
||||
.then(() => {
|
||||
this._view.showSuccess('Tag merged.');
|
||||
e.detail.tag.merge(e.detail.targetTagName, e.detail.addAlias).then(
|
||||
() => {
|
||||
this._view.showSuccess("Tag merged.");
|
||||
this._view.enableForm();
|
||||
router.replace(
|
||||
uri.formatClientLink(
|
||||
'tag', e.detail.targetTagName, 'merge'),
|
||||
"tag",
|
||||
e.detail.targetTagName,
|
||||
"merge"
|
||||
),
|
||||
null,
|
||||
false);
|
||||
}, error => {
|
||||
false
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtDelete(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
e.detail.tag.delete()
|
||||
.then(() => {
|
||||
const ctx = router.show(uri.formatClientLink('tags'));
|
||||
ctx.controller.showSuccess('Tag deleted.');
|
||||
}, error => {
|
||||
e.detail.tag.delete().then(
|
||||
() => {
|
||||
const ctx = router.show(uri.formatClientLink("tags"));
|
||||
ctx.controller.showSuccess("Tag deleted.");
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['tag', ':name', 'edit'], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, 'edit');
|
||||
module.exports = (router) => {
|
||||
router.enter(["tag", ":name", "edit"], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, "edit");
|
||||
});
|
||||
router.enter(['tag', ':name', 'merge'], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, 'merge');
|
||||
router.enter(["tag", ":name", "merge"], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, "merge");
|
||||
});
|
||||
router.enter(['tag', ':name', 'delete'], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, 'delete');
|
||||
router.enter(["tag", ":name", "delete"], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, "delete");
|
||||
});
|
||||
router.enter(['tag', ':name'], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, 'summary');
|
||||
router.enter(["tag", ":name"], (ctx, next) => {
|
||||
ctx.controller = new TagController(ctx, "summary");
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,46 +1,47 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const TagList = require('../models/tag_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const TagsHeaderView = require('../views/tags_header_view.js');
|
||||
const TagsPageView = require('../views/tags_page_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const TagList = require("../models/tag_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PageController = require("../controllers/page_controller.js");
|
||||
const TagsHeaderView = require("../views/tags_header_view.js");
|
||||
const TagsPageView = require("../views/tags_page_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
const fields = [
|
||||
'names',
|
||||
'suggestions',
|
||||
'implications',
|
||||
'creationTime',
|
||||
'usages',
|
||||
'category'
|
||||
"names",
|
||||
"suggestions",
|
||||
"implications",
|
||||
"creationTime",
|
||||
"usages",
|
||||
"category",
|
||||
];
|
||||
|
||||
class TagListController {
|
||||
constructor(ctx) {
|
||||
this._pageController = new PageController();
|
||||
|
||||
if (!api.hasPrivilege('tags:list')) {
|
||||
if (!api.hasPrivilege("tags:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view tags.');
|
||||
this._view.showError("You don't have privileges to view tags.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._ctx = ctx;
|
||||
|
||||
topNavigation.activate('tags');
|
||||
topNavigation.setTitle('Listing tags');
|
||||
topNavigation.activate("tags");
|
||||
topNavigation.setTitle("Listing tags");
|
||||
|
||||
this._headerView = new TagsHeaderView({
|
||||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||
parameters: ctx.parameters,
|
||||
canEditTagCategories: api.hasPrivilege('tagCategories:edit'),
|
||||
canEditTagCategories: api.hasPrivilege("tagCategories:edit"),
|
||||
});
|
||||
this._headerView.addEventListener(
|
||||
'navigate', e => this._evtNavigate(e));
|
||||
this._headerView.addEventListener("navigate", (e) =>
|
||||
this._evtNavigate(e)
|
||||
);
|
||||
|
||||
this._syncPageController();
|
||||
}
|
||||
|
@ -55,7 +56,8 @@ class TagListController {
|
|||
|
||||
_evtNavigate(e) {
|
||||
router.showNoDispatch(
|
||||
uri.formatClientLink('tags', e.detail.parameters));
|
||||
uri.formatClientLink("tags", e.detail.parameters)
|
||||
);
|
||||
Object.assign(this._ctx.parameters, e.detail.parameters);
|
||||
this._syncPageController();
|
||||
}
|
||||
|
@ -65,25 +67,29 @@ class TagListController {
|
|||
parameters: this._ctx.parameters,
|
||||
defaultLimit: 50,
|
||||
getClientUrlForPage: (offset, limit) => {
|
||||
const parameters = Object.assign(
|
||||
{}, this._ctx.parameters, {offset: offset, limit: limit});
|
||||
return uri.formatClientLink('tags', parameters);
|
||||
const parameters = Object.assign({}, this._ctx.parameters, {
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
return uri.formatClientLink("tags", parameters);
|
||||
},
|
||||
requestPage: (offset, limit) => {
|
||||
return TagList.search(
|
||||
this._ctx.parameters.query, offset, limit, fields);
|
||||
this._ctx.parameters.query,
|
||||
offset,
|
||||
limit,
|
||||
fields
|
||||
);
|
||||
},
|
||||
pageRenderer: pageCtx => {
|
||||
pageRenderer: (pageCtx) => {
|
||||
return new TagsPageView(pageCtx);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(
|
||||
['tags'],
|
||||
(ctx, next) => {
|
||||
ctx.controller = new TagListController(ctx);
|
||||
});
|
||||
module.exports = (router) => {
|
||||
router.enter(["tags"], (ctx, next) => {
|
||||
ctx.controller = new TagListController(ctx);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const TopNavigationView = require('../views/top_navigation_view.js');
|
||||
const api = require("../api.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const TopNavigationView = require("../views/top_navigation_view.js");
|
||||
|
||||
class TopNavigationController {
|
||||
constructor() {
|
||||
api.fetchConfig().then(() => {
|
||||
this._topNavigationView = new TopNavigationView();
|
||||
|
||||
topNavigation.addEventListener(
|
||||
'activate', e => this._evtActivate(e));
|
||||
topNavigation.addEventListener("activate", (e) =>
|
||||
this._evtActivate(e)
|
||||
);
|
||||
|
||||
api.addEventListener('login', e => this._evtAuthChange(e));
|
||||
api.addEventListener('logout', e => this._evtAuthChange(e));
|
||||
api.addEventListener("login", (e) => this._evtAuthChange(e));
|
||||
api.addEventListener("logout", (e) => this._evtAuthChange(e));
|
||||
|
||||
this._render();
|
||||
});
|
||||
|
@ -28,37 +29,38 @@ class TopNavigationController {
|
|||
}
|
||||
|
||||
_updateNavigationFromPrivileges() {
|
||||
topNavigation.get('account').url = 'user/' + api.userName;
|
||||
topNavigation.get('account').imageUrl =
|
||||
api.user ? api.user.avatarUrl : null;
|
||||
topNavigation.get("account").url = "user/" + api.userName;
|
||||
topNavigation.get("account").imageUrl = api.user
|
||||
? api.user.avatarUrl
|
||||
: null;
|
||||
|
||||
topNavigation.showAll();
|
||||
if (!api.hasPrivilege('posts:list')) {
|
||||
topNavigation.hide('posts');
|
||||
if (!api.hasPrivilege("posts:list")) {
|
||||
topNavigation.hide("posts");
|
||||
}
|
||||
if (!api.hasPrivilege('posts:create')) {
|
||||
topNavigation.hide('upload');
|
||||
if (!api.hasPrivilege("posts:create")) {
|
||||
topNavigation.hide("upload");
|
||||
}
|
||||
if (!api.hasPrivilege('comments:list')) {
|
||||
topNavigation.hide('comments');
|
||||
if (!api.hasPrivilege("comments:list")) {
|
||||
topNavigation.hide("comments");
|
||||
}
|
||||
if (!api.hasPrivilege('tags:list')) {
|
||||
topNavigation.hide('tags');
|
||||
if (!api.hasPrivilege("tags:list")) {
|
||||
topNavigation.hide("tags");
|
||||
}
|
||||
if (!api.hasPrivilege('users:list')) {
|
||||
topNavigation.hide('users');
|
||||
if (!api.hasPrivilege("users:list")) {
|
||||
topNavigation.hide("users");
|
||||
}
|
||||
if (api.isLoggedIn()) {
|
||||
if (!api.hasPrivilege('users:create:any')) {
|
||||
topNavigation.hide('register');
|
||||
if (!api.hasPrivilege("users:create:any")) {
|
||||
topNavigation.hide("register");
|
||||
}
|
||||
topNavigation.hide('login');
|
||||
topNavigation.hide("login");
|
||||
} else {
|
||||
if (!api.hasPrivilege('users:create:self')) {
|
||||
topNavigation.hide('register');
|
||||
if (!api.hasPrivilege("users:create:self")) {
|
||||
topNavigation.hide("register");
|
||||
}
|
||||
topNavigation.hide('account');
|
||||
topNavigation.hide('logout');
|
||||
topNavigation.hide("account");
|
||||
topNavigation.hide("logout");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,10 +68,11 @@ class TopNavigationController {
|
|||
this._updateNavigationFromPrivileges();
|
||||
this._topNavigationView.render({
|
||||
items: topNavigation.getAll(),
|
||||
name: api.getName()
|
||||
name: api.getName(),
|
||||
});
|
||||
this._topNavigationView.activate(
|
||||
topNavigation.activeItem ? topNavigation.activeItem.key : '');
|
||||
topNavigation.activeItem ? topNavigation.activeItem.key : ""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const User = require('../models/user.js');
|
||||
const UserToken = require('../models/user_token.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const UserView = require('../views/user_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const views = require("../util/views.js");
|
||||
const User = require("../models/user.js");
|
||||
const UserToken = require("../models/user_token.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const UserView = require("../views/user_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class UserController {
|
||||
constructor(ctx, section) {
|
||||
const userName = ctx.parameters.name;
|
||||
if (!api.hasPrivilege('users:view') &&
|
||||
!api.isLoggedIn({name: userName})) {
|
||||
if (
|
||||
!api.hasPrivilege("users:view") &&
|
||||
!api.isLoggedIn({ name: userName })
|
||||
) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view users.');
|
||||
this._view.showError("You don't have privileges to view users.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -25,100 +27,129 @@ class UserController {
|
|||
this._errorMessages = [];
|
||||
|
||||
let userTokenPromise = Promise.resolve([]);
|
||||
if (section === 'list-tokens') {
|
||||
userTokenPromise = UserToken.get(userName)
|
||||
.then(userTokens => {
|
||||
return userTokens.map(token => {
|
||||
token.isCurrentAuthToken = api.isCurrentAuthToken(token);
|
||||
if (section === "list-tokens") {
|
||||
userTokenPromise = UserToken.get(userName).then(
|
||||
(userTokens) => {
|
||||
return userTokens.map((token) => {
|
||||
token.isCurrentAuthToken = api.isCurrentAuthToken(
|
||||
token
|
||||
);
|
||||
return token;
|
||||
});
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
topNavigation.setTitle('User ' + userName);
|
||||
Promise.all([
|
||||
userTokenPromise,
|
||||
User.get(userName)
|
||||
]).then(responses => {
|
||||
const [userTokens, user] = responses;
|
||||
const isLoggedIn = api.isLoggedIn(user);
|
||||
const infix = isLoggedIn ? 'self' : 'any';
|
||||
topNavigation.setTitle("User " + userName);
|
||||
Promise.all([userTokenPromise, User.get(userName)]).then(
|
||||
(responses) => {
|
||||
const [userTokens, user] = responses;
|
||||
const isLoggedIn = api.isLoggedIn(user);
|
||||
const infix = isLoggedIn ? "self" : "any";
|
||||
|
||||
this._name = userName;
|
||||
user.addEventListener('change', e => this._evtSaved(e, section));
|
||||
this._name = userName;
|
||||
user.addEventListener("change", (e) =>
|
||||
this._evtSaved(e, section)
|
||||
);
|
||||
|
||||
const myRankIndex = api.user ?
|
||||
api.allRanks.indexOf(api.user.rank) :
|
||||
0;
|
||||
let ranks = {};
|
||||
for (let [rankIdx, rankIdentifier] of api.allRanks.entries()) {
|
||||
if (rankIdentifier === 'anonymous') {
|
||||
continue;
|
||||
const myRankIndex = api.user
|
||||
? api.allRanks.indexOf(api.user.rank)
|
||||
: 0;
|
||||
let ranks = {};
|
||||
for (let [rankIdx, rankIdentifier] of api.allRanks.entries()) {
|
||||
if (rankIdentifier === "anonymous") {
|
||||
continue;
|
||||
}
|
||||
if (rankIdx > myRankIndex) {
|
||||
continue;
|
||||
}
|
||||
ranks[rankIdentifier] = api.rankNames.get(rankIdentifier);
|
||||
}
|
||||
if (rankIdx > myRankIndex) {
|
||||
continue;
|
||||
|
||||
if (isLoggedIn) {
|
||||
topNavigation.activate("account");
|
||||
} else {
|
||||
topNavigation.activate("users");
|
||||
}
|
||||
ranks[rankIdentifier] = api.rankNames.get(rankIdentifier);
|
||||
|
||||
this._view = new UserView({
|
||||
user: user,
|
||||
section: section,
|
||||
isLoggedIn: isLoggedIn,
|
||||
canEditName: api.hasPrivilege(`users:edit:${infix}:name`),
|
||||
canEditPassword: api.hasPrivilege(
|
||||
`users:edit:${infix}:pass`
|
||||
),
|
||||
canEditEmail: api.hasPrivilege(
|
||||
`users:edit:${infix}:email`
|
||||
),
|
||||
canEditRank: api.hasPrivilege(`users:edit:${infix}:rank`),
|
||||
canEditAvatar: api.hasPrivilege(
|
||||
`users:edit:${infix}:avatar`
|
||||
),
|
||||
canEditAnything: api.hasPrivilege(`users:edit:${infix}`),
|
||||
canListTokens: api.hasPrivilege(
|
||||
`userTokens:list:${infix}`
|
||||
),
|
||||
canCreateToken: api.hasPrivilege(
|
||||
`userTokens:create:${infix}`
|
||||
),
|
||||
canEditToken: api.hasPrivilege(`userTokens:edit:${infix}`),
|
||||
canDeleteToken: api.hasPrivilege(
|
||||
`userTokens:delete:${infix}`
|
||||
),
|
||||
canDelete: api.hasPrivilege(`users:delete:${infix}`),
|
||||
ranks: ranks,
|
||||
tokens: userTokens,
|
||||
});
|
||||
this._view.addEventListener("change", (e) =>
|
||||
this._evtChange(e)
|
||||
);
|
||||
this._view.addEventListener("submit", (e) =>
|
||||
this._evtUpdate(e)
|
||||
);
|
||||
this._view.addEventListener("delete", (e) =>
|
||||
this._evtDelete(e)
|
||||
);
|
||||
this._view.addEventListener("create-token", (e) =>
|
||||
this._evtCreateToken(e)
|
||||
);
|
||||
this._view.addEventListener("delete-token", (e) =>
|
||||
this._evtDeleteToken(e)
|
||||
);
|
||||
this._view.addEventListener("update-token", (e) =>
|
||||
this._evtUpdateToken(e)
|
||||
);
|
||||
|
||||
for (let message of this._successMessages) {
|
||||
this.showSuccess(message);
|
||||
}
|
||||
|
||||
for (let message of this._errorMessages) {
|
||||
this.showError(message);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
}
|
||||
|
||||
if (isLoggedIn) {
|
||||
topNavigation.activate('account');
|
||||
} else {
|
||||
topNavigation.activate('users');
|
||||
}
|
||||
|
||||
this._view = new UserView({
|
||||
user: user,
|
||||
section: section,
|
||||
isLoggedIn: isLoggedIn,
|
||||
canEditName: api.hasPrivilege(`users:edit:${infix}:name`),
|
||||
canEditPassword: api.hasPrivilege(`users:edit:${infix}:pass`),
|
||||
canEditEmail: api.hasPrivilege(`users:edit:${infix}:email`),
|
||||
canEditRank: api.hasPrivilege(`users:edit:${infix}:rank`),
|
||||
canEditAvatar: api.hasPrivilege(`users:edit:${infix}:avatar`),
|
||||
canEditAnything: api.hasPrivilege(`users:edit:${infix}`),
|
||||
canListTokens: api.hasPrivilege(`userTokens:list:${infix}`),
|
||||
canCreateToken: api.hasPrivilege(`userTokens:create:${infix}`),
|
||||
canEditToken: api.hasPrivilege(`userTokens:edit:${infix}`),
|
||||
canDeleteToken: api.hasPrivilege(`userTokens:delete:${infix}`),
|
||||
canDelete: api.hasPrivilege(`users:delete:${infix}`),
|
||||
ranks: ranks,
|
||||
tokens: userTokens,
|
||||
});
|
||||
this._view.addEventListener('change', e => this._evtChange(e));
|
||||
this._view.addEventListener('submit', e => this._evtUpdate(e));
|
||||
this._view.addEventListener('delete', e => this._evtDelete(e));
|
||||
this._view.addEventListener('create-token', e => this._evtCreateToken(e));
|
||||
this._view.addEventListener('delete-token', e => this._evtDeleteToken(e));
|
||||
this._view.addEventListener('update-token', e => this._evtUpdateToken(e));
|
||||
|
||||
for (let message of this._successMessages) {
|
||||
this.showSuccess(message);
|
||||
}
|
||||
|
||||
for (let message of this._errorMessages) {
|
||||
this.showError(message);
|
||||
}
|
||||
|
||||
}, error => {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(error.message);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
if (typeof this._view === 'undefined') {
|
||||
this._successMessages.push(message)
|
||||
if (typeof this._view === "undefined") {
|
||||
this._successMessages.push(message);
|
||||
} else {
|
||||
this._view.showSuccess(message);
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
if (typeof this._view === 'undefined') {
|
||||
this._errorMessages.push(message)
|
||||
if (typeof this._view === "undefined") {
|
||||
this._errorMessages.push(message);
|
||||
} else {
|
||||
this._view.showError(message);
|
||||
}
|
||||
|
@ -132,9 +163,10 @@ class UserController {
|
|||
misc.disableExitConfirmation();
|
||||
if (this._name !== e.detail.user.name) {
|
||||
router.replace(
|
||||
uri.formatClientLink('user', e.detail.user.name, section),
|
||||
uri.formatClientLink("user", e.detail.user.name, section),
|
||||
null,
|
||||
false);
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +174,7 @@ class UserController {
|
|||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
||||
const infix = isLoggedIn ? 'self' : 'any';
|
||||
const infix = isLoggedIn ? "self" : "any";
|
||||
|
||||
if (e.detail.name !== undefined) {
|
||||
e.detail.user.name = e.detail.name;
|
||||
|
@ -165,72 +197,105 @@ class UserController {
|
|||
}
|
||||
}
|
||||
|
||||
e.detail.user.save().then(() => {
|
||||
return isLoggedIn ?
|
||||
api.login(
|
||||
e.detail.name || api.userName,
|
||||
e.detail.password || api.userPassword,
|
||||
false) :
|
||||
Promise.resolve();
|
||||
}).then(() => {
|
||||
this._view.showSuccess('Settings updated.');
|
||||
this._view.enableForm();
|
||||
}, error => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
e.detail.user
|
||||
.save()
|
||||
.then(() => {
|
||||
return isLoggedIn
|
||||
? api.login(
|
||||
e.detail.name || api.userName,
|
||||
e.detail.password || api.userPassword,
|
||||
false
|
||||
)
|
||||
: Promise.resolve();
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
this._view.showSuccess("Settings updated.");
|
||||
this._view.enableForm();
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtDelete(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
const isLoggedIn = api.isLoggedIn(e.detail.user);
|
||||
e.detail.user.delete()
|
||||
.then(() => {
|
||||
e.detail.user.delete().then(
|
||||
() => {
|
||||
if (isLoggedIn) {
|
||||
api.forget();
|
||||
api.logout();
|
||||
}
|
||||
if (api.hasPrivilege('users:list')) {
|
||||
const ctx = router.show(uri.formatClientLink('users'));
|
||||
ctx.controller.showSuccess('Account deleted.');
|
||||
if (api.hasPrivilege("users:list")) {
|
||||
const ctx = router.show(uri.formatClientLink("users"));
|
||||
ctx.controller.showSuccess("Account deleted.");
|
||||
} else {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showSuccess('Account deleted.');
|
||||
ctx.controller.showSuccess("Account deleted.");
|
||||
}
|
||||
}, error => {
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtCreateToken(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
UserToken.create(e.detail.user.name, e.detail.note, e.detail.expirationTime)
|
||||
.then(response => {
|
||||
const ctx = router.show(uri.formatClientLink('user', e.detail.user.name, 'list-tokens'));
|
||||
ctx.controller.showSuccess('Token ' + response.token + ' created.');
|
||||
}, error => {
|
||||
UserToken.create(
|
||||
e.detail.user.name,
|
||||
e.detail.note,
|
||||
e.detail.expirationTime
|
||||
).then(
|
||||
(response) => {
|
||||
const ctx = router.show(
|
||||
uri.formatClientLink(
|
||||
"user",
|
||||
e.detail.user.name,
|
||||
"list-tokens"
|
||||
)
|
||||
);
|
||||
ctx.controller.showSuccess(
|
||||
"Token " + response.token + " created."
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_evtDeleteToken(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
if (api.isCurrentAuthToken(e.detail.userToken)) {
|
||||
router.show(uri.formatClientLink('logout'));
|
||||
router.show(uri.formatClientLink("logout"));
|
||||
} else {
|
||||
e.detail.userToken.delete(e.detail.user.name)
|
||||
.then(() => {
|
||||
const ctx = router.show(uri.formatClientLink('user', e.detail.user.name, 'list-tokens'));
|
||||
ctx.controller.showSuccess('Token ' + e.detail.userToken.token + ' deleted.');
|
||||
}, error => {
|
||||
e.detail.userToken.delete(e.detail.user.name).then(
|
||||
() => {
|
||||
const ctx = router.show(
|
||||
uri.formatClientLink(
|
||||
"user",
|
||||
e.detail.user.name,
|
||||
"list-tokens"
|
||||
)
|
||||
);
|
||||
ctx.controller.showSuccess(
|
||||
"Token " + e.detail.userToken.token + " deleted."
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,27 +307,38 @@ class UserController {
|
|||
e.detail.userToken.note = e.detail.note;
|
||||
}
|
||||
|
||||
e.detail.userToken.save(e.detail.user.name).then(response => {
|
||||
const ctx = router.show(uri.formatClientLink('user', e.detail.user.name, 'list-tokens'));
|
||||
ctx.controller.showSuccess('Token ' + response.token + ' updated.');
|
||||
}, error => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
e.detail.userToken.save(e.detail.user.name).then(
|
||||
(response) => {
|
||||
const ctx = router.show(
|
||||
uri.formatClientLink(
|
||||
"user",
|
||||
e.detail.user.name,
|
||||
"list-tokens"
|
||||
)
|
||||
);
|
||||
ctx.controller.showSuccess(
|
||||
"Token " + response.token + " updated."
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['user', ':name'], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, 'summary');
|
||||
module.exports = (router) => {
|
||||
router.enter(["user", ":name"], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, "summary");
|
||||
});
|
||||
router.enter(['user', ':name', 'edit'], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, 'edit');
|
||||
router.enter(["user", ":name", "edit"], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, "edit");
|
||||
});
|
||||
router.enter(['user', ':name', 'list-tokens'], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, 'list-tokens');
|
||||
router.enter(["user", ":name", "list-tokens"], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, "list-tokens");
|
||||
});
|
||||
router.enter(['user', ':name', 'delete'], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, 'delete');
|
||||
router.enter(["user", ":name", "delete"], (ctx, next) => {
|
||||
ctx.controller = new UserController(ctx, "delete");
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const router = require('../router.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const UserList = require('../models/user_list.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const PageController = require('../controllers/page_controller.js');
|
||||
const UsersHeaderView = require('../views/users_header_view.js');
|
||||
const UsersPageView = require('../views/users_page_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const api = require("../api.js");
|
||||
const router = require("../router.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const UserList = require("../models/user_list.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const PageController = require("../controllers/page_controller.js");
|
||||
const UsersHeaderView = require("../views/users_header_view.js");
|
||||
const UsersPageView = require("../views/users_page_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class UserListController {
|
||||
constructor(ctx) {
|
||||
this._pageController = new PageController();
|
||||
|
||||
if (!api.hasPrivilege('users:list')) {
|
||||
if (!api.hasPrivilege("users:list")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('You don\'t have privileges to view users.');
|
||||
this._view.showError("You don't have privileges to view users.");
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('users');
|
||||
topNavigation.setTitle('Listing users');
|
||||
topNavigation.activate("users");
|
||||
topNavigation.setTitle("Listing users");
|
||||
|
||||
this._ctx = ctx;
|
||||
|
||||
|
@ -29,8 +29,9 @@ class UserListController {
|
|||
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||
parameters: ctx.parameters,
|
||||
});
|
||||
this._headerView.addEventListener(
|
||||
'navigate', e => this._evtNavigate(e));
|
||||
this._headerView.addEventListener("navigate", (e) =>
|
||||
this._evtNavigate(e)
|
||||
);
|
||||
|
||||
this._syncPageController();
|
||||
}
|
||||
|
@ -41,7 +42,8 @@ class UserListController {
|
|||
|
||||
_evtNavigate(e) {
|
||||
router.showNoDispatch(
|
||||
uri.formatClientLink('users', e.detail.parameters));
|
||||
uri.formatClientLink("users", e.detail.parameters)
|
||||
);
|
||||
Object.assign(this._ctx.parameters, e.detail.parameters);
|
||||
this._syncPageController();
|
||||
}
|
||||
|
@ -51,17 +53,22 @@ class UserListController {
|
|||
parameters: this._ctx.parameters,
|
||||
defaultLimit: 30,
|
||||
getClientUrlForPage: (offset, limit) => {
|
||||
const parameters = Object.assign(
|
||||
{}, this._ctx.parameters, {offset: offset, limit: limit});
|
||||
return uri.formatClientLink('users', parameters);
|
||||
const parameters = Object.assign({}, this._ctx.parameters, {
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
return uri.formatClientLink("users", parameters);
|
||||
},
|
||||
requestPage: (offset, limit) => {
|
||||
return UserList.search(
|
||||
this._ctx.parameters.query, offset, limit);
|
||||
this._ctx.parameters.query,
|
||||
offset,
|
||||
limit
|
||||
);
|
||||
},
|
||||
pageRenderer: pageCtx => {
|
||||
pageRenderer: (pageCtx) => {
|
||||
Object.assign(pageCtx, {
|
||||
canViewUsers: api.hasPrivilege('users:view'),
|
||||
canViewUsers: api.hasPrivilege("users:view"),
|
||||
});
|
||||
return new UsersPageView(pageCtx);
|
||||
},
|
||||
|
@ -69,10 +76,8 @@ class UserListController {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(
|
||||
['users'],
|
||||
(ctx, next) => {
|
||||
ctx.controller = new UserListController(ctx);
|
||||
});
|
||||
module.exports = (router) => {
|
||||
router.enter(["users"], (ctx, next) => {
|
||||
ctx.controller = new UserListController(ctx);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const User = require('../models/user.js');
|
||||
const topNavigation = require('../models/top_navigation.js');
|
||||
const RegistrationView = require('../views/registration_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const router = require("../router.js");
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const User = require("../models/user.js");
|
||||
const topNavigation = require("../models/top_navigation.js");
|
||||
const RegistrationView = require("../views/registration_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
class UserRegistrationController {
|
||||
constructor() {
|
||||
if (!api.hasPrivilege('users:create:self')) {
|
||||
if (!api.hasPrivilege("users:create:self")) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError('Registration is closed.');
|
||||
this._view.showError("Registration is closed.");
|
||||
return;
|
||||
}
|
||||
|
||||
topNavigation.activate('register');
|
||||
topNavigation.setTitle('Registration');
|
||||
topNavigation.activate("register");
|
||||
topNavigation.setTitle("Registration");
|
||||
this._view = new RegistrationView();
|
||||
this._view.addEventListener('submit', e => this._evtRegister(e));
|
||||
this._view.addEventListener("submit", (e) => this._evtRegister(e));
|
||||
}
|
||||
|
||||
_evtRegister(e) {
|
||||
|
@ -30,30 +30,35 @@ class UserRegistrationController {
|
|||
user.email = e.detail.email;
|
||||
user.password = e.detail.password;
|
||||
const isLoggedIn = api.isLoggedIn();
|
||||
user.save().then(() => {
|
||||
if (isLoggedIn) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
api.forget();
|
||||
return api.login(e.detail.name, e.detail.password, false);
|
||||
}
|
||||
}).then(() => {
|
||||
if (isLoggedIn) {
|
||||
const ctx = router.show(uri.formatClientLink('users'));
|
||||
ctx.controller.showSuccess('User added!');
|
||||
} else {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showSuccess('Welcome aboard!');
|
||||
}
|
||||
}, error => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
});
|
||||
user.save()
|
||||
.then(() => {
|
||||
if (isLoggedIn) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
api.forget();
|
||||
return api.login(e.detail.name, e.detail.password, false);
|
||||
}
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
if (isLoggedIn) {
|
||||
const ctx = router.show(uri.formatClientLink("users"));
|
||||
ctx.controller.showSuccess("User added!");
|
||||
} else {
|
||||
const ctx = router.show(uri.formatClientLink());
|
||||
ctx.controller.showSuccess("Welcome aboard!");
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
this._view.showError(error.message);
|
||||
this._view.enableForm();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router => {
|
||||
router.enter(['register'], (ctx, next) => {
|
||||
module.exports = (router) => {
|
||||
router.enter(["register"], (ctx, next) => {
|
||||
new UserRegistrationController();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const views = require('../util/views.js');
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const KEY_TAB = 9;
|
||||
const KEY_RETURN = 13;
|
||||
|
@ -10,14 +10,14 @@ const KEY_UP = 38;
|
|||
const KEY_DOWN = 40;
|
||||
|
||||
function _getSelectionStart(input) {
|
||||
if ('selectionStart' in input) {
|
||||
if ("selectionStart" in input) {
|
||||
return input.selectionStart;
|
||||
}
|
||||
if (document.selection) {
|
||||
input.focus();
|
||||
const sel = document.selection.createRange();
|
||||
const selLen = document.selection.createRange().text.length;
|
||||
sel.moveStart('character', -input.value.length);
|
||||
sel.moveStart("character", -input.value.length);
|
||||
return sel.text.length - selLen;
|
||||
}
|
||||
return 0;
|
||||
|
@ -27,18 +27,22 @@ class AutoCompleteControl {
|
|||
constructor(sourceInputNode, options) {
|
||||
this._sourceInputNode = sourceInputNode;
|
||||
this._options = {};
|
||||
Object.assign(this._options, {
|
||||
verticalShift: 2,
|
||||
maxResults: 15,
|
||||
getTextToFind: () => {
|
||||
const value = sourceInputNode.value;
|
||||
const start = _getSelectionStart(sourceInputNode);
|
||||
return value.substring(0, start).replace(/.*\s+/, '');
|
||||
Object.assign(
|
||||
this._options,
|
||||
{
|
||||
verticalShift: 2,
|
||||
maxResults: 15,
|
||||
getTextToFind: () => {
|
||||
const value = sourceInputNode.value;
|
||||
const start = _getSelectionStart(sourceInputNode);
|
||||
return value.substring(0, start).replace(/.*\s+/, "");
|
||||
},
|
||||
confirm: null,
|
||||
delete: null,
|
||||
getMatches: null,
|
||||
},
|
||||
confirm: null,
|
||||
delete: null,
|
||||
getMatches: null,
|
||||
}, options);
|
||||
options
|
||||
);
|
||||
|
||||
this._showTimeout = null;
|
||||
this._results = [];
|
||||
|
@ -49,22 +53,22 @@ class AutoCompleteControl {
|
|||
|
||||
hide() {
|
||||
window.clearTimeout(this._showTimeout);
|
||||
this._suggestionDiv.style.display = 'none';
|
||||
this._suggestionDiv.style.display = "none";
|
||||
this._isVisible = false;
|
||||
}
|
||||
|
||||
replaceSelectedText(result, addSpace) {
|
||||
const start = _getSelectionStart(this._sourceInputNode);
|
||||
let prefix = '';
|
||||
let prefix = "";
|
||||
let suffix = this._sourceInputNode.value.substring(start);
|
||||
let middle = this._sourceInputNode.value.substring(0, start);
|
||||
const index = middle.lastIndexOf(' ');
|
||||
const index = middle.lastIndexOf(" ");
|
||||
if (index !== -1) {
|
||||
prefix = this._sourceInputNode.value.substring(0, index + 1);
|
||||
middle = this._sourceInputNode.value.substring(index + 1);
|
||||
}
|
||||
this._sourceInputNode.value = (
|
||||
prefix + result.toString() + ' ' + suffix.trimLeft());
|
||||
this._sourceInputNode.value =
|
||||
prefix + result.toString() + " " + suffix.trimLeft();
|
||||
if (!addSpace) {
|
||||
this._sourceInputNode.value = this._sourceInputNode.value.trim();
|
||||
}
|
||||
|
@ -86,7 +90,7 @@ class AutoCompleteControl {
|
|||
}
|
||||
|
||||
_show() {
|
||||
this._suggestionDiv.style.display = 'block';
|
||||
this._suggestionDiv.style.display = "block";
|
||||
this._isVisible = true;
|
||||
}
|
||||
|
||||
|
@ -101,29 +105,32 @@ class AutoCompleteControl {
|
|||
|
||||
_install() {
|
||||
if (!this._sourceInputNode) {
|
||||
throw new Error('Input element was not found');
|
||||
throw new Error("Input element was not found");
|
||||
}
|
||||
if (this._sourceInputNode.getAttribute('data-autocomplete')) {
|
||||
if (this._sourceInputNode.getAttribute("data-autocomplete")) {
|
||||
throw new Error(
|
||||
'Autocompletion was already added for this element');
|
||||
"Autocompletion was already added for this element"
|
||||
);
|
||||
}
|
||||
this._sourceInputNode.setAttribute('data-autocomplete', true);
|
||||
this._sourceInputNode.setAttribute('autocomplete', 'off');
|
||||
this._sourceInputNode.setAttribute("data-autocomplete", true);
|
||||
this._sourceInputNode.setAttribute("autocomplete", "off");
|
||||
|
||||
this._sourceInputNode.addEventListener(
|
||||
'keydown', e => this._evtKeyDown(e));
|
||||
this._sourceInputNode.addEventListener(
|
||||
'blur', e => this._evtBlur(e));
|
||||
this._sourceInputNode.addEventListener("keydown", (e) =>
|
||||
this._evtKeyDown(e)
|
||||
);
|
||||
this._sourceInputNode.addEventListener("blur", (e) =>
|
||||
this._evtBlur(e)
|
||||
);
|
||||
|
||||
this._suggestionDiv = views.htmlToDom(
|
||||
'<div class="autocomplete"><ul></ul></div>');
|
||||
this._suggestionList = this._suggestionDiv.querySelector('ul');
|
||||
'<div class="autocomplete"><ul></ul></div>'
|
||||
);
|
||||
this._suggestionList = this._suggestionDiv.querySelector("ul");
|
||||
document.body.appendChild(this._suggestionDiv);
|
||||
|
||||
views.monitorNodeRemoval(
|
||||
this._sourceInputNode, () => {
|
||||
this._uninstall();
|
||||
});
|
||||
views.monitorNodeRemoval(this._sourceInputNode, () => {
|
||||
this._uninstall();
|
||||
});
|
||||
}
|
||||
|
||||
_uninstall() {
|
||||
|
@ -174,10 +181,9 @@ class AutoCompleteControl {
|
|||
func();
|
||||
} else {
|
||||
window.clearTimeout(this._showTimeout);
|
||||
this._showTimeout = window.setTimeout(
|
||||
() => {
|
||||
this._showOrHide();
|
||||
}, 250);
|
||||
this._showTimeout = window.setTimeout(() => {
|
||||
this._showOrHide();
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,9 +202,11 @@ class AutoCompleteControl {
|
|||
}
|
||||
|
||||
_selectPrevious() {
|
||||
this._select(this._activeResult === -1 ?
|
||||
this._results.length - 1 :
|
||||
this._activeResult - 1);
|
||||
this._select(
|
||||
this._activeResult === -1
|
||||
? this._results.length - 1
|
||||
: this._activeResult - 1
|
||||
);
|
||||
}
|
||||
|
||||
_selectNext() {
|
||||
|
@ -206,15 +214,18 @@ class AutoCompleteControl {
|
|||
}
|
||||
|
||||
_select(newActiveResult) {
|
||||
this._activeResult =
|
||||
newActiveResult.between(0, this._results.length - 1, true) ?
|
||||
newActiveResult :
|
||||
-1;
|
||||
this._activeResult = newActiveResult.between(
|
||||
0,
|
||||
this._results.length - 1,
|
||||
true
|
||||
)
|
||||
? newActiveResult
|
||||
: -1;
|
||||
this._refreshActiveResult();
|
||||
}
|
||||
|
||||
_updateResults(textToFind) {
|
||||
this._options.getMatches(textToFind).then(matches => {
|
||||
this._options.getMatches(textToFind).then((matches) => {
|
||||
const oldResults = this._results.slice();
|
||||
this._results = matches.slice(0, this._options.maxResults);
|
||||
const oldResultsHash = JSON.stringify(oldResults);
|
||||
|
@ -237,34 +248,30 @@ class AutoCompleteControl {
|
|||
}
|
||||
for (let [resultIndex, resultItem] of this._results.entries()) {
|
||||
let resultIndexWorkaround = resultIndex;
|
||||
const listItem = document.createElement('li');
|
||||
const link = document.createElement('a');
|
||||
const listItem = document.createElement("li");
|
||||
const link = document.createElement("a");
|
||||
link.innerHTML = resultItem.caption;
|
||||
link.setAttribute('href', '');
|
||||
link.setAttribute('data-key', resultItem.value);
|
||||
link.addEventListener(
|
||||
'mouseenter',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
this._activeResult = resultIndexWorkaround;
|
||||
this._refreshActiveResult();
|
||||
});
|
||||
link.addEventListener(
|
||||
'mousedown',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
this._activeResult = resultIndexWorkaround;
|
||||
this._confirm(this._getActiveSuggestion());
|
||||
this.hide();
|
||||
});
|
||||
link.setAttribute("href", "");
|
||||
link.setAttribute("data-key", resultItem.value);
|
||||
link.addEventListener("mouseenter", (e) => {
|
||||
e.preventDefault();
|
||||
this._activeResult = resultIndexWorkaround;
|
||||
this._refreshActiveResult();
|
||||
});
|
||||
link.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
this._activeResult = resultIndexWorkaround;
|
||||
this._confirm(this._getActiveSuggestion());
|
||||
this.hide();
|
||||
});
|
||||
listItem.appendChild(link);
|
||||
this._suggestionList.appendChild(listItem);
|
||||
}
|
||||
this._refreshActiveResult();
|
||||
|
||||
// display the suggestions offscreen to get the height
|
||||
this._suggestionDiv.style.left = '-9999px';
|
||||
this._suggestionDiv.style.top = '-9999px';
|
||||
this._suggestionDiv.style.left = "-9999px";
|
||||
this._suggestionDiv.style.top = "-9999px";
|
||||
this._show();
|
||||
const verticalShift = this._options.verticalShift;
|
||||
const inputRect = this._sourceInputNode.getBoundingClientRect();
|
||||
|
@ -275,17 +282,23 @@ 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 ?
|
||||
inputRect.bottom - bodyRect.top - verticalShift :
|
||||
inputRect.top - bodyRect.top - listRect.height + verticalShift;
|
||||
let y =
|
||||
direction === 1
|
||||
? inputRect.bottom - bodyRect.top - verticalShift
|
||||
: inputRect.top -
|
||||
bodyRect.top -
|
||||
listRect.height +
|
||||
verticalShift;
|
||||
|
||||
// remove offscreen items until whole suggestion list can fit on the
|
||||
// screen
|
||||
while ((y < 0 || y + listRect.height > viewPortHeight) &&
|
||||
this._suggestionList.childNodes.length) {
|
||||
while (
|
||||
(y < 0 || y + listRect.height > viewPortHeight) &&
|
||||
this._suggestionList.childNodes.length
|
||||
) {
|
||||
this._suggestionList.removeChild(this._suggestionList.lastChild);
|
||||
const prevHeight = listRect.height;
|
||||
listRect = this._suggestionDiv.getBoundingClientRect();
|
||||
|
@ -295,19 +308,19 @@ class AutoCompleteControl {
|
|||
}
|
||||
}
|
||||
|
||||
this._suggestionDiv.style.left = x + 'px';
|
||||
this._suggestionDiv.style.top = y + 'px';
|
||||
this._suggestionDiv.style.left = x + "px";
|
||||
this._suggestionDiv.style.top = y + "px";
|
||||
}
|
||||
|
||||
_refreshActiveResult() {
|
||||
let activeItem = this._suggestionList.querySelector('li.active');
|
||||
let activeItem = this._suggestionList.querySelector("li.active");
|
||||
if (activeItem) {
|
||||
activeItem.classList.remove('active');
|
||||
activeItem.classList.remove("active");
|
||||
}
|
||||
if (this._activeResult >= 0) {
|
||||
const allItems = this._suggestionList.querySelectorAll('li');
|
||||
const allItems = this._suggestionList.querySelectorAll("li");
|
||||
activeItem = allItems[this._activeResult];
|
||||
activeItem.classList.add('active');
|
||||
activeItem.classList.add("active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const api = require("../api.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('comment');
|
||||
const scoreTemplate = views.getTemplate('score');
|
||||
const template = views.getTemplate("comment");
|
||||
const scoreTemplate = views.getTemplate("score");
|
||||
|
||||
class CommentControl extends events.EventTarget {
|
||||
constructor(hostNode, comment, onlyEditing) {
|
||||
|
@ -16,104 +16,111 @@ class CommentControl extends events.EventTarget {
|
|||
this._onlyEditing = onlyEditing;
|
||||
|
||||
if (comment) {
|
||||
comment.addEventListener(
|
||||
'change', e => this._evtChange(e));
|
||||
comment.addEventListener(
|
||||
'changeScore', e => this._evtChangeScore(e));
|
||||
comment.addEventListener("change", (e) => this._evtChange(e));
|
||||
comment.addEventListener("changeScore", (e) =>
|
||||
this._evtChangeScore(e)
|
||||
);
|
||||
}
|
||||
|
||||
const isLoggedIn = comment && api.isLoggedIn(comment.user);
|
||||
const infix = isLoggedIn ? 'own' : 'any';
|
||||
views.replaceContent(this._hostNode, template({
|
||||
comment: comment,
|
||||
user: comment ? comment.user : api.user,
|
||||
canViewUsers: api.hasPrivilege('users:view'),
|
||||
canEditComment: api.hasPrivilege(`comments:edit:${infix}`),
|
||||
canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`),
|
||||
onlyEditing: onlyEditing,
|
||||
}));
|
||||
const infix = isLoggedIn ? "own" : "any";
|
||||
views.replaceContent(
|
||||
this._hostNode,
|
||||
template({
|
||||
comment: comment,
|
||||
user: comment ? comment.user : api.user,
|
||||
canViewUsers: api.hasPrivilege("users:view"),
|
||||
canEditComment: api.hasPrivilege(`comments:edit:${infix}`),
|
||||
canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`),
|
||||
onlyEditing: onlyEditing,
|
||||
})
|
||||
);
|
||||
|
||||
if (this._editButtonNodes) {
|
||||
for (let node of this._editButtonNodes) {
|
||||
node.addEventListener('click', e => this._evtEditClick(e));
|
||||
node.addEventListener("click", (e) => this._evtEditClick(e));
|
||||
}
|
||||
}
|
||||
if (this._deleteButtonNode) {
|
||||
this._deleteButtonNode.addEventListener(
|
||||
'click', e => this._evtDeleteClick(e));
|
||||
this._deleteButtonNode.addEventListener("click", (e) =>
|
||||
this._evtDeleteClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._previewEditingButtonNode) {
|
||||
this._previewEditingButtonNode.addEventListener(
|
||||
'click', e => this._evtPreviewEditingClick(e));
|
||||
this._previewEditingButtonNode.addEventListener("click", (e) =>
|
||||
this._evtPreviewEditingClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._saveChangesButtonNode) {
|
||||
this._saveChangesButtonNode.addEventListener(
|
||||
'click', e => this._evtSaveChangesClick(e));
|
||||
this._saveChangesButtonNode.addEventListener("click", (e) =>
|
||||
this._evtSaveChangesClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._cancelEditingButtonNode) {
|
||||
this._cancelEditingButtonNode.addEventListener(
|
||||
'click', e => this._evtCancelEditingClick(e));
|
||||
this._cancelEditingButtonNode.addEventListener("click", (e) =>
|
||||
this._evtCancelEditingClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
this._installScore();
|
||||
if (onlyEditing) {
|
||||
this._selectNav('edit');
|
||||
this._selectTab('edit');
|
||||
this._selectNav("edit");
|
||||
this._selectTab("edit");
|
||||
} else {
|
||||
this._selectNav('readonly');
|
||||
this._selectTab('preview');
|
||||
this._selectNav("readonly");
|
||||
this._selectTab("preview");
|
||||
}
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _scoreContainerNode() {
|
||||
return this._hostNode.querySelector('.score-container');
|
||||
return this._hostNode.querySelector(".score-container");
|
||||
}
|
||||
|
||||
get _editButtonNodes() {
|
||||
return this._hostNode.querySelectorAll('li.edit>a, a.edit');
|
||||
return this._hostNode.querySelectorAll("li.edit>a, a.edit");
|
||||
}
|
||||
|
||||
get _previewEditingButtonNode() {
|
||||
return this._hostNode.querySelector('li.preview>a');
|
||||
return this._hostNode.querySelector("li.preview>a");
|
||||
}
|
||||
|
||||
get _deleteButtonNode() {
|
||||
return this._hostNode.querySelector('.delete');
|
||||
return this._hostNode.querySelector(".delete");
|
||||
}
|
||||
|
||||
get _upvoteButtonNode() {
|
||||
return this._hostNode.querySelector('.upvote');
|
||||
return this._hostNode.querySelector(".upvote");
|
||||
}
|
||||
|
||||
get _downvoteButtonNode() {
|
||||
return this._hostNode.querySelector('.downvote');
|
||||
return this._hostNode.querySelector(".downvote");
|
||||
}
|
||||
|
||||
get _saveChangesButtonNode() {
|
||||
return this._hostNode.querySelector('.save-changes');
|
||||
return this._hostNode.querySelector(".save-changes");
|
||||
}
|
||||
|
||||
get _cancelEditingButtonNode() {
|
||||
return this._hostNode.querySelector('.cancel-editing');
|
||||
return this._hostNode.querySelector(".cancel-editing");
|
||||
}
|
||||
|
||||
get _textareaNode() {
|
||||
return this._hostNode.querySelector('.tab.edit textarea');
|
||||
return this._hostNode.querySelector(".tab.edit textarea");
|
||||
}
|
||||
|
||||
get _contentNode() {
|
||||
return this._hostNode.querySelector('.tab.preview .comment-content');
|
||||
return this._hostNode.querySelector(".tab.preview .comment-content");
|
||||
}
|
||||
|
||||
get _heightKeeperNode() {
|
||||
return this._hostNode.querySelector('.keep-height');
|
||||
return this._hostNode.querySelector(".keep-height");
|
||||
}
|
||||
|
||||
_installScore() {
|
||||
|
@ -122,32 +129,35 @@ class CommentControl extends events.EventTarget {
|
|||
scoreTemplate({
|
||||
score: this._comment ? this._comment.score : 0,
|
||||
ownScore: this._comment ? this._comment.ownScore : 0,
|
||||
canScore: api.hasPrivilege('comments:score'),
|
||||
}));
|
||||
canScore: api.hasPrivilege("comments:score"),
|
||||
})
|
||||
);
|
||||
|
||||
if (this._upvoteButtonNode) {
|
||||
this._upvoteButtonNode.addEventListener(
|
||||
'click', e => this._evtScoreClick(e, 1));
|
||||
this._upvoteButtonNode.addEventListener("click", (e) =>
|
||||
this._evtScoreClick(e, 1)
|
||||
);
|
||||
}
|
||||
if (this._downvoteButtonNode) {
|
||||
this._downvoteButtonNode.addEventListener(
|
||||
'click', e => this._evtScoreClick(e, -1));
|
||||
this._downvoteButtonNode.addEventListener("click", (e) =>
|
||||
this._evtScoreClick(e, -1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enterEditMode() {
|
||||
this._selectNav('edit');
|
||||
this._selectTab('edit');
|
||||
this._selectNav("edit");
|
||||
this._selectTab("edit");
|
||||
}
|
||||
|
||||
exitEditMode() {
|
||||
if (this._onlyEditing) {
|
||||
this._selectNav('edit');
|
||||
this._selectTab('edit');
|
||||
this._setText('');
|
||||
this._selectNav("edit");
|
||||
this._selectTab("edit");
|
||||
this._setText("");
|
||||
} else {
|
||||
this._selectNav('readonly');
|
||||
this._selectTab('preview');
|
||||
this._selectNav("readonly");
|
||||
this._selectTab("preview");
|
||||
this._setText(this._comment.text);
|
||||
}
|
||||
this._forgetHeight();
|
||||
|
@ -173,27 +183,31 @@ class CommentControl extends events.EventTarget {
|
|||
|
||||
_evtScoreClick(e, score) {
|
||||
e.preventDefault();
|
||||
if (!api.hasPrivilege('comments:score')) {
|
||||
if (!api.hasPrivilege("comments:score")) {
|
||||
return;
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('score', {
|
||||
detail: {
|
||||
comment: this._comment,
|
||||
score: this._comment.ownScore === score ? 0 : score,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("score", {
|
||||
detail: {
|
||||
comment: this._comment,
|
||||
score: this._comment.ownScore === score ? 0 : score,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_evtDeleteClick(e) {
|
||||
e.preventDefault();
|
||||
if (!window.confirm('Are you sure you want to delete this comment?')) {
|
||||
if (!window.confirm("Are you sure you want to delete this comment?")) {
|
||||
return;
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
comment: this._comment,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
comment: this._comment,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_evtChange(e) {
|
||||
|
@ -206,21 +220,24 @@ class CommentControl extends events.EventTarget {
|
|||
|
||||
_evtPreviewEditingClick(e) {
|
||||
e.preventDefault();
|
||||
this._contentNode.innerHTML =
|
||||
misc.formatMarkdown(this._textareaNode.value);
|
||||
this._selectTab('edit');
|
||||
this._selectTab('preview');
|
||||
this._contentNode.innerHTML = misc.formatMarkdown(
|
||||
this._textareaNode.value
|
||||
);
|
||||
this._selectTab("edit");
|
||||
this._selectTab("preview");
|
||||
}
|
||||
|
||||
_evtSaveChangesClick(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
target: this,
|
||||
comment: this._comment,
|
||||
text: this._textareaNode.value,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
target: this,
|
||||
comment: this._comment,
|
||||
text: this._textareaNode.value,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_evtCancelEditingClick(e) {
|
||||
|
@ -234,22 +251,22 @@ class CommentControl extends events.EventTarget {
|
|||
}
|
||||
|
||||
_selectNav(modeName) {
|
||||
for (let node of this._hostNode.querySelectorAll('nav')) {
|
||||
node.classList.toggle('active', node.classList.contains(modeName));
|
||||
for (let node of this._hostNode.querySelectorAll("nav")) {
|
||||
node.classList.toggle("active", node.classList.contains(modeName));
|
||||
}
|
||||
}
|
||||
|
||||
_selectTab(tabName) {
|
||||
this._ensureHeight();
|
||||
|
||||
for (let node of this._hostNode.querySelectorAll('.tab, .tabs li')) {
|
||||
node.classList.toggle('active', node.classList.contains(tabName));
|
||||
for (let node of this._hostNode.querySelectorAll(".tab, .tabs li")) {
|
||||
node.classList.toggle("active", node.classList.contains(tabName));
|
||||
}
|
||||
}
|
||||
|
||||
_ensureHeight() {
|
||||
this._heightKeeperNode.style.minHeight =
|
||||
this._heightKeeperNode.getBoundingClientRect().height + 'px';
|
||||
this._heightKeeperNode.getBoundingClientRect().height + "px";
|
||||
}
|
||||
|
||||
_forgetHeight() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const CommentControl = require('../controls/comment_control.js');
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
const CommentControl = require("../controls/comment_control.js");
|
||||
|
||||
const template = views.getTemplate('comment-list');
|
||||
const template = views.getTemplate("comment-list");
|
||||
|
||||
class CommentListControl extends events.EventTarget {
|
||||
constructor(hostNode, comments, reversed) {
|
||||
|
@ -13,8 +13,8 @@ class CommentListControl extends events.EventTarget {
|
|||
this._comments = comments;
|
||||
this._commentIdToNode = {};
|
||||
|
||||
comments.addEventListener('add', e => this._evtAdd(e));
|
||||
comments.addEventListener('remove', e => this._evtRemove(e));
|
||||
comments.addEventListener("add", (e) => this._evtAdd(e));
|
||||
comments.addEventListener("remove", (e) => this._evtRemove(e));
|
||||
|
||||
views.replaceContent(this._hostNode, template());
|
||||
|
||||
|
@ -28,16 +28,19 @@ class CommentListControl extends events.EventTarget {
|
|||
}
|
||||
|
||||
get _commentListNode() {
|
||||
return this._hostNode.querySelector('ul');
|
||||
return this._hostNode.querySelector("ul");
|
||||
}
|
||||
|
||||
_installCommentNode(comment) {
|
||||
const commentListItemNode = document.createElement('li');
|
||||
const commentListItemNode = document.createElement("li");
|
||||
const commentControl = new CommentControl(
|
||||
commentListItemNode, comment, false);
|
||||
events.proxyEvent(commentControl, this, 'submit');
|
||||
events.proxyEvent(commentControl, this, 'score');
|
||||
events.proxyEvent(commentControl, this, 'delete');
|
||||
commentListItemNode,
|
||||
comment,
|
||||
false
|
||||
);
|
||||
events.proxyEvent(commentControl, this, "submit");
|
||||
events.proxyEvent(commentControl, this, "score");
|
||||
events.proxyEvent(commentControl, this, "delete");
|
||||
this._commentIdToNode[comment.id] = commentListItemNode;
|
||||
this._commentListNode.appendChild(commentListItemNode);
|
||||
}
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const ICON_CLASS_OPENED = 'fa-chevron-down';
|
||||
const ICON_CLASS_CLOSED = 'fa-chevron-up';
|
||||
const ICON_CLASS_OPENED = "fa-chevron-down";
|
||||
const ICON_CLASS_CLOSED = "fa-chevron-up";
|
||||
|
||||
const views = require('../util/views.js');
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('expander');
|
||||
const template = views.getTemplate("expander");
|
||||
|
||||
class ExpanderControl {
|
||||
constructor(name, title, nodes) {
|
||||
this._name = name;
|
||||
|
||||
nodes = Array.from(nodes).filter(n => n);
|
||||
nodes = Array.from(nodes).filter((n) => n);
|
||||
if (!nodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expanderNode = template({title: title});
|
||||
const toggleLinkNode = expanderNode.querySelector('a');
|
||||
const toggleIconNode = expanderNode.querySelector('i');
|
||||
const expanderContentNode = expanderNode.querySelector('div');
|
||||
toggleLinkNode.addEventListener('click', e => this._evtToggleClick(e));
|
||||
const expanderNode = template({ title: title });
|
||||
const toggleLinkNode = expanderNode.querySelector("a");
|
||||
const toggleIconNode = expanderNode.querySelector("i");
|
||||
const expanderContentNode = expanderNode.querySelector("div");
|
||||
toggleLinkNode.addEventListener("click", (e) =>
|
||||
this._evtToggleClick(e)
|
||||
);
|
||||
|
||||
nodes[0].parentNode.insertBefore(expanderNode, nodes[0]);
|
||||
|
||||
|
@ -32,29 +34,30 @@ class ExpanderControl {
|
|||
this._toggleIconNode = toggleIconNode;
|
||||
|
||||
expanderNode.classList.toggle(
|
||||
'collapsed',
|
||||
this._allStates[this._name] === undefined ?
|
||||
false :
|
||||
!this._allStates[this._name]);
|
||||
"collapsed",
|
||||
this._allStates[this._name] === undefined
|
||||
? false
|
||||
: !this._allStates[this._name]
|
||||
);
|
||||
this._syncIcon();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line accessor-pairs
|
||||
set title(newTitle) {
|
||||
if (this._expanderNode) {
|
||||
this._expanderNode
|
||||
.querySelector('header span')
|
||||
.textContent = newTitle;
|
||||
this._expanderNode.querySelector(
|
||||
"header span"
|
||||
).textContent = newTitle;
|
||||
}
|
||||
}
|
||||
|
||||
get _isOpened() {
|
||||
return !this._expanderNode.classList.contains('collapsed');
|
||||
return !this._expanderNode.classList.contains("collapsed");
|
||||
}
|
||||
|
||||
get _allStates() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('expander')) || {};
|
||||
return JSON.parse(localStorage.getItem("expander")) || {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
|
@ -63,12 +66,12 @@ class ExpanderControl {
|
|||
_save() {
|
||||
const newStates = Object.assign({}, this._allStates);
|
||||
newStates[this._name] = this._isOpened;
|
||||
localStorage.setItem('expander', JSON.stringify(newStates));
|
||||
localStorage.setItem("expander", JSON.stringify(newStates));
|
||||
}
|
||||
|
||||
_evtToggleClick(e) {
|
||||
e.preventDefault();
|
||||
this._expanderNode.classList.toggle('collapsed');
|
||||
this._expanderNode.classList.toggle("collapsed");
|
||||
this._save();
|
||||
this._syncIcon();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('file-dropper');
|
||||
const template = views.getTemplate("file-dropper");
|
||||
|
||||
const KEY_RETURN = 13;
|
||||
|
||||
|
@ -17,37 +17,42 @@ class FileDropperControl extends events.EventTarget {
|
|||
allowMultiple: options.allowMultiple,
|
||||
allowUrls: options.allowUrls,
|
||||
lock: options.lock,
|
||||
id: 'file-' + Math.random().toString(36).substring(7),
|
||||
id: "file-" + Math.random().toString(36).substring(7),
|
||||
urlPlaceholder:
|
||||
options.urlPlaceholder || 'Alternatively, paste an URL here.',
|
||||
options.urlPlaceholder || "Alternatively, paste an URL here.",
|
||||
});
|
||||
|
||||
this._dropperNode = source.querySelector('.file-dropper');
|
||||
this._urlInputNode = source.querySelector('input[type=text]');
|
||||
this._urlConfirmButtonNode = source.querySelector('button');
|
||||
this._fileInputNode = source.querySelector('input[type=file]');
|
||||
this._fileInputNode.style.display = 'none';
|
||||
this._dropperNode = source.querySelector(".file-dropper");
|
||||
this._urlInputNode = source.querySelector("input[type=text]");
|
||||
this._urlConfirmButtonNode = source.querySelector("button");
|
||||
this._fileInputNode = source.querySelector("input[type=file]");
|
||||
this._fileInputNode.style.display = "none";
|
||||
this._fileInputNode.multiple = options.allowMultiple || false;
|
||||
|
||||
this._counter = 0;
|
||||
this._dropperNode.addEventListener(
|
||||
'dragenter', e => this._evtDragEnter(e));
|
||||
this._dropperNode.addEventListener(
|
||||
'dragleave', e => this._evtDragLeave(e));
|
||||
this._dropperNode.addEventListener(
|
||||
'dragover', e => this._evtDragOver(e));
|
||||
this._dropperNode.addEventListener(
|
||||
'drop', e => this._evtDrop(e));
|
||||
this._fileInputNode.addEventListener(
|
||||
'change', e => this._evtFileChange(e));
|
||||
this._dropperNode.addEventListener("dragenter", (e) =>
|
||||
this._evtDragEnter(e)
|
||||
);
|
||||
this._dropperNode.addEventListener("dragleave", (e) =>
|
||||
this._evtDragLeave(e)
|
||||
);
|
||||
this._dropperNode.addEventListener("dragover", (e) =>
|
||||
this._evtDragOver(e)
|
||||
);
|
||||
this._dropperNode.addEventListener("drop", (e) => this._evtDrop(e));
|
||||
this._fileInputNode.addEventListener("change", (e) =>
|
||||
this._evtFileChange(e)
|
||||
);
|
||||
|
||||
if (this._urlInputNode) {
|
||||
this._urlInputNode.addEventListener(
|
||||
'keydown', e => this._evtUrlInputKeyDown(e));
|
||||
this._urlInputNode.addEventListener("keydown", (e) =>
|
||||
this._evtUrlInputKeyDown(e)
|
||||
);
|
||||
}
|
||||
if (this._urlConfirmButtonNode) {
|
||||
this._urlConfirmButtonNode.addEventListener(
|
||||
'click', e => this._evtUrlConfirmButtonClick(e));
|
||||
this._urlConfirmButtonNode.addEventListener("click", (e) =>
|
||||
this._evtUrlConfirmButtonClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
this._originalHtml = this._dropperNode.innerHTML;
|
||||
|
@ -56,24 +61,27 @@ class FileDropperControl extends events.EventTarget {
|
|||
|
||||
reset() {
|
||||
this._dropperNode.innerHTML = this._originalHtml;
|
||||
this.dispatchEvent(new CustomEvent('reset'));
|
||||
this.dispatchEvent(new CustomEvent("reset"));
|
||||
}
|
||||
|
||||
_emitFiles(files) {
|
||||
files = Array.from(files);
|
||||
if (this._options.lock) {
|
||||
this._dropperNode.innerText =
|
||||
files.map(file => file.name).join(', ');
|
||||
this._dropperNode.innerText = files
|
||||
.map((file) => file.name)
|
||||
.join(", ");
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('fileadd', {detail: {files: files}}));
|
||||
new CustomEvent("fileadd", { detail: { files: files } })
|
||||
);
|
||||
}
|
||||
|
||||
_emitUrls(urls) {
|
||||
urls = Array.from(urls).map(url => url.trim());
|
||||
urls = Array.from(urls).map((url) => url.trim());
|
||||
if (this._options.lock) {
|
||||
this._dropperNode.innerText =
|
||||
urls.map(url => url.split(/\//).reverse()[0]).join(', ');
|
||||
this._dropperNode.innerText = urls
|
||||
.map((url) => url.split(/\//).reverse()[0])
|
||||
.join(", ");
|
||||
}
|
||||
for (let url of urls) {
|
||||
if (!url) {
|
||||
|
@ -84,18 +92,20 @@ class FileDropperControl extends events.EventTarget {
|
|||
return;
|
||||
}
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('urladd', {detail: {urls: urls}}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("urladd", { detail: { urls: urls } })
|
||||
);
|
||||
}
|
||||
|
||||
_evtDragEnter(e) {
|
||||
this._dropperNode.classList.add('active');
|
||||
this._dropperNode.classList.add("active");
|
||||
this._counter++;
|
||||
}
|
||||
|
||||
_evtDragLeave(e) {
|
||||
this._counter--;
|
||||
if (this._counter === 0) {
|
||||
this._dropperNode.classList.remove('active');
|
||||
this._dropperNode.classList.remove("active");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,12 +119,12 @@ class FileDropperControl extends events.EventTarget {
|
|||
|
||||
_evtDrop(e) {
|
||||
e.preventDefault();
|
||||
this._dropperNode.classList.remove('active');
|
||||
this._dropperNode.classList.remove("active");
|
||||
if (!e.dataTransfer.files.length) {
|
||||
window.alert('Only files are supported.');
|
||||
window.alert("Only files are supported.");
|
||||
}
|
||||
if (!this._options.allowMultiple && e.dataTransfer.files.length > 1) {
|
||||
window.alert('Cannot select multiple files.');
|
||||
window.alert("Cannot select multiple files.");
|
||||
}
|
||||
this._emitFiles(e.dataTransfer.files);
|
||||
}
|
||||
|
@ -124,16 +134,16 @@ class FileDropperControl extends events.EventTarget {
|
|||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
this._dropperNode.classList.remove('active');
|
||||
this._dropperNode.classList.remove("active");
|
||||
this._emitUrls(this._urlInputNode.value.split(/[\r\n]/));
|
||||
this._urlInputNode.value = '';
|
||||
this._urlInputNode.value = "";
|
||||
}
|
||||
|
||||
_evtUrlConfirmButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this._dropperNode.classList.remove('active');
|
||||
this._dropperNode.classList.remove("active");
|
||||
this._emitUrls(this._urlInputNode.value.split(/[\r\n]/));
|
||||
this._urlInputNode.value = '';
|
||||
this._urlInputNode.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +1,54 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const misc = require('../util/misc.js');
|
||||
const PoolList = require('../models/pool_list.js');
|
||||
const AutoCompleteControl = require('./auto_complete_control.js');
|
||||
const misc = require("../util/misc.js");
|
||||
const PoolList = require("../models/pool_list.js");
|
||||
const AutoCompleteControl = require("./auto_complete_control.js");
|
||||
|
||||
function _poolListToMatches(pools, options) {
|
||||
return [...pools].sort((pool1, pool2) => {
|
||||
return pool2.postCount - pool1.postCount;
|
||||
}).map(pool => {
|
||||
let cssName = misc.makeCssName(pool.category, 'pool');
|
||||
const caption = (
|
||||
'<span class="' + cssName + '">'
|
||||
+ misc.escapeHtml(pool.names[0] + ' (' + pool.postCount + ')')
|
||||
+ '</span>');
|
||||
return {
|
||||
caption: caption,
|
||||
value: pool,
|
||||
};
|
||||
});
|
||||
return [...pools]
|
||||
.sort((pool1, pool2) => {
|
||||
return pool2.postCount - pool1.postCount;
|
||||
})
|
||||
.map((pool) => {
|
||||
let cssName = misc.makeCssName(pool.category, "pool");
|
||||
const caption =
|
||||
'<span class="' +
|
||||
cssName +
|
||||
'">' +
|
||||
misc.escapeHtml(pool.names[0] + " (" + pool.postCount + ")") +
|
||||
"</span>";
|
||||
return {
|
||||
caption: caption,
|
||||
value: pool,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class PoolAutoCompleteControl extends AutoCompleteControl {
|
||||
constructor(input, options) {
|
||||
const minLengthForPartialSearch = 3;
|
||||
|
||||
options.getMatches = text => {
|
||||
options.getMatches = (text) => {
|
||||
const term = misc.escapeSearchTerm(text);
|
||||
const query = (
|
||||
text.length < minLengthForPartialSearch
|
||||
? term + '*'
|
||||
: '*' + term + '*') + ' sort:post-count';
|
||||
const query =
|
||||
(text.length < minLengthForPartialSearch
|
||||
? term + "*"
|
||||
: "*" + term + "*") + " sort:post-count";
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
PoolList.search(
|
||||
query, 0, this._options.maxResults, ['id', 'names', 'category', 'postCount', 'version'])
|
||||
.then(
|
||||
response => resolve(
|
||||
_poolListToMatches(response.results, this._options)),
|
||||
reject);
|
||||
PoolList.search(query, 0, this._options.maxResults, [
|
||||
"id",
|
||||
"names",
|
||||
"category",
|
||||
"postCount",
|
||||
"version",
|
||||
]).then(
|
||||
(response) =>
|
||||
resolve(
|
||||
_poolListToMatches(response.results, this._options)
|
||||
),
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const pools = require('../pools.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const Pool = require('../models/pool.js');
|
||||
const settings = require('../models/settings.js');
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const PoolAutoCompleteControl = require('./pool_auto_complete_control.js');
|
||||
const api = require("../api.js");
|
||||
const pools = require("../pools.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const Pool = require("../models/pool.js");
|
||||
const settings = require("../models/settings.js");
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
const PoolAutoCompleteControl = require("./pool_auto_complete_control.js");
|
||||
|
||||
const KEY_SPACE = 32;
|
||||
const KEY_RETURN = 13;
|
||||
|
||||
const SOURCE_INIT = 'init';
|
||||
const SOURCE_IMPLICATION = 'implication';
|
||||
const SOURCE_USER_INPUT = 'user-input';
|
||||
const SOURCE_CLIPBOARD = 'clipboard';
|
||||
const SOURCE_INIT = "init";
|
||||
const SOURCE_IMPLICATION = "implication";
|
||||
const SOURCE_USER_INPUT = "user-input";
|
||||
const SOURCE_CLIPBOARD = "clipboard";
|
||||
|
||||
const template = views.getTemplate('pool-input');
|
||||
const template = views.getTemplate("pool-input");
|
||||
|
||||
function _fadeOutListItemNodeStatus(listItemNode) {
|
||||
if (listItemNode.classList.length) {
|
||||
|
@ -27,8 +27,7 @@ function _fadeOutListItemNodeStatus(listItemNode) {
|
|||
}
|
||||
listItemNode.fadeTimeout = window.setTimeout(() => {
|
||||
while (listItemNode.classList.length) {
|
||||
listItemNode.classList.remove(
|
||||
listItemNode.classList.item(0));
|
||||
listItemNode.classList.remove(listItemNode.classList.item(0));
|
||||
}
|
||||
listItemNode.fadeTimeout = null;
|
||||
}, 2500);
|
||||
|
@ -45,29 +44,33 @@ class PoolInputControl extends events.EventTarget {
|
|||
// dom
|
||||
const editAreaNode = template();
|
||||
this._editAreaNode = editAreaNode;
|
||||
this._poolInputNode = editAreaNode.querySelector('input');
|
||||
this._poolListNode = editAreaNode.querySelector('ul.compact-pools');
|
||||
this._poolInputNode = editAreaNode.querySelector("input");
|
||||
this._poolListNode = editAreaNode.querySelector("ul.compact-pools");
|
||||
|
||||
this._autoCompleteControl = new PoolAutoCompleteControl(
|
||||
this._poolInputNode, {
|
||||
this._poolInputNode,
|
||||
{
|
||||
getTextToFind: () => {
|
||||
return this._poolInputNode.value;
|
||||
},
|
||||
confirm: pool => {
|
||||
this._poolInputNode.value = '';
|
||||
confirm: (pool) => {
|
||||
this._poolInputNode.value = "";
|
||||
this.addPool(pool, SOURCE_USER_INPUT);
|
||||
},
|
||||
delete: pool => {
|
||||
this._poolInputNode.value = '';
|
||||
delete: (pool) => {
|
||||
this._poolInputNode.value = "";
|
||||
this.deletePool(pool);
|
||||
},
|
||||
verticalShift: -2
|
||||
});
|
||||
verticalShift: -2,
|
||||
}
|
||||
);
|
||||
|
||||
// show
|
||||
this._hostNode.style.display = 'none';
|
||||
this._hostNode.style.display = "none";
|
||||
this._hostNode.parentNode.insertBefore(
|
||||
this._editAreaNode, hostNode.nextSibling);
|
||||
this._editAreaNode,
|
||||
hostNode.nextSibling
|
||||
);
|
||||
|
||||
// add existing pools
|
||||
for (let pool of [...this.pools]) {
|
||||
|
@ -81,19 +84,21 @@ class PoolInputControl extends events.EventTarget {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.pools.add(pool, false)
|
||||
this.pools.add(pool, false);
|
||||
|
||||
const listItemNode = this._createListItemNode(pool);
|
||||
if (!pool.category) {
|
||||
listItemNode.classList.add('new');
|
||||
listItemNode.classList.add("new");
|
||||
}
|
||||
this._poolListNode.prependChild(listItemNode);
|
||||
_fadeOutListItemNodeStatus(listItemNode);
|
||||
|
||||
this.dispatchEvent(new CustomEvent('add', {
|
||||
detail: {pool: pool, source: source},
|
||||
}));
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("add", {
|
||||
detail: { pool: pool, source: source },
|
||||
})
|
||||
);
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -107,52 +112,57 @@ class PoolInputControl extends events.EventTarget {
|
|||
|
||||
this._deleteListItemNode(pool);
|
||||
|
||||
this.dispatchEvent(new CustomEvent('remove', {
|
||||
detail: {pool: pool},
|
||||
}));
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("remove", {
|
||||
detail: { pool: pool },
|
||||
})
|
||||
);
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
|
||||
_createListItemNode(pool) {
|
||||
const className = pool.category ?
|
||||
misc.makeCssName(pool.category, 'pool') :
|
||||
null;
|
||||
const className = pool.category
|
||||
? misc.makeCssName(pool.category, "pool")
|
||||
: null;
|
||||
|
||||
const poolLinkNode = document.createElement('a');
|
||||
const poolLinkNode = document.createElement("a");
|
||||
if (className) {
|
||||
poolLinkNode.classList.add(className);
|
||||
}
|
||||
poolLinkNode.setAttribute(
|
||||
'href', uri.formatClientLink('pool', pool.names[0]));
|
||||
"href",
|
||||
uri.formatClientLink("pool", pool.names[0])
|
||||
);
|
||||
|
||||
const poolIconNode = document.createElement('i');
|
||||
poolIconNode.classList.add('fa');
|
||||
poolIconNode.classList.add('fa-pool');
|
||||
const poolIconNode = document.createElement("i");
|
||||
poolIconNode.classList.add("fa");
|
||||
poolIconNode.classList.add("fa-pool");
|
||||
poolLinkNode.appendChild(poolIconNode);
|
||||
|
||||
const searchLinkNode = document.createElement('a');
|
||||
const searchLinkNode = document.createElement("a");
|
||||
if (className) {
|
||||
searchLinkNode.classList.add(className);
|
||||
}
|
||||
searchLinkNode.setAttribute(
|
||||
'href', uri.formatClientLink(
|
||||
'posts', {query: "pool:" + pool.id}));
|
||||
searchLinkNode.textContent = pool.names[0] + ' ';
|
||||
"href",
|
||||
uri.formatClientLink("posts", { query: "pool:" + pool.id })
|
||||
);
|
||||
searchLinkNode.textContent = pool.names[0] + " ";
|
||||
|
||||
const usagesNode = document.createElement('span');
|
||||
usagesNode.classList.add('pool-usages');
|
||||
usagesNode.setAttribute('data-pseudo-content', pool.postCount);
|
||||
const usagesNode = document.createElement("span");
|
||||
usagesNode.classList.add("pool-usages");
|
||||
usagesNode.setAttribute("data-pseudo-content", pool.postCount);
|
||||
|
||||
const removalLinkNode = document.createElement('a');
|
||||
removalLinkNode.classList.add('remove-pool');
|
||||
removalLinkNode.setAttribute('href', '');
|
||||
removalLinkNode.setAttribute('data-pseudo-content', '×');
|
||||
removalLinkNode.addEventListener('click', e => {
|
||||
const removalLinkNode = document.createElement("a");
|
||||
removalLinkNode.classList.add("remove-pool");
|
||||
removalLinkNode.setAttribute("href", "");
|
||||
removalLinkNode.setAttribute("data-pseudo-content", "×");
|
||||
removalLinkNode.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.deletePool(pool);
|
||||
});
|
||||
|
||||
const listItemNode = document.createElement('li');
|
||||
const listItemNode = document.createElement("li");
|
||||
listItemNode.appendChild(removalLinkNode);
|
||||
listItemNode.appendChild(poolLinkNode);
|
||||
listItemNode.appendChild(searchLinkNode);
|
||||
|
|
|
@ -1,36 +1,38 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const settings = require('../models/settings.js');
|
||||
const views = require('../util/views.js');
|
||||
const optimizedResize = require('../util/optimized_resize.js');
|
||||
const settings = require("../models/settings.js");
|
||||
const views = require("../util/views.js");
|
||||
const optimizedResize = require("../util/optimized_resize.js");
|
||||
|
||||
class PostContentControl {
|
||||
constructor(hostNode, post, viewportSizeCalculator, fitFunctionOverride) {
|
||||
this._post = post;
|
||||
this._viewportSizeCalculator = viewportSizeCalculator;
|
||||
this._hostNode = hostNode;
|
||||
this._template = views.getTemplate('post-content');
|
||||
this._template = views.getTemplate("post-content");
|
||||
|
||||
let fitMode = settings.get().fitMode;
|
||||
if (typeof fitFunctionOverride !== 'undefined') {
|
||||
if (typeof fitFunctionOverride !== "undefined") {
|
||||
fitMode = fitFunctionOverride;
|
||||
}
|
||||
|
||||
this._currentFitFunction = {
|
||||
'fit-both': this.fitBoth,
|
||||
'fit-original': this.fitOriginal,
|
||||
'fit-width': this.fitWidth,
|
||||
'fit-height': this.fitHeight,
|
||||
}[fitMode] || this.fitBoth;
|
||||
this._currentFitFunction =
|
||||
{
|
||||
"fit-both": this.fitBoth,
|
||||
"fit-original": this.fitOriginal,
|
||||
"fit-width": this.fitWidth,
|
||||
"fit-height": this.fitHeight,
|
||||
}[fitMode] || this.fitBoth;
|
||||
|
||||
this._install();
|
||||
|
||||
this._post.addEventListener(
|
||||
'changeContent', e => this._evtPostContentChange(e));
|
||||
this._post.addEventListener("changeContent", (e) =>
|
||||
this._evtPostContentChange(e)
|
||||
);
|
||||
}
|
||||
|
||||
disableOverlay() {
|
||||
this._hostNode.querySelector('.post-overlay').style.display = 'none';
|
||||
this._hostNode.querySelector(".post-overlay").style.display = "none";
|
||||
}
|
||||
|
||||
fitWidth() {
|
||||
|
@ -92,10 +94,11 @@ class PostContentControl {
|
|||
|
||||
_resize(width, height) {
|
||||
const resizeListenerNodes = [this._postContentNode].concat(
|
||||
...this._postContentNode.querySelectorAll('.resize-listener'));
|
||||
...this._postContentNode.querySelectorAll(".resize-listener")
|
||||
);
|
||||
for (let node of resizeListenerNodes) {
|
||||
node.style.width = width + 'px';
|
||||
node.style.height = height + 'px';
|
||||
node.style.width = width + "px";
|
||||
node.style.height = height + "px";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,10 +109,9 @@ class PostContentControl {
|
|||
_install() {
|
||||
this._reinstall();
|
||||
optimizedResize.add(() => this._refreshSize());
|
||||
views.monitorNodeRemoval(
|
||||
this._hostNode, () => {
|
||||
this._uninstall();
|
||||
});
|
||||
views.monitorNodeRemoval(this._hostNode, () => {
|
||||
this._uninstall();
|
||||
});
|
||||
}
|
||||
|
||||
_reinstall() {
|
||||
|
@ -118,7 +120,7 @@ class PostContentControl {
|
|||
autoplay: settings.get().autoplayVideos,
|
||||
});
|
||||
if (settings.get().transparencyGrid) {
|
||||
newNode.classList.add('transparency-grid');
|
||||
newNode.classList.add("transparency-grid");
|
||||
}
|
||||
if (this._postContentNode) {
|
||||
this._hostNode.replaceChild(newNode, this._postContentNode);
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const events = require('../events.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const Note = require('../models/note.js');
|
||||
const Point = require('../models/point.js');
|
||||
const TagInputControl = require('./tag_input_control.js');
|
||||
const PoolInputControl = require('./pool_input_control.js');
|
||||
const ExpanderControl = require('../controls/expander_control.js');
|
||||
const FileDropperControl = require('../controls/file_dropper_control.js');
|
||||
const api = require("../api.js");
|
||||
const events = require("../events.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const views = require("../util/views.js");
|
||||
const Note = require("../models/note.js");
|
||||
const Point = require("../models/point.js");
|
||||
const TagInputControl = require("./tag_input_control.js");
|
||||
const PoolInputControl = require("./pool_input_control.js");
|
||||
const ExpanderControl = require("../controls/expander_control.js");
|
||||
const FileDropperControl = require("../controls/file_dropper_control.js");
|
||||
|
||||
const template = views.getTemplate('post-edit-sidebar');
|
||||
const template = views.getTemplate("post-edit-sidebar");
|
||||
|
||||
class PostEditSidebarControl extends events.EventTarget {
|
||||
constructor(hostNode, post, postContentControl, postNotesOverlayControl) {
|
||||
|
@ -24,182 +24,222 @@ class PostEditSidebarControl extends events.EventTarget {
|
|||
|
||||
this._postNotesOverlayControl.switchToPassiveEdit();
|
||||
|
||||
views.replaceContent(this._hostNode, template({
|
||||
post: this._post,
|
||||
enableSafety: api.safetyEnabled(),
|
||||
hasClipboard: document.queryCommandSupported('copy'),
|
||||
canEditPostSafety: api.hasPrivilege('posts:edit:safety'),
|
||||
canEditPostSource: api.hasPrivilege('posts:edit:source'),
|
||||
canEditPostTags: api.hasPrivilege('posts:edit:tags'),
|
||||
canEditPostRelations: api.hasPrivilege('posts:edit:relations'),
|
||||
canEditPostNotes: api.hasPrivilege('posts:edit:notes') &&
|
||||
post.type !== 'video' &&
|
||||
post.type !== 'flash',
|
||||
canEditPostFlags: api.hasPrivilege('posts:edit:flags'),
|
||||
canEditPostContent: api.hasPrivilege('posts:edit:content'),
|
||||
canEditPostThumbnail: api.hasPrivilege('posts:edit:thumbnail'),
|
||||
canEditPoolPosts: api.hasPrivilege('pools:edit:posts'),
|
||||
canCreateAnonymousPosts: api.hasPrivilege('posts:create:anonymous'),
|
||||
canDeletePosts: api.hasPrivilege('posts:delete'),
|
||||
canFeaturePosts: api.hasPrivilege('posts:feature'),
|
||||
canMergePosts: api.hasPrivilege('posts:merge'),
|
||||
}));
|
||||
views.replaceContent(
|
||||
this._hostNode,
|
||||
template({
|
||||
post: this._post,
|
||||
enableSafety: api.safetyEnabled(),
|
||||
hasClipboard: document.queryCommandSupported("copy"),
|
||||
canEditPostSafety: api.hasPrivilege("posts:edit:safety"),
|
||||
canEditPostSource: api.hasPrivilege("posts:edit:source"),
|
||||
canEditPostTags: api.hasPrivilege("posts:edit:tags"),
|
||||
canEditPostRelations: api.hasPrivilege("posts:edit:relations"),
|
||||
canEditPostNotes:
|
||||
api.hasPrivilege("posts:edit:notes") &&
|
||||
post.type !== "video" &&
|
||||
post.type !== "flash",
|
||||
canEditPostFlags: api.hasPrivilege("posts:edit:flags"),
|
||||
canEditPostContent: api.hasPrivilege("posts:edit:content"),
|
||||
canEditPostThumbnail: api.hasPrivilege("posts:edit:thumbnail"),
|
||||
canEditPoolPosts: api.hasPrivilege("pools:edit:posts"),
|
||||
canCreateAnonymousPosts: api.hasPrivilege(
|
||||
"posts:create:anonymous"
|
||||
),
|
||||
canDeletePosts: api.hasPrivilege("posts:delete"),
|
||||
canFeaturePosts: api.hasPrivilege("posts:feature"),
|
||||
canMergePosts: api.hasPrivilege("posts:merge"),
|
||||
})
|
||||
);
|
||||
|
||||
new ExpanderControl(
|
||||
'post-info',
|
||||
'Basic info',
|
||||
this._hostNode.querySelectorAll('.safety, .relations, .flags, .post-source'));
|
||||
"post-info",
|
||||
"Basic info",
|
||||
this._hostNode.querySelectorAll(
|
||||
".safety, .relations, .flags, .post-source"
|
||||
)
|
||||
);
|
||||
this._tagsExpander = new ExpanderControl(
|
||||
'post-tags',
|
||||
"post-tags",
|
||||
`Tags (${this._post.tags.length})`,
|
||||
this._hostNode.querySelectorAll('.tags'));
|
||||
this._hostNode.querySelectorAll(".tags")
|
||||
);
|
||||
this._notesExpander = new ExpanderControl(
|
||||
'post-notes',
|
||||
'Notes',
|
||||
this._hostNode.querySelectorAll('.notes'));
|
||||
"post-notes",
|
||||
"Notes",
|
||||
this._hostNode.querySelectorAll(".notes")
|
||||
);
|
||||
this._poolsExpander = new ExpanderControl(
|
||||
'post-pools',
|
||||
"post-pools",
|
||||
`Pools (${this._post.pools.length})`,
|
||||
this._hostNode.querySelectorAll('.pools'));
|
||||
this._hostNode.querySelectorAll(".pools")
|
||||
);
|
||||
new ExpanderControl(
|
||||
'post-content',
|
||||
'Content',
|
||||
this._hostNode.querySelectorAll('.post-content, .post-thumbnail'));
|
||||
"post-content",
|
||||
"Content",
|
||||
this._hostNode.querySelectorAll(".post-content, .post-thumbnail")
|
||||
);
|
||||
new ExpanderControl(
|
||||
'post-management',
|
||||
'Management',
|
||||
this._hostNode.querySelectorAll('.management'));
|
||||
"post-management",
|
||||
"Management",
|
||||
this._hostNode.querySelectorAll(".management")
|
||||
);
|
||||
|
||||
this._syncExpanderTitles();
|
||||
|
||||
if (this._formNode) {
|
||||
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
||||
this._formNode.addEventListener("submit", (e) =>
|
||||
this._evtSubmit(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._tagInputNode) {
|
||||
this._tagControl = new TagInputControl(
|
||||
this._tagInputNode, post.tags);
|
||||
this._tagInputNode,
|
||||
post.tags
|
||||
);
|
||||
}
|
||||
|
||||
if (this._poolInputNode) {
|
||||
this._poolControl = new PoolInputControl(
|
||||
this._poolInputNode, post.pools);
|
||||
this._poolInputNode,
|
||||
post.pools
|
||||
);
|
||||
}
|
||||
|
||||
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 => {
|
||||
urlPlaceholder: "...or paste an URL here.",
|
||||
}
|
||||
);
|
||||
this._contentFileDropper.addEventListener("fileadd", (e) => {
|
||||
this._newPostContent = e.detail.files[0];
|
||||
});
|
||||
this._contentFileDropper.addEventListener('urladd', e => {
|
||||
this._contentFileDropper.addEventListener("urladd", (e) => {
|
||||
this._newPostContent = e.detail.urls[0];
|
||||
});
|
||||
}
|
||||
|
||||
if (this._thumbnailInputNode) {
|
||||
this._thumbnailFileDropper = new FileDropperControl(
|
||||
this._thumbnailInputNode, {lock: true});
|
||||
this._thumbnailFileDropper.addEventListener('fileadd', e => {
|
||||
this._thumbnailInputNode,
|
||||
{ lock: true }
|
||||
);
|
||||
this._thumbnailFileDropper.addEventListener("fileadd", (e) => {
|
||||
this._newPostThumbnail = e.detail.files[0];
|
||||
this._thumbnailRemovalLinkNode.style.display = 'block';
|
||||
this._thumbnailRemovalLinkNode.style.display = "block";
|
||||
});
|
||||
}
|
||||
|
||||
if (this._thumbnailRemovalLinkNode) {
|
||||
this._thumbnailRemovalLinkNode.addEventListener(
|
||||
'click', e => this._evtRemoveThumbnailClick(e));
|
||||
this._thumbnailRemovalLinkNode.style.display =
|
||||
this._post.hasCustomThumbnail ? 'block' : 'none';
|
||||
this._thumbnailRemovalLinkNode.addEventListener("click", (e) =>
|
||||
this._evtRemoveThumbnailClick(e)
|
||||
);
|
||||
this._thumbnailRemovalLinkNode.style.display = this._post
|
||||
.hasCustomThumbnail
|
||||
? "block"
|
||||
: "none";
|
||||
}
|
||||
|
||||
if (this._addNoteLinkNode) {
|
||||
this._addNoteLinkNode.addEventListener(
|
||||
'click', e => this._evtAddNoteClick(e));
|
||||
this._addNoteLinkNode.addEventListener("click", (e) =>
|
||||
this._evtAddNoteClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._copyNotesLinkNode) {
|
||||
this._copyNotesLinkNode.addEventListener(
|
||||
'click', e => this._evtCopyNotesClick(e));
|
||||
this._copyNotesLinkNode.addEventListener("click", (e) =>
|
||||
this._evtCopyNotesClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._pasteNotesLinkNode) {
|
||||
this._pasteNotesLinkNode.addEventListener(
|
||||
'click', e => this._evtPasteNotesClick(e));
|
||||
this._pasteNotesLinkNode.addEventListener("click", (e) =>
|
||||
this._evtPasteNotesClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._deleteNoteLinkNode) {
|
||||
this._deleteNoteLinkNode.addEventListener(
|
||||
'click', e => this._evtDeleteNoteClick(e));
|
||||
this._deleteNoteLinkNode.addEventListener("click", (e) =>
|
||||
this._evtDeleteNoteClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._featureLinkNode) {
|
||||
this._featureLinkNode.addEventListener(
|
||||
'click', e => this._evtFeatureClick(e));
|
||||
this._featureLinkNode.addEventListener("click", (e) =>
|
||||
this._evtFeatureClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._mergeLinkNode) {
|
||||
this._mergeLinkNode.addEventListener(
|
||||
'click', e => this._evtMergeClick(e));
|
||||
this._mergeLinkNode.addEventListener("click", (e) =>
|
||||
this._evtMergeClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._deleteLinkNode) {
|
||||
this._deleteLinkNode.addEventListener(
|
||||
'click', e => this._evtDeleteClick(e));
|
||||
this._deleteLinkNode.addEventListener("click", (e) =>
|
||||
this._evtDeleteClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
this._postNotesOverlayControl.addEventListener(
|
||||
'blur', e => this._evtNoteBlur(e));
|
||||
this._postNotesOverlayControl.addEventListener("blur", (e) =>
|
||||
this._evtNoteBlur(e)
|
||||
);
|
||||
|
||||
this._postNotesOverlayControl.addEventListener(
|
||||
'focus', e => this._evtNoteFocus(e));
|
||||
this._postNotesOverlayControl.addEventListener("focus", (e) =>
|
||||
this._evtNoteFocus(e)
|
||||
);
|
||||
|
||||
this._post.addEventListener(
|
||||
'changeContent', e => this._evtPostContentChange(e));
|
||||
this._post.addEventListener("changeContent", (e) =>
|
||||
this._evtPostContentChange(e)
|
||||
);
|
||||
|
||||
this._post.addEventListener(
|
||||
'changeThumbnail', e => this._evtPostThumbnailChange(e));
|
||||
this._post.addEventListener("changeThumbnail", (e) =>
|
||||
this._evtPostThumbnailChange(e)
|
||||
);
|
||||
|
||||
if (this._formNode) {
|
||||
const inputNodes = this._formNode.querySelectorAll(
|
||||
'input, textarea');
|
||||
"input, textarea"
|
||||
);
|
||||
for (let node of inputNodes) {
|
||||
node.addEventListener(
|
||||
'change',
|
||||
e => this.dispatchEvent(new CustomEvent('change')));
|
||||
node.addEventListener("change", (e) =>
|
||||
this.dispatchEvent(new CustomEvent("change"))
|
||||
);
|
||||
}
|
||||
this._postNotesOverlayControl.addEventListener(
|
||||
'change',
|
||||
e => this.dispatchEvent(new CustomEvent('change')));
|
||||
this._postNotesOverlayControl.addEventListener("change", (e) =>
|
||||
this.dispatchEvent(new CustomEvent("change"))
|
||||
);
|
||||
}
|
||||
|
||||
for (let eventType of ['add', 'remove']) {
|
||||
this._post.notes.addEventListener(eventType, e => {
|
||||
for (let eventType of ["add", "remove"]) {
|
||||
this._post.notes.addEventListener(eventType, (e) => {
|
||||
this._syncExpanderTitles();
|
||||
});
|
||||
this._post.pools.addEventListener(eventType, e => {
|
||||
this._post.pools.addEventListener(eventType, (e) => {
|
||||
this._syncExpanderTitles();
|
||||
});
|
||||
}
|
||||
|
||||
this._tagControl.addEventListener(
|
||||
'change', e => {
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this._syncExpanderTitles();
|
||||
});
|
||||
this._tagControl.addEventListener("change", (e) => {
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
this._syncExpanderTitles();
|
||||
});
|
||||
|
||||
if (this._noteTextareaNode) {
|
||||
this._noteTextareaNode.addEventListener(
|
||||
'change', e => this._evtNoteTextChangeRequest(e));
|
||||
this._noteTextareaNode.addEventListener("change", (e) =>
|
||||
this._evtNoteTextChangeRequest(e)
|
||||
);
|
||||
}
|
||||
|
||||
this._poolControl.addEventListener(
|
||||
'change', e => {
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this._syncExpanderTitles();
|
||||
});
|
||||
this._poolControl.addEventListener("change", (e) => {
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
this._syncExpanderTitles();
|
||||
});
|
||||
}
|
||||
|
||||
_syncExpanderTitles() {
|
||||
|
@ -220,37 +260,43 @@ class PostEditSidebarControl extends events.EventTarget {
|
|||
e.preventDefault();
|
||||
this._thumbnailFileDropper.reset();
|
||||
this._newPostThumbnail = null;
|
||||
this._thumbnailRemovalLinkNode.style.display = 'none';
|
||||
this._thumbnailRemovalLinkNode.style.display = "none";
|
||||
}
|
||||
|
||||
_evtFeatureClick(e) {
|
||||
e.preventDefault();
|
||||
if (confirm('Are you sure you want to feature this post?')) {
|
||||
this.dispatchEvent(new CustomEvent('feature', {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
}));
|
||||
if (confirm("Are you sure you want to feature this post?")) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("feature", {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_evtMergeClick(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('merge', {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("merge", {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_evtDeleteClick(e) {
|
||||
e.preventDefault();
|
||||
if (confirm('Are you sure you want to delete this post?')) {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
}));
|
||||
if (confirm("Are you sure you want to delete this post?")) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,60 +308,64 @@ class PostEditSidebarControl extends events.EventTarget {
|
|||
|
||||
_evtNoteFocus(e) {
|
||||
this._editedNote = e.detail.note;
|
||||
this._addNoteLinkNode.classList.remove('inactive');
|
||||
this._deleteNoteLinkNode.classList.remove('inactive');
|
||||
this._noteTextareaNode.removeAttribute('disabled');
|
||||
this._addNoteLinkNode.classList.remove("inactive");
|
||||
this._deleteNoteLinkNode.classList.remove("inactive");
|
||||
this._noteTextareaNode.removeAttribute("disabled");
|
||||
this._noteTextareaNode.value = e.detail.note.text;
|
||||
}
|
||||
|
||||
_evtNoteBlur(e) {
|
||||
this._evtNoteTextChangeRequest(null);
|
||||
this._addNoteLinkNode.classList.remove('inactive');
|
||||
this._deleteNoteLinkNode.classList.add('inactive');
|
||||
this._addNoteLinkNode.classList.remove("inactive");
|
||||
this._deleteNoteLinkNode.classList.add("inactive");
|
||||
this._noteTextareaNode.blur();
|
||||
this._noteTextareaNode.setAttribute('disabled', 'disabled');
|
||||
this._noteTextareaNode.value = '';
|
||||
this._noteTextareaNode.setAttribute("disabled", "disabled");
|
||||
this._noteTextareaNode.value = "";
|
||||
}
|
||||
|
||||
_evtAddNoteClick(e) {
|
||||
e.preventDefault();
|
||||
if (e.target.classList.contains('inactive')) {
|
||||
if (e.target.classList.contains("inactive")) {
|
||||
return;
|
||||
}
|
||||
this._addNoteLinkNode.classList.add('inactive');
|
||||
this._addNoteLinkNode.classList.add("inactive");
|
||||
this._postNotesOverlayControl.switchToDrawing();
|
||||
}
|
||||
|
||||
_evtCopyNotesClick(e) {
|
||||
e.preventDefault();
|
||||
let textarea = document.createElement('textarea');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
textarea.value = JSON.stringify([...this._post.notes].map(note => ({
|
||||
polygon: [...note.polygon].map(
|
||||
point => [point.x, point.y]),
|
||||
text: note.text,
|
||||
})));
|
||||
let textarea = document.createElement("textarea");
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.opacity = "0";
|
||||
textarea.value = JSON.stringify(
|
||||
[...this._post.notes].map((note) => ({
|
||||
polygon: [...note.polygon].map((point) => [point.x, point.y]),
|
||||
text: note.text,
|
||||
}))
|
||||
);
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
success = document.execCommand('copy');
|
||||
success = document.execCommand("copy");
|
||||
} catch (err) {
|
||||
// continue regardless of error
|
||||
}
|
||||
textarea.blur();
|
||||
document.body.removeChild(textarea);
|
||||
alert(success
|
||||
? 'Notes copied to clipboard.'
|
||||
: 'Failed to copy the text to clipboard. Sorry.');
|
||||
alert(
|
||||
success
|
||||
? "Notes copied to clipboard."
|
||||
: "Failed to copy the text to clipboard. Sorry."
|
||||
);
|
||||
}
|
||||
|
||||
_evtPasteNotesClick(e) {
|
||||
e.preventDefault();
|
||||
const text = window.prompt(
|
||||
'Please enter the exported notes snapshot:');
|
||||
"Please enter the exported notes snapshot:"
|
||||
);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
@ -333,7 +383,7 @@ class PostEditSidebarControl extends events.EventTarget {
|
|||
|
||||
_evtDeleteNoteClick(e) {
|
||||
e.preventDefault();
|
||||
if (e.target.classList.contains('inactive')) {
|
||||
if (e.target.classList.contains("inactive")) {
|
||||
return;
|
||||
}
|
||||
this._post.notes.remove(this._editedNote);
|
||||
|
@ -342,72 +392,78 @@ class PostEditSidebarControl extends events.EventTarget {
|
|||
|
||||
_evtSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
post: this._post,
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
post: this._post,
|
||||
|
||||
safety: this._safetyButtonNodes.length ?
|
||||
Array.from(this._safetyButtonNodes)
|
||||
.filter(node => node.checked)[0]
|
||||
.value.toLowerCase() :
|
||||
undefined,
|
||||
safety: this._safetyButtonNodes.length
|
||||
? Array.from(this._safetyButtonNodes)
|
||||
.filter((node) => node.checked)[0]
|
||||
.value.toLowerCase()
|
||||
: undefined,
|
||||
|
||||
flags: this._videoFlags,
|
||||
flags: this._videoFlags,
|
||||
|
||||
tags: this._tagInputNode ?
|
||||
misc.splitByWhitespace(this._tagInputNode.value) :
|
||||
undefined,
|
||||
tags: this._tagInputNode
|
||||
? misc.splitByWhitespace(this._tagInputNode.value)
|
||||
: undefined,
|
||||
|
||||
pools: this._poolInputNode ?
|
||||
misc.splitByWhitespace(this._poolInputNode.value) :
|
||||
undefined,
|
||||
pools: this._poolInputNode
|
||||
? misc.splitByWhitespace(this._poolInputNode.value)
|
||||
: undefined,
|
||||
|
||||
relations: this._relationsInputNode ?
|
||||
misc.splitByWhitespace(this._relationsInputNode.value)
|
||||
.map(x => parseInt(x)) :
|
||||
undefined,
|
||||
relations: this._relationsInputNode
|
||||
? misc
|
||||
.splitByWhitespace(
|
||||
this._relationsInputNode.value
|
||||
)
|
||||
.map((x) => parseInt(x))
|
||||
: undefined,
|
||||
|
||||
content: this._newPostContent ?
|
||||
this._newPostContent :
|
||||
undefined,
|
||||
content: this._newPostContent
|
||||
? this._newPostContent
|
||||
: undefined,
|
||||
|
||||
thumbnail: this._newPostThumbnail !== undefined ?
|
||||
this._newPostThumbnail :
|
||||
undefined,
|
||||
thumbnail:
|
||||
this._newPostThumbnail !== undefined
|
||||
? this._newPostThumbnail
|
||||
: undefined,
|
||||
|
||||
source: this._sourceInputNode ?
|
||||
this._sourceInputNode.value :
|
||||
undefined,
|
||||
},
|
||||
}));
|
||||
source: this._sourceInputNode
|
||||
? this._sourceInputNode.value
|
||||
: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _submitButtonNode() {
|
||||
return this._hostNode.querySelector('.submit');
|
||||
return this._hostNode.querySelector(".submit");
|
||||
}
|
||||
|
||||
get _safetyButtonNodes() {
|
||||
return this._formNode.querySelectorAll('.safety input');
|
||||
return this._formNode.querySelectorAll(".safety input");
|
||||
}
|
||||
|
||||
get _tagInputNode() {
|
||||
return this._formNode.querySelector('.tags input');
|
||||
return this._formNode.querySelector(".tags input");
|
||||
}
|
||||
|
||||
get _poolInputNode() {
|
||||
return this._formNode.querySelector('.pools input');
|
||||
return this._formNode.querySelector(".pools input");
|
||||
}
|
||||
|
||||
get _loopVideoInputNode() {
|
||||
return this._formNode.querySelector('.flags input[name=loop]');
|
||||
return this._formNode.querySelector(".flags input[name=loop]");
|
||||
}
|
||||
|
||||
get _soundVideoInputNode() {
|
||||
return this._formNode.querySelector('.flags input[name=sound]');
|
||||
return this._formNode.querySelector(".flags input[name=sound]");
|
||||
}
|
||||
|
||||
get _videoFlags() {
|
||||
|
@ -416,65 +472,68 @@ class PostEditSidebarControl extends events.EventTarget {
|
|||
}
|
||||
let ret = [];
|
||||
if (this._loopVideoInputNode.checked) {
|
||||
ret.push('loop');
|
||||
ret.push("loop");
|
||||
}
|
||||
if (this._soundVideoInputNode.checked) {
|
||||
ret.push('sound');
|
||||
ret.push("sound");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
get _relationsInputNode() {
|
||||
return this._formNode.querySelector('.relations input');
|
||||
return this._formNode.querySelector(".relations input");
|
||||
}
|
||||
|
||||
get _contentInputNode() {
|
||||
return this._formNode.querySelector('.post-content .dropper-container');
|
||||
return this._formNode.querySelector(
|
||||
".post-content .dropper-container"
|
||||
);
|
||||
}
|
||||
|
||||
get _thumbnailInputNode() {
|
||||
return this._formNode.querySelector(
|
||||
'.post-thumbnail .dropper-container');
|
||||
".post-thumbnail .dropper-container"
|
||||
);
|
||||
}
|
||||
|
||||
get _thumbnailRemovalLinkNode() {
|
||||
return this._formNode.querySelector('.post-thumbnail a');
|
||||
return this._formNode.querySelector(".post-thumbnail a");
|
||||
}
|
||||
|
||||
get _sourceInputNode() {
|
||||
return this._formNode.querySelector('.post-source textarea');
|
||||
return this._formNode.querySelector(".post-source textarea");
|
||||
}
|
||||
|
||||
get _featureLinkNode() {
|
||||
return this._formNode.querySelector('.management .feature');
|
||||
return this._formNode.querySelector(".management .feature");
|
||||
}
|
||||
|
||||
get _mergeLinkNode() {
|
||||
return this._formNode.querySelector('.management .merge');
|
||||
return this._formNode.querySelector(".management .merge");
|
||||
}
|
||||
|
||||
get _deleteLinkNode() {
|
||||
return this._formNode.querySelector('.management .delete');
|
||||
return this._formNode.querySelector(".management .delete");
|
||||
}
|
||||
|
||||
get _addNoteLinkNode() {
|
||||
return this._formNode.querySelector('.notes .add');
|
||||
return this._formNode.querySelector(".notes .add");
|
||||
}
|
||||
|
||||
get _copyNotesLinkNode() {
|
||||
return this._formNode.querySelector('.notes .copy');
|
||||
return this._formNode.querySelector(".notes .copy");
|
||||
}
|
||||
|
||||
get _pasteNotesLinkNode() {
|
||||
return this._formNode.querySelector('.notes .paste');
|
||||
return this._formNode.querySelector(".notes .paste");
|
||||
}
|
||||
|
||||
get _deleteNoteLinkNode() {
|
||||
return this._formNode.querySelector('.notes .delete');
|
||||
return this._formNode.querySelector(".notes .delete");
|
||||
}
|
||||
|
||||
get _noteTextareaNode() {
|
||||
return this._formNode.querySelector('.notes textarea');
|
||||
return this._formNode.querySelector(".notes textarea");
|
||||
}
|
||||
|
||||
enableForm() {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const keyboard = require('../util/keyboard.js');
|
||||
const views = require('../util/views.js');
|
||||
const events = require('../events.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const Note = require('../models/note.js');
|
||||
const Point = require('../models/point.js');
|
||||
const keyboard = require("../util/keyboard.js");
|
||||
const views = require("../util/views.js");
|
||||
const events = require("../events.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const Note = require("../models/note.js");
|
||||
const Point = require("../models/point.js");
|
||||
|
||||
const svgNS = 'http://www.w3.org/2000/svg';
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
const snapThreshold = 10;
|
||||
const circleSize = 10;
|
||||
|
||||
|
@ -22,19 +22,19 @@ const KEY_RETURN = 13;
|
|||
|
||||
function _getDistance(point1, point2) {
|
||||
return Math.sqrt(
|
||||
Math.pow(point1.x - point2.x, 2) +
|
||||
Math.pow(point1.y - point2.y, 2));
|
||||
Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)
|
||||
);
|
||||
}
|
||||
|
||||
function _setNodeState(node, stateName) {
|
||||
if (node === null) {
|
||||
return;
|
||||
}
|
||||
node.setAttribute('data-state', stateName);
|
||||
node.setAttribute("data-state", stateName);
|
||||
}
|
||||
|
||||
function _clearEditedNote(hostNode) {
|
||||
const node = hostNode.querySelector('[data-state=\'editing\']');
|
||||
const node = hostNode.querySelector("[data-state='editing']");
|
||||
_setNodeState(node, null);
|
||||
return node !== null;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -82,32 +82,30 @@ class State {
|
|||
return false;
|
||||
}
|
||||
|
||||
evtCanvasKeyDown(e) {
|
||||
}
|
||||
evtCanvasKeyDown(e) {}
|
||||
|
||||
evtNoteMouseDown(e, hoveredNote) {
|
||||
}
|
||||
evtNoteMouseDown(e, hoveredNote) {}
|
||||
|
||||
evtCanvasMouseDown(e) {
|
||||
}
|
||||
evtCanvasMouseDown(e) {}
|
||||
|
||||
evtCanvasMouseMove(e) {
|
||||
}
|
||||
evtCanvasMouseMove(e) {}
|
||||
|
||||
evtCanvasMouseUp(e) {
|
||||
}
|
||||
evtCanvasMouseUp(e) {}
|
||||
|
||||
_getScreenPoint(point) {
|
||||
return new Point(
|
||||
point.x * this._control.boundingBox.width,
|
||||
point.y * this._control.boundingBox.height);
|
||||
point.y * this._control.boundingBox.height
|
||||
);
|
||||
}
|
||||
|
||||
_snapPoints(targetPoint, referencePoint) {
|
||||
const targetScreenPoint = this._getScreenPoint(targetPoint);
|
||||
const referenceScreenPoint = this._getScreenPoint(referencePoint);
|
||||
if (_getDistance(targetScreenPoint, referenceScreenPoint) <
|
||||
snapThreshold) {
|
||||
if (
|
||||
_getDistance(targetScreenPoint, referenceScreenPoint) <
|
||||
snapThreshold
|
||||
) {
|
||||
targetPoint.x = referencePoint.x;
|
||||
targetPoint.y = referencePoint.y;
|
||||
}
|
||||
|
@ -124,15 +122,16 @@ class State {
|
|||
(e.clientX - this._control.boundingBox.left) /
|
||||
this._control.boundingBox.width,
|
||||
(e.clientY - this._control.boundingBox.top) /
|
||||
this._control.boundingBox.height);
|
||||
this._control.boundingBox.height
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReadOnlyState extends State {
|
||||
constructor(control) {
|
||||
super(control, 'read-only');
|
||||
super(control, "read-only");
|
||||
if (_clearEditedNote(control._hostNode)) {
|
||||
this._control.dispatchEvent(new CustomEvent('blur'));
|
||||
this._control.dispatchEvent(new CustomEvent("blur"));
|
||||
}
|
||||
keyboard.unpause();
|
||||
}
|
||||
|
@ -144,9 +143,9 @@ class ReadOnlyState extends State {
|
|||
|
||||
class PassiveState extends State {
|
||||
constructor(control) {
|
||||
super(control, 'passive');
|
||||
super(control, "passive");
|
||||
if (_clearEditedNote(control._hostNode)) {
|
||||
this._control.dispatchEvent(new CustomEvent('blur'));
|
||||
this._control.dispatchEvent(new CustomEvent("blur"));
|
||||
}
|
||||
keyboard.unpause();
|
||||
}
|
||||
|
@ -164,23 +163,24 @@ class ActiveState extends State {
|
|||
constructor(control, note, stateName) {
|
||||
super(control, stateName);
|
||||
if (_clearEditedNote(control._hostNode)) {
|
||||
this._control.dispatchEvent(new CustomEvent('blur'));
|
||||
this._control.dispatchEvent(new CustomEvent("blur"));
|
||||
}
|
||||
keyboard.pause();
|
||||
if (note !== null) {
|
||||
this._note = note;
|
||||
this._control.dispatchEvent(
|
||||
new CustomEvent('focus', {
|
||||
detail: {note: note},
|
||||
}));
|
||||
_setNodeState(this._note.groupNode, 'editing');
|
||||
new CustomEvent("focus", {
|
||||
detail: { note: note },
|
||||
})
|
||||
);
|
||||
_setNodeState(this._note.groupNode, "editing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SelectedState extends ActiveState {
|
||||
constructor(control, note) {
|
||||
super(control, note, 'selected');
|
||||
super(control, note, "selected");
|
||||
this._clickTimeout = null;
|
||||
this._control._hideNoteText();
|
||||
}
|
||||
|
@ -211,27 +211,40 @@ class SelectedState extends ActiveState {
|
|||
const mouseScreenPoint = this._getScreenPoint(mousePoint);
|
||||
if (e.shiftKey) {
|
||||
this._control._state = new ScalingNoteState(
|
||||
this._control, this._note, mousePoint);
|
||||
this._control,
|
||||
this._note,
|
||||
mousePoint
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this._note !== hoveredNote) {
|
||||
this._control._state =
|
||||
new SelectedState(this._control, hoveredNote);
|
||||
this._control._state = new SelectedState(
|
||||
this._control,
|
||||
hoveredNote
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._clickTimeout = window.setTimeout(() => {
|
||||
for (let polygonPoint of this._note.polygon) {
|
||||
const distance = _getDistance(
|
||||
mouseScreenPoint,
|
||||
this._getScreenPoint(polygonPoint));
|
||||
this._getScreenPoint(polygonPoint)
|
||||
);
|
||||
if (distance < circleSize) {
|
||||
this._control._state = new MovingPointState(
|
||||
this._control, this._note, polygonPoint, mousePoint);
|
||||
this._control,
|
||||
this._note,
|
||||
polygonPoint,
|
||||
mousePoint
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._control._state = new MovingNoteState(
|
||||
this._control, this._note, mousePoint);
|
||||
this._control,
|
||||
this._note,
|
||||
mousePoint
|
||||
);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
@ -241,9 +254,12 @@ class SelectedState extends ActiveState {
|
|||
for (let polygonPoint of this._note.polygon) {
|
||||
const distance = _getDistance(
|
||||
mouseScreenPoint,
|
||||
this._getScreenPoint(polygonPoint));
|
||||
this._getScreenPoint(polygonPoint)
|
||||
);
|
||||
polygonPoint.edgeNode.classList.toggle(
|
||||
'nearby', distance < circleSize);
|
||||
"nearby",
|
||||
distance < circleSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,16 +268,24 @@ class SelectedState extends ActiveState {
|
|||
const mouseScreenPoint = this._getScreenPoint(mousePoint);
|
||||
if (e.shiftKey) {
|
||||
this._control._state = new ScalingNoteState(
|
||||
this._control, this._note, mousePoint);
|
||||
this._control,
|
||||
this._note,
|
||||
mousePoint
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (let polygonPoint of this._note.polygon) {
|
||||
const distance = _getDistance(
|
||||
mouseScreenPoint,
|
||||
this._getScreenPoint(polygonPoint));
|
||||
this._getScreenPoint(polygonPoint)
|
||||
);
|
||||
if (distance < circleSize) {
|
||||
this._control._state = new MovingPointState(
|
||||
this._control, this._note, polygonPoint, mousePoint);
|
||||
this._control,
|
||||
this._note,
|
||||
polygonPoint,
|
||||
mousePoint
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -283,32 +307,37 @@ 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);
|
||||
targetSize.y / originalSize.y
|
||||
);
|
||||
for (let point of this._note.polygon) {
|
||||
point.x = origin.x + ((point.x - origin.x) * scale.x);
|
||||
point.y = origin.y + ((point.y - origin.y) * scale.y);
|
||||
point.x = origin.x + (point.x - origin.x) * scale.x;
|
||||
point.y = origin.y + (point.y - origin.y) * scale.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MovingPointState extends ActiveState {
|
||||
constructor(control, note, notePoint, mousePoint) {
|
||||
super(control, note, 'moving-point');
|
||||
super(control, note, "moving-point");
|
||||
this._notePoint = notePoint;
|
||||
this._originalNotePoint = {x: notePoint.x, y: notePoint.y};
|
||||
this._originalNotePoint = { x: notePoint.x, y: notePoint.y };
|
||||
this._originalPosition = mousePoint;
|
||||
_setNodeState(this._note.groupNode, 'editing');
|
||||
_setNodeState(this._note.groupNode, "editing");
|
||||
}
|
||||
|
||||
evtCanvasKeyDown(e) {
|
||||
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);
|
||||
this._control._state = new SelectedState(
|
||||
this._control,
|
||||
this._note
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,9 +355,11 @@ class MovingPointState extends ActiveState {
|
|||
|
||||
class MovingNoteState extends ActiveState {
|
||||
constructor(control, note, mousePoint) {
|
||||
super(control, note, 'moving-note');
|
||||
this._originalPolygon = [...note.polygon].map(
|
||||
point => ({x: point.x, y: point.y}));
|
||||
super(control, note, "moving-note");
|
||||
this._originalPolygon = [...note.polygon].map((point) => ({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
}));
|
||||
this._originalPosition = mousePoint;
|
||||
}
|
||||
|
||||
|
@ -338,7 +369,10 @@ class MovingNoteState extends ActiveState {
|
|||
this._note.polygon.at(i).x = this._originalPolygon[i].x;
|
||||
this._note.polygon.at(i).y = this._originalPolygon[i].y;
|
||||
}
|
||||
this._control._state = new SelectedState(this._control, this._note);
|
||||
this._control._state = new SelectedState(
|
||||
this._control,
|
||||
this._note
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,9 +392,11 @@ class MovingNoteState extends ActiveState {
|
|||
|
||||
class ScalingNoteState extends ActiveState {
|
||||
constructor(control, note, mousePoint) {
|
||||
super(control, note, 'scaling-note');
|
||||
this._originalPolygon = [...note.polygon].map(
|
||||
point => ({x: point.x, y: point.y}));
|
||||
super(control, note, "scaling-note");
|
||||
this._originalPolygon = [...note.polygon].map((point) => ({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
}));
|
||||
this._originalMousePoint = mousePoint;
|
||||
this._originalSize = _getNoteSize(note);
|
||||
}
|
||||
|
@ -371,7 +407,10 @@ class ScalingNoteState extends ActiveState {
|
|||
this._note.polygon.at(i).x = this._originalPolygon[i].x;
|
||||
this._note.polygon.at(i).y = this._originalPolygon[i].y;
|
||||
}
|
||||
this._control._state = new SelectedState(this._control, this._note);
|
||||
this._control._state = new SelectedState(
|
||||
this._control,
|
||||
this._note
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,12 +423,16 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -400,7 +443,7 @@ class ScalingNoteState extends ActiveState {
|
|||
|
||||
class ReadyToDrawState extends ActiveState {
|
||||
constructor(control) {
|
||||
super(control, null, 'ready-to-draw');
|
||||
super(control, null, "ready-to-draw");
|
||||
}
|
||||
|
||||
evtNoteMouseDown(e, hoveredNote) {
|
||||
|
@ -411,23 +454,27 @@ class ReadyToDrawState extends ActiveState {
|
|||
const mousePoint = this._getPointFromEvent(e);
|
||||
if (e.shiftKey) {
|
||||
this._control._state = new DrawingRectangleState(
|
||||
this._control, mousePoint);
|
||||
this._control,
|
||||
mousePoint
|
||||
);
|
||||
} else {
|
||||
this._control._state = new DrawingPolygonState(
|
||||
this._control, mousePoint);
|
||||
this._control,
|
||||
mousePoint
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DrawingRectangleState extends ActiveState {
|
||||
constructor(control, mousePoint) {
|
||||
super(control, null, 'drawing-rectangle');
|
||||
super(control, null, "drawing-rectangle");
|
||||
this._note = this._createNote();
|
||||
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
||||
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
||||
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
||||
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
||||
_setNodeState(this._note.groupNode, 'drawing');
|
||||
_setNodeState(this._note.groupNode, "drawing");
|
||||
}
|
||||
|
||||
evtCanvasMouseUp(e) {
|
||||
|
@ -443,7 +490,10 @@ class DrawingRectangleState extends ActiveState {
|
|||
this._control._state = new ReadyToDrawState(this._control);
|
||||
} else {
|
||||
this._control._post.notes.add(this._note);
|
||||
this._control._state = new SelectedState(this._control, this._note);
|
||||
this._control._state = new SelectedState(
|
||||
this._control,
|
||||
this._note
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,11 +508,11 @@ class DrawingRectangleState extends ActiveState {
|
|||
|
||||
class DrawingPolygonState extends ActiveState {
|
||||
constructor(control, mousePoint) {
|
||||
super(control, null, 'drawing-polygon');
|
||||
super(control, null, "drawing-polygon");
|
||||
this._note = this._createNote();
|
||||
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
||||
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
||||
_setNodeState(this._note.groupNode, 'drawing');
|
||||
_setNodeState(this._note.groupNode, "drawing");
|
||||
}
|
||||
|
||||
evtCanvasKeyDown(e) {
|
||||
|
@ -502,11 +552,16 @@ class DrawingPolygonState extends ActiveState {
|
|||
}
|
||||
|
||||
if (e.shiftKey && secondLastPoint) {
|
||||
const direction = (Math.round(
|
||||
Math.atan2(
|
||||
secondLastPoint.y - mousePoint.y,
|
||||
secondLastPoint.x - mousePoint.x) /
|
||||
(2 * Math.PI / 4)) + 4) % 4;
|
||||
const direction =
|
||||
(Math.round(
|
||||
Math.atan2(
|
||||
secondLastPoint.y - mousePoint.y,
|
||||
secondLastPoint.x - mousePoint.x
|
||||
) /
|
||||
((2 * Math.PI) / 4)
|
||||
) +
|
||||
4) %
|
||||
4;
|
||||
if (direction === 0 || direction === 2) {
|
||||
lastPoint.x = mousePoint.x;
|
||||
lastPoint.y = secondLastPoint.y;
|
||||
|
@ -533,7 +588,10 @@ class DrawingPolygonState extends ActiveState {
|
|||
} else {
|
||||
this._control._deleteDomNode(this._note);
|
||||
this._control._post.notes.add(this._note);
|
||||
this._control._state = new SelectedState(this._control, this._note);
|
||||
this._control._state = new SelectedState(
|
||||
this._control,
|
||||
this._note
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -544,48 +602,51 @@ class PostNotesOverlayControl extends events.EventTarget {
|
|||
this._post = post;
|
||||
this._hostNode = hostNode;
|
||||
|
||||
this._svgNode = document.createElementNS(svgNS, 'svg');
|
||||
this._svgNode.classList.add('resize-listener');
|
||||
this._svgNode.classList.add('notes-overlay');
|
||||
this._svgNode.setAttribute('preserveAspectRatio', 'none');
|
||||
this._svgNode.setAttribute('viewBox', '0 0 1 1');
|
||||
this._svgNode = document.createElementNS(svgNS, "svg");
|
||||
this._svgNode.classList.add("resize-listener");
|
||||
this._svgNode.classList.add("notes-overlay");
|
||||
this._svgNode.setAttribute("preserveAspectRatio", "none");
|
||||
this._svgNode.setAttribute("viewBox", "0 0 1 1");
|
||||
for (let note of this._post.notes) {
|
||||
this._createPolygonNode(note);
|
||||
}
|
||||
this._hostNode.appendChild(this._svgNode);
|
||||
this._post.addEventListener('change', e => this._evtPostChange(e));
|
||||
this._post.notes.addEventListener('remove', e => {
|
||||
this._post.addEventListener("change", (e) => this._evtPostChange(e));
|
||||
this._post.notes.addEventListener("remove", (e) => {
|
||||
this._deleteDomNode(e.detail.note);
|
||||
});
|
||||
this._post.notes.addEventListener('add', e => {
|
||||
this._post.notes.addEventListener("add", (e) => {
|
||||
this._createPolygonNode(e.detail.note);
|
||||
});
|
||||
|
||||
const keyHandler = e => this._evtCanvasKeyDown(e);
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
this._svgNode.addEventListener(
|
||||
'mousedown', e => this._evtCanvasMouseDown(e));
|
||||
this._svgNode.addEventListener(
|
||||
'mouseup', e => this._evtCanvasMouseUp(e));
|
||||
this._svgNode.addEventListener(
|
||||
'mousemove', e => this._evtCanvasMouseMove(e));
|
||||
const keyHandler = (e) => this._evtCanvasKeyDown(e);
|
||||
document.addEventListener("keydown", keyHandler);
|
||||
this._svgNode.addEventListener("mousedown", (e) =>
|
||||
this._evtCanvasMouseDown(e)
|
||||
);
|
||||
this._svgNode.addEventListener("mouseup", (e) =>
|
||||
this._evtCanvasMouseUp(e)
|
||||
);
|
||||
this._svgNode.addEventListener("mousemove", (e) =>
|
||||
this._evtCanvasMouseMove(e)
|
||||
);
|
||||
|
||||
const wrapperNode = document.createElement('div');
|
||||
wrapperNode.classList.add('wrapper');
|
||||
this._textNode = document.createElement('div');
|
||||
this._textNode.classList.add('note-text');
|
||||
const wrapperNode = document.createElement("div");
|
||||
wrapperNode.classList.add("wrapper");
|
||||
this._textNode = document.createElement("div");
|
||||
this._textNode.classList.add("note-text");
|
||||
this._textNode.appendChild(wrapperNode);
|
||||
this._textNode.addEventListener(
|
||||
'mouseleave', e => this._evtNoteMouseLeave(e));
|
||||
this._textNode.addEventListener("mouseleave", (e) =>
|
||||
this._evtNoteMouseLeave(e)
|
||||
);
|
||||
document.body.appendChild(this._textNode);
|
||||
|
||||
views.monitorNodeRemoval(
|
||||
this._hostNode, () => {
|
||||
this._hostNode.removeChild(this._svgNode);
|
||||
document.removeEventListener('keydown', keyHandler);
|
||||
document.body.removeChild(this._textNode);
|
||||
this._state = new ReadOnlyState(this);
|
||||
});
|
||||
views.monitorNodeRemoval(this._hostNode, () => {
|
||||
this._hostNode.removeChild(this._svgNode);
|
||||
document.removeEventListener("keydown", keyHandler);
|
||||
document.body.removeChild(this._textNode);
|
||||
this._state = new ReadOnlyState(this);
|
||||
});
|
||||
|
||||
this._state = new ReadOnlyState(this);
|
||||
}
|
||||
|
@ -613,7 +674,7 @@ class PostNotesOverlayControl extends events.EventTarget {
|
|||
}
|
||||
|
||||
_evtCanvasKeyDown(e) {
|
||||
const illegalNodeNames = ['textarea', 'input', 'select'];
|
||||
const illegalNodeNames = ["textarea", "input", "select"];
|
||||
if (illegalNodeNames.includes(e.target.nodeName.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
@ -655,53 +716,58 @@ class PostNotesOverlayControl extends events.EventTarget {
|
|||
|
||||
_evtNoteMouseLeave(e) {
|
||||
const newElement = e.relatedTarget;
|
||||
if (newElement === this._svgNode ||
|
||||
(!this._svgNode.contains(newElement) &&
|
||||
if (
|
||||
newElement === this._svgNode ||
|
||||
(!this._svgNode.contains(newElement) &&
|
||||
!this._textNode.contains(newElement) &&
|
||||
newElement !== this._textNode)) {
|
||||
newElement !== this._textNode)
|
||||
) {
|
||||
this._hideNoteText();
|
||||
}
|
||||
}
|
||||
|
||||
_showNoteText(note) {
|
||||
this._textNode.querySelector('.wrapper').innerHTML =
|
||||
misc.formatMarkdown(note.text);
|
||||
this._textNode.style.display = 'block';
|
||||
this._textNode.querySelector(
|
||||
".wrapper"
|
||||
).innerHTML = misc.formatMarkdown(note.text);
|
||||
this._textNode.style.display = "block";
|
||||
const bodyRect = document.body.getBoundingClientRect();
|
||||
const noteRect = this._textNode.getBoundingClientRect();
|
||||
const svgRect = this.boundingBox;
|
||||
const centroid = _getNoteCentroid(note);
|
||||
const x = (
|
||||
const x =
|
||||
-bodyRect.left +
|
||||
svgRect.left +
|
||||
(svgRect.width * centroid.x) -
|
||||
(noteRect.width / 2));
|
||||
const y = (
|
||||
svgRect.width * centroid.x -
|
||||
noteRect.width / 2;
|
||||
const y =
|
||||
-bodyRect.top +
|
||||
svgRect.top +
|
||||
(svgRect.height * centroid.y) -
|
||||
(noteRect.height / 2));
|
||||
this._textNode.style.left = x + 'px';
|
||||
this._textNode.style.top = y + 'px';
|
||||
svgRect.height * centroid.y -
|
||||
noteRect.height / 2;
|
||||
this._textNode.style.left = x + "px";
|
||||
this._textNode.style.top = y + "px";
|
||||
}
|
||||
|
||||
_hideNoteText() {
|
||||
this._textNode.style.display = 'none';
|
||||
this._textNode.style.display = "none";
|
||||
}
|
||||
|
||||
_updatePolygonNotePoints(note) {
|
||||
note.polygonNode.setAttribute(
|
||||
'points',
|
||||
[...note.polygon].map(
|
||||
point => [point.x, point.y].join(',')).join(' '));
|
||||
"points",
|
||||
[...note.polygon]
|
||||
.map((point) => [point.x, point.y].join(","))
|
||||
.join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
_createEdgeNode(point, groupNode) {
|
||||
const node = document.createElementNS(svgNS, 'ellipse');
|
||||
node.setAttribute('cx', point.x);
|
||||
node.setAttribute('cy', point.y);
|
||||
node.setAttribute('rx', circleSize / 2 / this.boundingBox.width);
|
||||
node.setAttribute('ry', circleSize / 2 / this.boundingBox.height);
|
||||
const node = document.createElementNS(svgNS, "ellipse");
|
||||
node.setAttribute("cx", point.x);
|
||||
node.setAttribute("cy", point.y);
|
||||
node.setAttribute("rx", circleSize / 2 / this.boundingBox.width);
|
||||
node.setAttribute("ry", circleSize / 2 / this.boundingBox.height);
|
||||
point.edgeNode = node;
|
||||
groupNode.appendChild(node);
|
||||
}
|
||||
|
@ -713,8 +779,8 @@ class PostNotesOverlayControl extends events.EventTarget {
|
|||
|
||||
_updateEdgeNode(point, note) {
|
||||
this._updatePolygonNotePoints(note);
|
||||
point.edgeNode.setAttribute('cx', point.x);
|
||||
point.edgeNode.setAttribute('cy', point.y);
|
||||
point.edgeNode.setAttribute("cx", point.x);
|
||||
point.edgeNode.setAttribute("cy", point.y);
|
||||
}
|
||||
|
||||
_deleteDomNode(note) {
|
||||
|
@ -722,17 +788,19 @@ class PostNotesOverlayControl extends events.EventTarget {
|
|||
}
|
||||
|
||||
_createPolygonNode(note) {
|
||||
const groupNode = document.createElementNS(svgNS, 'g');
|
||||
const groupNode = document.createElementNS(svgNS, "g");
|
||||
note.groupNode = groupNode;
|
||||
{
|
||||
const node = document.createElementNS(svgNS, 'polygon');
|
||||
const node = document.createElementNS(svgNS, "polygon");
|
||||
note.polygonNode = node;
|
||||
node.setAttribute('vector-effect', 'non-scaling-stroke');
|
||||
node.setAttribute('stroke-alignment', 'inside');
|
||||
node.addEventListener(
|
||||
'mouseenter', e => this._evtNoteMouseEnter(e, note));
|
||||
node.addEventListener(
|
||||
'mouseleave', e => this._evtNoteMouseLeave(e));
|
||||
node.setAttribute("vector-effect", "non-scaling-stroke");
|
||||
node.setAttribute("stroke-alignment", "inside");
|
||||
node.addEventListener("mouseenter", (e) =>
|
||||
this._evtNoteMouseEnter(e, note)
|
||||
);
|
||||
node.addEventListener("mouseleave", (e) =>
|
||||
this._evtNoteMouseLeave(e)
|
||||
);
|
||||
this._updatePolygonNotePoints(note);
|
||||
groupNode.appendChild(node);
|
||||
}
|
||||
|
@ -740,17 +808,17 @@ class PostNotesOverlayControl extends events.EventTarget {
|
|||
this._createEdgeNode(point, groupNode);
|
||||
}
|
||||
|
||||
note.polygon.addEventListener('change', e => {
|
||||
note.polygon.addEventListener("change", (e) => {
|
||||
this._updateEdgeNode(e.detail.point, note);
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
});
|
||||
note.polygon.addEventListener('remove', e => {
|
||||
note.polygon.addEventListener("remove", (e) => {
|
||||
this._deleteEdgeNode(e.detail.point, note);
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
});
|
||||
note.polygon.addEventListener('add', e => {
|
||||
note.polygon.addEventListener("add", (e) => {
|
||||
this._createEdgeNode(e.detail.point, groupNode);
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
});
|
||||
|
||||
this._svgNode.appendChild(groupNode);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const api = require("../api.js");
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const misc = require("../util/misc.js");
|
||||
|
||||
const template = views.getTemplate('post-readonly-sidebar');
|
||||
const scoreTemplate = views.getTemplate('score');
|
||||
const favTemplate = views.getTemplate('fav');
|
||||
const template = views.getTemplate("post-readonly-sidebar");
|
||||
const scoreTemplate = views.getTemplate("score");
|
||||
const favTemplate = views.getTemplate("fav");
|
||||
|
||||
class PostReadonlySidebarControl extends events.EventTarget {
|
||||
constructor(hostNode, post, postContentControl) {
|
||||
|
@ -17,19 +17,22 @@ class PostReadonlySidebarControl extends events.EventTarget {
|
|||
this._post = post;
|
||||
this._postContentControl = postContentControl;
|
||||
|
||||
post.addEventListener('changeFavorite', e => this._evtChangeFav(e));
|
||||
post.addEventListener('changeScore', e => this._evtChangeScore(e));
|
||||
post.addEventListener("changeFavorite", (e) => this._evtChangeFav(e));
|
||||
post.addEventListener("changeScore", (e) => this._evtChangeScore(e));
|
||||
|
||||
views.replaceContent(this._hostNode, template({
|
||||
post: this._post,
|
||||
enableSafety: api.safetyEnabled(),
|
||||
canListPosts: api.hasPrivilege('posts:list'),
|
||||
canEditPosts: api.hasPrivilege('posts:edit'),
|
||||
canViewTags: api.hasPrivilege('tags:view'),
|
||||
escapeColons: uri.escapeColons,
|
||||
extractRootDomain: uri.extractRootDomain,
|
||||
getPrettyTagName: misc.getPrettyTagName,
|
||||
}));
|
||||
views.replaceContent(
|
||||
this._hostNode,
|
||||
template({
|
||||
post: this._post,
|
||||
enableSafety: api.safetyEnabled(),
|
||||
canListPosts: api.hasPrivilege("posts:list"),
|
||||
canEditPosts: api.hasPrivilege("posts:edit"),
|
||||
canViewTags: api.hasPrivilege("tags:view"),
|
||||
escapeColons: uri.escapeColons,
|
||||
extractRootDomain: uri.extractRootDomain,
|
||||
getPrettyTagName: misc.getPrettyTagName,
|
||||
})
|
||||
);
|
||||
|
||||
this._installFav();
|
||||
this._installScore();
|
||||
|
@ -38,58 +41,62 @@ class PostReadonlySidebarControl extends events.EventTarget {
|
|||
}
|
||||
|
||||
get _scoreContainerNode() {
|
||||
return this._hostNode.querySelector('.score-container');
|
||||
return this._hostNode.querySelector(".score-container");
|
||||
}
|
||||
|
||||
get _favContainerNode() {
|
||||
return this._hostNode.querySelector('.fav-container');
|
||||
return this._hostNode.querySelector(".fav-container");
|
||||
}
|
||||
|
||||
get _upvoteButtonNode() {
|
||||
return this._hostNode.querySelector('.upvote');
|
||||
return this._hostNode.querySelector(".upvote");
|
||||
}
|
||||
|
||||
get _downvoteButtonNode() {
|
||||
return this._hostNode.querySelector('.downvote');
|
||||
return this._hostNode.querySelector(".downvote");
|
||||
}
|
||||
|
||||
get _addFavButtonNode() {
|
||||
return this._hostNode.querySelector('.add-favorite');
|
||||
return this._hostNode.querySelector(".add-favorite");
|
||||
}
|
||||
|
||||
get _remFavButtonNode() {
|
||||
return this._hostNode.querySelector('.remove-favorite');
|
||||
return this._hostNode.querySelector(".remove-favorite");
|
||||
}
|
||||
|
||||
get _fitBothButtonNode() {
|
||||
return this._hostNode.querySelector('.fit-both');
|
||||
return this._hostNode.querySelector(".fit-both");
|
||||
}
|
||||
|
||||
get _fitOriginalButtonNode() {
|
||||
return this._hostNode.querySelector('.fit-original');
|
||||
return this._hostNode.querySelector(".fit-original");
|
||||
}
|
||||
|
||||
get _fitWidthButtonNode() {
|
||||
return this._hostNode.querySelector('.fit-width');
|
||||
return this._hostNode.querySelector(".fit-width");
|
||||
}
|
||||
|
||||
get _fitHeightButtonNode() {
|
||||
return this._hostNode.querySelector('.fit-height');
|
||||
return this._hostNode.querySelector(".fit-height");
|
||||
}
|
||||
|
||||
_installFitButtons() {
|
||||
this._fitBothButtonNode.addEventListener(
|
||||
'click', this._eventZoomProxy(
|
||||
() => this._postContentControl.fitBoth()));
|
||||
"click",
|
||||
this._eventZoomProxy(() => this._postContentControl.fitBoth())
|
||||
);
|
||||
this._fitOriginalButtonNode.addEventListener(
|
||||
'click', this._eventZoomProxy(
|
||||
() => this._postContentControl.fitOriginal()));
|
||||
"click",
|
||||
this._eventZoomProxy(() => this._postContentControl.fitOriginal())
|
||||
);
|
||||
this._fitWidthButtonNode.addEventListener(
|
||||
'click', this._eventZoomProxy(
|
||||
() => this._postContentControl.fitWidth()));
|
||||
"click",
|
||||
this._eventZoomProxy(() => this._postContentControl.fitWidth())
|
||||
);
|
||||
this._fitHeightButtonNode.addEventListener(
|
||||
'click', this._eventZoomProxy(
|
||||
() => this._postContentControl.fitHeight()));
|
||||
"click",
|
||||
this._eventZoomProxy(() => this._postContentControl.fitHeight())
|
||||
);
|
||||
}
|
||||
|
||||
_installFav() {
|
||||
|
@ -98,16 +105,19 @@ class PostReadonlySidebarControl extends events.EventTarget {
|
|||
favTemplate({
|
||||
favoriteCount: this._post.favoriteCount,
|
||||
ownFavorite: this._post.ownFavorite,
|
||||
canFavorite: api.hasPrivilege('posts:favorite'),
|
||||
}));
|
||||
canFavorite: api.hasPrivilege("posts:favorite"),
|
||||
})
|
||||
);
|
||||
|
||||
if (this._addFavButtonNode) {
|
||||
this._addFavButtonNode.addEventListener(
|
||||
'click', e => this._evtAddToFavoritesClick(e));
|
||||
this._addFavButtonNode.addEventListener("click", (e) =>
|
||||
this._evtAddToFavoritesClick(e)
|
||||
);
|
||||
}
|
||||
if (this._remFavButtonNode) {
|
||||
this._remFavButtonNode.addEventListener(
|
||||
'click', e => this._evtRemoveFromFavoritesClick(e));
|
||||
this._remFavButtonNode.addEventListener("click", (e) =>
|
||||
this._evtRemoveFromFavoritesClick(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,77 +127,88 @@ class PostReadonlySidebarControl extends events.EventTarget {
|
|||
scoreTemplate({
|
||||
score: this._post.score,
|
||||
ownScore: this._post.ownScore,
|
||||
canScore: api.hasPrivilege('posts:score'),
|
||||
}));
|
||||
canScore: api.hasPrivilege("posts:score"),
|
||||
})
|
||||
);
|
||||
if (this._upvoteButtonNode) {
|
||||
this._upvoteButtonNode.addEventListener(
|
||||
'click', e => this._evtScoreClick(e, 1));
|
||||
this._upvoteButtonNode.addEventListener("click", (e) =>
|
||||
this._evtScoreClick(e, 1)
|
||||
);
|
||||
}
|
||||
if (this._downvoteButtonNode) {
|
||||
this._downvoteButtonNode.addEventListener(
|
||||
'click', e => this._evtScoreClick(e, -1));
|
||||
this._downvoteButtonNode.addEventListener("click", (e) =>
|
||||
this._evtScoreClick(e, -1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_eventZoomProxy(func) {
|
||||
return e => {
|
||||
return (e) => {
|
||||
e.preventDefault();
|
||||
e.target.blur();
|
||||
func();
|
||||
this._syncFitButton();
|
||||
this.dispatchEvent(new CustomEvent('fitModeChange', {
|
||||
detail: {
|
||||
mode: this._getFitMode(),
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("fitModeChange", {
|
||||
detail: {
|
||||
mode: this._getFitMode(),
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
_getFitMode() {
|
||||
const funcToName = {};
|
||||
funcToName[this._postContentControl.fitBoth] = 'fit-both';
|
||||
funcToName[this._postContentControl.fitOriginal] = 'fit-original';
|
||||
funcToName[this._postContentControl.fitWidth] = 'fit-width';
|
||||
funcToName[this._postContentControl.fitHeight] = 'fit-height';
|
||||
funcToName[this._postContentControl.fitBoth] = "fit-both";
|
||||
funcToName[this._postContentControl.fitOriginal] = "fit-original";
|
||||
funcToName[this._postContentControl.fitWidth] = "fit-width";
|
||||
funcToName[this._postContentControl.fitHeight] = "fit-height";
|
||||
return funcToName[this._postContentControl._currentFitFunction];
|
||||
}
|
||||
|
||||
_syncFitButton() {
|
||||
const className = this._getFitMode();
|
||||
const oldNode = this._hostNode.querySelector('.zoom a.active');
|
||||
const oldNode = this._hostNode.querySelector(".zoom a.active");
|
||||
const newNode = this._hostNode.querySelector(`.zoom a.${className}`);
|
||||
if (oldNode) {
|
||||
oldNode.classList.remove('active');
|
||||
oldNode.classList.remove("active");
|
||||
}
|
||||
newNode.classList.add('active');
|
||||
newNode.classList.add("active");
|
||||
}
|
||||
|
||||
_evtAddToFavoritesClick(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('favorite', {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("favorite", {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_evtRemoveFromFavoritesClick(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('unfavorite', {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("unfavorite", {
|
||||
detail: {
|
||||
post: this._post,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_evtScoreClick(e, score) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('score', {
|
||||
detail: {
|
||||
post: this._post,
|
||||
score: this._post.ownScore === score ? 0 : score,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("score", {
|
||||
detail: {
|
||||
post: this._post,
|
||||
score: this._post.ownScore === score ? 0 : score,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_evtChangeFav(e) {
|
||||
|
|
|
@ -1,51 +1,63 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const TagList = require('../models/tag_list.js');
|
||||
const AutoCompleteControl = require('./auto_complete_control.js');
|
||||
const misc = require("../util/misc.js");
|
||||
const views = require("../util/views.js");
|
||||
const TagList = require("../models/tag_list.js");
|
||||
const AutoCompleteControl = require("./auto_complete_control.js");
|
||||
|
||||
function _tagListToMatches(tags, options) {
|
||||
return [...tags].sort((tag1, tag2) => {
|
||||
return tag2.usages - tag1.usages;
|
||||
}).map(tag => {
|
||||
let cssName = misc.makeCssName(tag.category, 'tag');
|
||||
if (options.isTaggedWith(tag.names[0])) {
|
||||
cssName += ' disabled';
|
||||
}
|
||||
const caption = (
|
||||
'<span class="' + cssName + '">'
|
||||
+ misc.escapeHtml(tag.names[0] + ' (' + tag.postCount + ')')
|
||||
+ '</span>');
|
||||
return {
|
||||
caption: caption,
|
||||
value: tag,
|
||||
};
|
||||
});
|
||||
return [...tags]
|
||||
.sort((tag1, tag2) => {
|
||||
return tag2.usages - tag1.usages;
|
||||
})
|
||||
.map((tag) => {
|
||||
let cssName = misc.makeCssName(tag.category, "tag");
|
||||
if (options.isTaggedWith(tag.names[0])) {
|
||||
cssName += " disabled";
|
||||
}
|
||||
const caption =
|
||||
'<span class="' +
|
||||
cssName +
|
||||
'">' +
|
||||
misc.escapeHtml(tag.names[0] + " (" + tag.postCount + ")") +
|
||||
"</span>";
|
||||
return {
|
||||
caption: caption,
|
||||
value: tag,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class TagAutoCompleteControl extends AutoCompleteControl {
|
||||
constructor(input, options) {
|
||||
const minLengthForPartialSearch = 3;
|
||||
|
||||
options = Object.assign({
|
||||
isTaggedWith: tag => false,
|
||||
}, options);
|
||||
options = Object.assign(
|
||||
{
|
||||
isTaggedWith: (tag) => false,
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
options.getMatches = text => {
|
||||
options.getMatches = (text) => {
|
||||
const term = misc.escapeSearchTerm(text);
|
||||
const query = (
|
||||
text.length < minLengthForPartialSearch
|
||||
? term + '*'
|
||||
: '*' + term + '*') + ' sort:usages';
|
||||
const query =
|
||||
(text.length < minLengthForPartialSearch
|
||||
? 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);
|
||||
TagList.search(query, 0, this._options.maxResults, [
|
||||
"names",
|
||||
"category",
|
||||
"usages",
|
||||
]).then(
|
||||
(response) =>
|
||||
resolve(
|
||||
_tagListToMatches(response.results, this._options)
|
||||
),
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const tags = require('../tags.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const Tag = require('../models/tag.js');
|
||||
const settings = require('../models/settings.js');
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const TagAutoCompleteControl = require('./tag_auto_complete_control.js');
|
||||
const api = require("../api.js");
|
||||
const tags = require("../tags.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const Tag = require("../models/tag.js");
|
||||
const settings = require("../models/settings.js");
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
const TagAutoCompleteControl = require("./tag_auto_complete_control.js");
|
||||
|
||||
const KEY_SPACE = 32;
|
||||
const KEY_RETURN = 13;
|
||||
|
||||
const SOURCE_INIT = 'init';
|
||||
const SOURCE_IMPLICATION = 'implication';
|
||||
const SOURCE_USER_INPUT = 'user-input';
|
||||
const SOURCE_SUGGESTION = 'suggestions';
|
||||
const SOURCE_CLIPBOARD = 'clipboard';
|
||||
const SOURCE_INIT = "init";
|
||||
const SOURCE_IMPLICATION = "implication";
|
||||
const SOURCE_USER_INPUT = "user-input";
|
||||
const SOURCE_SUGGESTION = "suggestions";
|
||||
const SOURCE_CLIPBOARD = "clipboard";
|
||||
|
||||
const template = views.getTemplate('tag-input');
|
||||
const template = views.getTemplate("tag-input");
|
||||
|
||||
function _fadeOutListItemNodeStatus(listItemNode) {
|
||||
if (listItemNode.classList.length) {
|
||||
|
@ -28,8 +28,7 @@ function _fadeOutListItemNodeStatus(listItemNode) {
|
|||
}
|
||||
listItemNode.fadeTimeout = window.setTimeout(() => {
|
||||
while (listItemNode.classList.length) {
|
||||
listItemNode.classList.remove(
|
||||
listItemNode.classList.item(0));
|
||||
listItemNode.classList.remove(listItemNode.classList.item(0));
|
||||
}
|
||||
listItemNode.fadeTimeout = null;
|
||||
}, 2500);
|
||||
|
@ -51,7 +50,9 @@ class SuggestionList {
|
|||
}
|
||||
|
||||
set(suggestion, weight) {
|
||||
if (Object.prototype.hasOwnProperty.call(this._suggestions, suggestion)) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(this._suggestions, suggestion)
|
||||
) {
|
||||
weight = Math.max(weight, this._suggestions[suggestion]);
|
||||
}
|
||||
this._suggestions[suggestion] = weight;
|
||||
|
@ -74,8 +75,8 @@ class SuggestionList {
|
|||
let nameDiff = a[0].localeCompare(b[0]);
|
||||
return weightDiff === 0 ? nameDiff : weightDiff;
|
||||
});
|
||||
return tuples.map(tuple => {
|
||||
return {tagName: tuple[0], weight: tuple[1]};
|
||||
return tuples.map((tuple) => {
|
||||
return { tagName: tuple[0], weight: tuple[1] };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -91,45 +92,58 @@ class TagInputControl extends events.EventTarget {
|
|||
// dom
|
||||
const editAreaNode = template();
|
||||
this._editAreaNode = editAreaNode;
|
||||
this._tagInputNode = editAreaNode.querySelector('input');
|
||||
this._suggestionsNode = editAreaNode.querySelector('.tag-suggestions');
|
||||
this._tagListNode = editAreaNode.querySelector('ul.compact-tags');
|
||||
this._tagInputNode = editAreaNode.querySelector("input");
|
||||
this._suggestionsNode = editAreaNode.querySelector(".tag-suggestions");
|
||||
this._tagListNode = editAreaNode.querySelector("ul.compact-tags");
|
||||
|
||||
this._autoCompleteControl = new TagAutoCompleteControl(
|
||||
this._tagInputNode, {
|
||||
this._tagInputNode,
|
||||
{
|
||||
getTextToFind: () => {
|
||||
return this._tagInputNode.value;
|
||||
},
|
||||
confirm: tag => {
|
||||
this._tagInputNode.value = '';
|
||||
confirm: (tag) => {
|
||||
this._tagInputNode.value = "";
|
||||
// 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);
|
||||
},
|
||||
delete: tag => {
|
||||
this._tagInputNode.value = '';
|
||||
delete: (tag) => {
|
||||
this._tagInputNode.value = "";
|
||||
this.deleteTag(tag);
|
||||
},
|
||||
verticalShift: -2,
|
||||
isTaggedWith: tagName => this.tags.isTaggedWith(tagName),
|
||||
});
|
||||
isTaggedWith: (tagName) => this.tags.isTaggedWith(tagName),
|
||||
}
|
||||
);
|
||||
|
||||
// dom events
|
||||
this._tagInputNode.addEventListener(
|
||||
'keydown', e => this._evtInputKeyDown(e));
|
||||
this._tagInputNode.addEventListener(
|
||||
'paste', e => this._evtInputPaste(e));
|
||||
this._editAreaNode.querySelector('a.opacity').addEventListener(
|
||||
'click', e => this._evtToggleSuggestionsPopupOpacityClick(e));
|
||||
this._editAreaNode.querySelector('a.close').addEventListener(
|
||||
'click', e => this._evtCloseSuggestionsPopupClick(e));
|
||||
this._editAreaNode.querySelector('button').addEventListener(
|
||||
'click', e => this._evtAddTagButtonClick(e));
|
||||
this._tagInputNode.addEventListener("keydown", (e) =>
|
||||
this._evtInputKeyDown(e)
|
||||
);
|
||||
this._tagInputNode.addEventListener("paste", (e) =>
|
||||
this._evtInputPaste(e)
|
||||
);
|
||||
this._editAreaNode
|
||||
.querySelector("a.opacity")
|
||||
.addEventListener("click", (e) =>
|
||||
this._evtToggleSuggestionsPopupOpacityClick(e)
|
||||
);
|
||||
this._editAreaNode
|
||||
.querySelector("a.close")
|
||||
.addEventListener("click", (e) =>
|
||||
this._evtCloseSuggestionsPopupClick(e)
|
||||
);
|
||||
this._editAreaNode
|
||||
.querySelector("button")
|
||||
.addEventListener("click", (e) => this._evtAddTagButtonClick(e));
|
||||
|
||||
// show
|
||||
this._hostNode.style.display = 'none';
|
||||
this._hostNode.style.display = "none";
|
||||
this._hostNode.parentNode.insertBefore(
|
||||
this._editAreaNode, hostNode.nextSibling);
|
||||
this._editAreaNode,
|
||||
hostNode.nextSibling
|
||||
);
|
||||
|
||||
// add existing tags
|
||||
for (let tag of [...this.tags]) {
|
||||
|
@ -139,7 +153,10 @@ class TagInputControl extends events.EventTarget {
|
|||
}
|
||||
|
||||
addTagByText(text, source) {
|
||||
for (let tagName of text.split(/\s+/).filter(word => word).reverse()) {
|
||||
for (let tagName of text
|
||||
.split(/\s+/)
|
||||
.filter((word) => word)
|
||||
.reverse()) {
|
||||
this.addTagByName(tagName, source);
|
||||
}
|
||||
}
|
||||
|
@ -149,48 +166,60 @@ class TagInputControl extends events.EventTarget {
|
|||
if (!name) {
|
||||
return;
|
||||
}
|
||||
return Tag.get(name).then(tag => {
|
||||
return this.addTag(tag, source);
|
||||
}, () => {
|
||||
const tag = new Tag();
|
||||
tag.names = [name];
|
||||
tag.category = null;
|
||||
return this.addTag(tag, source);
|
||||
});
|
||||
return Tag.get(name).then(
|
||||
(tag) => {
|
||||
return this.addTag(tag, source);
|
||||
},
|
||||
() => {
|
||||
const tag = new Tag();
|
||||
tag.names = [name];
|
||||
tag.category = null;
|
||||
return this.addTag(tag, source);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
addTag(tag, source) {
|
||||
if (source !== SOURCE_INIT && this.tags.isTaggedWith(tag.names[0])) {
|
||||
const listItemNode = this._getListItemNode(tag);
|
||||
if (source !== SOURCE_IMPLICATION) {
|
||||
listItemNode.classList.add('duplicate');
|
||||
listItemNode.classList.add("duplicate");
|
||||
_fadeOutListItemNodeStatus(listItemNode);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.tags.addByName(tag.names[0], false).then(() => {
|
||||
const listItemNode = this._createListItemNode(tag);
|
||||
if (!tag.category) {
|
||||
listItemNode.classList.add('new');
|
||||
}
|
||||
if (source === SOURCE_IMPLICATION) {
|
||||
listItemNode.classList.add('implication');
|
||||
}
|
||||
this._tagListNode.prependChild(listItemNode);
|
||||
_fadeOutListItemNodeStatus(listItemNode);
|
||||
return this.tags
|
||||
.addByName(tag.names[0], false)
|
||||
.then(() => {
|
||||
const listItemNode = this._createListItemNode(tag);
|
||||
if (!tag.category) {
|
||||
listItemNode.classList.add("new");
|
||||
}
|
||||
if (source === SOURCE_IMPLICATION) {
|
||||
listItemNode.classList.add("implication");
|
||||
}
|
||||
this._tagListNode.prependChild(listItemNode);
|
||||
_fadeOutListItemNodeStatus(listItemNode);
|
||||
|
||||
return Promise.all(
|
||||
tag.implications.map(
|
||||
implication => this.addTagByName(
|
||||
implication.names[0], SOURCE_IMPLICATION)));
|
||||
}).then(() => {
|
||||
this.dispatchEvent(new CustomEvent('add', {
|
||||
detail: {tag: tag, source: source},
|
||||
}));
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
return Promise.resolve();
|
||||
});
|
||||
return Promise.all(
|
||||
tag.implications.map((implication) =>
|
||||
this.addTagByName(
|
||||
implication.names[0],
|
||||
SOURCE_IMPLICATION
|
||||
)
|
||||
)
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("add", {
|
||||
detail: { tag: tag, source: source },
|
||||
})
|
||||
);
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
deleteTag(tag) {
|
||||
|
@ -202,25 +231,27 @@ class TagInputControl extends events.EventTarget {
|
|||
|
||||
this._deleteListItemNode(tag);
|
||||
|
||||
this.dispatchEvent(new CustomEvent('remove', {
|
||||
detail: {tag: tag},
|
||||
}));
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("remove", {
|
||||
detail: { tag: tag },
|
||||
})
|
||||
);
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
|
||||
_evtInputPaste(e) {
|
||||
e.preventDefault();
|
||||
const pastedText = window.clipboardData ?
|
||||
window.clipboardData.getData('Text') :
|
||||
(e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
const pastedText = window.clipboardData
|
||||
? window.clipboardData.getData("Text")
|
||||
: (e.originalEvent || e).clipboardData.getData("text/plain");
|
||||
|
||||
if (pastedText.length > 2000) {
|
||||
window.alert('Pasted text is too long.');
|
||||
window.alert("Pasted text is too long.");
|
||||
return;
|
||||
}
|
||||
this._hideAutoComplete();
|
||||
this.addTagByText(pastedText, SOURCE_CLIPBOARD);
|
||||
this._tagInputNode.value = '';
|
||||
this._tagInputNode.value = "";
|
||||
}
|
||||
|
||||
_evtCloseSuggestionsPopupClick(e) {
|
||||
|
@ -231,7 +262,7 @@ class TagInputControl extends events.EventTarget {
|
|||
_evtAddTagButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.addTagByName(this._tagInputNode.value, SOURCE_USER_INPUT);
|
||||
this._tagInputNode.value = '';
|
||||
this._tagInputNode.value = "";
|
||||
}
|
||||
|
||||
_evtToggleSuggestionsPopupOpacityClick(e) {
|
||||
|
@ -244,36 +275,41 @@ class TagInputControl extends events.EventTarget {
|
|||
e.preventDefault();
|
||||
this._hideAutoComplete();
|
||||
this.addTagByText(this._tagInputNode.value, SOURCE_USER_INPUT);
|
||||
this._tagInputNode.value = '';
|
||||
this._tagInputNode.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
_createListItemNode(tag) {
|
||||
const className = tag.category ?
|
||||
misc.makeCssName(tag.category, 'tag') :
|
||||
null;
|
||||
const className = tag.category
|
||||
? misc.makeCssName(tag.category, "tag")
|
||||
: null;
|
||||
|
||||
const tagLinkNode = document.createElement('a');
|
||||
const tagLinkNode = document.createElement("a");
|
||||
if (className) {
|
||||
tagLinkNode.classList.add(className);
|
||||
}
|
||||
tagLinkNode.setAttribute(
|
||||
'href', uri.formatClientLink('tag', tag.names[0]));
|
||||
"href",
|
||||
uri.formatClientLink("tag", tag.names[0])
|
||||
);
|
||||
|
||||
const tagIconNode = document.createElement('i');
|
||||
tagIconNode.classList.add('fa');
|
||||
tagIconNode.classList.add('fa-tag');
|
||||
const tagIconNode = document.createElement("i");
|
||||
tagIconNode.classList.add("fa");
|
||||
tagIconNode.classList.add("fa-tag");
|
||||
tagLinkNode.appendChild(tagIconNode);
|
||||
|
||||
const searchLinkNode = document.createElement('a');
|
||||
const searchLinkNode = document.createElement("a");
|
||||
if (className) {
|
||||
searchLinkNode.classList.add(className);
|
||||
}
|
||||
searchLinkNode.setAttribute(
|
||||
'href', uri.formatClientLink(
|
||||
'posts', {query: uri.escapeColons(tag.names[0])}));
|
||||
searchLinkNode.textContent = tag.names[0] + ' ';
|
||||
searchLinkNode.addEventListener('click', e => {
|
||||
"href",
|
||||
uri.formatClientLink("posts", {
|
||||
query: uri.escapeColons(tag.names[0]),
|
||||
})
|
||||
);
|
||||
searchLinkNode.textContent = tag.names[0] + " ";
|
||||
searchLinkNode.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this._suggestions.clear();
|
||||
if (tag.postCount > 0) {
|
||||
|
@ -284,20 +320,20 @@ class TagInputControl extends events.EventTarget {
|
|||
}
|
||||
});
|
||||
|
||||
const usagesNode = document.createElement('span');
|
||||
usagesNode.classList.add('tag-usages');
|
||||
usagesNode.setAttribute('data-pseudo-content', tag.postCount);
|
||||
const usagesNode = document.createElement("span");
|
||||
usagesNode.classList.add("tag-usages");
|
||||
usagesNode.setAttribute("data-pseudo-content", tag.postCount);
|
||||
|
||||
const removalLinkNode = document.createElement('a');
|
||||
removalLinkNode.classList.add('remove-tag');
|
||||
removalLinkNode.setAttribute('href', '');
|
||||
removalLinkNode.setAttribute('data-pseudo-content', '×');
|
||||
removalLinkNode.addEventListener('click', e => {
|
||||
const removalLinkNode = document.createElement("a");
|
||||
removalLinkNode.classList.add("remove-tag");
|
||||
removalLinkNode.setAttribute("href", "");
|
||||
removalLinkNode.setAttribute("data-pseudo-content", "×");
|
||||
removalLinkNode.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.deleteTag(tag);
|
||||
});
|
||||
|
||||
const listItemNode = document.createElement('li');
|
||||
const listItemNode = document.createElement("li");
|
||||
listItemNode.appendChild(removalLinkNode);
|
||||
listItemNode.appendChild(tagLinkNode);
|
||||
listItemNode.appendChild(searchLinkNode);
|
||||
|
@ -327,20 +363,25 @@ class TagInputControl extends events.EventTarget {
|
|||
if (!browsingSettings.tagSuggestions) {
|
||||
return;
|
||||
}
|
||||
api.get(
|
||||
uri.formatApiLink('tag-siblings', tag.names[0]),
|
||||
{noProgress: true})
|
||||
.then(response => {
|
||||
return Promise.resolve(response.results);
|
||||
}, response => {
|
||||
return Promise.resolve([]);
|
||||
}).then(siblings => {
|
||||
const args = siblings.map(s => s.occurrences);
|
||||
api.get(uri.formatApiLink("tag-siblings", tag.names[0]), {
|
||||
noProgress: true,
|
||||
})
|
||||
.then(
|
||||
(response) => {
|
||||
return Promise.resolve(response.results);
|
||||
},
|
||||
(response) => {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
)
|
||||
.then((siblings) => {
|
||||
const args = siblings.map((s) => s.occurrences);
|
||||
let maxSiblingOccurrences = Math.max(1, ...args);
|
||||
for (let sibling of siblings) {
|
||||
this._suggestions.set(
|
||||
sibling.tag.names[0],
|
||||
sibling.occurrences * 4.9 / maxSiblingOccurrences);
|
||||
(sibling.occurrences * 4.9) / maxSiblingOccurrences
|
||||
);
|
||||
}
|
||||
for (let suggestion of tag.suggestions || []) {
|
||||
this._suggestions.set(suggestion, 5);
|
||||
|
@ -354,10 +395,10 @@ class TagInputControl extends events.EventTarget {
|
|||
}
|
||||
|
||||
_refreshSuggestionsPopup() {
|
||||
if (!this._suggestionsNode.classList.contains('shown')) {
|
||||
if (!this._suggestionsNode.classList.contains("shown")) {
|
||||
return;
|
||||
}
|
||||
const listNode = this._suggestionsNode.querySelector('ul');
|
||||
const listNode = this._suggestionsNode.querySelector("ul");
|
||||
listNode.scrollTop = 0;
|
||||
while (listNode.firstChild) {
|
||||
listNode.removeChild(listNode.firstChild);
|
||||
|
@ -369,35 +410,36 @@ class TagInputControl extends events.EventTarget {
|
|||
continue;
|
||||
}
|
||||
|
||||
const addLinkNode = document.createElement('a');
|
||||
const addLinkNode = document.createElement("a");
|
||||
addLinkNode.textContent = tagName;
|
||||
addLinkNode.classList.add('add-tag');
|
||||
addLinkNode.setAttribute('href', '');
|
||||
Tag.get(tagName).then(tag => {
|
||||
addLinkNode.classList.add("add-tag");
|
||||
addLinkNode.setAttribute("href", "");
|
||||
Tag.get(tagName).then((tag) => {
|
||||
addLinkNode.classList.add(
|
||||
misc.makeCssName(tag.category, 'tag'));
|
||||
misc.makeCssName(tag.category, "tag")
|
||||
);
|
||||
});
|
||||
addLinkNode.addEventListener('click', e => {
|
||||
addLinkNode.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
listNode.removeChild(listItemNode);
|
||||
this.addTagByName(tagName, SOURCE_SUGGESTION);
|
||||
});
|
||||
|
||||
const weightNode = document.createElement('span');
|
||||
weightNode.classList.add('tag-weight');
|
||||
weightNode.setAttribute('data-pseudo-content', weight);
|
||||
const weightNode = document.createElement("span");
|
||||
weightNode.classList.add("tag-weight");
|
||||
weightNode.setAttribute("data-pseudo-content", weight);
|
||||
|
||||
const removalLinkNode = document.createElement('a');
|
||||
removalLinkNode.classList.add('remove-tag');
|
||||
removalLinkNode.setAttribute('href', '');
|
||||
removalLinkNode.setAttribute('data-pseudo-content', '×');
|
||||
removalLinkNode.addEventListener('click', e => {
|
||||
const removalLinkNode = document.createElement("a");
|
||||
removalLinkNode.classList.add("remove-tag");
|
||||
removalLinkNode.setAttribute("href", "");
|
||||
removalLinkNode.setAttribute("data-pseudo-content", "×");
|
||||
removalLinkNode.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
listNode.removeChild(listItemNode);
|
||||
this._suggestions.ban(tagName);
|
||||
});
|
||||
|
||||
const listItemNode = document.createElement('li');
|
||||
const listItemNode = document.createElement("li");
|
||||
listItemNode.appendChild(removalLinkNode);
|
||||
listItemNode.appendChild(weightNode);
|
||||
listItemNode.appendChild(addLinkNode);
|
||||
|
@ -407,19 +449,19 @@ class TagInputControl extends events.EventTarget {
|
|||
|
||||
_closeSuggestionsPopup() {
|
||||
this._suggestions.clear();
|
||||
this._suggestionsNode.classList.remove('shown');
|
||||
this._suggestionsNode.classList.remove("shown");
|
||||
}
|
||||
|
||||
_removeSuggestionsPopupOpacity() {
|
||||
this._suggestionsNode.classList.remove('translucent');
|
||||
this._suggestionsNode.classList.remove("translucent");
|
||||
}
|
||||
|
||||
_toggleSuggestionsPopupOpacity() {
|
||||
this._suggestionsNode.classList.toggle('translucent');
|
||||
this._suggestionsNode.classList.toggle("translucent");
|
||||
}
|
||||
|
||||
_openSuggestionsPopup() {
|
||||
this._suggestionsNode.classList.add('shown');
|
||||
this._suggestionsNode.classList.add("shown");
|
||||
this._refreshSuggestionsPopup();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
class EventTarget {
|
||||
constructor() {
|
||||
this.eventTarget = document.createDocumentFragment();
|
||||
for (let method of [
|
||||
'addEventListener',
|
||||
'dispatchEvent',
|
||||
'removeEventListener'
|
||||
"addEventListener",
|
||||
"dispatchEvent",
|
||||
"removeEventListener",
|
||||
]) {
|
||||
this[method] = this.eventTarget[method].bind(this.eventTarget);
|
||||
}
|
||||
|
@ -20,17 +20,19 @@ function proxyEvent(source, target, sourceEventType, targetEventType) {
|
|||
if (!targetEventType) {
|
||||
targetEventType = sourceEventType;
|
||||
}
|
||||
source.addEventListener(sourceEventType, e => {
|
||||
target.dispatchEvent(new CustomEvent(targetEventType, {
|
||||
detail: e.detail,
|
||||
}));
|
||||
source.addEventListener(sourceEventType, (e) => {
|
||||
target.dispatchEvent(
|
||||
new CustomEvent(targetEventType, {
|
||||
detail: e.detail,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Success: 'success',
|
||||
Error: 'error',
|
||||
Info: 'info',
|
||||
Success: "success",
|
||||
Error: "error",
|
||||
Info: "info",
|
||||
|
||||
proxyEvent: proxyEvent,
|
||||
EventTarget: EventTarget,
|
||||
|
|
|
@ -1,82 +1,101 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
require('./util/polyfill.js');
|
||||
const misc = require('./util/misc.js');
|
||||
const views = require('./util/views.js');
|
||||
const router = require('./router.js');
|
||||
require("./util/polyfill.js");
|
||||
const misc = require("./util/misc.js");
|
||||
const views = require("./util/views.js");
|
||||
const router = require("./router.js");
|
||||
|
||||
history.scrollRestoration = 'manual';
|
||||
history.scrollRestoration = "manual";
|
||||
|
||||
router.exit(
|
||||
null,
|
||||
(ctx, next) => {
|
||||
ctx.state.scrollX = window.scrollX;
|
||||
ctx.state.scrollY = window.scrollY;
|
||||
router.replace(router.url, ctx.state);
|
||||
if (misc.confirmPageExit()) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
const mousetrap = require('mousetrap');
|
||||
router.enter(
|
||||
null,
|
||||
(ctx, next) => {
|
||||
mousetrap.reset();
|
||||
router.exit(null, (ctx, next) => {
|
||||
ctx.state.scrollX = window.scrollX;
|
||||
ctx.state.scrollY = window.scrollY;
|
||||
router.replace(router.url, ctx.state);
|
||||
if (misc.confirmPageExit()) {
|
||||
next();
|
||||
});
|
||||
|
||||
const tags = require('./tags.js');
|
||||
const pools = require('./pools.js');
|
||||
const api = require('./api.js');
|
||||
|
||||
api.fetchConfig().then(() => {
|
||||
// register controller routes
|
||||
let controllers = [];
|
||||
controllers.push(require('./controllers/home_controller.js'));
|
||||
controllers.push(require('./controllers/help_controller.js'));
|
||||
controllers.push(require('./controllers/auth_controller.js'));
|
||||
controllers.push(require('./controllers/password_reset_controller.js'));
|
||||
controllers.push(require('./controllers/comments_controller.js'));
|
||||
controllers.push(require('./controllers/snapshots_controller.js'));
|
||||
controllers.push(require('./controllers/post_detail_controller.js'));
|
||||
controllers.push(require('./controllers/post_main_controller.js'));
|
||||
controllers.push(require('./controllers/post_list_controller.js'));
|
||||
controllers.push(require('./controllers/post_upload_controller.js'));
|
||||
controllers.push(require('./controllers/tag_controller.js'));
|
||||
controllers.push(require('./controllers/tag_list_controller.js'));
|
||||
controllers.push(require('./controllers/tag_categories_controller.js'));
|
||||
controllers.push(require('./controllers/pool_create_controller.js'));
|
||||
controllers.push(require('./controllers/pool_controller.js'));
|
||||
controllers.push(require('./controllers/pool_list_controller.js'));
|
||||
controllers.push(require('./controllers/pool_categories_controller.js'));
|
||||
controllers.push(require('./controllers/settings_controller.js'));
|
||||
controllers.push(require('./controllers/user_controller.js'));
|
||||
controllers.push(require('./controllers/user_list_controller.js'));
|
||||
controllers.push(require('./controllers/user_registration_controller.js'));
|
||||
|
||||
// 404 controller needs to be registered last
|
||||
controllers.push(require('./controllers/not_found_controller.js'));
|
||||
|
||||
for (let controller of controllers) {
|
||||
controller(router);
|
||||
}
|
||||
}, error => {
|
||||
window.alert('Could not fetch basic configuration from server');
|
||||
}).then(() => {
|
||||
api.loginFromCookies().then(() => {
|
||||
tags.refreshCategoryColorMap();
|
||||
pools.refreshCategoryColorMap();
|
||||
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: ' +
|
||||
error.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const mousetrap = require("mousetrap");
|
||||
router.enter(null, (ctx, next) => {
|
||||
mousetrap.reset();
|
||||
next();
|
||||
});
|
||||
|
||||
const tags = require("./tags.js");
|
||||
const pools = require("./pools.js");
|
||||
const api = require("./api.js");
|
||||
|
||||
api.fetchConfig()
|
||||
.then(
|
||||
() => {
|
||||
// register controller routes
|
||||
let controllers = [];
|
||||
controllers.push(require("./controllers/home_controller.js"));
|
||||
controllers.push(require("./controllers/help_controller.js"));
|
||||
controllers.push(require("./controllers/auth_controller.js"));
|
||||
controllers.push(
|
||||
require("./controllers/password_reset_controller.js")
|
||||
);
|
||||
controllers.push(require("./controllers/comments_controller.js"));
|
||||
controllers.push(require("./controllers/snapshots_controller.js"));
|
||||
controllers.push(
|
||||
require("./controllers/post_detail_controller.js")
|
||||
);
|
||||
controllers.push(require("./controllers/post_main_controller.js"));
|
||||
controllers.push(require("./controllers/post_list_controller.js"));
|
||||
controllers.push(
|
||||
require("./controllers/post_upload_controller.js")
|
||||
);
|
||||
controllers.push(require("./controllers/tag_controller.js"));
|
||||
controllers.push(require("./controllers/tag_list_controller.js"));
|
||||
controllers.push(
|
||||
require("./controllers/tag_categories_controller.js")
|
||||
);
|
||||
controllers.push(
|
||||
require("./controllers/pool_create_controller.js")
|
||||
);
|
||||
controllers.push(require("./controllers/pool_controller.js"));
|
||||
controllers.push(require("./controllers/pool_list_controller.js"));
|
||||
controllers.push(
|
||||
require("./controllers/pool_categories_controller.js")
|
||||
);
|
||||
controllers.push(require("./controllers/settings_controller.js"));
|
||||
controllers.push(require("./controllers/user_controller.js"));
|
||||
controllers.push(require("./controllers/user_list_controller.js"));
|
||||
controllers.push(
|
||||
require("./controllers/user_registration_controller.js")
|
||||
);
|
||||
|
||||
// 404 controller needs to be registered last
|
||||
controllers.push(require("./controllers/not_found_controller.js"));
|
||||
|
||||
for (let controller of controllers) {
|
||||
controller(router);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
window.alert("Could not fetch basic configuration from server");
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
api.loginFromCookies().then(
|
||||
() => {
|
||||
tags.refreshCategoryColorMap();
|
||||
pools.refreshCategoryColorMap();
|
||||
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: " +
|
||||
error.message
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const events = require("../events.js");
|
||||
|
||||
class AbstractList extends events.EventTarget {
|
||||
constructor() {
|
||||
|
@ -13,13 +13,15 @@ class AbstractList extends events.EventTarget {
|
|||
for (let item of response) {
|
||||
const addedItem = this._itemClass.fromResponse(item);
|
||||
if (addedItem.addEventListener) {
|
||||
addedItem.addEventListener('delete', e => {
|
||||
addedItem.addEventListener("delete", (e) => {
|
||||
ret.remove(addedItem);
|
||||
});
|
||||
addedItem.addEventListener('change', e => {
|
||||
ret.dispatchEvent(new CustomEvent('change', {
|
||||
detail: e.detail,
|
||||
}));
|
||||
addedItem.addEventListener("change", (e) => {
|
||||
ret.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: e.detail,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
ret._list.push(addedItem);
|
||||
|
@ -29,28 +31,32 @@ class AbstractList extends events.EventTarget {
|
|||
|
||||
sync(plainList) {
|
||||
this.clear();
|
||||
for (let item of (plainList || [])) {
|
||||
for (let item of plainList || []) {
|
||||
this.add(this.constructor._itemClass.fromResponse(item));
|
||||
}
|
||||
}
|
||||
|
||||
add(item) {
|
||||
if (item.addEventListener) {
|
||||
item.addEventListener('delete', e => {
|
||||
item.addEventListener("delete", (e) => {
|
||||
this.remove(item);
|
||||
});
|
||||
item.addEventListener('change', e => {
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: e.detail,
|
||||
}));
|
||||
item.addEventListener("change", (e) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: e.detail,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
this._list.push(item);
|
||||
const detail = {};
|
||||
detail[this.constructor._itemName] = item;
|
||||
this.dispatchEvent(new CustomEvent('add', {
|
||||
detail: detail,
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("add", {
|
||||
detail: detail,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
@ -67,9 +73,11 @@ class AbstractList extends events.EventTarget {
|
|||
this._list.splice(index, 1);
|
||||
const detail = {};
|
||||
detail[this.constructor._itemName] = itemToRemove;
|
||||
this.dispatchEvent(new CustomEvent('remove', {
|
||||
detail: detail,
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("remove", {
|
||||
detail: detail,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const events = require('../events.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const events = require("../events.js");
|
||||
|
||||
class Comment extends events.EventTarget {
|
||||
constructor() {
|
||||
|
@ -31,7 +31,7 @@ class Comment extends events.EventTarget {
|
|||
}
|
||||
|
||||
get text() {
|
||||
return this._text || '';
|
||||
return this._text || "";
|
||||
}
|
||||
|
||||
get user() {
|
||||
|
@ -63,47 +63,57 @@ class Comment extends events.EventTarget {
|
|||
version: this._version,
|
||||
text: this._text,
|
||||
};
|
||||
let promise = this._id ?
|
||||
api.put(uri.formatApiLink('comment', this.id), detail) :
|
||||
api.post(uri.formatApiLink('comments'),
|
||||
Object.assign({postId: this._postId}, detail));
|
||||
let promise = this._id
|
||||
? api.put(uri.formatApiLink("comment", this.id), detail)
|
||||
: api.post(
|
||||
uri.formatApiLink("comments"),
|
||||
Object.assign({ postId: this._postId }, detail)
|
||||
);
|
||||
|
||||
return promise.then(response => {
|
||||
return promise.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete(
|
||||
uri.formatApiLink('comment', this.id),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(uri.formatApiLink("comment", this.id), {
|
||||
version: this._version,
|
||||
})
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
setScore(score) {
|
||||
return api.put(
|
||||
uri.formatApiLink('comment', this.id, 'score'),
|
||||
{score: score})
|
||||
.then(response => {
|
||||
return api
|
||||
.put(uri.formatApiLink("comment", this.id, "score"), {
|
||||
score: score,
|
||||
})
|
||||
.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeScore", {
|
||||
detail: {
|
||||
comment: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Comment = require('./comment.js');
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const Comment = require("./comment.js");
|
||||
|
||||
class CommentList extends AbstractList {
|
||||
}
|
||||
class CommentList extends AbstractList {}
|
||||
|
||||
CommentList._itemClass = Comment;
|
||||
CommentList._itemName = 'comment';
|
||||
CommentList._itemName = "comment";
|
||||
|
||||
module.exports = CommentList;
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const Post = require('./post.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const Post = require("./post.js");
|
||||
|
||||
class Info {
|
||||
static get() {
|
||||
return api.get(uri.formatApiLink('info'))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{
|
||||
featuredPost: response.featuredPost ?
|
||||
Post.fromResponse(response.featuredPost) :
|
||||
undefined
|
||||
}));
|
||||
});
|
||||
return api.get(uri.formatApiLink("info")).then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
featuredPost: response.featuredPost
|
||||
? Post.fromResponse(response.featuredPost)
|
||||
: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const Point = require('./point.js');
|
||||
const PointList = require('./point_list.js');
|
||||
const events = require("../events.js");
|
||||
const Point = require("./point.js");
|
||||
const PointList = require("./point_list.js");
|
||||
|
||||
class Note extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._text = '…';
|
||||
this._text = "…";
|
||||
this._polygon = new PointList();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Note = require('./note.js');
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const Note = require("./note.js");
|
||||
|
||||
class NoteList extends AbstractList {
|
||||
}
|
||||
class NoteList extends AbstractList {}
|
||||
|
||||
NoteList._itemClass = Note;
|
||||
NoteList._itemName = 'note';
|
||||
NoteList._itemName = "note";
|
||||
|
||||
module.exports = NoteList;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const events = require("../events.js");
|
||||
|
||||
class Point extends events.EventTarget {
|
||||
constructor(x, y) {
|
||||
|
@ -19,12 +19,16 @@ class Point extends events.EventTarget {
|
|||
|
||||
set x(value) {
|
||||
this._x = value;
|
||||
this.dispatchEvent(new CustomEvent('change', {detail: {point: this}}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", { detail: { point: this } })
|
||||
);
|
||||
}
|
||||
|
||||
set y(value) {
|
||||
this._y = value;
|
||||
this.dispatchEvent(new CustomEvent('change', {detail: {point: this}}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", { detail: { point: this } })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Point = require('./point.js');
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const Point = require("./point.js");
|
||||
|
||||
class PointList extends AbstractList {
|
||||
get firstPoint() {
|
||||
|
@ -18,6 +18,6 @@ class PointList extends AbstractList {
|
|||
}
|
||||
|
||||
PointList._itemClass = Point;
|
||||
PointList._itemName = 'point';
|
||||
PointList._itemName = "point";
|
||||
|
||||
module.exports = PointList;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const events = require('../events.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const events = require("../events.js");
|
||||
const misc = require("../util/misc.js");
|
||||
|
||||
class Pool extends events.EventTarget {
|
||||
constructor() {
|
||||
const PostList = require('./post_list.js');
|
||||
const PostList = require("./post_list.js");
|
||||
|
||||
super();
|
||||
this._orig = {};
|
||||
|
@ -70,14 +70,13 @@ class Pool extends events.EventTarget {
|
|||
}
|
||||
|
||||
static get(id) {
|
||||
return api.get(uri.formatApiLink('pool', id))
|
||||
.then(response => {
|
||||
return Promise.resolve(Pool.fromResponse(response));
|
||||
});
|
||||
return api.get(uri.formatApiLink("pool", id)).then((response) => {
|
||||
return Promise.resolve(Pool.fromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
const detail = {version: this._version};
|
||||
const detail = { version: this._version };
|
||||
|
||||
// send only changed fields to avoid user privilege violation
|
||||
if (misc.arraysDiffer(this._names, this._orig._names, true)) {
|
||||
|
@ -90,62 +89,71 @@ class Pool extends events.EventTarget {
|
|||
detail.description = this._description;
|
||||
}
|
||||
if (misc.arraysDiffer(this._posts, this._orig._posts)) {
|
||||
detail.posts = this._posts.map(post => post.id);
|
||||
detail.posts = this._posts.map((post) => post.id);
|
||||
}
|
||||
|
||||
let promise = this._id ?
|
||||
api.put(uri.formatApiLink('pool', this._id), detail) :
|
||||
api.post(uri.formatApiLink('pools'), detail);
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
let promise = this._id
|
||||
? api.put(uri.formatApiLink("pool", this._id), detail)
|
||||
: api.post(uri.formatApiLink("pools"), detail);
|
||||
return promise.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
pool: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
});
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
merge(targetId, addAlias) {
|
||||
return api.get(uri.formatApiLink('pool', targetId))
|
||||
.then(response => {
|
||||
return api.post(uri.formatApiLink('pool-merge'), {
|
||||
return api
|
||||
.get(uri.formatApiLink("pool", targetId))
|
||||
.then((response) => {
|
||||
return api.post(uri.formatApiLink("pool-merge"), {
|
||||
removeVersion: this._version,
|
||||
remove: this._id,
|
||||
mergeToVersion: response.version,
|
||||
mergeTo: targetId,
|
||||
});
|
||||
}).then(response => {
|
||||
})
|
||||
.then((response) => {
|
||||
if (!addAlias) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
return api.put(uri.formatApiLink('pool', targetId), {
|
||||
return api.put(uri.formatApiLink("pool", targetId), {
|
||||
version: response.version,
|
||||
names: response.names.concat(this._names),
|
||||
});
|
||||
}).then(response => {
|
||||
})
|
||||
.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
pool: this,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
pool: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete(
|
||||
uri.formatApiLink('pool', this._id),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
pool: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(uri.formatApiLink("pool", this._id), {
|
||||
version: this._version,
|
||||
})
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
pool: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const events = require('../events.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const events = require("../events.js");
|
||||
|
||||
class PoolCategory extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._name = '';
|
||||
this._color = '#000000';
|
||||
this._name = "";
|
||||
this._color = "#000000";
|
||||
this._poolCount = 0;
|
||||
this._isDefault = false;
|
||||
this._origName = null;
|
||||
|
@ -50,7 +50,7 @@ class PoolCategory extends events.EventTarget {
|
|||
}
|
||||
|
||||
save() {
|
||||
const detail = {version: this._version};
|
||||
const detail = { version: this._version };
|
||||
|
||||
if (this.name !== this._origName) {
|
||||
detail.name = this.name;
|
||||
|
@ -63,34 +63,39 @@ class PoolCategory extends events.EventTarget {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let promise = this._origName ?
|
||||
api.put(
|
||||
uri.formatApiLink('pool-category', this._origName),
|
||||
detail) :
|
||||
api.post(uri.formatApiLink('pool-categories'), detail);
|
||||
let promise = this._origName
|
||||
? api.put(
|
||||
uri.formatApiLink("pool-category", this._origName),
|
||||
detail
|
||||
)
|
||||
: api.post(uri.formatApiLink("pool-categories"), detail);
|
||||
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
return promise.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
poolCategory: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
});
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete(
|
||||
uri.formatApiLink('pool-category', this._origName),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
poolCategory: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(uri.formatApiLink("pool-category", this._origName), {
|
||||
version: this._version,
|
||||
})
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
poolCategory: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const PoolCategory = require('./pool_category.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const PoolCategory = require("./pool_category.js");
|
||||
|
||||
class PoolCategoryList extends AbstractList {
|
||||
constructor() {
|
||||
|
@ -11,7 +11,7 @@ class PoolCategoryList extends AbstractList {
|
|||
this._defaultCategory = null;
|
||||
this._origDefaultCategory = null;
|
||||
this._deletedCategories = [];
|
||||
this.addEventListener('remove', e => this._evtCategoryDeleted(e));
|
||||
this.addEventListener("remove", (e) => this._evtCategoryDeleted(e));
|
||||
}
|
||||
|
||||
static fromResponse(response) {
|
||||
|
@ -27,12 +27,16 @@ class PoolCategoryList extends AbstractList {
|
|||
}
|
||||
|
||||
static get() {
|
||||
return api.get(uri.formatApiLink('pool-categories'))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: PoolCategoryList.fromResponse(response.results)}));
|
||||
return api
|
||||
.get(uri.formatApiLink("pool-categories"))
|
||||
.then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
results: PoolCategoryList.fromResponse(
|
||||
response.results
|
||||
),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -57,16 +61,18 @@ class PoolCategoryList extends AbstractList {
|
|||
promises.push(
|
||||
api.put(
|
||||
uri.formatApiLink(
|
||||
'pool-category',
|
||||
"pool-category",
|
||||
this._defaultCategory.name,
|
||||
'default')));
|
||||
"default"
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(response => {
|
||||
this._deletedCategories = [];
|
||||
return Promise.resolve();
|
||||
});
|
||||
return Promise.all(promises).then((response) => {
|
||||
this._deletedCategories = [];
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
_evtCategoryDeleted(e) {
|
||||
|
@ -77,6 +83,6 @@ class PoolCategoryList extends AbstractList {
|
|||
}
|
||||
|
||||
PoolCategoryList._itemClass = PoolCategory;
|
||||
PoolCategoryList._itemName = 'poolCategory';
|
||||
PoolCategoryList._itemName = "poolCategory";
|
||||
|
||||
module.exports = PoolCategoryList;
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Pool = require('./pool.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const Pool = require("./pool.js");
|
||||
|
||||
class PoolList extends AbstractList {
|
||||
static search(text, offset, limit, fields) {
|
||||
return api.get(
|
||||
uri.formatApiLink(
|
||||
'pools', {
|
||||
return api
|
||||
.get(
|
||||
uri.formatApiLink("pools", {
|
||||
query: text,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
fields: fields.join(','),
|
||||
}))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: PoolList.fromResponse(response.results)}));
|
||||
fields: fields.join(","),
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
results: PoolList.fromResponse(response.results),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -42,6 +44,6 @@ class PoolList extends AbstractList {
|
|||
}
|
||||
|
||||
PoolList._itemClass = Pool;
|
||||
PoolList._itemName = 'pool';
|
||||
PoolList._itemName = "pool";
|
||||
|
||||
module.exports = PoolList;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const tags = require('../tags.js');
|
||||
const events = require('../events.js');
|
||||
const TagList = require('./tag_list.js');
|
||||
const NoteList = require('./note_list.js');
|
||||
const CommentList = require('./comment_list.js');
|
||||
const PoolList = require('./pool_list.js');
|
||||
const Pool = require('./pool.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const tags = require("../tags.js");
|
||||
const events = require("../events.js");
|
||||
const TagList = require("./tag_list.js");
|
||||
const NoteList = require("./note_list.js");
|
||||
const CommentList = require("./comment_list.js");
|
||||
const PoolList = require("./pool_list.js");
|
||||
const Pool = require("./pool.js");
|
||||
const misc = require("../util/misc.js");
|
||||
|
||||
class Post extends events.EventTarget {
|
||||
constructor() {
|
||||
|
@ -67,7 +67,7 @@ class Post extends events.EventTarget {
|
|||
}
|
||||
|
||||
get sourceSplit() {
|
||||
return this._source.split('\n');
|
||||
return this._source.split("\n");
|
||||
}
|
||||
|
||||
get canvasWidth() {
|
||||
|
@ -83,11 +83,11 @@ class Post extends events.EventTarget {
|
|||
}
|
||||
|
||||
get newContent() {
|
||||
throw 'Invalid operation';
|
||||
throw "Invalid operation";
|
||||
}
|
||||
|
||||
get newThumbnail() {
|
||||
throw 'Invalid operation';
|
||||
throw "Invalid operation";
|
||||
}
|
||||
|
||||
get flags() {
|
||||
|
@ -99,7 +99,7 @@ class Post extends events.EventTarget {
|
|||
}
|
||||
|
||||
get tagNames() {
|
||||
return this._tags.map(tag => tag.names[0]);
|
||||
return this._tags.map((tag) => tag.names[0]);
|
||||
}
|
||||
|
||||
get notes() {
|
||||
|
@ -174,32 +174,31 @@ class Post extends events.EventTarget {
|
|||
|
||||
static reverseSearch(content) {
|
||||
let apiPromise = api.post(
|
||||
uri.formatApiLink('posts', 'reverse-search'),
|
||||
uri.formatApiLink("posts", "reverse-search"),
|
||||
{},
|
||||
{content: content});
|
||||
let returnedPromise = apiPromise
|
||||
.then(response => {
|
||||
if (response.exactPost) {
|
||||
response.exactPost = Post.fromResponse(response.exactPost);
|
||||
}
|
||||
for (let item of response.similarPosts) {
|
||||
item.post = Post.fromResponse(item.post);
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
{ content: content }
|
||||
);
|
||||
let returnedPromise = apiPromise.then((response) => {
|
||||
if (response.exactPost) {
|
||||
response.exactPost = Post.fromResponse(response.exactPost);
|
||||
}
|
||||
for (let item of response.similarPosts) {
|
||||
item.post = Post.fromResponse(item.post);
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
returnedPromise.abort = () => apiPromise.abort();
|
||||
return returnedPromise;
|
||||
}
|
||||
|
||||
static get(id) {
|
||||
return api.get(uri.formatApiLink('post', id))
|
||||
.then(response => {
|
||||
return Promise.resolve(Post.fromResponse(response));
|
||||
});
|
||||
return api.get(uri.formatApiLink("post", id)).then((response) => {
|
||||
return Promise.resolve(Post.fromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
_savePoolPosts() {
|
||||
const difference = (a, b) => a.filter(post => !b.hasPoolId(post.id));
|
||||
const difference = (a, b) => a.filter((post) => !b.hasPoolId(post.id));
|
||||
|
||||
// find the pools where the post was added or removed
|
||||
const added = difference(this.pools, this._orig._pools);
|
||||
|
@ -209,7 +208,7 @@ class Post extends events.EventTarget {
|
|||
|
||||
// update each pool's list of posts
|
||||
for (let pool of added) {
|
||||
let op = Pool.get(pool.id).then(response => {
|
||||
let op = Pool.get(pool.id).then((response) => {
|
||||
if (!response.posts.hasPostId(this._id)) {
|
||||
response.posts.addById(this._id);
|
||||
return response.save();
|
||||
|
@ -221,7 +220,7 @@ class Post extends events.EventTarget {
|
|||
}
|
||||
|
||||
for (let pool of removed) {
|
||||
let op = Pool.get(pool.id).then(response => {
|
||||
let op = Pool.get(pool.id).then((response) => {
|
||||
if (response.posts.hasPostId(this._id)) {
|
||||
response.posts.removeById(this._id);
|
||||
return response.save();
|
||||
|
@ -237,7 +236,7 @@ class Post extends events.EventTarget {
|
|||
|
||||
save(anonymous) {
|
||||
const files = {};
|
||||
const detail = {version: this._version};
|
||||
const detail = { version: this._version };
|
||||
|
||||
// send only changed fields to avoid user privilege violation
|
||||
if (anonymous === true) {
|
||||
|
@ -250,14 +249,14 @@ class Post extends events.EventTarget {
|
|||
detail.flags = this._flags;
|
||||
}
|
||||
if (misc.arraysDiffer(this._tags, this._orig._tags)) {
|
||||
detail.tags = this._tags.map(tag => tag.names[0]);
|
||||
detail.tags = this._tags.map((tag) => tag.names[0]);
|
||||
}
|
||||
if (misc.arraysDiffer(this._relations, this._orig._relations)) {
|
||||
detail.relations = this._relations;
|
||||
}
|
||||
if (misc.arraysDiffer(this._notes, this._orig._notes)) {
|
||||
detail.notes = this._notes.map(note => ({
|
||||
polygon: note.polygon.map(point => [point.x, point.y]),
|
||||
detail.notes = this._notes.map((note) => ({
|
||||
polygon: note.polygon.map((point) => [point.x, point.y]),
|
||||
text: note.text,
|
||||
}));
|
||||
}
|
||||
|
@ -271,154 +270,187 @@ class Post extends events.EventTarget {
|
|||
detail.source = this._source;
|
||||
}
|
||||
|
||||
let apiPromise = this._id ?
|
||||
api.put(uri.formatApiLink('post', this.id), detail, files) :
|
||||
api.post(uri.formatApiLink('posts'), detail, files);
|
||||
let apiPromise = this._id
|
||||
? api.put(uri.formatApiLink("post", this.id), detail, files)
|
||||
: api.post(uri.formatApiLink("posts"), detail, files);
|
||||
|
||||
return apiPromise.then(response => {
|
||||
if (misc.arraysDiffer(this._pools, this._orig._pools)) {
|
||||
return this._savePoolPosts()
|
||||
.then(() => Promise.resolve(response));
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
}).then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('change', {detail: {post: this}}));
|
||||
if (this._newContent) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('changeContent', {detail: {post: this}}));
|
||||
}
|
||||
if (this._newThumbnail) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('changeThumbnail', {detail: {post: this}}));
|
||||
}
|
||||
return apiPromise
|
||||
.then((response) => {
|
||||
if (misc.arraysDiffer(this._pools, this._orig._pools)) {
|
||||
return this._savePoolPosts().then(() =>
|
||||
Promise.resolve(response)
|
||||
);
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
})
|
||||
.then(
|
||||
(response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", { detail: { post: this } })
|
||||
);
|
||||
if (this._newContent) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeContent", {
|
||||
detail: { post: this },
|
||||
})
|
||||
);
|
||||
}
|
||||
if (this._newThumbnail) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeThumbnail", {
|
||||
detail: { post: this },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}, error => {
|
||||
if (error.response &&
|
||||
error.response.name === 'PostAlreadyUploadedError') {
|
||||
error.message =
|
||||
`Post already uploaded (@${error.response.otherPostId})`;
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
(error) => {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.name === "PostAlreadyUploadedError"
|
||||
) {
|
||||
error.message = `Post already uploaded (@${error.response.otherPostId})`;
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
feature() {
|
||||
return api.post(
|
||||
uri.formatApiLink('featured-post'),
|
||||
{id: this._id})
|
||||
.then(response => {
|
||||
return api
|
||||
.post(uri.formatApiLink("featured-post"), { id: this._id })
|
||||
.then((response) => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete(
|
||||
uri.formatApiLink('post', this.id),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(uri.formatApiLink("post", this.id), {
|
||||
version: this._version,
|
||||
})
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
merge(targetId, useOldContent) {
|
||||
return api.get(uri.formatApiLink('post', targetId))
|
||||
.then(response => {
|
||||
return api.post(uri.formatApiLink('post-merge'), {
|
||||
return api
|
||||
.get(uri.formatApiLink("post", targetId))
|
||||
.then((response) => {
|
||||
return api.post(uri.formatApiLink("post-merge"), {
|
||||
removeVersion: this._version,
|
||||
remove: this._id,
|
||||
mergeToVersion: response.version,
|
||||
mergeTo: targetId,
|
||||
replaceContent: useOldContent,
|
||||
});
|
||||
}).then(response => {
|
||||
})
|
||||
.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
setScore(score) {
|
||||
return api.put(
|
||||
uri.formatApiLink('post', this.id, 'score'),
|
||||
{score: score})
|
||||
.then(response => {
|
||||
return api
|
||||
.put(uri.formatApiLink("post", this.id, "score"), { score: score })
|
||||
.then((response) => {
|
||||
const prevFavorite = this._ownFavorite;
|
||||
this._updateFromResponse(response);
|
||||
if (this._ownFavorite !== prevFavorite) {
|
||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeFavorite", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeScore", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
addToFavorites() {
|
||||
return api.post(uri.formatApiLink('post', this.id, 'favorite'))
|
||||
.then(response => {
|
||||
return api
|
||||
.post(uri.formatApiLink("post", this.id, "favorite"))
|
||||
.then((response) => {
|
||||
const prevScore = this._ownScore;
|
||||
this._updateFromResponse(response);
|
||||
if (this._ownScore !== prevScore) {
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeScore", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeFavorite", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
removeFromFavorites() {
|
||||
return api.delete(uri.formatApiLink('post', this.id, 'favorite'))
|
||||
.then(response => {
|
||||
return api
|
||||
.delete(uri.formatApiLink("post", this.id, "favorite"))
|
||||
.then((response) => {
|
||||
const prevScore = this._ownScore;
|
||||
this._updateFromResponse(response);
|
||||
if (this._ownScore !== prevScore) {
|
||||
this.dispatchEvent(new CustomEvent('changeScore', {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeScore", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("changeFavorite", {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('changeFavorite', {
|
||||
detail: {
|
||||
post: this,
|
||||
},
|
||||
}));
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
mutateContentUrl() {
|
||||
this._contentUrl =
|
||||
this._orig._contentUrl +
|
||||
'?bypass-cache=' +
|
||||
Math.round(Math.random() * 1000);
|
||||
this._orig._contentUrl +
|
||||
"?bypass-cache=" +
|
||||
Math.round(Math.random() * 1000);
|
||||
}
|
||||
|
||||
_updateFromResponse(response) {
|
||||
|
@ -431,15 +463,18 @@ class Post extends events.EventTarget {
|
|||
_user: response.user,
|
||||
_safety: response.safety,
|
||||
_contentUrl: response.contentUrl,
|
||||
_fullContentUrl: new URL(response.contentUrl, document.getElementsByTagName('base')[0].href).href,
|
||||
_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,
|
||||
|
||||
_flags: [...response.flags || []],
|
||||
_relations: [...response.relations || []],
|
||||
_flags: [...(response.flags || [])],
|
||||
_relations: [...(response.relations || [])],
|
||||
|
||||
_score: response.score,
|
||||
_commentCount: response.commentCount,
|
||||
|
|
|
@ -1,35 +1,37 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const settings = require('../models/settings.js');
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Post = require('./post.js');
|
||||
const settings = require("../models/settings.js");
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const Post = require("./post.js");
|
||||
|
||||
class PostList extends AbstractList {
|
||||
static getAround(id, searchQuery) {
|
||||
return api.get(
|
||||
uri.formatApiLink(
|
||||
'post', id, 'around', {
|
||||
query: PostList._decorateSearchQuery(searchQuery || ''),
|
||||
fields: 'id',
|
||||
}));
|
||||
uri.formatApiLink("post", id, "around", {
|
||||
query: PostList._decorateSearchQuery(searchQuery || ""),
|
||||
fields: "id",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
static search(text, offset, limit, fields) {
|
||||
return api.get(
|
||||
uri.formatApiLink(
|
||||
'posts', {
|
||||
query: PostList._decorateSearchQuery(text || ''),
|
||||
return api
|
||||
.get(
|
||||
uri.formatApiLink("posts", {
|
||||
query: PostList._decorateSearchQuery(text || ""),
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
fields: fields.join(','),
|
||||
}))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: PostList.fromResponse(response.results)}));
|
||||
fields: fields.join(","),
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
results: PostList.fromResponse(response.results),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -43,7 +45,7 @@ class PostList extends AbstractList {
|
|||
}
|
||||
}
|
||||
if (disabledSafety.length) {
|
||||
text = `-rating:${disabledSafety.join(',')} ${text}`;
|
||||
text = `-rating:${disabledSafety.join(",")} ${text}`;
|
||||
}
|
||||
}
|
||||
return text.trim();
|
||||
|
@ -63,7 +65,7 @@ class PostList extends AbstractList {
|
|||
return;
|
||||
}
|
||||
|
||||
let post = Post.fromResponse({id: id});
|
||||
let post = Post.fromResponse({ id: id });
|
||||
this.add(post);
|
||||
}
|
||||
|
||||
|
@ -77,6 +79,6 @@ class PostList extends AbstractList {
|
|||
}
|
||||
|
||||
PostList._itemClass = Post;
|
||||
PostList._itemName = 'post';
|
||||
PostList._itemName = "post";
|
||||
|
||||
module.exports = PostList;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const events = require("../events.js");
|
||||
|
||||
const defaultSettings = {
|
||||
listPosts: {
|
||||
|
@ -12,7 +12,7 @@ const defaultSettings = {
|
|||
endlessScroll: false,
|
||||
keyboardShortcuts: true,
|
||||
transparencyGrid: true,
|
||||
fitMode: 'fit-both',
|
||||
fitMode: "fit-both",
|
||||
tagSuggestions: true,
|
||||
autoplayVideos: false,
|
||||
postsPerPage: 42,
|
||||
|
@ -28,7 +28,7 @@ class Settings extends events.EventTarget {
|
|||
_getFromLocalStorage() {
|
||||
let ret = Object.assign({}, defaultSettings);
|
||||
try {
|
||||
Object.assign(ret, JSON.parse(localStorage.getItem('settings')));
|
||||
Object.assign(ret, JSON.parse(localStorage.getItem("settings")));
|
||||
} catch (e) {
|
||||
// continue regardless of error
|
||||
}
|
||||
|
@ -37,14 +37,16 @@ class Settings extends events.EventTarget {
|
|||
|
||||
save(newSettings, silent) {
|
||||
newSettings = Object.assign(this.cache, newSettings);
|
||||
localStorage.setItem('settings', JSON.stringify(newSettings));
|
||||
localStorage.setItem("settings", JSON.stringify(newSettings));
|
||||
this.cache = this._getFromLocalStorage();
|
||||
if (silent !== true) {
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
settings: this.cache,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
settings: this.cache,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const events = require('../events.js');
|
||||
const api = require("../api.js");
|
||||
const events = require("../events.js");
|
||||
|
||||
class Snapshot extends events.EventTarget {
|
||||
constructor() {
|
||||
|
|
|
@ -1,24 +1,31 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Snapshot = require('./snapshot.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
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}))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: SnapshotList.fromResponse(response.results)}));
|
||||
return api
|
||||
.get(
|
||||
uri.formatApiLink("snapshots", {
|
||||
query: text,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
results: SnapshotList.fromResponse(response.results),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SnapshotList._itemClass = Snapshot;
|
||||
SnapshotList._itemName = 'snapshot';
|
||||
SnapshotList._itemName = "snapshot";
|
||||
|
||||
module.exports = SnapshotList;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const events = require('../events.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const events = require("../events.js");
|
||||
const misc = require("../util/misc.js");
|
||||
|
||||
class Tag extends events.EventTarget {
|
||||
constructor() {
|
||||
const TagList = require('./tag_list.js');
|
||||
const TagList = require("./tag_list.js");
|
||||
|
||||
super();
|
||||
this._orig = {};
|
||||
|
@ -71,14 +71,13 @@ class Tag extends events.EventTarget {
|
|||
}
|
||||
|
||||
static get(name) {
|
||||
return api.get(uri.formatApiLink('tag', name))
|
||||
.then(response => {
|
||||
return Promise.resolve(Tag.fromResponse(response));
|
||||
});
|
||||
return api.get(uri.formatApiLink("tag", name)).then((response) => {
|
||||
return Promise.resolve(Tag.fromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
const detail = {version: this._version};
|
||||
const detail = { version: this._version };
|
||||
|
||||
// send only changed fields to avoid user privilege violation
|
||||
if (misc.arraysDiffer(this._names, this._orig._names, true)) {
|
||||
|
@ -92,66 +91,77 @@ class Tag extends events.EventTarget {
|
|||
}
|
||||
if (misc.arraysDiffer(this._implications, this._orig._implications)) {
|
||||
detail.implications = this._implications.map(
|
||||
relation => relation.names[0]);
|
||||
(relation) => relation.names[0]
|
||||
);
|
||||
}
|
||||
if (misc.arraysDiffer(this._suggestions, this._orig._suggestions)) {
|
||||
detail.suggestions = this._suggestions.map(
|
||||
relation => relation.names[0]);
|
||||
(relation) => relation.names[0]
|
||||
);
|
||||
}
|
||||
|
||||
let promise = this._origName ?
|
||||
api.put(uri.formatApiLink('tag', this._origName), detail) :
|
||||
api.post(uri.formatApiLink('tags'), detail);
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
let promise = this._origName
|
||||
? api.put(uri.formatApiLink("tag", this._origName), detail)
|
||||
: api.post(uri.formatApiLink("tags"), detail);
|
||||
return promise.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
});
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
merge(targetName, addAlias) {
|
||||
return api.get(uri.formatApiLink('tag', targetName))
|
||||
.then(response => {
|
||||
return api.post(uri.formatApiLink('tag-merge'), {
|
||||
return api
|
||||
.get(uri.formatApiLink("tag", targetName))
|
||||
.then((response) => {
|
||||
return api.post(uri.formatApiLink("tag-merge"), {
|
||||
removeVersion: this._version,
|
||||
remove: this._origName,
|
||||
mergeToVersion: response.version,
|
||||
mergeTo: targetName,
|
||||
});
|
||||
}).then(response => {
|
||||
})
|
||||
.then((response) => {
|
||||
if (!addAlias) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
return api.put(uri.formatApiLink('tag', targetName), {
|
||||
return api.put(uri.formatApiLink("tag", targetName), {
|
||||
version: response.version,
|
||||
names: response.names.concat(this._names),
|
||||
});
|
||||
}).then(response => {
|
||||
})
|
||||
.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete(
|
||||
uri.formatApiLink('tag', this._origName),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(uri.formatApiLink("tag", this._origName), {
|
||||
version: this._version,
|
||||
})
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
tag: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const events = require('../events.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const events = require("../events.js");
|
||||
|
||||
class TagCategory extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._name = '';
|
||||
this._color = '#000000';
|
||||
this._name = "";
|
||||
this._color = "#000000";
|
||||
this._tagCount = 0;
|
||||
this._isDefault = false;
|
||||
this._origName = null;
|
||||
|
@ -50,7 +50,7 @@ class TagCategory extends events.EventTarget {
|
|||
}
|
||||
|
||||
save() {
|
||||
const detail = {version: this._version};
|
||||
const detail = { version: this._version };
|
||||
|
||||
if (this.name !== this._origName) {
|
||||
detail.name = this.name;
|
||||
|
@ -63,34 +63,39 @@ class TagCategory extends events.EventTarget {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let promise = this._origName ?
|
||||
api.put(
|
||||
uri.formatApiLink('tag-category', this._origName),
|
||||
detail) :
|
||||
api.post(uri.formatApiLink('tag-categories'), detail);
|
||||
let promise = this._origName
|
||||
? api.put(
|
||||
uri.formatApiLink("tag-category", this._origName),
|
||||
detail
|
||||
)
|
||||
: api.post(uri.formatApiLink("tag-categories"), detail);
|
||||
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
return promise.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
tagCategory: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
});
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete(
|
||||
uri.formatApiLink('tag-category', this._origName),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
tagCategory: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(uri.formatApiLink("tag-category", this._origName), {
|
||||
version: this._version,
|
||||
})
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
tagCategory: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const TagCategory = require('./tag_category.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const TagCategory = require("./tag_category.js");
|
||||
|
||||
class TagCategoryList extends AbstractList {
|
||||
constructor() {
|
||||
|
@ -11,7 +11,7 @@ class TagCategoryList extends AbstractList {
|
|||
this._defaultCategory = null;
|
||||
this._origDefaultCategory = null;
|
||||
this._deletedCategories = [];
|
||||
this.addEventListener('remove', e => this._evtCategoryDeleted(e));
|
||||
this.addEventListener("remove", (e) => this._evtCategoryDeleted(e));
|
||||
}
|
||||
|
||||
static fromResponse(response) {
|
||||
|
@ -27,12 +27,16 @@ class TagCategoryList extends AbstractList {
|
|||
}
|
||||
|
||||
static get() {
|
||||
return api.get(uri.formatApiLink('tag-categories'))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: TagCategoryList.fromResponse(response.results)}));
|
||||
return api
|
||||
.get(uri.formatApiLink("tag-categories"))
|
||||
.then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
results: TagCategoryList.fromResponse(
|
||||
response.results
|
||||
),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -57,16 +61,18 @@ class TagCategoryList extends AbstractList {
|
|||
promises.push(
|
||||
api.put(
|
||||
uri.formatApiLink(
|
||||
'tag-category',
|
||||
"tag-category",
|
||||
this._defaultCategory.name,
|
||||
'default')));
|
||||
"default"
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(response => {
|
||||
this._deletedCategories = [];
|
||||
return Promise.resolve();
|
||||
});
|
||||
return Promise.all(promises).then((response) => {
|
||||
this._deletedCategories = [];
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
_evtCategoryDeleted(e) {
|
||||
|
@ -77,6 +83,6 @@ class TagCategoryList extends AbstractList {
|
|||
}
|
||||
|
||||
TagCategoryList._itemClass = TagCategory;
|
||||
TagCategoryList._itemName = 'tagCategory';
|
||||
TagCategoryList._itemName = "tagCategory";
|
||||
|
||||
module.exports = TagCategoryList;
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const Tag = require('./tag.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
const Tag = require("./tag.js");
|
||||
|
||||
class TagList extends AbstractList {
|
||||
static search(text, offset, limit, fields) {
|
||||
return api.get(
|
||||
uri.formatApiLink(
|
||||
'tags', {
|
||||
return api
|
||||
.get(
|
||||
uri.formatApiLink("tags", {
|
||||
query: text,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
fields: fields.join(','),
|
||||
}))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: TagList.fromResponse(response.results)}));
|
||||
fields: fields.join(","),
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
results: TagList.fromResponse(response.results),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,10 +47,12 @@ class TagList extends AbstractList {
|
|||
this.add(tag);
|
||||
|
||||
if (addImplications !== false) {
|
||||
return Tag.get(tagName).then(actualTag => {
|
||||
return Tag.get(tagName).then((actualTag) => {
|
||||
return Promise.all(
|
||||
actualTag.implications.map(
|
||||
relation => this.addByName(relation.names[0], true)));
|
||||
actualTag.implications.map((relation) =>
|
||||
this.addByName(relation.names[0], true)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -67,6 +71,6 @@ class TagList extends AbstractList {
|
|||
}
|
||||
|
||||
TagList._itemClass = Tag;
|
||||
TagList._itemName = 'tag';
|
||||
TagList._itemName = "tag";
|
||||
|
||||
module.exports = TagList;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const api = require('../api.js');
|
||||
const events = require("../events.js");
|
||||
const api = require("../api.js");
|
||||
|
||||
class TopNavigationItem {
|
||||
constructor(accessKey, title, url, available, imageUrl) {
|
||||
|
@ -44,18 +44,20 @@ class TopNavigation extends events.EventTarget {
|
|||
|
||||
activate(key) {
|
||||
this.activeItem = null;
|
||||
this.dispatchEvent(new CustomEvent('activate', {
|
||||
detail: {
|
||||
key: key,
|
||||
item: key ? this.get(key) : null,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("activate", {
|
||||
detail: {
|
||||
key: key,
|
||||
item: key ? this.get(key) : null,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
api.fetchConfig().then(() => {
|
||||
document.oldTitle = null;
|
||||
document.title = api.getName() + (title ? (' – ' + title) : '');
|
||||
document.title = api.getName() + (title ? " – " + title : "");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -76,24 +78,22 @@ class TopNavigation extends events.EventTarget {
|
|||
|
||||
function _makeTopNavigation() {
|
||||
const ret = new TopNavigation();
|
||||
ret.add('home', new TopNavigationItem('H', 'Home', ''));
|
||||
ret.add('posts', new TopNavigationItem('P', 'Posts', 'posts'));
|
||||
ret.add('upload', new TopNavigationItem('U', 'Upload', 'upload'));
|
||||
ret.add('comments', new TopNavigationItem('C', 'Comments', 'comments'));
|
||||
ret.add('tags', new TopNavigationItem('T', 'Tags', 'tags'));
|
||||
ret.add('pools', new TopNavigationItem('O', 'Pools', 'pools'));
|
||||
ret.add('users', new TopNavigationItem('S', 'Users', 'users'));
|
||||
ret.add('account', new TopNavigationItem('A', 'Account', 'user/{me}'));
|
||||
ret.add('register', new TopNavigationItem('R', 'Register', 'register'));
|
||||
ret.add('login', new TopNavigationItem('L', 'Log in', 'login'));
|
||||
ret.add('logout', new TopNavigationItem('O', 'Logout', 'logout'));
|
||||
ret.add('help', new TopNavigationItem('E', 'Help', 'help'));
|
||||
ret.add("home", new TopNavigationItem("H", "Home", ""));
|
||||
ret.add("posts", new TopNavigationItem("P", "Posts", "posts"));
|
||||
ret.add("upload", new TopNavigationItem("U", "Upload", "upload"));
|
||||
ret.add("comments", new TopNavigationItem("C", "Comments", "comments"));
|
||||
ret.add("tags", new TopNavigationItem("T", "Tags", "tags"));
|
||||
ret.add("pools", new TopNavigationItem("O", "Pools", "pools"));
|
||||
ret.add("users", new TopNavigationItem("S", "Users", "users"));
|
||||
ret.add("account", new TopNavigationItem("A", "Account", "user/{me}"));
|
||||
ret.add("register", new TopNavigationItem("R", "Register", "register"));
|
||||
ret.add("login", new TopNavigationItem("L", "Log in", "login"));
|
||||
ret.add("logout", new TopNavigationItem("O", "Logout", "logout"));
|
||||
ret.add("help", new TopNavigationItem("E", "Help", "help"));
|
||||
ret.add(
|
||||
'settings',
|
||||
new TopNavigationItem(
|
||||
null,
|
||||
'<i class=\'fa fa-cog\'></i>',
|
||||
'settings'));
|
||||
"settings",
|
||||
new TopNavigationItem(null, "<i class='fa fa-cog'></i>", "settings")
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const events = require('../events.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const events = require("../events.js");
|
||||
|
||||
class User extends events.EventTarget {
|
||||
constructor() {
|
||||
|
@ -64,11 +64,11 @@ class User extends events.EventTarget {
|
|||
}
|
||||
|
||||
get avatarContent() {
|
||||
throw 'Invalid operation';
|
||||
throw "Invalid operation";
|
||||
}
|
||||
|
||||
get password() {
|
||||
throw 'Invalid operation';
|
||||
throw "Invalid operation";
|
||||
}
|
||||
|
||||
set name(value) {
|
||||
|
@ -102,15 +102,14 @@ class User extends events.EventTarget {
|
|||
}
|
||||
|
||||
static get(name) {
|
||||
return api.get(uri.formatApiLink('user', name))
|
||||
.then(response => {
|
||||
return Promise.resolve(User.fromResponse(response));
|
||||
});
|
||||
return api.get(uri.formatApiLink("user", name)).then((response) => {
|
||||
return Promise.resolve(User.fromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
const files = [];
|
||||
const detail = {version: this._version};
|
||||
const detail = { version: this._version };
|
||||
const transient = this._orig._name;
|
||||
|
||||
if (this._name !== this._orig._name) {
|
||||
|
@ -133,33 +132,40 @@ class User extends events.EventTarget {
|
|||
detail.password = this._password;
|
||||
}
|
||||
|
||||
let promise = this._orig._name ?
|
||||
api.put(
|
||||
uri.formatApiLink('user', this._orig._name), detail, files) :
|
||||
api.post(uri.formatApiLink('users'), detail, files);
|
||||
let promise = this._orig._name
|
||||
? api.put(
|
||||
uri.formatApiLink("user", this._orig._name),
|
||||
detail,
|
||||
files
|
||||
)
|
||||
: api.post(uri.formatApiLink("users"), detail, files);
|
||||
|
||||
return promise
|
||||
.then(response => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
return promise.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
user: this,
|
||||
},
|
||||
}));
|
||||
return Promise.resolve();
|
||||
});
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
return api.delete(
|
||||
uri.formatApiLink('user', this._orig._name),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
user: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(uri.formatApiLink("user", this._orig._name), {
|
||||
version: this._version,
|
||||
})
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
user: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const AbstractList = require('./abstract_list.js');
|
||||
const User = require('./user.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const AbstractList = require("./abstract_list.js");
|
||||
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}))
|
||||
.then(response => {
|
||||
return Promise.resolve(Object.assign(
|
||||
{},
|
||||
response,
|
||||
{results: UserList.fromResponse(response.results)}));
|
||||
return api
|
||||
.get(
|
||||
uri.formatApiLink("users", {
|
||||
query: text,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
return Promise.resolve(
|
||||
Object.assign({}, response, {
|
||||
results: UserList.fromResponse(response.results),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
UserList._itemClass = User;
|
||||
UserList._itemName = 'user';
|
||||
UserList._itemName = "user";
|
||||
|
||||
module.exports = UserList;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const events = require('../events.js');
|
||||
const api = require("../api.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const events = require("../events.js");
|
||||
|
||||
class UserToken extends events.EventTarget {
|
||||
constructor() {
|
||||
|
@ -48,12 +48,12 @@ class UserToken extends events.EventTarget {
|
|||
}
|
||||
|
||||
static fromResponse(response) {
|
||||
if (typeof response.results !== 'undefined') {
|
||||
if (typeof response.results !== "undefined") {
|
||||
let tokenList = [];
|
||||
for (let responseToken of response.results) {
|
||||
const token = new UserToken();
|
||||
token._updateFromResponse(responseToken);
|
||||
tokenList.push(token)
|
||||
tokenList.push(token);
|
||||
}
|
||||
return tokenList;
|
||||
} else {
|
||||
|
@ -64,15 +64,16 @@ class UserToken extends events.EventTarget {
|
|||
}
|
||||
|
||||
static get(userName) {
|
||||
return api.get(uri.formatApiLink('user-tokens', userName))
|
||||
.then(response => {
|
||||
return api
|
||||
.get(uri.formatApiLink("user-tokens", userName))
|
||||
.then((response) => {
|
||||
return Promise.resolve(UserToken.fromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
static create(userName, note, expirationTime) {
|
||||
let userTokenRequest = {
|
||||
enabled: true
|
||||
enabled: true,
|
||||
};
|
||||
if (note) {
|
||||
userTokenRequest.note = note;
|
||||
|
@ -80,43 +81,54 @@ class UserToken extends events.EventTarget {
|
|||
if (expirationTime) {
|
||||
userTokenRequest.expirationTime = expirationTime;
|
||||
}
|
||||
return api.post(uri.formatApiLink('user-token', userName), userTokenRequest)
|
||||
.then(response => {
|
||||
return Promise.resolve(UserToken.fromResponse(response))
|
||||
return api
|
||||
.post(uri.formatApiLink("user-token", userName), userTokenRequest)
|
||||
.then((response) => {
|
||||
return Promise.resolve(UserToken.fromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
save(userName) {
|
||||
const detail = {version: this._version};
|
||||
const detail = { version: this._version };
|
||||
|
||||
if (this._note !== this._orig._note) {
|
||||
detail.note = this._note;
|
||||
}
|
||||
|
||||
return api.put(
|
||||
uri.formatApiLink('user-token', userName, this._orig._token),
|
||||
detail)
|
||||
.then(response => {
|
||||
return api
|
||||
.put(
|
||||
uri.formatApiLink("user-token", userName, this._orig._token),
|
||||
detail
|
||||
)
|
||||
.then((response) => {
|
||||
this._updateFromResponse(response);
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
userToken: this,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
userToken: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve(this);
|
||||
});
|
||||
}
|
||||
|
||||
delete(userName) {
|
||||
return api.delete(
|
||||
uri.formatApiLink('user-token', userName, this._orig._token),
|
||||
{version: this._version})
|
||||
.then(response => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
userToken: this,
|
||||
},
|
||||
}));
|
||||
return api
|
||||
.delete(
|
||||
uri.formatApiLink("user-token", userName, this._orig._token),
|
||||
{
|
||||
version: this._version,
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("delete", {
|
||||
detail: {
|
||||
userToken: this,
|
||||
},
|
||||
})
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const misc = require('./util/misc.js');
|
||||
const PoolCategoryList = require('./models/pool_category_list.js');
|
||||
const misc = require("./util/misc.js");
|
||||
const PoolCategoryList = require("./models/pool_category_list.js");
|
||||
|
||||
let _stylesheet = null;
|
||||
|
||||
function refreshCategoryColorMap() {
|
||||
return PoolCategoryList.get().then(response => {
|
||||
return PoolCategoryList.get().then((response) => {
|
||||
if (_stylesheet) {
|
||||
document.head.removeChild(_stylesheet);
|
||||
}
|
||||
_stylesheet = document.createElement('style');
|
||||
_stylesheet = document.createElement("style");
|
||||
document.head.appendChild(_stylesheet);
|
||||
for (let category of response.results) {
|
||||
const ruleName = misc.makeCssName(category.name, 'pool');
|
||||
const ruleName = misc.makeCssName(category.name, "pool");
|
||||
_stylesheet.sheet.insertRule(
|
||||
`.${ruleName} { color: ${category.color} }`,
|
||||
_stylesheet.sheet.cssRules.length);
|
||||
_stylesheet.sheet.cssRules.length
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
// modified page.js by visionmedia
|
||||
// - changed regexes to components
|
||||
|
@ -10,13 +10,17 @@
|
|||
// - rename .save() to .replaceState()
|
||||
// - offer .url
|
||||
|
||||
const clickEvent = document.ontouchstart ? 'touchstart' : 'click';
|
||||
const uri = require('./util/uri.js');
|
||||
const clickEvent = document.ontouchstart ? "touchstart" : "click";
|
||||
const uri = require("./util/uri.js");
|
||||
let location = window.history.location || window.location;
|
||||
|
||||
function _getOrigin() {
|
||||
return location.protocol + '//' + location.hostname
|
||||
+ (location.port ? (':' + location.port) : '');
|
||||
return (
|
||||
location.protocol +
|
||||
"//" +
|
||||
location.hostname +
|
||||
(location.port ? ":" + location.port : "")
|
||||
);
|
||||
}
|
||||
|
||||
function _isSameOrigin(href) {
|
||||
|
@ -24,15 +28,16 @@ function _isSameOrigin(href) {
|
|||
}
|
||||
|
||||
function _getBaseHref() {
|
||||
const bases = document.getElementsByTagName('base');
|
||||
return bases.length > 0 ?
|
||||
bases[0].href.replace(_getOrigin(), '').replace(/\/+$/, '') : '';
|
||||
const bases = document.getElementsByTagName("base");
|
||||
return bases.length > 0
|
||||
? bases[0].href.replace(_getOrigin(), "").replace(/\/+$/, "")
|
||||
: "";
|
||||
}
|
||||
|
||||
class Context {
|
||||
constructor(path, state) {
|
||||
const base = _getBaseHref();
|
||||
path = path.indexOf('/') !== 0 ? '/' + path : path;
|
||||
path = path.indexOf("/") !== 0 ? "/" + path : path;
|
||||
path = path.indexOf(base) !== 0 ? base + path : path;
|
||||
|
||||
this.canonicalPath = path;
|
||||
|
@ -55,7 +60,7 @@ class Context {
|
|||
|
||||
class Route {
|
||||
constructor(path) {
|
||||
this.method = 'GET';
|
||||
this.method = "GET";
|
||||
this.path = path;
|
||||
|
||||
this.parameterNames = [];
|
||||
|
@ -64,16 +69,17 @@ class Route {
|
|||
} else {
|
||||
let parts = [];
|
||||
for (let component of this.path) {
|
||||
if (component[0] === ':') {
|
||||
parts.push('([^/]+)');
|
||||
if (component[0] === ":") {
|
||||
parts.push("([^/]+)");
|
||||
this.parameterNames.push(component.substr(1));
|
||||
} else { // assert [a-z]+
|
||||
} else {
|
||||
// assert [a-z]+
|
||||
parts.push(component);
|
||||
}
|
||||
}
|
||||
let regexString = '^/' + parts.join('/');
|
||||
regexString += '(?:/*|/((?:(?:[a-z]+=[^/]+);)*(?:[a-z]+=[^/]+)))$';
|
||||
this.parameterNames.push('variable');
|
||||
let regexString = "^/" + parts.join("/");
|
||||
regexString += "(?:/*|/((?:(?:[a-z]+=[^/]+);)*(?:[a-z]+=[^/]+)))$";
|
||||
this.parameterNames.push("variable");
|
||||
this.regex = new RegExp(regexString);
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +94,7 @@ class Route {
|
|||
}
|
||||
|
||||
match(path, parameters) {
|
||||
const qsIndex = path.indexOf('?');
|
||||
const qsIndex = path.indexOf("?");
|
||||
const pathname = ~qsIndex ? path.slice(0, qsIndex) : path;
|
||||
const match = this.regex.exec(pathname);
|
||||
|
||||
|
@ -104,8 +110,8 @@ class Route {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (name === 'variable') {
|
||||
for (let word of (value || '').split(/;/)) {
|
||||
if (name === "variable") {
|
||||
for (let word of (value || "").split(/;/)) {
|
||||
const [key, subvalue] = word.split(/=/, 2);
|
||||
parameters[key] = uri.unescapeParam(subvalue);
|
||||
}
|
||||
|
@ -148,7 +154,7 @@ class Router {
|
|||
this._running = true;
|
||||
this._onPopState = _onPopState(this);
|
||||
this._onClick = _onClick(this);
|
||||
window.addEventListener('popstate', this._onPopState, false);
|
||||
window.addEventListener("popstate", this._onPopState, false);
|
||||
document.addEventListener(clickEvent, this._onClick, false);
|
||||
const url = location.pathname + location.search + location.hash;
|
||||
return this.replace(url, history.state, true);
|
||||
|
@ -160,7 +166,7 @@ class Router {
|
|||
}
|
||||
this._running = false;
|
||||
document.removeEventListener(clickEvent, this._onClick, false);
|
||||
window.removeEventListener('popstate', this._onPopState, false);
|
||||
window.removeEventListener("popstate", this._onPopState, false);
|
||||
}
|
||||
|
||||
showNoDispatch(path, state) {
|
||||
|
@ -199,11 +205,11 @@ class Router {
|
|||
middle();
|
||||
next();
|
||||
};
|
||||
const callChain = (this.ctx ? this._exits : [])
|
||||
.concat(
|
||||
[swap],
|
||||
this._callbacks,
|
||||
[this._unhandled, (ctx, next) => {}]);
|
||||
const callChain = (this.ctx ? this._exits : []).concat(
|
||||
[swap],
|
||||
this._callbacks,
|
||||
[this._unhandled, (ctx, next) => {}]
|
||||
);
|
||||
|
||||
let i = 0;
|
||||
let fn = () => {
|
||||
|
@ -226,20 +232,18 @@ class Router {
|
|||
}
|
||||
}
|
||||
|
||||
const _onPopState = router => {
|
||||
const _onPopState = (router) => {
|
||||
let loaded = false;
|
||||
if (document.readyState === 'complete') {
|
||||
if (document.readyState === "complete") {
|
||||
loaded = true;
|
||||
} else {
|
||||
window.addEventListener(
|
||||
'load',
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
loaded = true;
|
||||
}, 0);
|
||||
});
|
||||
window.addEventListener("load", () => {
|
||||
setTimeout(() => {
|
||||
loaded = true;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
return e => {
|
||||
return (e) => {
|
||||
if (!loaded) {
|
||||
return;
|
||||
}
|
||||
|
@ -247,16 +251,13 @@ const _onPopState = router => {
|
|||
const path = e.state.path;
|
||||
router.replace(path, e.state, true);
|
||||
} else {
|
||||
router.show(
|
||||
location.pathname + location.hash,
|
||||
undefined,
|
||||
false);
|
||||
router.show(location.pathname + location.hash, undefined, false);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const _onClick = router => {
|
||||
return e => {
|
||||
const _onClick = (router) => {
|
||||
return (e) => {
|
||||
if (1 !== _which(e)) {
|
||||
return;
|
||||
}
|
||||
|
@ -268,23 +269,25 @@ const _onClick = router => {
|
|||
}
|
||||
|
||||
let el = e.path ? e.path[0] : e.target;
|
||||
while (el && el.nodeName !== 'A') {
|
||||
while (el && el.nodeName !== "A") {
|
||||
el = el.parentNode;
|
||||
}
|
||||
if (!el || el.nodeName !== 'A') {
|
||||
if (!el || el.nodeName !== "A") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.hasAttribute('download') ||
|
||||
el.getAttribute('rel') === 'external') {
|
||||
if (
|
||||
el.hasAttribute("download") ||
|
||||
el.getAttribute("rel") === "external"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const link = el.getAttribute('href');
|
||||
if (el.pathname === location.pathname && (el.hash || '#' === link)) {
|
||||
const link = el.getAttribute("href");
|
||||
if (el.pathname === location.pathname && (el.hash || "#" === link)) {
|
||||
return;
|
||||
}
|
||||
if (link && link.indexOf('mailto:') > -1) {
|
||||
if (link && link.indexOf("mailto:") > -1) {
|
||||
return;
|
||||
}
|
||||
if (el.target) {
|
||||
|
@ -295,7 +298,7 @@ const _onClick = router => {
|
|||
}
|
||||
|
||||
const base = _getBaseHref();
|
||||
const orig = el.pathname + el.search + (el.hash || '');
|
||||
const orig = el.pathname + el.search + (el.hash || "");
|
||||
const path = !orig.indexOf(base) ? orig.slice(base.length) : orig;
|
||||
|
||||
if (base && orig === path) {
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const misc = require('./util/misc.js');
|
||||
const TagCategoryList = require('./models/tag_category_list.js');
|
||||
const misc = require("./util/misc.js");
|
||||
const TagCategoryList = require("./models/tag_category_list.js");
|
||||
|
||||
let _stylesheet = null;
|
||||
|
||||
function refreshCategoryColorMap() {
|
||||
return TagCategoryList.get().then(response => {
|
||||
return TagCategoryList.get().then((response) => {
|
||||
if (_stylesheet) {
|
||||
document.head.removeChild(_stylesheet);
|
||||
}
|
||||
_stylesheet = document.createElement('style');
|
||||
_stylesheet = document.createElement("style");
|
||||
document.head.appendChild(_stylesheet);
|
||||
for (let category of response.results) {
|
||||
const ruleName = misc.makeCssName(category.name, 'tag');
|
||||
const ruleName = misc.makeCssName(category.name, "tag");
|
||||
_stylesheet.sheet.insertRule(
|
||||
`.${ruleName} { color: ${category.color} }`,
|
||||
_stylesheet.sheet.cssRules.length);
|
||||
_stylesheet.sheet.cssRules.length
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
module.exports = require('./.templates.autogen.js');
|
||||
module.exports = require("./.templates.autogen.js");
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const mousetrap = require('mousetrap');
|
||||
const settings = require('../models/settings.js');
|
||||
const mousetrap = require("mousetrap");
|
||||
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) {
|
||||
mousetrap.prototype.stopCallback = function (...args) {
|
||||
var self = this;
|
||||
if (paused) {
|
||||
return true;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const marked = require('marked');
|
||||
const marked = require("marked");
|
||||
|
||||
class BaseMarkdownWrapper {
|
||||
preprocess(text) {
|
||||
|
@ -20,42 +20,44 @@ class SjisWrapper extends BaseMarkdownWrapper {
|
|||
|
||||
preprocess(text) {
|
||||
return text.replace(
|
||||
/\[sjis\]((?:[^\[]|\[(?!\/?sjis\]))+)\[\/sjis\]/ig,
|
||||
/\[sjis\]((?:[^\[]|\[(?!\/?sjis\]))+)\[\/sjis\]/gi,
|
||||
(match, capture) => {
|
||||
var ret = '%%%SJIS' + this.buf.length;
|
||||
var ret = "%%%SJIS" + this.buf.length;
|
||||
this.buf.push(capture);
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
postprocess(text) {
|
||||
return text.replace(
|
||||
/(?:<p>)?%%%SJIS(\d+)(?:<\/p>)?/,
|
||||
(match, capture) => {
|
||||
return '<div class="sjis">' + this.buf[capture] + '</div>';
|
||||
});
|
||||
return '<div class="sjis">' + this.buf[capture] + "</div>";
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// fix \ before ~ being stripped away
|
||||
class TildeWrapper extends BaseMarkdownWrapper {
|
||||
preprocess(text) {
|
||||
return text.replace(/\\~/g, '%%%T');
|
||||
return text.replace(/\\~/g, "%%%T");
|
||||
}
|
||||
|
||||
postprocess(text) {
|
||||
return text.replace(/%%%T/g, '\\~');
|
||||
return text.replace(/%%%T/g, "\\~");
|
||||
}
|
||||
}
|
||||
|
||||
// prevent ^#... from being treated as headers, due to tag permalinks
|
||||
class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
|
||||
preprocess(text) {
|
||||
return text.replace(/^#/g, '%%%#');
|
||||
return text.replace(/^#/g, "%%%#");
|
||||
}
|
||||
|
||||
postprocess(text) {
|
||||
return text.replace(/%%%#/g, '#');
|
||||
return text.replace(/%%%#/g, "#");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,19 +65,23 @@ class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
|
|||
class EntityPermalinkWrapper extends BaseMarkdownWrapper {
|
||||
preprocess(text) {
|
||||
// URL-based permalinks
|
||||
text = text.replace(new RegExp("\\b/post/(\\d+)/?\\b", "g"), "@$1");
|
||||
text = text.replace(
|
||||
new RegExp('\\b/post/(\\d+)/?\\b', 'g'), '@$1');
|
||||
new RegExp("\\b/tag/([a-zA-Z0-9_-]+?)/?", "g"),
|
||||
"#$1"
|
||||
);
|
||||
text = text.replace(
|
||||
new RegExp('\\b/tag/([a-zA-Z0-9_-]+?)/?', 'g'), '#$1');
|
||||
text = text.replace(
|
||||
new RegExp('\\b/user/([a-zA-Z0-9_-]+?)/?', 'g'), '+$1');
|
||||
new RegExp("\\b/user/([a-zA-Z0-9_-]+?)/?", "g"),
|
||||
"+$1"
|
||||
);
|
||||
|
||||
text = text.replace(
|
||||
/(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g,
|
||||
'$1[$2]($2)');
|
||||
text = text.replace(/\]\(@(\d+)\)/g, '](/post/$1)');
|
||||
text = text.replace(/\]\(\+([a-zA-Z0-9_-]+)\)/g, '](/user/$1)');
|
||||
text = text.replace(/\]\(#([a-zA-Z0-9_-]+)\)/g, '](/posts/query=$1)');
|
||||
"$1[$2]($2)"
|
||||
);
|
||||
text = text.replace(/\]\(@(\d+)\)/g, "](/post/$1)");
|
||||
text = text.replace(/\]\(\+([a-zA-Z0-9_-]+)\)/g, "](/user/$1)");
|
||||
text = text.replace(/\]\(#([a-zA-Z0-9_-]+)\)/g, "](/posts/query=$1)");
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
@ -83,51 +89,58 @@ class EntityPermalinkWrapper extends BaseMarkdownWrapper {
|
|||
class SearchPermalinkWrapper extends BaseMarkdownWrapper {
|
||||
postprocess(text) {
|
||||
return text.replace(
|
||||
/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig,
|
||||
'<a href="/posts/query=$1"><code>$1</code></a>');
|
||||
/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/gi,
|
||||
'<a href="/posts/query=$1"><code>$1</code></a>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpoilersWrapper extends BaseMarkdownWrapper {
|
||||
postprocess(text) {
|
||||
return text.replace(
|
||||
/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig,
|
||||
'<span class="spoiler">$1</span>');
|
||||
/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/gi,
|
||||
'<span class="spoiler">$1</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SmallWrapper extends BaseMarkdownWrapper {
|
||||
postprocess(text) {
|
||||
return text.replace(
|
||||
/\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig,
|
||||
'<small>$1</small>');
|
||||
/\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/gi,
|
||||
"<small>$1</small>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StrikeThroughWrapper extends BaseMarkdownWrapper {
|
||||
postprocess(text) {
|
||||
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>');
|
||||
return text.replace(/\\~/g, '~');
|
||||
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, "$1<del>$3</del>");
|
||||
return text.replace(/\\~/g, "~");
|
||||
}
|
||||
}
|
||||
|
||||
function createRenderer() {
|
||||
function sanitize(str) {
|
||||
return str.replace(/&<"/g, m => {
|
||||
if (m === '&') {
|
||||
return '&';
|
||||
return str.replace(/&<"/g, (m) => {
|
||||
if (m === "&") {
|
||||
return "&";
|
||||
}
|
||||
if (m === '<') {
|
||||
return '<';
|
||||
if (m === "<") {
|
||||
return "<";
|
||||
}
|
||||
return '"';
|
||||
return """;
|
||||
});
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.image = (href, title, alt) => {
|
||||
let [_, url, width, height] =
|
||||
(/^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/).exec(href);
|
||||
let [
|
||||
_,
|
||||
url,
|
||||
width,
|
||||
height,
|
||||
] = /^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/.exec(href);
|
||||
let res = '<img src="' + sanitize(url) + '" alt="' + sanitize(alt);
|
||||
if (width) {
|
||||
res += '" width="' + width;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const markdown = require('./markdown.js');
|
||||
const uri = require('./uri.js');
|
||||
const settings = require('../models/settings.js');
|
||||
const markdown = require("./markdown.js");
|
||||
const uri = require("./uri.js");
|
||||
const settings = require("../models/settings.js");
|
||||
|
||||
function decamelize(str, sep) {
|
||||
sep = sep === undefined ? '-' : sep;
|
||||
sep = sep === undefined ? "-" : sep;
|
||||
return str
|
||||
.replace(/([a-z\d])([A-Z])/g, '$1' + sep + '$2')
|
||||
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + sep + '$2')
|
||||
.replace(/([a-z\d])([A-Z])/g, "$1" + sep + "$2")
|
||||
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, "$1" + sep + "$2")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function *range(start = 0, end = null, step = 1) {
|
||||
function* range(start = 0, end = null, step = 1) {
|
||||
if (end === null) {
|
||||
end = start;
|
||||
start = 0;
|
||||
|
@ -45,16 +45,17 @@ function formatFileSize(fileSize) {
|
|||
return _formatUnits(
|
||||
fileSize,
|
||||
1024,
|
||||
['B', 'K', 'M', 'G'],
|
||||
["B", "K", "M", "G"],
|
||||
(number, suffix) => {
|
||||
const decimalPlaces = number < 20 && suffix !== 'B' ? 1 : 0;
|
||||
const decimalPlaces = number < 20 && suffix !== "B" ? 1 : 0;
|
||||
return number.toFixed(decimalPlaces) + suffix;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function formatRelativeTime(timeString) {
|
||||
if (!timeString) {
|
||||
return 'never';
|
||||
return "never";
|
||||
}
|
||||
|
||||
const then = Date.parse(timeString);
|
||||
|
@ -63,17 +64,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;
|
||||
|
@ -87,10 +88,10 @@ function formatRelativeTime(timeString) {
|
|||
}
|
||||
}
|
||||
|
||||
if (text === 'a day') {
|
||||
return future ? 'tomorrow' : 'yesterday';
|
||||
if (text === "a day") {
|
||||
return future ? "tomorrow" : "yesterday";
|
||||
}
|
||||
return future ? 'in ' + text : text + ' ago';
|
||||
return future ? "in " + text : text + " ago";
|
||||
}
|
||||
|
||||
function formatMarkdown(text) {
|
||||
|
@ -102,7 +103,7 @@ function formatInlineMarkdown(text) {
|
|||
}
|
||||
|
||||
function splitByWhitespace(str) {
|
||||
return str.split(/\s+/).filter(s => s);
|
||||
return str.split(/\s+/).filter((s) => s);
|
||||
}
|
||||
|
||||
function unindent(callSite, ...args) {
|
||||
|
@ -110,28 +111,30 @@ function unindent(callSite, ...args) {
|
|||
let size = -1;
|
||||
return str.replace(/\n(\s+)/g, (m, m1) => {
|
||||
if (size < 0) {
|
||||
size = m1.replace(/\t/g, ' ').length;
|
||||
size = m1.replace(/\t/g, " ").length;
|
||||
}
|
||||
return '\n' + m1.slice(Math.min(m1.length, size));
|
||||
return "\n" + m1.slice(Math.min(m1.length, size));
|
||||
});
|
||||
}
|
||||
if (typeof callSite === 'string') {
|
||||
if (typeof callSite === "string") {
|
||||
return format(callSite);
|
||||
}
|
||||
if (typeof callSite === 'function') {
|
||||
if (typeof callSite === "function") {
|
||||
return (...args) => format(callSite(...args));
|
||||
}
|
||||
let output = callSite
|
||||
.slice(0, args.length + 1)
|
||||
.map((text, i) => (i === 0 ? '' : args[i - 1]) + text)
|
||||
.join('');
|
||||
.map((text, i) => (i === 0 ? "" : args[i - 1]) + text)
|
||||
.join("");
|
||||
return format(output);
|
||||
}
|
||||
|
||||
function enableExitConfirmation() {
|
||||
window.onbeforeunload = e => {
|
||||
return 'Are you sure you want to leave? ' +
|
||||
'Data you have entered may not be saved.';
|
||||
window.onbeforeunload = (e) => {
|
||||
return (
|
||||
"Are you sure you want to leave? " +
|
||||
"Data you have entered may not be saved."
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -150,16 +153,17 @@ function confirmPageExit() {
|
|||
}
|
||||
|
||||
function makeCssName(text, suffix) {
|
||||
return suffix + '-' + text.replace(/[^a-z0-9]/g, '_');
|
||||
return suffix + "-" + text.replace(/[^a-z0-9]/g, "_");
|
||||
}
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe.toString()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
return unsafe
|
||||
.toString()
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function arraysDiffer(source1, source2, orderImportant) {
|
||||
|
@ -177,25 +181,27 @@ function arraysDiffer(source1, source2, orderImportant) {
|
|||
return false;
|
||||
}
|
||||
return (
|
||||
source1.filter(value => !source2.includes(value)).length > 0 ||
|
||||
source2.filter(value => !source1.includes(value)).length > 0);
|
||||
source1.filter((value) => !source2.includes(value)).length > 0 ||
|
||||
source2.filter((value) => !source1.includes(value)).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function escapeSearchTerm(text) {
|
||||
return text.replace(/([a-z_-]):/g, '$1\\:');
|
||||
return text.replace(/([a-z_-]):/g, "$1\\:");
|
||||
}
|
||||
|
||||
function dataURItoBlob(dataURI) {
|
||||
const chunks = dataURI.split(',');
|
||||
const byteString = chunks[0].indexOf('base64') >= 0 ?
|
||||
window.atob(chunks[1]) :
|
||||
unescape(chunks[1]);
|
||||
const mimeString = chunks[0].split(':')[1].split(';')[0];
|
||||
const chunks = dataURI.split(",");
|
||||
const byteString =
|
||||
chunks[0].indexOf("base64") >= 0
|
||||
? window.atob(chunks[1])
|
||||
: unescape(chunks[1]);
|
||||
const mimeString = chunks[0].split(":")[1].split(";")[0];
|
||||
const data = new Uint8Array(byteString.length);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
data[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
return new Blob([data], {type: mimeString});
|
||||
return new Blob([data], { type: mimeString });
|
||||
}
|
||||
|
||||
function getPrettyTagName(tag) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
let callbacks = [];
|
||||
let running = false;
|
||||
|
@ -15,7 +15,7 @@ function resize() {
|
|||
}
|
||||
|
||||
function runCallbacks() {
|
||||
callbacks.forEach(callback => {
|
||||
callbacks.forEach((callback) => {
|
||||
callback();
|
||||
});
|
||||
running = false;
|
||||
|
@ -26,8 +26,8 @@ function add(callback) {
|
|||
}
|
||||
|
||||
function remove(callback) {
|
||||
callbacks = callbacks.filter(c => c !== callback);
|
||||
callbacks = callbacks.filter((c) => c !== callback);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
module.exports = {add: add, remove: remove};
|
||||
window.addEventListener("resize", resize);
|
||||
module.exports = { add: add, remove: remove };
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/* eslint-disable func-names, no-extend-native */
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
// fix iterating over NodeList in Chrome and Opera
|
||||
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
|
||||
|
||||
NodeList.prototype.querySelector = function(...args) {
|
||||
NodeList.prototype.querySelector = function (...args) {
|
||||
for (let node of this) {
|
||||
if (node.nodeType === 3) {
|
||||
continue;
|
||||
|
@ -18,7 +18,7 @@ NodeList.prototype.querySelector = function(...args) {
|
|||
return null;
|
||||
};
|
||||
|
||||
NodeList.prototype.querySelectorAll = function(...args) {
|
||||
NodeList.prototype.querySelectorAll = function (...args) {
|
||||
let result = [];
|
||||
for (let node of this) {
|
||||
if (node.nodeType === 3) {
|
||||
|
@ -32,7 +32,7 @@ NodeList.prototype.querySelectorAll = function(...args) {
|
|||
};
|
||||
|
||||
// non standard
|
||||
Node.prototype.prependChild = function(child) {
|
||||
Node.prototype.prependChild = function (child) {
|
||||
if (this.firstChild) {
|
||||
this.insertBefore(child, this.firstChild);
|
||||
} else {
|
||||
|
@ -41,29 +41,25 @@ Node.prototype.prependChild = function(child) {
|
|||
};
|
||||
|
||||
// non standard
|
||||
Promise.prototype.always = function(onResolveOrReject) {
|
||||
return this.then(
|
||||
onResolveOrReject,
|
||||
reason => {
|
||||
onResolveOrReject(reason);
|
||||
throw reason;
|
||||
});
|
||||
Promise.prototype.always = function (onResolveOrReject) {
|
||||
return this.then(onResolveOrReject, (reason) => {
|
||||
onResolveOrReject(reason);
|
||||
throw reason;
|
||||
});
|
||||
};
|
||||
|
||||
// non standard
|
||||
Number.prototype.between = function(a, b, inclusive) {
|
||||
Number.prototype.between = function (a, b, inclusive) {
|
||||
const min = Math.min(a, b);
|
||||
const max = Math.max(a, b);
|
||||
return inclusive ?
|
||||
this >= min && this <= max :
|
||||
this > min && this < max;
|
||||
return inclusive ? this >= min && this <= max : this > min && this < max;
|
||||
};
|
||||
|
||||
// non standard
|
||||
Promise.prototype.abort = () => {};
|
||||
|
||||
// non standard
|
||||
Date.prototype.addDays = function(days) {
|
||||
Date.prototype.addDays = function (days) {
|
||||
let dat = new Date(this.valueOf());
|
||||
dat.setDate(dat.getDate() + days);
|
||||
return dat;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const nprogress = require('nprogress');
|
||||
const nprogress = require("nprogress");
|
||||
|
||||
let nesting = 0;
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const misc = require('./misc.js');
|
||||
const keyboard = require('../util/keyboard.js');
|
||||
const views = require('./views.js');
|
||||
const misc = require("./misc.js");
|
||||
const keyboard = require("../util/keyboard.js");
|
||||
const views = require("./views.js");
|
||||
|
||||
function searchInputNodeFocusHelper(inputNode) {
|
||||
keyboard.bind('q', () => {
|
||||
keyboard.bind("q", () => {
|
||||
inputNode.focus();
|
||||
inputNode.setSelectionRange(
|
||||
inputNode.value.length, inputNode.value.length);
|
||||
inputNode.value.length,
|
||||
inputNode.value.length
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const direction = {
|
||||
NONE: null,
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
DOWN: 'down',
|
||||
UP: 'up'
|
||||
LEFT: "left",
|
||||
RIGHT: "right",
|
||||
DOWN: "down",
|
||||
UP: "up",
|
||||
};
|
||||
|
||||
function handleTouchStart(handler, evt) {
|
||||
|
@ -37,20 +37,20 @@ function handleTouchMove(handler, evt) {
|
|||
|
||||
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();
|
||||
// no default
|
||||
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,11 +58,13 @@ function handleTouchEnd(handler) {
|
|||
}
|
||||
|
||||
class Touch {
|
||||
constructor(target,
|
||||
constructor(
|
||||
target,
|
||||
swipeLeft = () => {},
|
||||
swipeRight = () => {},
|
||||
swipeUp = () => {},
|
||||
swipeDown = () => {}) {
|
||||
swipeDown = () => {}
|
||||
) {
|
||||
this._target = target;
|
||||
|
||||
this._swipeLeftTask = swipeLeft;
|
||||
|
@ -74,18 +76,15 @@ class Touch {
|
|||
this._yStart = null;
|
||||
this._direction = direction.NONE;
|
||||
|
||||
this._target.addEventListener('touchstart',
|
||||
evt => {
|
||||
handleTouchStart(this, evt);
|
||||
});
|
||||
this._target.addEventListener('touchmove',
|
||||
evt => {
|
||||
handleTouchMove(this, evt);
|
||||
});
|
||||
this._target.addEventListener('touchend',
|
||||
() => {
|
||||
handleTouchEnd(this);
|
||||
});
|
||||
this._target.addEventListener("touchstart", (evt) => {
|
||||
handleTouchStart(this, evt);
|
||||
});
|
||||
this._target.addEventListener("touchmove", (evt) => {
|
||||
handleTouchMove(this, evt);
|
||||
});
|
||||
this._target.addEventListener("touchend", () => {
|
||||
handleTouchEnd(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
function formatApiLink(...values) {
|
||||
let parts = [];
|
||||
|
@ -9,18 +9,19 @@ function formatApiLink(...values) {
|
|||
for (let key of Object.keys(value)) {
|
||||
if (value[key]) {
|
||||
variableParts.push(
|
||||
key + '=' + encodeURIComponent(value[key].toString()));
|
||||
key + "=" + encodeURIComponent(value[key].toString())
|
||||
);
|
||||
}
|
||||
}
|
||||
if (variableParts.length) {
|
||||
parts.push('?' + variableParts.join('&'));
|
||||
parts.push("?" + variableParts.join("&"));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
parts.push(encodeURIComponent(value.toString()));
|
||||
}
|
||||
}
|
||||
return '/' + parts.join('/');
|
||||
return "/" + parts.join("/");
|
||||
}
|
||||
|
||||
function escapeParam(text) {
|
||||
|
@ -40,48 +41,52 @@ function formatClientLink(...values) {
|
|||
for (let key of Object.keys(value)) {
|
||||
if (value[key]) {
|
||||
variableParts.push(
|
||||
key + '=' + escapeParam(value[key].toString()));
|
||||
key + "=" + escapeParam(value[key].toString())
|
||||
);
|
||||
}
|
||||
}
|
||||
if (variableParts.length) {
|
||||
parts.push(variableParts.join(';'));
|
||||
parts.push(variableParts.join(";"));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
parts.push(escapeParam(value.toString()));
|
||||
}
|
||||
}
|
||||
return parts.join('/');
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
function extractHostname(url) {
|
||||
// https://stackoverflow.com/a/23945027
|
||||
return url
|
||||
.split('/')[url.indexOf("//") > -1 ? 2 : 0]
|
||||
.split(':')[0]
|
||||
.split('?')[0];
|
||||
.split("/")
|
||||
[url.indexOf("//") > -1 ? 2 : 0].split(":")[0]
|
||||
.split("?")[0];
|
||||
}
|
||||
|
||||
function extractRootDomain(url) {
|
||||
// https://stackoverflow.com/a/23945027
|
||||
let domain = extractHostname(url);
|
||||
let splitArr = domain.split('.');
|
||||
let splitArr = domain.split(".");
|
||||
let arrLen = splitArr.length;
|
||||
|
||||
// if there is a subdomain
|
||||
if (arrLen > 2) {
|
||||
domain = splitArr[arrLen - 2] + '.' + splitArr[arrLen - 1];
|
||||
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;
|
||||
domain = splitArr[arrLen - 3] + "." + domain;
|
||||
}
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
function escapeColons(text) {
|
||||
return text.replace(new RegExp(':', 'g'), '\\:');
|
||||
return text.replace(new RegExp(":", "g"), "\\:");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
require('../util/polyfill.js');
|
||||
const api = require('../api.js');
|
||||
const templates = require('../templates.js');
|
||||
require("../util/polyfill.js");
|
||||
const api = require("../api.js");
|
||||
const templates = require("../templates.js");
|
||||
const domParser = new DOMParser();
|
||||
const misc = require('./misc.js');
|
||||
const uri = require('./uri.js');
|
||||
const misc = require("./misc.js");
|
||||
const uri = require("./uri.js");
|
||||
|
||||
function _imbueId(options) {
|
||||
if (!options.id) {
|
||||
options.id = 'gen-' + Math.random().toString(36).substring(7);
|
||||
options.id = "gen-" + Math.random().toString(36).substring(7);
|
||||
}
|
||||
}
|
||||
|
||||
function _makeLabel(options, attrs) {
|
||||
if (!options.text) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
if (!attrs) {
|
||||
attrs = {};
|
||||
}
|
||||
attrs.for = options.id;
|
||||
return makeElement('label', attrs, options.text);
|
||||
return makeElement("label", attrs, options.text);
|
||||
}
|
||||
|
||||
function makeFileSize(fileSize) {
|
||||
|
@ -34,251 +34,282 @@ function makeMarkdown(text) {
|
|||
|
||||
function makeRelativeTime(time) {
|
||||
return makeElement(
|
||||
'time', {datetime: time, title: time}, misc.formatRelativeTime(time));
|
||||
"time",
|
||||
{ datetime: time, title: time },
|
||||
misc.formatRelativeTime(time)
|
||||
);
|
||||
}
|
||||
|
||||
function makeThumbnail(url) {
|
||||
return makeElement(
|
||||
'span',
|
||||
url ?
|
||||
{class: 'thumbnail', style: `background-image: url(\'${url}\')`} :
|
||||
{class: 'thumbnail empty'},
|
||||
makeElement('img', {alt: 'thumbnail', src: url}));
|
||||
"span",
|
||||
url
|
||||
? {
|
||||
class: "thumbnail",
|
||||
style: `background-image: url(\'${url}\')`,
|
||||
}
|
||||
: { class: "thumbnail empty" },
|
||||
makeElement("img", { alt: "thumbnail", src: url })
|
||||
);
|
||||
}
|
||||
|
||||
function makeRadio(options) {
|
||||
_imbueId(options);
|
||||
return makeElement(
|
||||
'label',
|
||||
{for: options.id},
|
||||
makeElement(
|
||||
'input',
|
||||
{
|
||||
id: options.id,
|
||||
name: options.name,
|
||||
value: options.value,
|
||||
type: 'radio',
|
||||
checked: options.selectedValue === options.value,
|
||||
disabled: options.readonly,
|
||||
required: options.required,
|
||||
}),
|
||||
makeElement('span', {class: 'radio'}, options.text));
|
||||
"label",
|
||||
{ for: options.id },
|
||||
makeElement("input", {
|
||||
id: options.id,
|
||||
name: options.name,
|
||||
value: options.value,
|
||||
type: "radio",
|
||||
checked: options.selectedValue === options.value,
|
||||
disabled: options.readonly,
|
||||
required: options.required,
|
||||
}),
|
||||
makeElement("span", { class: "radio" }, options.text)
|
||||
);
|
||||
}
|
||||
|
||||
function makeCheckbox(options) {
|
||||
_imbueId(options);
|
||||
return makeElement(
|
||||
'label',
|
||||
{for: options.id},
|
||||
makeElement(
|
||||
'input',
|
||||
{
|
||||
id: options.id,
|
||||
name: options.name,
|
||||
value: options.value,
|
||||
type: 'checkbox',
|
||||
checked: options.checked !== undefined ?
|
||||
options.checked : false,
|
||||
disabled: options.readonly,
|
||||
required: options.required,
|
||||
}),
|
||||
makeElement('span', {class: 'checkbox'}, options.text));
|
||||
"label",
|
||||
{ for: options.id },
|
||||
makeElement("input", {
|
||||
id: options.id,
|
||||
name: options.name,
|
||||
value: options.value,
|
||||
type: "checkbox",
|
||||
checked: options.checked !== undefined ? options.checked : false,
|
||||
disabled: options.readonly,
|
||||
required: options.required,
|
||||
}),
|
||||
makeElement("span", { class: "checkbox" }, options.text)
|
||||
);
|
||||
}
|
||||
|
||||
function makeSelect(options) {
|
||||
return _makeLabel(options) +
|
||||
return (
|
||||
_makeLabel(options) +
|
||||
makeElement(
|
||||
'select',
|
||||
"select",
|
||||
{
|
||||
id: options.id,
|
||||
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) {
|
||||
options.value = options.value || '';
|
||||
return _makeLabel(options) + makeElement('input', options);
|
||||
options.value = options.value || "";
|
||||
return _makeLabel(options) + makeElement("input", options);
|
||||
}
|
||||
|
||||
function makeButton(options) {
|
||||
options.type = 'button';
|
||||
options.type = "button";
|
||||
return makeInput(options);
|
||||
}
|
||||
|
||||
function makeTextInput(options) {
|
||||
options.type = 'text';
|
||||
options.type = "text";
|
||||
return makeInput(options);
|
||||
}
|
||||
|
||||
function makeTextarea(options) {
|
||||
const value = options.value || '';
|
||||
const value = options.value || "";
|
||||
delete options.value;
|
||||
return _makeLabel(options) + makeElement('textarea', options, value);
|
||||
return _makeLabel(options) + makeElement("textarea", options, value);
|
||||
}
|
||||
|
||||
function makePasswordInput(options) {
|
||||
options.type = 'password';
|
||||
options.type = "password";
|
||||
return makeInput(options);
|
||||
}
|
||||
|
||||
function makeEmailInput(options) {
|
||||
options.type = 'email';
|
||||
options.type = "email";
|
||||
return makeInput(options);
|
||||
}
|
||||
|
||||
function makeColorInput(options) {
|
||||
const textInput = makeElement(
|
||||
'input', {
|
||||
type: 'text',
|
||||
value: options.value || '',
|
||||
required: options.required,
|
||||
class: 'color',
|
||||
});
|
||||
const backgroundPreviewNode = makeElement(
|
||||
'div',
|
||||
{
|
||||
class: 'preview background-preview',
|
||||
style:
|
||||
`border-color: ${options.value};
|
||||
const textInput = makeElement("input", {
|
||||
type: "text",
|
||||
value: options.value || "",
|
||||
required: options.required,
|
||||
class: "color",
|
||||
});
|
||||
const backgroundPreviewNode = makeElement("div", {
|
||||
class: "preview background-preview",
|
||||
style: `border-color: ${options.value};
|
||||
background-color: ${options.value}`,
|
||||
});
|
||||
const textPreviewNode = makeElement(
|
||||
'div',
|
||||
{
|
||||
class: 'preview text-preview',
|
||||
style:
|
||||
`border-color: ${options.value};
|
||||
});
|
||||
const textPreviewNode = makeElement("div", {
|
||||
class: "preview text-preview",
|
||||
style: `border-color: ${options.value};
|
||||
color: ${options.value}`,
|
||||
});
|
||||
});
|
||||
return makeElement(
|
||||
'label', {class: 'color'}, textInput, backgroundPreviewNode, textPreviewNode);
|
||||
"label",
|
||||
{ class: "color" },
|
||||
textInput,
|
||||
backgroundPreviewNode,
|
||||
textPreviewNode
|
||||
);
|
||||
}
|
||||
|
||||
function makeNumericInput(options) {
|
||||
options.type = 'number';
|
||||
options.type = "number";
|
||||
return makeInput(options);
|
||||
}
|
||||
|
||||
function makeDateInput(options) {
|
||||
options.type = 'date';
|
||||
return makeInput(options)
|
||||
options.type = "date";
|
||||
return makeInput(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) {
|
||||
let text = id;
|
||||
if (includeHash) {
|
||||
text = '@' + id;
|
||||
text = "@" + id;
|
||||
}
|
||||
return api.hasPrivilege('posts:view') ?
|
||||
makeElement(
|
||||
'a',
|
||||
{href: uri.formatClientLink('post', id)},
|
||||
misc.escapeHtml(text)) :
|
||||
misc.escapeHtml(text);
|
||||
return api.hasPrivilege("posts:view")
|
||||
? makeElement(
|
||||
"a",
|
||||
{ href: uri.formatClientLink("post", id) },
|
||||
misc.escapeHtml(text)
|
||||
)
|
||||
: misc.escapeHtml(text);
|
||||
}
|
||||
|
||||
function makeTagLink(name, includeHash, includeCount, tag) {
|
||||
const category = tag ? tag.category : 'unknown';
|
||||
const category = tag ? tag.category : "unknown";
|
||||
let text = misc.getPrettyTagName(name);
|
||||
if (includeHash === true) {
|
||||
text = '#' + text;
|
||||
text = "#" + text;
|
||||
}
|
||||
if (includeCount === true) {
|
||||
text += ' (' + (tag ? tag.postCount : 0) + ')';
|
||||
text += " (" + (tag ? tag.postCount : 0) + ")";
|
||||
}
|
||||
return api.hasPrivilege('tags:view') ?
|
||||
makeElement(
|
||||
'a',
|
||||
{
|
||||
href: uri.formatClientLink('tag', name),
|
||||
class: misc.makeCssName(category, 'tag'),
|
||||
},
|
||||
misc.escapeHtml(text)) :
|
||||
makeElement(
|
||||
'span',
|
||||
{class: misc.makeCssName(category, 'tag')},
|
||||
misc.escapeHtml(text));
|
||||
return api.hasPrivilege("tags:view")
|
||||
? makeElement(
|
||||
"a",
|
||||
{
|
||||
href: uri.formatClientLink("tag", name),
|
||||
class: misc.makeCssName(category, "tag"),
|
||||
},
|
||||
misc.escapeHtml(text)
|
||||
)
|
||||
: makeElement(
|
||||
"span",
|
||||
{ class: misc.makeCssName(category, "tag") },
|
||||
misc.escapeHtml(text)
|
||||
);
|
||||
}
|
||||
|
||||
function makePoolLink(id, includeHash, includeCount, pool, name) {
|
||||
const category = pool ? pool.category : 'unknown';
|
||||
const category = pool ? pool.category : "unknown";
|
||||
let text = name ? name : pool.names[0];
|
||||
if (includeHash === true) {
|
||||
text = '#' + text;
|
||||
text = "#" + text;
|
||||
}
|
||||
if (includeCount === true) {
|
||||
text += ' (' + (pool ? pool.postCount : 0) + ')';
|
||||
text += " (" + (pool ? pool.postCount : 0) + ")";
|
||||
}
|
||||
return api.hasPrivilege('pools:view') ?
|
||||
makeElement(
|
||||
'a',
|
||||
{
|
||||
href: uri.formatClientLink('pool', id),
|
||||
class: misc.makeCssName(category, 'pool'),
|
||||
},
|
||||
misc.escapeHtml(text)) :
|
||||
makeElement(
|
||||
'span',
|
||||
{class: misc.makeCssName(category, 'pool')},
|
||||
misc.escapeHtml(text));
|
||||
return api.hasPrivilege("pools:view")
|
||||
? makeElement(
|
||||
"a",
|
||||
{
|
||||
href: uri.formatClientLink("pool", id),
|
||||
class: misc.makeCssName(category, "pool"),
|
||||
},
|
||||
misc.escapeHtml(text)
|
||||
)
|
||||
: makeElement(
|
||||
"span",
|
||||
{ class: misc.makeCssName(category, "pool") },
|
||||
misc.escapeHtml(text)
|
||||
);
|
||||
}
|
||||
|
||||
function makeUserLink(user) {
|
||||
let text = makeThumbnail(user ? user.avatarUrl : null);
|
||||
text += user && user.name ? misc.escapeHtml(user.name) : 'Anonymous';
|
||||
const link = user && api.hasPrivilege('users:view') ?
|
||||
makeElement(
|
||||
'a', {href: uri.formatClientLink('user', user.name)}, text) :
|
||||
text;
|
||||
return makeElement('span', {class: 'user'}, link);
|
||||
text += user && user.name ? misc.escapeHtml(user.name) : "Anonymous";
|
||||
const link =
|
||||
user && api.hasPrivilege("users:view")
|
||||
? makeElement(
|
||||
"a",
|
||||
{ href: uri.formatClientLink("user", user.name) },
|
||||
text
|
||||
)
|
||||
: text;
|
||||
return makeElement("span", { class: "user" }, link);
|
||||
}
|
||||
|
||||
function makeFlexboxAlign(options) {
|
||||
return [...misc.range(20)]
|
||||
.map(() => '<li class="flexbox-dummy"></li>').join('');
|
||||
.map(() => '<li class="flexbox-dummy"></li>')
|
||||
.join("");
|
||||
}
|
||||
|
||||
function makeAccessKey(html, key) {
|
||||
const regex = new RegExp('(' + key + ')', 'i');
|
||||
const regex = new RegExp("(" + key + ")", "i");
|
||||
html = html.replace(
|
||||
regex, '<span class="access-key" data-accesskey="$1">$1</span>');
|
||||
regex,
|
||||
'<span class="access-key" data-accesskey="$1">$1</span>'
|
||||
);
|
||||
return html;
|
||||
}
|
||||
|
||||
function _serializeElement(name, attributes) {
|
||||
return [name]
|
||||
.concat(Object.keys(attributes).map(key => {
|
||||
if (attributes[key] === true) {
|
||||
return key;
|
||||
} else if (attributes[key] === false ||
|
||||
attributes[key] === undefined) {
|
||||
return '';
|
||||
}
|
||||
const attribute = misc.escapeHtml(attributes[key] || '');
|
||||
return `${key}="${attribute}"`;
|
||||
}))
|
||||
.join(' ');
|
||||
.concat(
|
||||
Object.keys(attributes).map((key) => {
|
||||
if (attributes[key] === true) {
|
||||
return key;
|
||||
} else if (
|
||||
attributes[key] === false ||
|
||||
attributes[key] === undefined
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
const attribute = misc.escapeHtml(attributes[key] || "");
|
||||
return `${key}="${attribute}"`;
|
||||
})
|
||||
)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function makeElement(name, attrs, ...content) {
|
||||
return content.length !== undefined ?
|
||||
`<${_serializeElement(name, attrs)}>${content.join('')}</${name}>` :
|
||||
`<${_serializeElement(name, attrs)}/>`;
|
||||
return content.length !== undefined
|
||||
? `<${_serializeElement(name, attrs)}>${content.join("")}</${name}>`
|
||||
: `<${_serializeElement(name, attrs)}/>`;
|
||||
}
|
||||
|
||||
function emptyContent(target) {
|
||||
|
@ -302,25 +333,25 @@ function replaceContent(target, source) {
|
|||
|
||||
function showMessage(target, message, className) {
|
||||
if (!message) {
|
||||
message = 'Unknown message';
|
||||
message = "Unknown message";
|
||||
}
|
||||
const messagesHolderNode = target.querySelector('.messages');
|
||||
const messagesHolderNode = target.querySelector(".messages");
|
||||
if (!messagesHolderNode) {
|
||||
return false;
|
||||
}
|
||||
const textNode = document.createElement('div');
|
||||
textNode.innerHTML = message.replace(/\n/g, '<br/>');
|
||||
textNode.classList.add('message');
|
||||
const textNode = document.createElement("div");
|
||||
textNode.innerHTML = message.replace(/\n/g, "<br/>");
|
||||
textNode.classList.add("message");
|
||||
textNode.classList.add(className);
|
||||
const wrapperNode = document.createElement('div');
|
||||
wrapperNode.classList.add('message-wrapper');
|
||||
const wrapperNode = document.createElement("div");
|
||||
wrapperNode.classList.add("message-wrapper");
|
||||
wrapperNode.appendChild(textNode);
|
||||
messagesHolderNode.appendChild(wrapperNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
function appendExclamationMark() {
|
||||
if (!document.title.startsWith('!')) {
|
||||
if (!document.title.startsWith("!")) {
|
||||
document.oldTitle = document.title;
|
||||
document.title = `! ${document.title}`;
|
||||
}
|
||||
|
@ -328,15 +359,15 @@ function appendExclamationMark() {
|
|||
|
||||
function showError(target, message) {
|
||||
appendExclamationMark();
|
||||
return showMessage(target, misc.formatInlineMarkdown(message), 'error');
|
||||
return showMessage(target, misc.formatInlineMarkdown(message), "error");
|
||||
}
|
||||
|
||||
function showSuccess(target, message) {
|
||||
return showMessage(target, misc.formatInlineMarkdown(message), 'success');
|
||||
return showMessage(target, misc.formatInlineMarkdown(message), "success");
|
||||
}
|
||||
|
||||
function showInfo(target, message) {
|
||||
return showMessage(target, misc.formatInlineMarkdown(message), 'info');
|
||||
return showMessage(target, misc.formatInlineMarkdown(message), "info");
|
||||
}
|
||||
|
||||
function clearMessages(target) {
|
||||
|
@ -344,7 +375,7 @@ function clearMessages(target) {
|
|||
document.title = document.oldTitle;
|
||||
document.oldTitle = null;
|
||||
}
|
||||
for (let messagesHolderNode of target.querySelectorAll('.messages')) {
|
||||
for (let messagesHolderNode of target.querySelectorAll(".messages")) {
|
||||
emptyContent(messagesHolderNode);
|
||||
}
|
||||
}
|
||||
|
@ -352,15 +383,15 @@ function clearMessages(target) {
|
|||
function htmlToDom(html) {
|
||||
// code taken from jQuery + Krasimir Tsonev's blog
|
||||
const wrapMap = {
|
||||
_: [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>'],
|
||||
_: [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>"],
|
||||
};
|
||||
wrapMap.optgroup = wrapMap.option;
|
||||
wrapMap.tbody = wrapMap.thead;
|
||||
|
@ -369,8 +400,8 @@ function htmlToDom(html) {
|
|||
wrapMap.caption = wrapMap.thead;
|
||||
wrapMap.th = wrapMap.td;
|
||||
|
||||
let element = document.createElement('div');
|
||||
const match = (/<\s*(\w+)[^>]*?>/g).exec(html);
|
||||
let element = document.createElement("div");
|
||||
const match = /<\s*(\w+)[^>]*?>/g.exec(html);
|
||||
|
||||
if (match) {
|
||||
const tag = match[1];
|
||||
|
@ -382,9 +413,9 @@ function htmlToDom(html) {
|
|||
} else {
|
||||
element.innerHTML = html;
|
||||
}
|
||||
return element.childNodes.length > 1 ?
|
||||
element.childNodes :
|
||||
element.firstChild;
|
||||
return element.childNodes.length > 1
|
||||
? element.childNodes
|
||||
: element.firstChild;
|
||||
}
|
||||
|
||||
function getTemplate(templatePath) {
|
||||
|
@ -392,7 +423,7 @@ function getTemplate(templatePath) {
|
|||
throw `Missing template: ${templatePath}`;
|
||||
}
|
||||
const templateFactory = templates[templatePath];
|
||||
return ctx => {
|
||||
return (ctx) => {
|
||||
if (!ctx) {
|
||||
ctx = {};
|
||||
}
|
||||
|
@ -423,7 +454,7 @@ function getTemplate(templatePath) {
|
|||
makeElement: makeElement,
|
||||
makeCssName: misc.makeCssName,
|
||||
makeNumericInput: makeNumericInput,
|
||||
formatClientLink: uri.formatClientLink
|
||||
formatClientLink: uri.formatClientLink,
|
||||
});
|
||||
return htmlToDom(templateFactory(ctx));
|
||||
};
|
||||
|
@ -432,49 +463,51 @@ function getTemplate(templatePath) {
|
|||
function decorateValidator(form) {
|
||||
// postpone showing form fields validity until user actually tries
|
||||
// to submit it (seeing red/green form w/o doing anything breaks POLA)
|
||||
let submitButton = form.querySelector('.buttons input');
|
||||
let submitButton = form.querySelector(".buttons input");
|
||||
if (!submitButton) {
|
||||
submitButton = form.querySelector('input[type=submit]');
|
||||
submitButton = form.querySelector("input[type=submit]");
|
||||
}
|
||||
if (submitButton) {
|
||||
submitButton.addEventListener('click', e => {
|
||||
form.classList.add('show-validation');
|
||||
submitButton.addEventListener("click", (e) => {
|
||||
form.classList.add("show-validation");
|
||||
});
|
||||
}
|
||||
form.addEventListener('submit', e => {
|
||||
form.classList.remove('show-validation');
|
||||
form.addEventListener("submit", (e) => {
|
||||
form.classList.remove("show-validation");
|
||||
});
|
||||
}
|
||||
|
||||
function disableForm(form) {
|
||||
for (let input of form.querySelectorAll('input')) {
|
||||
for (let input of form.querySelectorAll("input")) {
|
||||
input.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function enableForm(form) {
|
||||
for (let input of form.querySelectorAll('input')) {
|
||||
for (let input of form.querySelectorAll("input")) {
|
||||
input.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function syncScrollPosition() {
|
||||
window.requestAnimationFrame(
|
||||
() => {
|
||||
if (history.state && Object.prototype.hasOwnProperty.call(history.state, 'scrollX')) {
|
||||
window.scrollTo(history.state.scrollX, history.state.scrollY);
|
||||
} else {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
window.requestAnimationFrame(() => {
|
||||
if (
|
||||
history.state &&
|
||||
Object.prototype.hasOwnProperty.call(history.state, "scrollX")
|
||||
) {
|
||||
window.scrollTo(history.state.scrollX, history.state.scrollY);
|
||||
} else {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function slideDown(element) {
|
||||
const duration = 500;
|
||||
return new Promise((resolve, reject) => {
|
||||
const height = element.getBoundingClientRect().height;
|
||||
element.style.maxHeight = '0';
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.maxHeight = "0";
|
||||
element.style.overflow = "hidden";
|
||||
window.setTimeout(() => {
|
||||
element.style.transition = `all ${duration}ms ease`;
|
||||
element.style.maxHeight = `${height}px`;
|
||||
|
@ -489,7 +522,7 @@ function slideUp(element) {
|
|||
const duration = 500;
|
||||
return new Promise((resolve, reject) => {
|
||||
const height = element.getBoundingClientRect().height;
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.overflow = "hidden";
|
||||
element.style.maxHeight = `${height}px`;
|
||||
element.style.transition = `all ${duration}ms ease`;
|
||||
window.setTimeout(() => {
|
||||
|
@ -502,26 +535,27 @@ function slideUp(element) {
|
|||
}
|
||||
|
||||
function monitorNodeRemoval(monitoredNode, callback) {
|
||||
const mutationObserver = new MutationObserver(
|
||||
mutations => {
|
||||
for (let mutation of mutations) {
|
||||
for (let node of mutation.removedNodes) {
|
||||
if (node.contains(monitoredNode)) {
|
||||
mutationObserver.disconnect();
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
const mutationObserver = new MutationObserver((mutations) => {
|
||||
for (let mutation of mutations) {
|
||||
for (let node of mutation.removedNodes) {
|
||||
if (node.contains(monitoredNode)) {
|
||||
mutationObserver.disconnect();
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
mutationObserver.observe(
|
||||
document.body, {childList: true, subtree: true});
|
||||
}
|
||||
});
|
||||
mutationObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('input', e => {
|
||||
if (e.target.classList.contains('color')) {
|
||||
let bkNode = e.target.parentNode.querySelector('.background-preview');
|
||||
let textNode = e.target.parentNode.querySelector('.text-preview');
|
||||
document.addEventListener("input", (e) => {
|
||||
if (e.target.classList.contains("color")) {
|
||||
let bkNode = e.target.parentNode.querySelector(".background-preview");
|
||||
let textNode = e.target.parentNode.querySelector(".text-preview");
|
||||
bkNode.style.backgroundColor = e.target.value;
|
||||
bkNode.style.borderColor = e.target.value;
|
||||
textNode.style.color = e.target.value;
|
||||
|
@ -530,8 +564,8 @@ document.addEventListener('input', e => {
|
|||
});
|
||||
|
||||
// prevent opening buttons in new tabs
|
||||
document.addEventListener('click', e => {
|
||||
if (e.target.getAttribute('href') === '' && e.which === 2) {
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target.getAttribute("href") === "" && e.which === 2) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const CommentListControl = require('../controls/comment_list_control.js');
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
const CommentListControl = require("../controls/comment_list_control.js");
|
||||
|
||||
const template = views.getTemplate('comments-page');
|
||||
const template = views.getTemplate("comments-page");
|
||||
|
||||
class CommentsPageView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
|
@ -16,12 +16,14 @@ class CommentsPageView extends events.EventTarget {
|
|||
for (let post of ctx.response.results) {
|
||||
const commentListControl = new CommentListControl(
|
||||
sourceNode.querySelector(
|
||||
`.comments-container[data-for="${post.id}"]`),
|
||||
`.comments-container[data-for="${post.id}"]`
|
||||
),
|
||||
post.comments,
|
||||
true);
|
||||
events.proxyEvent(commentListControl, this, 'submit');
|
||||
events.proxyEvent(commentListControl, this, 'score');
|
||||
events.proxyEvent(commentListControl, this, 'delete');
|
||||
true
|
||||
);
|
||||
events.proxyEvent(commentListControl, this, "submit");
|
||||
events.proxyEvent(commentListControl, this, "score");
|
||||
events.proxyEvent(commentListControl, this, "delete");
|
||||
}
|
||||
|
||||
views.replaceContent(this._hostNode, sourceNode);
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const views = require('../util/views.js');
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = () => {
|
||||
return views.htmlToDom(
|
||||
'<div class="wrapper"><div class="messages"></div></div>');
|
||||
'<div class="wrapper"><div class="messages"></div></div>'
|
||||
);
|
||||
};
|
||||
|
||||
class EmptyView {
|
||||
constructor() {
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
views.replaceContent(this._hostNode, template());
|
||||
views.syncScrollPosition();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const views = require('../util/views.js');
|
||||
const router = require("../router.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const holderTemplate = views.getTemplate('endless-pager');
|
||||
const pageTemplate = views.getTemplate('endless-pager-page');
|
||||
const holderTemplate = views.getTemplate("endless-pager");
|
||||
const pageTemplate = views.getTemplate("endless-pager-page");
|
||||
|
||||
function isScrolledIntoView(element) {
|
||||
let top = 0;
|
||||
|
@ -12,14 +12,12 @@ function isScrolledIntoView(element) {
|
|||
top += element.offsetTop || 0;
|
||||
element = element.offsetParent;
|
||||
} while (element);
|
||||
return (
|
||||
(top >= window.scrollY) &&
|
||||
(top <= window.scrollY + window.innerHeight));
|
||||
return top >= window.scrollY && top <= window.scrollY + window.innerHeight;
|
||||
}
|
||||
|
||||
class EndlessPageView {
|
||||
constructor(ctx) {
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
views.replaceContent(this._hostNode, holderTemplate());
|
||||
}
|
||||
|
||||
|
@ -40,12 +38,13 @@ class EndlessPageView {
|
|||
this.defaultLimit = parseInt(ctx.parameters.limit || ctx.defaultLimit);
|
||||
|
||||
const initialOffset = parseInt(ctx.parameters.offset || 0);
|
||||
this._loadPage(ctx, initialOffset, this.defaultLimit, true)
|
||||
.then(pageNode => {
|
||||
this._loadPage(ctx, initialOffset, this.defaultLimit, true).then(
|
||||
(pageNode) => {
|
||||
if (initialOffset !== 0) {
|
||||
pageNode.scrollIntoView();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this._timeout = window.setInterval(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
|
@ -58,19 +57,19 @@ class EndlessPageView {
|
|||
}
|
||||
|
||||
get pageHeaderHolderNode() {
|
||||
return this._hostNode.querySelector('.page-header-holder');
|
||||
return this._hostNode.querySelector(".page-header-holder");
|
||||
}
|
||||
|
||||
get topPageGuardNode() {
|
||||
return this._hostNode.querySelector('.page-guard.top');
|
||||
return this._hostNode.querySelector(".page-guard.top");
|
||||
}
|
||||
|
||||
get bottomPageGuardNode() {
|
||||
return this._hostNode.querySelector('.page-guard.bottom');
|
||||
return this._hostNode.querySelector(".page-guard.bottom");
|
||||
}
|
||||
|
||||
get _pagesHolderNode() {
|
||||
return this._hostNode.querySelector('.pages-holder');
|
||||
return this._hostNode.querySelector(".pages-holder");
|
||||
}
|
||||
|
||||
_destroy() {
|
||||
|
@ -82,9 +81,10 @@ class EndlessPageView {
|
|||
let topPageNode = null;
|
||||
let element = document.elementFromPoint(
|
||||
window.innerWidth / 2,
|
||||
window.innerHeight / 2);
|
||||
window.innerHeight / 2
|
||||
);
|
||||
while (element.parentNode !== null) {
|
||||
if (element.classList.contains('page')) {
|
||||
if (element.classList.contains("page")) {
|
||||
topPageNode = element;
|
||||
break;
|
||||
}
|
||||
|
@ -93,15 +93,17 @@ class EndlessPageView {
|
|||
if (!topPageNode) {
|
||||
return;
|
||||
}
|
||||
let topOffset = parseInt(topPageNode.getAttribute('data-offset'));
|
||||
let topLimit = parseInt(topPageNode.getAttribute('data-limit'));
|
||||
let topOffset = parseInt(topPageNode.getAttribute("data-offset"));
|
||||
let topLimit = parseInt(topPageNode.getAttribute("data-limit"));
|
||||
if (topOffset !== this.currentOffset) {
|
||||
router.replace(
|
||||
ctx.getClientUrlForPage(
|
||||
topOffset,
|
||||
topLimit === ctx.defaultLimit ? null : topLimit),
|
||||
topLimit === ctx.defaultLimit ? null : topLimit
|
||||
),
|
||||
ctx.state,
|
||||
false);
|
||||
false
|
||||
);
|
||||
this.currentOffset = topOffset;
|
||||
}
|
||||
}
|
||||
|
@ -115,43 +117,47 @@ class EndlessPageView {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.minOffsetShown > 0 &&
|
||||
isScrolledIntoView(this.topPageGuardNode)) {
|
||||
if (
|
||||
this.minOffsetShown > 0 &&
|
||||
isScrolledIntoView(this.topPageGuardNode)
|
||||
) {
|
||||
this._loadPage(
|
||||
ctx,
|
||||
this.minOffsetShown - this.defaultLimit,
|
||||
this.defaultLimit,
|
||||
false);
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (this.maxOffsetShown < this.totalRecords &&
|
||||
isScrolledIntoView(this.bottomPageGuardNode)) {
|
||||
this._loadPage(
|
||||
ctx,
|
||||
this.maxOffsetShown,
|
||||
this.defaultLimit,
|
||||
true);
|
||||
if (
|
||||
this.maxOffsetShown < this.totalRecords &&
|
||||
isScrolledIntoView(this.bottomPageGuardNode)
|
||||
) {
|
||||
this._loadPage(ctx, this.maxOffsetShown, this.defaultLimit, true);
|
||||
}
|
||||
}
|
||||
|
||||
_loadPage(ctx, offset, limit, append) {
|
||||
this._runningRequests++;
|
||||
return new Promise((resolve, reject) => {
|
||||
ctx.requestPage(offset, limit).then(response => {
|
||||
if (!this._active) {
|
||||
ctx.requestPage(offset, limit).then(
|
||||
(response) => {
|
||||
if (!this._active) {
|
||||
this._runningRequests--;
|
||||
return Promise.reject();
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
let pageNode = this._renderPage(ctx, append, response);
|
||||
this._runningRequests--;
|
||||
resolve(pageNode);
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
this.showError(error.message);
|
||||
this._runningRequests--;
|
||||
return Promise.reject();
|
||||
reject();
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
let pageNode = this._renderPage(ctx, append, response);
|
||||
this._runningRequests--;
|
||||
resolve(pageNode);
|
||||
});
|
||||
}, error => {
|
||||
this.showError(error.message);
|
||||
this._runningRequests--;
|
||||
reject();
|
||||
});
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -162,30 +168,35 @@ class EndlessPageView {
|
|||
pageNode = pageTemplate({
|
||||
totalPages: Math.ceil(response.total / response.limit),
|
||||
page: Math.ceil(
|
||||
(response.offset + response.limit) / response.limit),
|
||||
(response.offset + response.limit) / response.limit
|
||||
),
|
||||
});
|
||||
pageNode.setAttribute('data-offset', response.offset);
|
||||
pageNode.setAttribute('data-limit', response.limit);
|
||||
pageNode.setAttribute("data-offset", response.offset);
|
||||
pageNode.setAttribute("data-limit", response.limit);
|
||||
|
||||
ctx.pageRenderer({
|
||||
parameters: ctx.parameters,
|
||||
response: response,
|
||||
hostNode: pageNode.querySelector('.page-content-holder'),
|
||||
hostNode: pageNode.querySelector(".page-content-holder"),
|
||||
});
|
||||
|
||||
this.totalRecords = response.total;
|
||||
|
||||
if (response.offset < this.minOffsetShown ||
|
||||
this.minOffsetShown === null) {
|
||||
if (
|
||||
response.offset < this.minOffsetShown ||
|
||||
this.minOffsetShown === null
|
||||
) {
|
||||
this.minOffsetShown = response.offset;
|
||||
}
|
||||
if (response.offset + response.results.length
|
||||
> this.maxOffsetShown ||
|
||||
this.maxOffsetShown === null) {
|
||||
if (
|
||||
response.offset + response.results.length >
|
||||
this.maxOffsetShown ||
|
||||
this.maxOffsetShown === null
|
||||
) {
|
||||
this.maxOffsetShown =
|
||||
response.offset + response.results.length;
|
||||
}
|
||||
response.results.addEventListener('remove', e => {
|
||||
response.results.addEventListener("remove", (e) => {
|
||||
this.maxOffsetShown--;
|
||||
this.totalRecords--;
|
||||
});
|
||||
|
@ -200,10 +211,11 @@ class EndlessPageView {
|
|||
|
||||
window.scroll(
|
||||
window.scrollX,
|
||||
window.scrollY + pageNode.offsetHeight);
|
||||
window.scrollY + pageNode.offsetHeight
|
||||
);
|
||||
}
|
||||
} else if (!response.results.length) {
|
||||
this.showInfo('No data to show');
|
||||
this.showInfo("No data to show");
|
||||
}
|
||||
|
||||
this._initialPageLoad = false;
|
||||
|
|
|
@ -1,73 +1,81 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const api = require('../api.js');
|
||||
const views = require('../util/views.js');
|
||||
const api = require("../api.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('help');
|
||||
const template = views.getTemplate("help");
|
||||
const sectionTemplates = {
|
||||
'about': views.getTemplate('help-about'),
|
||||
'keyboard': views.getTemplate('help-keyboard'),
|
||||
'search': views.getTemplate('help-search'),
|
||||
'comments': views.getTemplate('help-comments'),
|
||||
'tos': views.getTemplate('help-tos'),
|
||||
about: views.getTemplate("help-about"),
|
||||
keyboard: views.getTemplate("help-keyboard"),
|
||||
search: views.getTemplate("help-search"),
|
||||
comments: views.getTemplate("help-comments"),
|
||||
tos: views.getTemplate("help-tos"),
|
||||
};
|
||||
const subsectionTemplates = {
|
||||
'search': {
|
||||
'default': views.getTemplate('help-search-general'),
|
||||
'posts': views.getTemplate('help-search-posts'),
|
||||
'users': views.getTemplate('help-search-users'),
|
||||
'tags': views.getTemplate('help-search-tags'),
|
||||
'pools': views.getTemplate('help-search-pools'),
|
||||
search: {
|
||||
default: views.getTemplate("help-search-general"),
|
||||
posts: views.getTemplate("help-search-posts"),
|
||||
users: views.getTemplate("help-search-users"),
|
||||
tags: views.getTemplate("help-search-tags"),
|
||||
pools: views.getTemplate("help-search-pools"),
|
||||
},
|
||||
};
|
||||
|
||||
class HelpView {
|
||||
constructor(section, subsection) {
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
|
||||
const sourceNode = template();
|
||||
const ctx = {
|
||||
name: api.getName(),
|
||||
};
|
||||
|
||||
section = section || 'about';
|
||||
section = section || "about";
|
||||
if (section in sectionTemplates) {
|
||||
views.replaceContent(
|
||||
sourceNode.querySelector('.content'),
|
||||
sectionTemplates[section](ctx));
|
||||
sourceNode.querySelector(".content"),
|
||||
sectionTemplates[section](ctx)
|
||||
);
|
||||
}
|
||||
|
||||
subsection = subsection || 'default';
|
||||
if (section in subsectionTemplates &&
|
||||
subsection in subsectionTemplates[section]) {
|
||||
subsection = subsection || "default";
|
||||
if (
|
||||
section in subsectionTemplates &&
|
||||
subsection in subsectionTemplates[section]
|
||||
) {
|
||||
views.replaceContent(
|
||||
sourceNode.querySelector('.subcontent'),
|
||||
subsectionTemplates[section][subsection](ctx));
|
||||
sourceNode.querySelector(".subcontent"),
|
||||
subsectionTemplates[section][subsection](ctx)
|
||||
);
|
||||
}
|
||||
|
||||
views.replaceContent(this._hostNode, sourceNode);
|
||||
|
||||
for (let itemNode of
|
||||
sourceNode.querySelectorAll('.primary [data-name]')) {
|
||||
for (let itemNode of sourceNode.querySelectorAll(
|
||||
".primary [data-name]"
|
||||
)) {
|
||||
itemNode.classList.toggle(
|
||||
'active',
|
||||
itemNode.getAttribute('data-name') === section);
|
||||
if (itemNode.getAttribute('data-name') === section) {
|
||||
"active",
|
||||
itemNode.getAttribute("data-name") === section
|
||||
);
|
||||
if (itemNode.getAttribute("data-name") === section) {
|
||||
itemNode.parentNode.scrollLeft =
|
||||
itemNode.getBoundingClientRect().left -
|
||||
itemNode.parentNode.getBoundingClientRect().left
|
||||
itemNode.parentNode.getBoundingClientRect().left;
|
||||
}
|
||||
}
|
||||
|
||||
for (let itemNode of
|
||||
sourceNode.querySelectorAll('.secondary [data-name]')) {
|
||||
for (let itemNode of sourceNode.querySelectorAll(
|
||||
".secondary [data-name]"
|
||||
)) {
|
||||
itemNode.classList.toggle(
|
||||
'active',
|
||||
itemNode.getAttribute('data-name') === subsection);
|
||||
if (itemNode.getAttribute('data-name') === subsection) {
|
||||
"active",
|
||||
itemNode.getAttribute("data-name") === subsection
|
||||
);
|
||||
if (itemNode.getAttribute("data-name") === subsection) {
|
||||
itemNode.parentNode.scrollLeft =
|
||||
itemNode.getBoundingClientRect().left -
|
||||
itemNode.parentNode.getBoundingClientRect().left
|
||||
itemNode.parentNode.getBoundingClientRect().left;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const uri = require('../util/uri.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const PostContentControl = require('../controls/post_content_control.js');
|
||||
const PostNotesOverlayControl
|
||||
= require('../controls/post_notes_overlay_control.js');
|
||||
const TagAutoCompleteControl =
|
||||
require('../controls/tag_auto_complete_control.js');
|
||||
const router = require("../router.js");
|
||||
const uri = require("../util/uri.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const views = require("../util/views.js");
|
||||
const PostContentControl = require("../controls/post_content_control.js");
|
||||
const PostNotesOverlayControl = require("../controls/post_notes_overlay_control.js");
|
||||
const TagAutoCompleteControl = require("../controls/tag_auto_complete_control.js");
|
||||
|
||||
const template = views.getTemplate('home');
|
||||
const footerTemplate = views.getTemplate('home-footer');
|
||||
const featuredPostTemplate = views.getTemplate('home-featured-post');
|
||||
const template = views.getTemplate("home");
|
||||
const footerTemplate = views.getTemplate("home-footer");
|
||||
const featuredPostTemplate = views.getTemplate("home-featured-post");
|
||||
|
||||
class HomeView {
|
||||
constructor(ctx) {
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
this._ctx = ctx;
|
||||
|
||||
const sourceNode = template(ctx);
|
||||
|
@ -27,11 +25,16 @@ class HomeView {
|
|||
this._autoCompleteControl = new TagAutoCompleteControl(
|
||||
this._searchInputNode,
|
||||
{
|
||||
confirm: tag => this._autoCompleteControl.replaceSelectedText(
|
||||
misc.escapeSearchTerm(tag.names[0]), true),
|
||||
});
|
||||
this._formNode.addEventListener(
|
||||
'submit', e => this._evtFormSubmit(e));
|
||||
confirm: (tag) =>
|
||||
this._autoCompleteControl.replaceSelectedText(
|
||||
misc.escapeSearchTerm(tag.names[0]),
|
||||
true
|
||||
),
|
||||
}
|
||||
);
|
||||
this._formNode.addEventListener("submit", (e) =>
|
||||
this._evtFormSubmit(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,59 +49,67 @@ class HomeView {
|
|||
setStats(stats) {
|
||||
views.replaceContent(
|
||||
this._footerContainerNode,
|
||||
footerTemplate(Object.assign({}, stats, this._ctx)));
|
||||
footerTemplate(Object.assign({}, stats, this._ctx))
|
||||
);
|
||||
}
|
||||
|
||||
setFeaturedPost(postInfo) {
|
||||
views.replaceContent(
|
||||
this._postInfoContainerNode, featuredPostTemplate(postInfo));
|
||||
this._postInfoContainerNode,
|
||||
featuredPostTemplate(postInfo)
|
||||
);
|
||||
if (this._postContainerNode && postInfo.featuredPost) {
|
||||
this._postContentControl = new PostContentControl(
|
||||
this._postContainerNode,
|
||||
postInfo.featuredPost,
|
||||
() => {
|
||||
return [
|
||||
window.innerWidth * 0.8,
|
||||
window.innerHeight * 0.7,
|
||||
];
|
||||
return [window.innerWidth * 0.8, window.innerHeight * 0.7];
|
||||
},
|
||||
'fit-both');
|
||||
"fit-both"
|
||||
);
|
||||
|
||||
this._postNotesOverlay = new PostNotesOverlayControl(
|
||||
this._postContainerNode.querySelector('.post-overlay'),
|
||||
postInfo.featuredPost);
|
||||
this._postContainerNode.querySelector(".post-overlay"),
|
||||
postInfo.featuredPost
|
||||
);
|
||||
|
||||
if (postInfo.featuredPost.type === 'video'
|
||||
|| postInfo.featuredPost.type === 'flash') {
|
||||
if (
|
||||
postInfo.featuredPost.type === "video" ||
|
||||
postInfo.featuredPost.type === "flash"
|
||||
) {
|
||||
this._postContentControl.disableOverlay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get _footerContainerNode() {
|
||||
return this._hostNode.querySelector('.footer-container');
|
||||
return this._hostNode.querySelector(".footer-container");
|
||||
}
|
||||
|
||||
get _postInfoContainerNode() {
|
||||
return this._hostNode.querySelector('.post-info-container');
|
||||
return this._hostNode.querySelector(".post-info-container");
|
||||
}
|
||||
|
||||
get _postContainerNode() {
|
||||
return this._hostNode.querySelector('.post-container');
|
||||
return this._hostNode.querySelector(".post-container");
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _searchInputNode() {
|
||||
return this._formNode.querySelector('input[name=search-text]');
|
||||
return this._formNode.querySelector("input[name=search-text]");
|
||||
}
|
||||
|
||||
_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,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,52 +1,63 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const api = require('../api.js');
|
||||
const views = require('../util/views.js');
|
||||
const events = require("../events.js");
|
||||
const api = require("../api.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('login');
|
||||
const template = views.getTemplate("login");
|
||||
|
||||
class LoginView extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
|
||||
views.replaceContent(this._hostNode, template({
|
||||
userNamePattern: api.getUserNameRegex(),
|
||||
passwordPattern: api.getPasswordRegex(),
|
||||
canSendMails: api.canSendMails(),
|
||||
}));
|
||||
views.replaceContent(
|
||||
this._hostNode,
|
||||
template({
|
||||
userNamePattern: api.getUserNameRegex(),
|
||||
passwordPattern: api.getPasswordRegex(),
|
||||
canSendMails: api.canSendMails(),
|
||||
})
|
||||
);
|
||||
views.syncScrollPosition();
|
||||
|
||||
views.decorateValidator(this._formNode);
|
||||
this._userNameInputNode.setAttribute('pattern', api.getUserNameRegex());
|
||||
this._passwordInputNode.setAttribute('pattern', api.getPasswordRegex());
|
||||
this._formNode.addEventListener('submit', e => {
|
||||
this._userNameInputNode.setAttribute(
|
||||
"pattern",
|
||||
api.getUserNameRegex()
|
||||
);
|
||||
this._passwordInputNode.setAttribute(
|
||||
"pattern",
|
||||
api.getPasswordRegex()
|
||||
);
|
||||
this._formNode.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
name: this._userNameInputNode.value,
|
||||
password: this._passwordInputNode.value,
|
||||
remember: this._rememberInputNode.checked,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
name: this._userNameInputNode.value,
|
||||
password: this._passwordInputNode.value,
|
||||
remember: this._rememberInputNode.checked,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _userNameInputNode() {
|
||||
return this._formNode.querySelector('[name=name]');
|
||||
return this._formNode.querySelector("[name=name]");
|
||||
}
|
||||
|
||||
get _passwordInputNode() {
|
||||
return this._formNode.querySelector('[name=password]');
|
||||
return this._formNode.querySelector("[name=password]");
|
||||
}
|
||||
|
||||
get _rememberInputNode() {
|
||||
return this._formNode.querySelector('[name=remember-user]');
|
||||
return this._formNode.querySelector("[name=remember-user]");
|
||||
}
|
||||
|
||||
disableForm() {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const router = require('../router.js');
|
||||
const keyboard = require('../util/keyboard.js');
|
||||
const views = require('../util/views.js');
|
||||
const router = require("../router.js");
|
||||
const keyboard = require("../util/keyboard.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const holderTemplate = views.getTemplate('manual-pager');
|
||||
const navTemplate = views.getTemplate('manual-pager-nav');
|
||||
const holderTemplate = views.getTemplate("manual-pager");
|
||||
const navTemplate = views.getTemplate("manual-pager-nav");
|
||||
|
||||
function _removeConsecutiveDuplicates(a) {
|
||||
return a.filter((item, pos, ary) => {
|
||||
|
@ -22,9 +22,7 @@ function _getVisiblePageNumbers(currentPage, totalPages) {
|
|||
for (let i = totalPages - threshold; i <= totalPages; i++) {
|
||||
pagesVisible.push(i);
|
||||
}
|
||||
for (let i = currentPage - threshold;
|
||||
i <= currentPage + threshold;
|
||||
i++) {
|
||||
for (let i = currentPage - threshold; i <= currentPage + threshold; i++) {
|
||||
pagesVisible.push(i);
|
||||
}
|
||||
pagesVisible = pagesVisible.filter((item, pos, ary) => {
|
||||
|
@ -38,18 +36,22 @@ function _getVisiblePageNumbers(currentPage, totalPages) {
|
|||
}
|
||||
|
||||
function _getPages(
|
||||
currentPage, pageNumbers, limit, defaultLimit, removedItems) {
|
||||
currentPage,
|
||||
pageNumbers,
|
||||
limit,
|
||||
defaultLimit,
|
||||
removedItems
|
||||
) {
|
||||
const pages = new Map();
|
||||
let prevPage = 0;
|
||||
for (let page of pageNumbers) {
|
||||
if (page !== prevPage + 1) {
|
||||
pages.set(page - 1, {ellipsis: true});
|
||||
pages.set(page - 1, { ellipsis: true });
|
||||
}
|
||||
pages.set(page, {
|
||||
number: page,
|
||||
offset:
|
||||
((page - 1) * limit) -
|
||||
(page > currentPage ? removedItems : 0),
|
||||
(page - 1) * limit - (page > currentPage ? removedItems : 0),
|
||||
limit: limit === defaultLimit ? null : limit,
|
||||
active: currentPage === page,
|
||||
});
|
||||
|
@ -60,7 +62,7 @@ function _getPages(
|
|||
|
||||
class ManualPageView {
|
||||
constructor(ctx) {
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
views.replaceContent(this._hostNode, holderTemplate());
|
||||
}
|
||||
|
||||
|
@ -70,52 +72,65 @@ class ManualPageView {
|
|||
this.clearMessages();
|
||||
views.emptyContent(this._pageNavNode);
|
||||
|
||||
ctx.requestPage(offset, limit).then(response => {
|
||||
ctx.pageRenderer({
|
||||
parameters: ctx.parameters,
|
||||
response: response,
|
||||
hostNode: this._pageContentHolderNode,
|
||||
});
|
||||
ctx.requestPage(offset, limit).then(
|
||||
(response) => {
|
||||
ctx.pageRenderer({
|
||||
parameters: ctx.parameters,
|
||||
response: response,
|
||||
hostNode: this._pageContentHolderNode,
|
||||
});
|
||||
|
||||
keyboard.bind(['a', 'left'], () => {
|
||||
this._navigateToPrevNextPage('prev');
|
||||
});
|
||||
keyboard.bind(['d', 'right'], () => {
|
||||
this._navigateToPrevNextPage('next');
|
||||
});
|
||||
keyboard.bind(["a", "left"], () => {
|
||||
this._navigateToPrevNextPage("prev");
|
||||
});
|
||||
keyboard.bind(["d", "right"], () => {
|
||||
this._navigateToPrevNextPage("next");
|
||||
});
|
||||
|
||||
let removedItems = 0;
|
||||
if (response.total) {
|
||||
this._refreshNav(
|
||||
offset, limit, response.total, removedItems, ctx);
|
||||
let removedItems = 0;
|
||||
if (response.total) {
|
||||
this._refreshNav(
|
||||
offset,
|
||||
limit,
|
||||
response.total,
|
||||
removedItems,
|
||||
ctx
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.results.length) {
|
||||
this.showInfo("No data to show");
|
||||
}
|
||||
|
||||
response.results.addEventListener("remove", (e) => {
|
||||
removedItems++;
|
||||
this._refreshNav(
|
||||
offset,
|
||||
limit,
|
||||
response.total,
|
||||
removedItems,
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
views.syncScrollPosition();
|
||||
},
|
||||
(response) => {
|
||||
this.showError(response.message);
|
||||
}
|
||||
|
||||
if (!response.results.length) {
|
||||
this.showInfo('No data to show');
|
||||
}
|
||||
|
||||
response.results.addEventListener('remove', e => {
|
||||
removedItems++;
|
||||
this._refreshNav(
|
||||
offset, limit, response.total, removedItems, ctx);
|
||||
});
|
||||
|
||||
views.syncScrollPosition();
|
||||
}, response => {
|
||||
this.showError(response.message);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
get pageHeaderHolderNode() {
|
||||
return this._hostNode.querySelector('.page-header-holder');
|
||||
return this._hostNode.querySelector(".page-header-holder");
|
||||
}
|
||||
|
||||
get _pageContentHolderNode() {
|
||||
return this._hostNode.querySelector('.page-content-holder');
|
||||
return this._hostNode.querySelector(".page-content-holder");
|
||||
}
|
||||
|
||||
get _pageNavNode() {
|
||||
return this._hostNode.querySelector('.page-nav');
|
||||
return this._hostNode.querySelector(".page-nav");
|
||||
}
|
||||
|
||||
clearMessages() {
|
||||
|
@ -135,11 +150,11 @@ class ManualPageView {
|
|||
}
|
||||
|
||||
_navigateToPrevNextPage(className) {
|
||||
const linkNode = this._hostNode.querySelector('a.' + className);
|
||||
if (linkNode.classList.contains('disabled')) {
|
||||
const linkNode = this._hostNode.querySelector("a." + className);
|
||||
if (linkNode.classList.contains("disabled")) {
|
||||
return;
|
||||
}
|
||||
router.show(linkNode.getAttribute('href'));
|
||||
router.show(linkNode.getAttribute("href"));
|
||||
}
|
||||
|
||||
_refreshNav(offset, limit, total, removedItems, ctx) {
|
||||
|
@ -147,7 +162,12 @@ class ManualPageView {
|
|||
const totalPages = Math.ceil((total - removedItems) / limit);
|
||||
const pageNumbers = _getVisiblePageNumbers(currentPage, totalPages);
|
||||
const pages = _getPages(
|
||||
currentPage, pageNumbers, limit, ctx.defaultLimit, removedItems);
|
||||
currentPage,
|
||||
pageNumbers,
|
||||
limit,
|
||||
ctx.defaultLimit,
|
||||
removedItems
|
||||
);
|
||||
|
||||
views.replaceContent(
|
||||
this._pageNavNode,
|
||||
|
@ -158,7 +178,8 @@ class ManualPageView {
|
|||
currentPage: currentPage,
|
||||
totalPages: totalPages,
|
||||
pages: pages,
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const views = require('../util/views.js');
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('not-found');
|
||||
const template = views.getTemplate("not-found");
|
||||
|
||||
class NotFoundView {
|
||||
constructor(path) {
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
|
||||
const sourceNode = template({path: path});
|
||||
const sourceNode = template({ path: path });
|
||||
views.replaceContent(this._hostNode, sourceNode);
|
||||
views.syncScrollPosition();
|
||||
}
|
||||
|
|
|
@ -1,30 +1,35 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const api = require('../api.js');
|
||||
const views = require('../util/views.js');
|
||||
const events = require("../events.js");
|
||||
const api = require("../api.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('password-reset');
|
||||
const template = views.getTemplate("password-reset");
|
||||
|
||||
class PasswordResetView extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
|
||||
views.replaceContent(this._hostNode, template({
|
||||
canSendMails: api.canSendMails(),
|
||||
contactEmail: api.getContactEmail(),
|
||||
}));
|
||||
views.replaceContent(
|
||||
this._hostNode,
|
||||
template({
|
||||
canSendMails: api.canSendMails(),
|
||||
contactEmail: api.getContactEmail(),
|
||||
})
|
||||
);
|
||||
views.syncScrollPosition();
|
||||
|
||||
views.decorateValidator(this._formNode);
|
||||
this._formNode.addEventListener('submit', e => {
|
||||
this._formNode.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
userNameOrEmail: this._userNameOrEmailFieldNode.value,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
userNameOrEmail: this._userNameOrEmailFieldNode.value,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -49,11 +54,11 @@ class PasswordResetView extends events.EventTarget {
|
|||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _userNameOrEmailFieldNode() {
|
||||
return this._formNode.querySelector('[name=user-name]');
|
||||
return this._formNode.querySelector("[name=user-name]");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const PoolCategory = require('../models/pool_category.js');
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
const PoolCategory = require("../models/pool_category.js");
|
||||
|
||||
const template = views.getTemplate('pool-categories');
|
||||
const rowTemplate = views.getTemplate('pool-category-row');
|
||||
const template = views.getTemplate("pool-categories");
|
||||
const rowTemplate = views.getTemplate("pool-category-row");
|
||||
|
||||
class PoolCategoriesView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
super();
|
||||
this._ctx = ctx;
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
views.syncScrollPosition();
|
||||
|
@ -31,18 +31,22 @@ class PoolCategoriesView extends events.EventTarget {
|
|||
}
|
||||
|
||||
if (this._addLinkNode) {
|
||||
this._addLinkNode.addEventListener(
|
||||
'click', e => this._evtAddButtonClick(e));
|
||||
this._addLinkNode.addEventListener("click", (e) =>
|
||||
this._evtAddButtonClick(e)
|
||||
);
|
||||
}
|
||||
|
||||
ctx.poolCategories.addEventListener(
|
||||
'add', e => this._evtPoolCategoryAdded(e));
|
||||
ctx.poolCategories.addEventListener("add", (e) =>
|
||||
this._evtPoolCategoryAdded(e)
|
||||
);
|
||||
|
||||
ctx.poolCategories.addEventListener(
|
||||
'remove', e => this._evtPoolCategoryDeleted(e));
|
||||
ctx.poolCategories.addEventListener("remove", (e) =>
|
||||
this._evtPoolCategoryDeleted(e)
|
||||
);
|
||||
|
||||
this._formNode.addEventListener(
|
||||
'submit', e => this._evtSaveButtonClick(e, ctx));
|
||||
this._formNode.addEventListener("submit", (e) =>
|
||||
this._evtSaveButtonClick(e, ctx)
|
||||
);
|
||||
}
|
||||
|
||||
enableForm() {
|
||||
|
@ -66,44 +70,48 @@ class PoolCategoriesView extends events.EventTarget {
|
|||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _tableBodyNode() {
|
||||
return this._hostNode.querySelector('tbody');
|
||||
return this._hostNode.querySelector("tbody");
|
||||
}
|
||||
|
||||
get _addLinkNode() {
|
||||
return this._hostNode.querySelector('a.add');
|
||||
return this._hostNode.querySelector("a.add");
|
||||
}
|
||||
|
||||
_addPoolCategoryRowNode(poolCategory) {
|
||||
const rowNode = rowTemplate(
|
||||
Object.assign(
|
||||
{}, this._ctx, {poolCategory: poolCategory}));
|
||||
Object.assign({}, this._ctx, { poolCategory: poolCategory })
|
||||
);
|
||||
|
||||
const nameInput = rowNode.querySelector('.name input');
|
||||
const nameInput = rowNode.querySelector(".name input");
|
||||
if (nameInput) {
|
||||
nameInput.addEventListener(
|
||||
'change', e => this._evtNameChange(e, rowNode));
|
||||
nameInput.addEventListener("change", (e) =>
|
||||
this._evtNameChange(e, rowNode)
|
||||
);
|
||||
}
|
||||
|
||||
const colorInput = rowNode.querySelector('.color input');
|
||||
const colorInput = rowNode.querySelector(".color input");
|
||||
if (colorInput) {
|
||||
colorInput.addEventListener(
|
||||
'change', e => this._evtColorChange(e, rowNode));
|
||||
colorInput.addEventListener("change", (e) =>
|
||||
this._evtColorChange(e, rowNode)
|
||||
);
|
||||
}
|
||||
|
||||
const removeLinkNode = rowNode.querySelector('.remove a');
|
||||
const removeLinkNode = rowNode.querySelector(".remove a");
|
||||
if (removeLinkNode) {
|
||||
removeLinkNode.addEventListener(
|
||||
'click', e => this._evtDeleteButtonClick(e, rowNode));
|
||||
removeLinkNode.addEventListener("click", (e) =>
|
||||
this._evtDeleteButtonClick(e, rowNode)
|
||||
);
|
||||
}
|
||||
|
||||
const defaultLinkNode = rowNode.querySelector('.set-default a');
|
||||
const defaultLinkNode = rowNode.querySelector(".set-default a");
|
||||
if (defaultLinkNode) {
|
||||
defaultLinkNode.addEventListener(
|
||||
'click', e => this._evtSetDefaultButtonClick(e, rowNode));
|
||||
defaultLinkNode.addEventListener("click", (e) =>
|
||||
this._evtSetDefaultButtonClick(e, rowNode)
|
||||
);
|
||||
}
|
||||
|
||||
this._tableBodyNode.appendChild(rowNode);
|
||||
|
@ -141,7 +149,7 @@ class PoolCategoriesView extends events.EventTarget {
|
|||
|
||||
_evtDeleteButtonClick(e, rowNode, link) {
|
||||
e.preventDefault();
|
||||
if (e.target.classList.contains('inactive')) {
|
||||
if (e.target.classList.contains("inactive")) {
|
||||
return;
|
||||
}
|
||||
this._ctx.poolCategories.remove(rowNode._poolCategory);
|
||||
|
@ -150,16 +158,16 @@ class PoolCategoriesView extends events.EventTarget {
|
|||
_evtSetDefaultButtonClick(e, rowNode) {
|
||||
e.preventDefault();
|
||||
this._ctx.poolCategories.defaultCategory = rowNode._poolCategory;
|
||||
const oldRowNode = rowNode.parentNode.querySelector('tr.default');
|
||||
const oldRowNode = rowNode.parentNode.querySelector("tr.default");
|
||||
if (oldRowNode) {
|
||||
oldRowNode.classList.remove('default');
|
||||
oldRowNode.classList.remove("default");
|
||||
}
|
||||
rowNode.classList.add('default');
|
||||
rowNode.classList.add("default");
|
||||
}
|
||||
|
||||
_evtSaveButtonClick(e, ctx) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit'));
|
||||
this.dispatchEvent(new CustomEvent("submit"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,41 +1,43 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const Pool = require('../models/pool.js')
|
||||
const events = require("../events.js");
|
||||
const api = require("../api.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const views = require("../util/views.js");
|
||||
const Pool = require("../models/pool.js");
|
||||
|
||||
const template = views.getTemplate('pool-create');
|
||||
const template = views.getTemplate("pool-create");
|
||||
|
||||
class PoolCreateView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
super();
|
||||
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
|
||||
views.decorateValidator(this._formNode);
|
||||
|
||||
if (this._namesFieldNode) {
|
||||
this._namesFieldNode.addEventListener(
|
||||
'input', e => this._evtNameInput(e));
|
||||
this._namesFieldNode.addEventListener("input", (e) =>
|
||||
this._evtNameInput(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._postsFieldNode) {
|
||||
this._postsFieldNode.addEventListener(
|
||||
'input', e => this._evtPostsInput(e));
|
||||
this._postsFieldNode.addEventListener("input", (e) =>
|
||||
this._evtPostsInput(e)
|
||||
);
|
||||
}
|
||||
|
||||
for (let node of this._formNode.querySelectorAll(
|
||||
'input, select, textarea, posts')) {
|
||||
node.addEventListener(
|
||||
'change', e => {
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
});
|
||||
"input, select, textarea, posts"
|
||||
)) {
|
||||
node.addEventListener("change", (e) => {
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
});
|
||||
}
|
||||
|
||||
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
||||
this._formNode.addEventListener("submit", (e) => this._evtSubmit(e));
|
||||
}
|
||||
|
||||
clearMessages() {
|
||||
|
@ -64,19 +66,21 @@ class PoolCreateView extends events.EventTarget {
|
|||
|
||||
if (!list.length) {
|
||||
this._namesFieldNode.setCustomValidity(
|
||||
'Pools must have at least one name.');
|
||||
"Pools must have at least one name."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of list) {
|
||||
if (!regex.test(item)) {
|
||||
this._namesFieldNode.setCustomValidity(
|
||||
`Pool name "${item}" contains invalid symbols.`);
|
||||
`Pool name "${item}" contains invalid symbols.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._namesFieldNode.setCustomValidity('');
|
||||
this._namesFieldNode.setCustomValidity("");
|
||||
}
|
||||
|
||||
_evtPostsInput(e) {
|
||||
|
@ -86,46 +90,50 @@ class PoolCreateView extends events.EventTarget {
|
|||
for (let item of list) {
|
||||
if (!regex.test(item)) {
|
||||
this._postsFieldNode.setCustomValidity(
|
||||
`Pool ID "${item}" is not an integer.`);
|
||||
`Pool ID "${item}" is not an integer.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._postsFieldNode.setCustomValidity('');
|
||||
this._postsFieldNode.setCustomValidity("");
|
||||
}
|
||||
|
||||
_evtSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
names: misc.splitByWhitespace(this._namesFieldNode.value),
|
||||
category: this._categoryFieldNode.value,
|
||||
description: this._descriptionFieldNode.value,
|
||||
posts: misc.splitByWhitespace(this._postsFieldNode.value)
|
||||
.map(i => parseInt(i))
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
names: misc.splitByWhitespace(this._namesFieldNode.value),
|
||||
category: this._categoryFieldNode.value,
|
||||
description: this._descriptionFieldNode.value,
|
||||
posts: misc
|
||||
.splitByWhitespace(this._postsFieldNode.value)
|
||||
.map((i) => parseInt(i)),
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _namesFieldNode() {
|
||||
return this._formNode.querySelector('.names input');
|
||||
return this._formNode.querySelector(".names input");
|
||||
}
|
||||
|
||||
get _categoryFieldNode() {
|
||||
return this._formNode.querySelector('.category select');
|
||||
return this._formNode.querySelector(".category select");
|
||||
}
|
||||
|
||||
get _descriptionFieldNode() {
|
||||
return this._formNode.querySelector('.description textarea');
|
||||
return this._formNode.querySelector(".description textarea");
|
||||
}
|
||||
|
||||
get _postsFieldNode() {
|
||||
return this._formNode.querySelector('.posts input');
|
||||
return this._formNode.querySelector(".posts input");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('pool-delete');
|
||||
const template = views.getTemplate("pool-delete");
|
||||
|
||||
class PoolDeleteView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
|
@ -13,7 +13,7 @@ class PoolDeleteView extends events.EventTarget {
|
|||
this._pool = ctx.pool;
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
views.decorateValidator(this._formNode);
|
||||
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
||||
this._formNode.addEventListener("submit", (e) => this._evtSubmit(e));
|
||||
}
|
||||
|
||||
clearMessages() {
|
||||
|
@ -38,15 +38,17 @@ class PoolDeleteView extends events.EventTarget {
|
|||
|
||||
_evtSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
pool: this._pool,
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
pool: this._pool,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const Post = require('../models/post.js');
|
||||
const events = require("../events.js");
|
||||
const api = require("../api.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const views = require("../util/views.js");
|
||||
const Post = require("../models/post.js");
|
||||
|
||||
const template = views.getTemplate('pool-edit');
|
||||
const template = views.getTemplate("pool-edit");
|
||||
|
||||
class PoolEditView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
|
@ -19,24 +19,26 @@ class PoolEditView extends events.EventTarget {
|
|||
views.decorateValidator(this._formNode);
|
||||
|
||||
if (this._namesFieldNode) {
|
||||
this._namesFieldNode.addEventListener(
|
||||
'input', e => this._evtNameInput(e));
|
||||
this._namesFieldNode.addEventListener("input", (e) =>
|
||||
this._evtNameInput(e)
|
||||
);
|
||||
}
|
||||
|
||||
if (this._postsFieldNode) {
|
||||
this._postsFieldNode.addEventListener(
|
||||
'input', e => this._evtPostsInput(e));
|
||||
this._postsFieldNode.addEventListener("input", (e) =>
|
||||
this._evtPostsInput(e)
|
||||
);
|
||||
}
|
||||
|
||||
for (let node of this._formNode.querySelectorAll(
|
||||
'input, select, textarea, posts')) {
|
||||
node.addEventListener(
|
||||
'change', e => {
|
||||
this.dispatchEvent(new CustomEvent('change'));
|
||||
});
|
||||
"input, select, textarea, posts"
|
||||
)) {
|
||||
node.addEventListener("change", (e) => {
|
||||
this.dispatchEvent(new CustomEvent("change"));
|
||||
});
|
||||
}
|
||||
|
||||
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
||||
this._formNode.addEventListener("submit", (e) => this._evtSubmit(e));
|
||||
}
|
||||
|
||||
clearMessages() {
|
||||
|
@ -65,19 +67,21 @@ class PoolEditView extends events.EventTarget {
|
|||
|
||||
if (!list.length) {
|
||||
this._namesFieldNode.setCustomValidity(
|
||||
'Pools must have at least one name.');
|
||||
"Pools must have at least one name."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of list) {
|
||||
if (!regex.test(item)) {
|
||||
this._namesFieldNode.setCustomValidity(
|
||||
`Pool name "${item}" contains invalid symbols.`);
|
||||
`Pool name "${item}" contains invalid symbols.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._namesFieldNode.setCustomValidity('');
|
||||
this._namesFieldNode.setCustomValidity("");
|
||||
}
|
||||
|
||||
_evtPostsInput(e) {
|
||||
|
@ -87,57 +91,60 @@ class PoolEditView extends events.EventTarget {
|
|||
for (let item of list) {
|
||||
if (!regex.test(item)) {
|
||||
this._postsFieldNode.setCustomValidity(
|
||||
`Pool ID "${item}" is not an integer.`);
|
||||
`Pool ID "${item}" is not an integer.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._postsFieldNode.setCustomValidity('');
|
||||
this._postsFieldNode.setCustomValidity("");
|
||||
}
|
||||
|
||||
_evtSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
pool: this._pool,
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
pool: this._pool,
|
||||
|
||||
names: this._namesFieldNode ?
|
||||
misc.splitByWhitespace(this._namesFieldNode.value) :
|
||||
undefined,
|
||||
names: this._namesFieldNode
|
||||
? misc.splitByWhitespace(this._namesFieldNode.value)
|
||||
: undefined,
|
||||
|
||||
category: this._categoryFieldNode ?
|
||||
this._categoryFieldNode.value :
|
||||
undefined,
|
||||
category: this._categoryFieldNode
|
||||
? this._categoryFieldNode.value
|
||||
: undefined,
|
||||
|
||||
description: this._descriptionFieldNode ?
|
||||
this._descriptionFieldNode.value :
|
||||
undefined,
|
||||
description: this._descriptionFieldNode
|
||||
? this._descriptionFieldNode.value
|
||||
: undefined,
|
||||
|
||||
posts: this._postsFieldNode ?
|
||||
misc.splitByWhitespace(this._postsFieldNode.value) :
|
||||
undefined,
|
||||
},
|
||||
}));
|
||||
posts: this._postsFieldNode
|
||||
? misc.splitByWhitespace(this._postsFieldNode.value)
|
||||
: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _namesFieldNode() {
|
||||
return this._formNode.querySelector('.names input');
|
||||
return this._formNode.querySelector(".names input");
|
||||
}
|
||||
|
||||
get _categoryFieldNode() {
|
||||
return this._formNode.querySelector('.category select');
|
||||
return this._formNode.querySelector(".category select");
|
||||
}
|
||||
|
||||
get _descriptionFieldNode() {
|
||||
return this._formNode.querySelector('.description textarea');
|
||||
return this._formNode.querySelector(".description textarea");
|
||||
}
|
||||
|
||||
get _postsFieldNode() {
|
||||
return this._formNode.querySelector('.posts input');
|
||||
return this._formNode.querySelector(".posts input");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const api = require('../api.js');
|
||||
const views = require('../util/views.js');
|
||||
const PoolAutoCompleteControl =
|
||||
require('../controls/pool_auto_complete_control.js');
|
||||
const events = require("../events.js");
|
||||
const api = require("../api.js");
|
||||
const views = require("../util/views.js");
|
||||
const PoolAutoCompleteControl = require("../controls/pool_auto_complete_control.js");
|
||||
|
||||
const template = views.getTemplate('pool-merge');
|
||||
const template = views.getTemplate("pool-merge");
|
||||
|
||||
class PoolMergeView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
|
@ -23,15 +22,18 @@ class PoolMergeView extends events.EventTarget {
|
|||
this._autoCompleteControl = new PoolAutoCompleteControl(
|
||||
this._targetPoolFieldNode,
|
||||
{
|
||||
confirm: pool => {
|
||||
confirm: (pool) => {
|
||||
this._targetPoolId = pool.id;
|
||||
this._autoCompleteControl.replaceSelectedText(
|
||||
pool.names[0], false);
|
||||
}
|
||||
});
|
||||
pool.names[0],
|
||||
false
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
||||
this._formNode.addEventListener("submit", (e) => this._evtSubmit(e));
|
||||
}
|
||||
|
||||
clearMessages() {
|
||||
|
@ -56,24 +58,26 @@ class PoolMergeView extends events.EventTarget {
|
|||
|
||||
_evtSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
pool: this._pool,
|
||||
targetPoolId: this._targetPoolId
|
||||
},
|
||||
}));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("submit", {
|
||||
detail: {
|
||||
pool: this._pool,
|
||||
targetPoolId: this._targetPoolId,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this._hostNode.querySelector('form');
|
||||
return this._hostNode.querySelector("form");
|
||||
}
|
||||
|
||||
get _targetPoolFieldNode() {
|
||||
return this._formNode.querySelector('input[name=target-pool]');
|
||||
return this._formNode.querySelector("input[name=target-pool]");
|
||||
}
|
||||
|
||||
get _addAliasCheckboxNode() {
|
||||
return this._formNode.querySelector('input[name=alias]');
|
||||
return this._formNode.querySelector("input[name=alias]");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const views = require('../util/views.js');
|
||||
const views = require("../util/views.js");
|
||||
|
||||
const template = views.getTemplate('pool-summary');
|
||||
const template = views.getTemplate("pool-summary");
|
||||
|
||||
class PoolSummaryView {
|
||||
constructor(ctx) {
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const events = require('../events.js');
|
||||
const views = require('../util/views.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const PoolSummaryView = require('./pool_summary_view.js');
|
||||
const PoolEditView = require('./pool_edit_view.js');
|
||||
const PoolMergeView = require('./pool_merge_view.js');
|
||||
const PoolDeleteView = require('./pool_delete_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
const events = require("../events.js");
|
||||
const views = require("../util/views.js");
|
||||
const misc = require("../util/misc.js");
|
||||
const PoolSummaryView = require("./pool_summary_view.js");
|
||||
const PoolEditView = require("./pool_edit_view.js");
|
||||
const PoolMergeView = require("./pool_merge_view.js");
|
||||
const PoolDeleteView = require("./pool_delete_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
const template = views.getTemplate('pool');
|
||||
const template = views.getTemplate("pool");
|
||||
|
||||
class PoolView extends events.EventTarget {
|
||||
constructor(ctx) {
|
||||
super();
|
||||
|
||||
this._ctx = ctx;
|
||||
ctx.pool.addEventListener('change', e => this._evtChange(e));
|
||||
ctx.section = ctx.section || 'summary';
|
||||
ctx.pool.addEventListener("change", (e) => this._evtChange(e));
|
||||
ctx.section = ctx.section || "summary";
|
||||
ctx.getPrettyPoolName = misc.getPrettyPoolName;
|
||||
|
||||
this._hostNode = document.getElementById('content-holder');
|
||||
this._hostNode = document.getElementById("content-holder");
|
||||
this._install();
|
||||
}
|
||||
|
||||
|
@ -28,52 +28,54 @@ class PoolView extends events.EventTarget {
|
|||
const ctx = this._ctx;
|
||||
views.replaceContent(this._hostNode, template(ctx));
|
||||
|
||||
for (let item of this._hostNode.querySelectorAll('[data-name]')) {
|
||||
for (let item of this._hostNode.querySelectorAll("[data-name]")) {
|
||||
item.classList.toggle(
|
||||
'active', item.getAttribute('data-name') === ctx.section);
|
||||
if (item.getAttribute('data-name') === ctx.section) {
|
||||
"active",
|
||||
item.getAttribute("data-name") === ctx.section
|
||||
);
|
||||
if (item.getAttribute("data-name") === ctx.section) {
|
||||
item.parentNode.scrollLeft =
|
||||
item.getBoundingClientRect().left -
|
||||
item.parentNode.getBoundingClientRect().left
|
||||
item.parentNode.getBoundingClientRect().left;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.hostNode = this._hostNode.querySelector('.pool-content-holder');
|
||||
if (ctx.section === 'edit') {
|
||||
ctx.hostNode = this._hostNode.querySelector(".pool-content-holder");
|
||||
if (ctx.section === "edit") {
|
||||
if (!this._ctx.canEditAnything) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(
|
||||
'You don\'t have privileges to edit pools.');
|
||||
"You don't have privileges to edit pools."
|
||||
);
|
||||
} else {
|
||||
this._view = new PoolEditView(ctx);
|
||||
events.proxyEvent(this._view, this, 'submit');
|
||||
events.proxyEvent(this._view, this, "submit");
|
||||
}
|
||||
|
||||
} else if (ctx.section === 'merge') {
|
||||
} else if (ctx.section === "merge") {
|
||||
if (!this._ctx.canMerge) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(
|
||||
'You don\'t have privileges to merge pools.');
|
||||
"You don't have privileges to merge pools."
|
||||
);
|
||||
} else {
|
||||
this._view = new PoolMergeView(ctx);
|
||||
events.proxyEvent(this._view, this, 'submit', 'merge');
|
||||
events.proxyEvent(this._view, this, "submit", "merge");
|
||||
}
|
||||
|
||||
} else if (ctx.section === 'delete') {
|
||||
} else if (ctx.section === "delete") {
|
||||
if (!this._ctx.canDelete) {
|
||||
this._view = new EmptyView();
|
||||
this._view.showError(
|
||||
'You don\'t have privileges to delete pools.');
|
||||
"You don't have privileges to delete pools."
|
||||
);
|
||||
} else {
|
||||
this._view = new PoolDeleteView(ctx);
|
||||
events.proxyEvent(this._view, this, 'submit', 'delete');
|
||||
events.proxyEvent(this._view, this, "submit", "delete");
|
||||
}
|
||||
|
||||
} else {
|
||||
this._view = new PoolSummaryView(ctx);
|
||||
}
|
||||
|
||||
events.proxyEvent(this._view, this, 'change');
|
||||
events.proxyEvent(this._view, this, "change");
|
||||
views.syncScrollPosition();
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue