diff options
Diffstat (limited to 'client/js/models')
25 files changed, 1629 insertions, 674 deletions
diff --git a/client/js/models/abstract_list.js b/client/js/models/abstract_list.js index 31a0505..7505905 100644 --- a/client/js/models/abstract_list.js +++ b/client/js/models/abstract_list.js @@ -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; } } diff --git a/client/js/models/comment.js b/client/js/models/comment.js index e10e83c..4707a3a 100644 --- a/client/js/models/comment.js +++ b/client/js/models/comment.js @@ -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() { @@ -22,77 +22,112 @@ class Comment extends events.EventTarget { return comment; } - get id() { return this._id; } - get postId() { return this._postId; } - get text() { return this._text || ''; } - get user() { return this._user; } - get creationTime() { return this._creationTime; } - get lastEditTime() { return this._lastEditTime; } - get score() { return this._score; } - get ownScore() { return this._ownScore; } + get id() { + return this._id; + } + + get postId() { + return this._postId; + } - set text(value) { this._text = value; } + get text() { + return this._text || ""; + } + + get user() { + return this._user; + } + + get creationTime() { + return this._creationTime; + } + + get lastEditTime() { + return this._lastEditTime; + } + + get score() { + return this._score; + } + + get ownScore() { + return this._ownScore; + } + + set text(value) { + this._text = value; + } save() { const detail = { 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(); }); } _updateFromResponse(response) { - this._version = response.version; - this._id = response.id; - this._postId = response.postId; - this._text = response.text; - this._user = response.user; + this._version = response.version; + this._id = response.id; + this._postId = response.postId; + this._text = response.text; + this._user = response.user; this._creationTime = response.creationTime; this._lastEditTime = response.lastEditTime; - this._score = parseInt(response.score); - this._ownScore = parseInt(response.ownScore); + this._score = parseInt(response.score); + this._ownScore = parseInt(response.ownScore); } } diff --git a/client/js/models/comment_list.js b/client/js/models/comment_list.js index a8e1150..bae2d7a 100644 --- a/client/js/models/comment_list.js +++ b/client/js/models/comment_list.js @@ -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; diff --git a/client/js/models/info.js b/client/js/models/info.js index 35ba867..6b03b38 100644 --- a/client/js/models/info.js +++ b/client/js/models/info.js @@ -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, + }) + ); + }); } } diff --git a/client/js/models/note.js b/client/js/models/note.js index 877db4e..87c8b3b 100644 --- a/client/js/models/note.js +++ b/client/js/models/note.js @@ -1,20 +1,27 @@ -'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(); } - get text() { return this._text; } - get polygon() { return this._polygon; } + get text() { + return this._text; + } + + get polygon() { + return this._polygon; + } - set text(value) { this._text = value; } + set text(value) { + this._text = value; + } static fromResponse(response) { const note = new Note(); diff --git a/client/js/models/note_list.js b/client/js/models/note_list.js index b54d7f3..10db435 100644 --- a/client/js/models/note_list.js +++ b/client/js/models/note_list.js @@ -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; diff --git a/client/js/models/point.js b/client/js/models/point.js index f1c551e..70b4961 100644 --- a/client/js/models/point.js +++ b/client/js/models/point.js @@ -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) { @@ -9,18 +9,27 @@ class Point extends events.EventTarget { this._y = y; } - get x() { return this._x; } - get y() { return this._y; } + get x() { + return this._x; + } + + get y() { + return this._y; + } set x(value) { this._x = value; - 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 } }) + ); } -}; +} module.exports = Point; diff --git a/client/js/models/point_list.js b/client/js/models/point_list.js index 166e664..3ecd7d7 100644 --- a/client/js/models/point_list.js +++ b/client/js/models/point_list.js @@ -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; diff --git a/client/js/models/pool.js b/client/js/models/pool.js new file mode 100644 index 0000000..51fa8a0 --- /dev/null +++ b/client/js/models/pool.js @@ -0,0 +1,183 @@ +"use strict"; + +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"); + + super(); + this._orig = {}; + + for (let obj of [this, this._orig]) { + obj._posts = new PostList(); + } + + this._updateFromResponse({}); + } + + get id() { + return this._id; + } + + get names() { + return this._names; + } + + get category() { + return this._category; + } + + get description() { + return this._description; + } + + get posts() { + return this._posts; + } + + get postCount() { + return this._postCount; + } + + get creationTime() { + return this._creationTime; + } + + get lastEditTime() { + return this._lastEditTime; + } + + set names(value) { + this._names = value; + } + + set category(value) { + this._category = value; + } + + set description(value) { + this._description = value; + } + + static fromResponse(response) { + const ret = new Pool(); + ret._updateFromResponse(response); + return ret; + } + + static get(id) { + return api.get(uri.formatApiLink("pool", id)).then((response) => { + return Promise.resolve(Pool.fromResponse(response)); + }); + } + + save() { + const detail = { version: this._version }; + + // send only changed fields to avoid user privilege violation + if (misc.arraysDiffer(this._names, this._orig._names, true)) { + detail.names = this._names; + } + if (this._category !== this._orig._category) { + detail.category = this._category; + } + if (this._description !== this._orig._description) { + detail.description = this._description; + } + if (misc.arraysDiffer(this._posts, this._orig._posts)) { + 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", { + detail: { + pool: this, + }, + }) + ); + return Promise.resolve(); + }); + } + + merge(targetId, addAlias) { + 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) => { + if (!addAlias) { + return Promise.resolve(response); + } + return api.put(uri.formatApiLink("pool", targetId), { + version: response.version, + names: response.names.concat(this._names), + }); + }) + .then((response) => { + this._updateFromResponse(response); + 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 Promise.resolve(); + }); + } + + _updateFromResponse(response) { + const map = { + _id: response.id, + _version: response.version, + _origName: response.names ? response.names[0] : null, + _names: response.names, + _category: response.category, + _description: response.description, + _creationTime: response.creationTime, + _lastEditTime: response.lastEditTime, + _postCount: response.postCount || 0, + }; + + for (let obj of [this, this._orig]) { + obj._posts.sync(response.posts); + } + + Object.assign(this, map); + Object.assign(this._orig, map); + } +} + +module.exports = Pool; diff --git a/client/js/models/pool_category.js b/client/js/models/pool_category.js new file mode 100644 index 0000000..8c7df46 --- /dev/null +++ b/client/js/models/pool_category.js @@ -0,0 +1,114 @@ +"use strict"; + +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._poolCount = 0; + this._isDefault = false; + this._origName = null; + this._origColor = null; + } + + get name() { + return this._name; + } + + get color() { + return this._color; + } + + get poolCount() { + return this._poolCount; + } + + get isDefault() { + return this._isDefault; + } + + get isTransient() { + return !this._origName; + } + + set name(value) { + this._name = value; + } + + set color(value) { + this._color = value; + } + + static fromResponse(response) { + const ret = new PoolCategory(); + ret._updateFromResponse(response); + return ret; + } + + save() { + const detail = { version: this._version }; + + if (this.name !== this._origName) { + detail.name = this.name; + } + if (this.color !== this._origColor) { + detail.color = this.color; + } + + if (!Object.keys(detail).length) { + return Promise.resolve(); + } + + 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", { + detail: { + poolCategory: this, + }, + }) + ); + 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 Promise.resolve(); + }); + } + + _updateFromResponse(response) { + this._version = response.version; + this._name = response.name; + this._color = response.color; + this._isDefault = response.default; + this._poolCount = response.usages; + this._origName = this.name; + this._origColor = this.color; + } +} + +module.exports = PoolCategory; diff --git a/client/js/models/pool_category_list.js b/client/js/models/pool_category_list.js new file mode 100644 index 0000000..46b7838 --- /dev/null +++ b/client/js/models/pool_category_list.js @@ -0,0 +1,88 @@ +"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"); + +class PoolCategoryList extends AbstractList { + constructor() { + super(); + this._defaultCategory = null; + this._origDefaultCategory = null; + this._deletedCategories = []; + this.addEventListener("remove", (e) => this._evtCategoryDeleted(e)); + } + + static fromResponse(response) { + const ret = super.fromResponse(response); + ret._defaultCategory = null; + for (let poolCategory of ret) { + if (poolCategory.isDefault) { + ret._defaultCategory = poolCategory; + } + } + ret._origDefaultCategory = ret._defaultCategory; + return ret; + } + + static get() { + return api + .get(uri.formatApiLink("pool-categories")) + .then((response) => { + return Promise.resolve( + Object.assign({}, response, { + results: PoolCategoryList.fromResponse( + response.results + ), + }) + ); + }); + } + + get defaultCategory() { + return this._defaultCategory; + } + + set defaultCategory(poolCategory) { + this._defaultCategory = poolCategory; + } + + save() { + let promises = []; + for (let poolCategory of this) { + promises.push(poolCategory.save()); + } + for (let poolCategory of this._deletedCategories) { + promises.push(poolCategory.delete()); + } + + if (this._defaultCategory !== this._origDefaultCategory) { + promises.push( + api.put( + uri.formatApiLink( + "pool-category", + this._defaultCategory.name, + "default" + ) + ) + ); + } + + return Promise.all(promises).then((response) => { + this._deletedCategories = []; + return Promise.resolve(); + }); + } + + _evtCategoryDeleted(e) { + if (!e.detail.poolCategory.isTransient) { + this._deletedCategories.push(e.detail.poolCategory); + } + } +} + +PoolCategoryList._itemClass = PoolCategory; +PoolCategoryList._itemName = "poolCategory"; + +module.exports = PoolCategoryList; diff --git a/client/js/models/pool_list.js b/client/js/models/pool_list.js new file mode 100644 index 0000000..a8839bb --- /dev/null +++ b/client/js/models/pool_list.js @@ -0,0 +1,49 @@ +"use strict"; + +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", { + query: text, + offset: offset, + limit: limit, + fields: fields.join(","), + }) + ) + .then((response) => { + return Promise.resolve( + Object.assign({}, response, { + results: PoolList.fromResponse(response.results), + }) + ); + }); + } + + hasPoolId(poolId) { + for (let pool of this._list) { + if (pool.id === poolId) { + return true; + } + } + return false; + } + + removeById(poolId) { + for (let pool of this._list) { + if (pool.id === poolId) { + this.remove(pool); + } + } + } +} + +PoolList._itemClass = Pool; +PoolList._itemName = "pool"; + +module.exports = PoolList; diff --git a/client/js/models/post.js b/client/js/models/post.js index 13e4c3d..a22f3b5 100644 --- a/client/js/models/post.js +++ b/client/js/models/post.js @@ -1,15 +1,17 @@ -'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 PostMetricList = require('./post_metric_list.js'); -const PostMetricRangeList = require('./post_metric_range_list.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 PostMetricList = require("./post_metric_list.js"); +const PostMetricRangeList = require("./post_metric_range_list.js"); +const misc = require("../util/misc.js"); class Post extends events.EventTarget { constructor() { @@ -20,6 +22,7 @@ class Post extends events.EventTarget { obj._tags = new TagList(); obj._notes = new NoteList(); obj._comments = new CommentList(); + obj._pools = new PoolList(); obj._metrics = new PostMetricList(); obj._metricRanges = new PostMetricRangeList(); } @@ -27,44 +30,153 @@ class Post extends events.EventTarget { this._updateFromResponse({}); } - get id() { return this._id; } - get type() { return this._type; } - get mimeType() { return this._mimeType; } - get creationTime() { return this._creationTime; } - get user() { return this._user; } - get safety() { return this._safety; } - get contentUrl() { return this._contentUrl; } - get fullContentUrl() { return this._fullContentUrl; } - get thumbnailUrl() { return this._thumbnailUrl; } - get source() { return this._source; } - get canvasWidth() { return this._canvasWidth || 800; } - get canvasHeight() { return this._canvasHeight || 450; } - get fileSize() { return this._fileSize || 0; } - get newContent() { throw 'Invalid operation'; } - get newThumbnail() { throw 'Invalid operation'; } + get id() { + return this._id; + } + + get type() { + return this._type; + } + + get mimeType() { + return this._mimeType; + } + + get creationTime() { + return this._creationTime; + } + + get user() { + return this._user; + } + + get safety() { + return this._safety; + } + + get contentUrl() { + return this._contentUrl; + } + + get fullContentUrl() { + return this._fullContentUrl; + } + + get thumbnailUrl() { + return this._thumbnailUrl; + } + + get source() { + return this._source; + } + + get sourceSplit() { + return this._source.split("\n"); + } + + get canvasWidth() { + return this._canvasWidth || 800; + } + + get canvasHeight() { + return this._canvasHeight || 450; + } + + get fileSize() { + return this._fileSize || 0; + } + + get newContent() { + throw "Invalid operation"; + } + + get newThumbnail() { + throw "Invalid operation"; + } + + get flags() { + return this._flags; + } + + get tags() { + return this._tags; + } + + get tagNames() { + return this._tags.map((tag) => tag.names[0]); + } + + get notes() { + return this._notes; + } + + get comments() { + return this._comments; + } + + get relations() { + return this._relations; + } + + get pools() { + return this._pools; + } - get flags() { return this._flags; } - get tags() { return this._tags; } - get tagNames() { return this._tags.map(tag => tag.names[0]); } - get notes() { return this._notes; } - get comments() { return this._comments; } - get relations() { return this._relations; } - get metrics() { return this._metrics; } - get metricRanges() { return this._metricRanges; } + get metrics() { + return this._metrics; + } + + get metricRanges() { + return this._metricRanges; + } + + get score() { + return this._score; + } + + get commentCount() { + return this._commentCount; + } - get score() { return this._score; } - get commentCount() { return this._commentCount; } - get favoriteCount() { return this._favoriteCount; } - get ownFavorite() { return this._ownFavorite; } - get ownScore() { return this._ownScore; } - get hasCustomThumbnail() { return this._hasCustomThumbnail; } + get favoriteCount() { + return this._favoriteCount; + } - set flags(value) { this._flags = value; } - set safety(value) { this._safety = value; } - set relations(value) { this._relations = value; } - set newContent(value) { this._newContent = value; } - set newThumbnail(value) { this._newThumbnail = value; } - set source(value) { this._source = value; } + get ownFavorite() { + return this._ownFavorite; + } + + get ownScore() { + return this._ownScore; + } + + get hasCustomThumbnail() { + return this._hasCustomThumbnail; + } + + set flags(value) { + this._flags = value; + } + + set safety(value) { + this._safety = value; + } + + set relations(value) { + this._relations = value; + } + + set newContent(value) { + this._newContent = value; + } + + set newThumbnail(value) { + this._newThumbnail = value; + } + + set source(value) { + this._source = value; + } static fromResponse(response) { const ret = new Post(); @@ -74,33 +186,69 @@ 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)); + + // find the pools where the post was added or removed + const added = difference(this.pools, this._orig._pools); + const removed = difference(this._orig._pools, this.pools); + + let ops = []; + + // update each pool's list of posts + for (let pool of added) { + let op = Pool.get(pool.id).then((response) => { + if (!response.posts.hasPostId(this._id)) { + response.posts.addById(this._id); + return response.save(); + } else { + return Promise.resolve(response); + } }); + ops.push(op); + } + + for (let pool of removed) { + let op = Pool.get(pool.id).then((response) => { + if (response.posts.hasPostId(this._id)) { + response.posts.removeById(this._id); + return response.save(); + } else { + return Promise.resolve(response); + } + }); + ops.push(op); + } + + return Promise.all(ops); } 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) { @@ -113,14 +261,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, })); } @@ -147,138 +295,178 @@ 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 => { - 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 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); + } + ); } 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(); }); } @@ -293,39 +481,38 @@ class Post extends events.EventTarget { mutateContentUrl() { this._contentUrl = this._orig._contentUrl + - '?bypass-cache=' + + "?bypass-cache=" + Math.round(Math.random() * 1000); } - prettyPrintSource() { - return uri.extractRootDomain(this._source); - } - _updateFromResponse(response) { const map = () => ({ - _version: response.version, - _id: response.id, - _type: response.type, - _mimeType: response.mimeType, - _creationTime: response.creationTime, - _user: response.user, - _safety: response.safety, - _contentUrl: response.contentUrl, - _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, + _version: response.version, + _id: response.id, + _type: response.type, + _mimeType: response.mimeType, + _creationTime: response.creationTime, + _user: response.user, + _safety: response.safety, + _contentUrl: response.contentUrl, + _fullContentUrl: new URL( + response.contentUrl, + document.getElementsByTagName("base")[0].href + ).href, + _thumbnailUrl: response.thumbnailUrl, + _source: response.source, + _canvasWidth: response.canvasWidth, + _canvasHeight: response.canvasHeight, + _fileSize: response.fileSize, - _flags: [...response.flags || []], - _relations: [...response.relations || []], + _flags: [...(response.flags || [])], + _relations: [...(response.relations || [])], - _score: response.score, - _commentCount: response.commentCount, + _score: response.score, + _commentCount: response.commentCount, _favoriteCount: response.favoriteCount, - _ownScore: response.ownScore, - _ownFavorite: response.ownFavorite, + _ownScore: response.ownScore, + _ownFavorite: response.ownFavorite, _hasCustomThumbnail: response.hasCustomThumbnail, }); @@ -333,6 +520,7 @@ class Post extends events.EventTarget { obj._tags.sync(response.tags); obj._notes.sync(response.notes); obj._comments.sync(response.comments); + obj._pools.sync(response.pools); obj._metrics.sync(response.metrics); obj._metricRanges.sync(response.metricRanges); } @@ -340,6 +528,6 @@ class Post extends events.EventTarget { Object.assign(this, map()); Object.assign(this._orig, map()); } -}; +} module.exports = Post; diff --git a/client/js/models/post_list.js b/client/js/models/post_list.js index 8024ae4..3db7ade 100644 --- a/client/js/models/post_list.js +++ b/client/js/models/post_list.js @@ -1,52 +1,56 @@ -'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, cachenumber) { return api.get( - uri.formatApiLink( - 'post', id, 'around', { - query: PostList._decorateSearchQuery(searchQuery || ''), - fields: 'id', - cachenumber: cachenumber, - })); + uri.formatApiLink("post", id, "around", { + query: PostList._decorateSearchQuery(searchQuery || ""), + fields: "id", + cachenumber: cachenumber, + }) + ); } static search(text, offset, limit, fields, cachenumber) { - return api.get( - uri.formatApiLink( - 'posts', { - query: PostList._decorateSearchQuery(text || ''), - offset: offset, - limit: limit, - fields: fields.join(','), - cachenumber: cachenumber, - })) - .then(response => { - return Promise.resolve(Object.assign( - {}, - response, - {results: PostList.fromResponse(response.results)})); + return api + .get( + uri.formatApiLink("posts", { + query: PostList._decorateSearchQuery(text || ""), + offset: offset, + limit: limit, + fields: fields.join(","), + cachenumber: cachenumber, + }) + ) + .then((response) => { + return Promise.resolve( + Object.assign({}, response, { + results: PostList.fromResponse(response.results), + }) + ); }); } static getMedian(text, fields) { - return api.get( - uri.formatApiLink( - 'posts', 'median', { - query: PostList._decorateSearchQuery(text || ''), - fields: fields.join(','), - })) - .then(response => { - return Promise.resolve(Object.assign( - {}, - response, - {results: PostList.fromResponse(response.results)})); + return api + .get( + uri.formatApiLink("posts", "median", { + query: PostList._decorateSearchQuery(text || ""), + fields: fields.join(","), + }) + ) + .then((response) => { + return Promise.resolve( + Object.assign({}, response, { + results: PostList.fromResponse(response.results) + }) + ); }); } @@ -60,15 +64,40 @@ class PostList extends AbstractList { } } if (disabledSafety.length) { - text = `-rating:${disabledSafety.join(',')} ${text}`; + text = `-rating:${disabledSafety.join(",")} ${text}`; } } return text.trim(); } + hasPostId(testId) { + for (let post of this._list) { + if (post.id === testId) { + return true; + } + } + return false; + } + + addById(id) { + if (this.hasPostId(id)) { + return; + } + + let post = Post.fromResponse({ id: id }); + this.add(post); + } + + removeById(testId) { + for (let post of this._list) { + if (post.id === testId) { + this.remove(post); + } + } + } } PostList._itemClass = Post; -PostList._itemName = 'post'; +PostList._itemName = "post"; module.exports = PostList; diff --git a/client/js/models/settings.js b/client/js/models/settings.js index 0001360..af937fa 100644 --- a/client/js/models/settings.js +++ b/client/js/models/settings.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -const events = require('../events.js'); -const api = require('../api.js'); +const api = require("../api.js"); +const events = require("../events.js"); const defaultSettings = { listPosts: { @@ -9,42 +9,58 @@ const defaultSettings = { sketchy: false, unsafe: false, }, - uploadSafety: 'safe', + uploadSafety: "safe", upscaleSmallPosts: false, endlessScroll: false, keyboardShortcuts: true, transparencyGrid: false, - fitMode: 'fit-both', + fitMode: "fit-both", tagSuggestions: true, autoplayVideos: false, postsPerPage: 40, + tagUnderscoresAsSpaces: false, + darkTheme: false, + postFlow: false, }; class Settings extends events.EventTarget { - save(newSettings, silent) { - newSettings = Object.assign(this.get(), newSettings); - localStorage.setItem(this._settingsKey, JSON.stringify(newSettings)); - if (silent !== true) { - this.dispatchEvent(new CustomEvent('change', { - detail: { - settings: this.get(), - }, - })); - } + constructor() { + super(); + this.cache = this._getFromLocalStorage(); } - get() { + _getFromLocalStorage() { let ret = Object.assign({}, defaultSettings); try { Object.assign(ret, JSON.parse(localStorage.getItem(this._settingsKey))); } catch (e) { + // continue regardless of error } return ret; } + save(newSettings, silent) { + newSettings = Object.assign(this.cache, newSettings); + localStorage.setItem(this._settingsKey, JSON.stringify(newSettings)); + this.cache = this._getFromLocalStorage(); + if (silent !== true) { + this.dispatchEvent( + new CustomEvent("change", { + detail: { + settings: this.cache, + }, + }) + ); + } + } + + get() { + return this.cache; + } + get _settingsKey() { - return 'settings-' + api.userName + return "settings-" + api.userName; } -}; +} module.exports = new Settings(); diff --git a/client/js/models/snapshot.js b/client/js/models/snapshot.js index 78b367c..5f8e2ae 100644 --- a/client/js/models/snapshot.js +++ b/client/js/models/snapshot.js @@ -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() { @@ -10,12 +10,29 @@ class Snapshot extends events.EventTarget { this._updateFromResponse({}); } - get operation() { return this._operation; } - get type() { return this._type; } - get id() { return this._id; } - get user() { return this._user; } - get data() { return this._data; } - get time() { return this._time; } + get operation() { + return this._operation; + } + + get type() { + return this._type; + } + + get id() { + return this._id; + } + + get user() { + return this._user; + } + + get data() { + return this._data; + } + + get time() { + return this._time; + } static fromResponse(response) { const ret = new Snapshot(); @@ -26,11 +43,11 @@ class Snapshot extends events.EventTarget { _updateFromResponse(response) { const map = { _operation: response.operation, - _type: response.type, - _id: response.id, - _user: response.user, - _data: response.data, - _time: response.time, + _type: response.type, + _id: response.id, + _user: response.user, + _data: response.data, + _time: response.time, }; Object.assign(this, map); diff --git a/client/js/models/snapshot_list.js b/client/js/models/snapshot_list.js index 3263a6a..9ea1bdd 100644 --- a/client/js/models/snapshot_list.js +++ b/client/js/models/snapshot_list.js @@ -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; diff --git a/client/js/models/tag.js b/client/js/models/tag.js index 1c04eb3..eb24ccd 100644 --- a/client/js/models/tag.js +++ b/client/js/models/tag.js @@ -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 = {}; @@ -20,20 +20,57 @@ class Tag extends events.EventTarget { this._updateFromResponse({}); } - get names() { return this._names; } - get category() { return this._category; } - get description() { return this._description; } - get suggestions() { return this._suggestions; } - get implications() { return this._implications; } - get postCount() { return this._postCount; } - get creationTime() { return this._creationTime; } - get lastEditTime() { return this._lastEditTime; } - get metric() { return this._metric; } + get names() { + return this._names; + } + + get category() { + return this._category; + } + + get description() { + return this._description; + } - set names(value) { this._names = value; } - set category(value) { this._category = value; } - set description(value) { this._description = value; } - set metric(value) { this._metric = value; } + get suggestions() { + return this._suggestions; + } + + get implications() { + return this._implications; + } + + get postCount() { + return this._postCount; + } + + get creationTime() { + return this._creationTime; + } + + get lastEditTime() { + return this._lastEditTime; + } + + set names(value) { + this._names = value; + } + + set category(value) { + this._category = value; + } + + set description(value) { + this._description = value; + } + + get metric() { + return this._metric; + } + + set metric(value) { + this._metric = value; + } static fromResponse(response) { const ret = new Tag(); @@ -42,14 +79,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)) { @@ -63,11 +99,13 @@ 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] + ); } if (this._metric !== this._orig._metric) { detail.metric = { @@ -76,88 +114,99 @@ class Tag extends events.EventTarget { }; } - 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(); }); } deleteMetric() { return api.delete( - uri.formatApiLink('metric', this._origName), + uri.formatApiLink("metric", this._origName), {version: this.metric.version}) - .then(response => { - this.dispatchEvent(new CustomEvent('delete', { - detail: { - metric: this.metric, - }, - })); + .then((response) => { + this.dispatchEvent( + new CustomEvent("delete", { + detail: { + metric: this.metric, + }, + }) + ); return Promise.resolve(); }); } _updateFromResponse(response) { const map = { - _version: response.version, - _origName: response.names ? response.names[0] : null, - _names: response.names || [], - _category: response.category, - _description: response.description, + _version: response.version, + _origName: response.names ? response.names[0] : null, + _names: response.names || [], + _category: response.category, + _description: response.description, _creationTime: response.creationTime, _lastEditTime: response.lastEditTime, - _postCount: response.usages || 0, - _metric: response.metric, + _postCount: response.usages || 0, + _metric: response.metric, }; for (let obj of [this, this._orig]) { @@ -168,6 +217,6 @@ class Tag extends events.EventTarget { Object.assign(this, map); Object.assign(this._orig, map); } -}; +} module.exports = Tag; diff --git a/client/js/models/tag_category.js b/client/js/models/tag_category.js index 04bd8fe..1641862 100644 --- a/client/js/models/tag_category.js +++ b/client/js/models/tag_category.js @@ -1,28 +1,57 @@ -'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._tagCount = 0; + this._name = ""; + this._color = "#000000"; + this._order = 1; + this._tagCount = 0; this._isDefault = false; - this._origName = null; + this._origName = null; this._origColor = null; + this._origOrder = null; } - get name() { return this._name; } - get color() { return this._color; } - get tagCount() { return this._tagCount; } - get isDefault() { return this._isDefault; } - get isTransient() { return !this._origName; } + get name() { + return this._name; + } + + get color() { + return this._color; + } + + get order() { + return this._order; + } + + get tagCount() { + return this._tagCount; + } + + get isDefault() { + return this._isDefault; + } - set name(value) { this._name = value; } - set color(value) { this._color = value; } + get isTransient() { + return !this._origName; + } + + set name(value) { + this._name = value; + } + + set color(value) { + this._color = value; + } + + set order(value) { + this._order = value; + } static fromResponse(response) { const ret = new TagCategory(); @@ -31,7 +60,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; @@ -39,51 +68,61 @@ class TagCategory extends events.EventTarget { if (this.color !== this._origColor) { detail.color = this.color; } + if (this.order !== this._origOrder) { + detail.order = this.order; + } if (!Object.keys(detail).length) { 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(); }); } _updateFromResponse(response) { - this._version = response.version; - this._name = response.name; - this._color = response.color; + this._version = response.version; + this._name = response.name; + this._color = response.color; + this._order = response.order; this._isDefault = response.default; - this._tagCount = response.usages; - this._origName = this.name; + this._tagCount = response.usages; + this._origName = this.name; this._origColor = this.color; + this._origOrder = this.order; } } diff --git a/client/js/models/tag_category_list.js b/client/js/models/tag_category_list.js index 6c1182f..2fc1522 100644 --- a/client/js/models/tag_category_list.js +++ b/client/js/models/tag_category_list.js @@ -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; diff --git a/client/js/models/tag_list.js b/client/js/models/tag_list.js index 1478f8b..7882164 100644 --- a/client/js/models/tag_list.js +++ b/client/js/models/tag_list.js @@ -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', { - query: text, - offset: offset, - limit: limit, - fields: fields.join(','), - })) - .then(response => { - return Promise.resolve(Object.assign( - {}, - response, - {results: TagList.fromResponse(response.results)})); + 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), + }) + ); }); } @@ -52,10 +54,12 @@ class TagList extends AbstractList { this.add(tag); if (addImplications !== false) { - return Tag.get(tag.names[0]).then(actualTag => { + return Tag.get(tag.names[0]).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) + ) + ); }); } @@ -78,6 +82,6 @@ class TagList extends AbstractList { } TagList._itemClass = Tag; -TagList._itemName = 'tag'; +TagList._itemName = "tag"; module.exports = TagList; diff --git a/client/js/models/top_navigation.js b/client/js/models/top_navigation.js index bf2cffe..a469a03 100644 --- a/client/js/models/top_navigation.js +++ b/client/js/models/top_navigation.js @@ -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) { @@ -12,7 +12,7 @@ class TopNavigationItem { this.imageUrl = imageUrl === undefined ? null : imageUrl; this.key = null; } -}; +} class TopNavigation extends events.EventTarget { constructor() { @@ -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 : ""); }); } @@ -72,27 +74,26 @@ class TopNavigation extends events.EventTarget { hide(key) { this.get(key).available = false; } -}; +} 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('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; } diff --git a/client/js/models/user.js b/client/js/models/user.js index abb5f57..28dc3ef 100644 --- a/client/js/models/user.js +++ b/client/js/models/user.js @@ -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() { @@ -11,28 +11,89 @@ class User extends events.EventTarget { this._updateFromResponse({}); } - get name() { return this._name; } - get rank() { return this._rank; } - get email() { return this._email; } - get avatarStyle() { return this._avatarStyle; } - get avatarUrl() { return this._avatarUrl; } - get creationTime() { return this._creationTime; } - get lastLoginTime() { return this._lastLoginTime; } - get commentCount() { return this._commentCount; } - get favoritePostCount() { return this._favoritePostCount; } - get uploadedPostCount() { return this._uploadedPostCount; } - get likedPostCount() { return this._likedPostCount; } - get dislikedPostCount() { return this._dislikedPostCount; } - get rankName() { return api.rankNames.get(this.rank); } - get avatarContent() { throw 'Invalid operation'; } - get password() { throw 'Invalid operation'; } + get name() { + return this._name; + } + + get rank() { + return this._rank; + } + + get email() { + return this._email; + } + + get avatarStyle() { + return this._avatarStyle; + } + + get avatarUrl() { + return this._avatarUrl; + } + + get creationTime() { + return this._creationTime; + } + + get lastLoginTime() { + return this._lastLoginTime; + } + + get commentCount() { + return this._commentCount; + } + + get favoritePostCount() { + return this._favoritePostCount; + } + + get uploadedPostCount() { + return this._uploadedPostCount; + } + + get likedPostCount() { + return this._likedPostCount; + } + + get dislikedPostCount() { + return this._dislikedPostCount; + } - set name(value) { this._name = value; } - set rank(value) { this._rank = value; } - set email(value) { this._email = value || null; } - set avatarStyle(value) { this._avatarStyle = value; } - set avatarContent(value) { this._avatarContent = value; } - set password(value) { this._password = value; } + get rankName() { + return api.rankNames.get(this.rank); + } + + get avatarContent() { + throw "Invalid operation"; + } + + get password() { + throw "Invalid operation"; + } + + set name(value) { + this._name = value; + } + + set rank(value) { + this._rank = value; + } + + set email(value) { + this._email = value || null; + } + + set avatarStyle(value) { + this._avatarStyle = value; + } + + set avatarContent(value) { + this._avatarContent = value; + } + + set password(value) { + this._password = value; + } static fromResponse(response) { const ret = new User(); @@ -41,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) { @@ -72,60 +132,67 @@ 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(); }); } _updateFromResponse(response) { const map = { - _version: response.version, - _name: response.name, - _rank: response.rank, - _email: response.email, - _avatarStyle: response.avatarStyle, - _avatarUrl: response.avatarUrl, - _creationTime: response.creationTime, - _lastLoginTime: response.lastLoginTime, - _commentCount: response.commentCount, + _version: response.version, + _name: response.name, + _rank: response.rank, + _email: response.email, + _avatarStyle: response.avatarStyle, + _avatarUrl: response.avatarUrl, + _creationTime: response.creationTime, + _lastLoginTime: response.lastLoginTime, + _commentCount: response.commentCount, _favoritePostCount: response.favoritePostCount, _uploadedPostCount: response.uploadedPostCount, - _likedPostCount: response.likedPostCount, + _likedPostCount: response.likedPostCount, _dislikedPostCount: response.dislikedPostCount, }; Object.assign(this, map); Object.assign(this._orig, map); - this._password = null; - this._avatarContent = null; + this._password = null; + this._avatarContent = null; } -}; +} module.exports = User; diff --git a/client/js/models/user_list.js b/client/js/models/user_list.js index c48fc88..c537f8f 100644 --- a/client/js/models/user_list.js +++ b/client/js/models/user_list.js @@ -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; diff --git a/client/js/models/user_token.js b/client/js/models/user_token.js index 6e70a94..c9d28a2 100644 --- a/client/js/models/user_token.js +++ b/client/js/models/user_token.js @@ -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() { @@ -11,24 +11,49 @@ class UserToken extends events.EventTarget { this._updateFromResponse({}); } - get token() { return this._token; } - get note() { return this._note; } - get enabled() { return this._enabled; } - get version() { return this._version; } - get expirationTime() { return this._expirationTime; } - get creationTime() { return this._creationTime; } - get lastEditTime() { return this._lastEditTime; } - get lastUsageTime() { return this._lastUsageTime; } + get token() { + return this._token; + } - set note(value) { this._note = value; } + get note() { + return this._note; + } + + get enabled() { + return this._enabled; + } + + get version() { + return this._version; + } + + get expirationTime() { + return this._expirationTime; + } + + get creationTime() { + return this._creationTime; + } + + get lastEditTime() { + return this._lastEditTime; + } + + get lastUsageTime() { + return this._lastUsageTime; + } + + set note(value) { + this._note = value; + } static fromResponse(response) { - if (typeof response.results !== 'undefined') { + 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 { @@ -39,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; @@ -55,57 +81,68 @@ 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(); }); } _updateFromResponse(response) { const map = { - _token: response.token, - _note: response.note, - _enabled: response.enabled, - _expirationTime: response.expirationTime, - _version: response.version, - _creationTime: response.creationTime, - _lastEditTime: response.lastEditTime, - _lastUsageTime: response.lastUsageTime, + _token: response.token, + _note: response.note, + _enabled: response.enabled, + _expirationTime: response.expirationTime, + _version: response.version, + _creationTime: response.creationTime, + _lastEditTime: response.lastEditTime, + _lastUsageTime: response.lastUsageTime, }; Object.assign(this, map); |