'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 misc = require('../util/misc.js'); class Post extends events.EventTarget { constructor() { super(); this._orig = {}; for (let obj of [this, this._orig]) { obj._tags = new TagList(); obj._notes = new NoteList(); obj._comments = new CommentList(); } 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 canvasWidth() { return this._canvasWidth || 800; } get canvasHeight() { return this._canvasHeight || 450; } get fileSize() { return this._fileSize || 0; } get newContent() { throw 'Invalid operation'; } get newThumbnail() { throw 'Invalid operation'; } get flags() { return this._flags; } get tags() { return this._tags; } get tagNames() { return this._tags.map(tag => tag.names[0]); } get notes() { return this._notes; } get comments() { return this._comments; } get relations() { return this._relations; } get score() { return this._score; } get commentCount() { return this._commentCount; } get favoriteCount() { return this._favoriteCount; } get ownFavorite() { return this._ownFavorite; } get ownScore() { return this._ownScore; } get hasCustomThumbnail() { return this._hasCustomThumbnail; } set flags(value) { this._flags = value; } set safety(value) { this._safety = value; } set relations(value) { this._relations = value; } set newContent(value) { this._newContent = value; } set newThumbnail(value) { this._newThumbnail = value; } static fromResponse(response) { const ret = new Post(); ret._updateFromResponse(response); return ret; } static reverseSearch(content) { let apiPromise = api.post( 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); }); returnedPromise.abort = () => apiPromise.abort(); return returnedPromise; } static get(id) { return api.get(uri.formatApiLink('post', id)) .then(response => { return Promise.resolve(Post.fromResponse(response)); }); } save(anonymous) { const files = {}; const detail = {version: this._version}; // send only changed fields to avoid user privilege violation if (anonymous === true) { detail.anonymous = true; } if (this._safety !== this._orig._safety) { detail.safety = this._safety; } if (misc.arraysDiffer(this._flags, this._orig._flags)) { detail.flags = this._flags; } if (misc.arraysDiffer(this._tags, this._orig._tags)) { 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]), text: note.text, })); } if (this._newContent) { files.content = this._newContent; } if (this._newThumbnail !== undefined) { files.thumbnail = this._newThumbnail; } 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); }); } feature() { 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 Promise.resolve(); }); } merge(targetId, useOldContent) { 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 => { this._updateFromResponse(response); 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 => { const prevFavorite = this._ownFavorite; this._updateFromResponse(response); if (this._ownFavorite !== prevFavorite) { this.dispatchEvent(new CustomEvent('changeFavorite', { 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 => { const prevScore = this._ownScore; this._updateFromResponse(response); if (this._ownScore !== prevScore) { this.dispatchEvent(new CustomEvent('changeScore', { 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 => { const prevScore = this._ownScore; this._updateFromResponse(response); if (this._ownScore !== prevScore) { this.dispatchEvent(new CustomEvent('changeScore', { 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); } _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, window.location.href).href, _thumbnailUrl: response.thumbnailUrl, _canvasWidth: response.canvasWidth, _canvasHeight: response.canvasHeight, _fileSize: response.fileSize, _flags: [...response.flags || []], _relations: [...response.relations || []], _score: response.score, _commentCount: response.commentCount, _favoriteCount: response.favoriteCount, _ownScore: response.ownScore, _ownFavorite: response.ownFavorite, _hasCustomThumbnail: response.hasCustomThumbnail, }); for (let obj of [this, this._orig]) { obj._tags.sync(response.tags); obj._notes.sync(response.notes); obj._comments.sync(response.comments); } Object.assign(this, map()); Object.assign(this._orig, map()); } }; module.exports = Post;