UNPKG

@orfeas126/box-ui-elements

Version:
1,296 lines (1,281 loc) 72.4 kB
const _excluded = ["replies"], _excluded2 = ["replies"], _excluded3 = ["replies", "total_reply_count"], _excluded4 = ["replies", "total_reply_count"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; } /** * * @file Helper for activity feed API's * @author Box */ import noop from 'lodash/noop'; import uniqueId from 'lodash/uniqueId'; import { getBadItemError, getBadUserError, getMissingItemTextOrStatus, isUserCorrectableError } from '../utils/error'; import commonMessages from '../elements/common/messages'; import messages from './messages'; import { sortFeedItems } from '../utils/sorter'; import { FEED_FILE_VERSIONS_FIELDS_TO_FETCH } from '../utils/fields'; import Base from './Base'; import AnnotationsAPI from './Annotations'; import CommentsAPI from './Comments'; import ThreadedCommentsAPI from './ThreadedComments'; import FileActivitiesAPI from './FileActivities'; import VersionsAPI from './Versions'; import TasksNewAPI from './tasks/TasksNew'; import GroupsAPI from './Groups'; import TaskCollaboratorsAPI from './tasks/TaskCollaborators'; import TaskLinksAPI from './tasks/TaskLinks'; import AppActivityAPI from './AppActivity'; import { ACTION_TYPE_CREATED, ACTION_TYPE_RESTORED, ACTION_TYPE_TRASHED, ERROR_CODE_CREATE_TASK, ERROR_CODE_UPDATE_TASK, ERROR_CODE_GROUP_EXCEEDS_LIMIT, FEED_ITEM_TYPE_ANNOTATION, FEED_ITEM_TYPE_COMMENT, FEED_ITEM_TYPE_TASK, FEED_ITEM_TYPE_VERSION, FILE_ACTIVITY_TYPE_ANNOTATION, FILE_ACTIVITY_TYPE_APP_ACTIVITY, FILE_ACTIVITY_TYPE_COMMENT, FILE_ACTIVITY_TYPE_TASK, FILE_ACTIVITY_TYPE_VERSION, HTTP_STATUS_CODE_CONFLICT, IS_ERROR_DISPLAYED, PERMISSION_CAN_VIEW_ANNOTATIONS, PERMISSION_CAN_COMMENT, TASK_NEW_APPROVED, TASK_NEW_COMPLETED, TASK_NEW_REJECTED, TASK_NEW_NOT_STARTED, TYPED_ID_FEED_PREFIX, TASK_MAX_GROUP_ASSIGNEES } from '../constants'; import { collapseFeedState } from '../elements/content-sidebar/activity-feed/activity-feed/activityFeedUtils'; const TASK_NEW_INITIAL_STATUS = TASK_NEW_NOT_STARTED; const getItemWithFilteredReplies = (item, replyId) => { const { replies = [] } = item, rest = _objectWithoutProperties(item, _excluded); return _objectSpread({ replies: replies.filter(({ id }) => id !== replyId) }, rest); }; const getItemWithPendingReply = (item, reply) => { const { replies = [] } = item, rest = _objectWithoutProperties(item, _excluded2); return _objectSpread({ replies: [...replies, reply] }, rest); }; const parseReplies = replies => { const parsedReplies = [...replies]; return parsedReplies.map(reply => { return _objectSpread(_objectSpread({}, reply), {}, { tagged_message: reply.tagged_message || reply.message || '' }); }); }; export const getParsedFileActivitiesResponse = (response, permissions = {}) => { if (!response || !response.entries || !response.entries.length) { return []; } const data = response.entries; const parsedData = data.map(item => { if (!item.source) { return null; } const source = _objectSpread({}, item.source); switch (item.activity_type) { case FILE_ACTIVITY_TYPE_TASK: { const taskItem = _objectSpread({}, source[FILE_ACTIVITY_TYPE_TASK]); // UAA follows a lowercased enum naming convention, convert to uppercase to align with task api if (taskItem.assigned_to?.entries) { const assignedToEntries = taskItem.assigned_to.entries.map(entry => { const assignedToEntry = _objectSpread({}, entry); assignedToEntry.role = entry.role.toUpperCase(); assignedToEntry.status = entry.status.toUpperCase(); return assignedToEntry; }); // $FlowFixMe Using the toUpperCase method makes Flow assume role and status is a string type, which is incompatible with string literal taskItem.assigned_to.entries = assignedToEntries; } if (taskItem.completion_rule) { taskItem.completion_rule = taskItem.completion_rule.toUpperCase(); } if (taskItem.status) { taskItem.status = taskItem.status.toUpperCase(); } if (taskItem.task_type) { taskItem.task_type = taskItem.task_type.toUpperCase(); } // $FlowFixMe File Activities only returns a created_by user, Flow type fix is needed taskItem.created_by = { target: taskItem.created_by }; return taskItem; } case FILE_ACTIVITY_TYPE_COMMENT: { const commentItem = _objectSpread({}, source[FILE_ACTIVITY_TYPE_COMMENT]); if (commentItem.replies && commentItem.replies.length) { const replies = parseReplies(commentItem.replies); commentItem.replies = replies; } commentItem.tagged_message = commentItem.tagged_message || commentItem.message || ''; return commentItem; } case FILE_ACTIVITY_TYPE_ANNOTATION: { const annotationItem = _objectSpread({}, source[FILE_ACTIVITY_TYPE_ANNOTATION]); if (annotationItem.replies && annotationItem.replies.length) { const replies = parseReplies(annotationItem.replies); annotationItem.replies = replies; } return annotationItem; } case FILE_ACTIVITY_TYPE_APP_ACTIVITY: { const appActivityItem = _objectSpread({}, source[FILE_ACTIVITY_TYPE_APP_ACTIVITY]); const { can_delete } = permissions; appActivityItem.created_at = appActivityItem.occurred_at; appActivityItem.permissions = { can_delete }; return appActivityItem; } case FILE_ACTIVITY_TYPE_VERSION: { const versionsItem = _objectSpread({}, source[FILE_ACTIVITY_TYPE_VERSION]); versionsItem.type = FEED_ITEM_TYPE_VERSION; if (versionsItem.action_by) { const collaborators = {}; if (versionsItem.action_by.length === 1) { versionsItem.uploader_display_name = versionsItem.action_by[0].name; } versionsItem.action_by.map(collaborator => { collaborators[collaborator.id] = _objectSpread({}, collaborator); return collaborator; }); versionsItem.collaborators = collaborators; } if (versionsItem.end?.number) { versionsItem.version_end = versionsItem.end.number; versionsItem.id = versionsItem.end.id; } if (versionsItem.start?.number) { versionsItem.version_start = versionsItem.start.number; } if (versionsItem.version_start === versionsItem.version_end) { versionsItem.version_number = versionsItem.version_start; if (versionsItem.action_type === ACTION_TYPE_CREATED && versionsItem.start?.created_at && versionsItem.start?.created_by) { versionsItem.modified_at = versionsItem.start.created_at; versionsItem.modified_by = _objectSpread({}, versionsItem.start.created_by); } if (versionsItem.action_type === ACTION_TYPE_TRASHED && versionsItem.start?.trashed_at && versionsItem.start?.trashed_by) { versionsItem.trashed_at = versionsItem.start.trashed_at; versionsItem.trashed_by = _objectSpread({}, versionsItem.start.trashed_by); } if (versionsItem.action_type === ACTION_TYPE_RESTORED && versionsItem.start?.restored_at && versionsItem.start?.restored_by) { versionsItem.restored_at = versionsItem.start.restored_at; versionsItem.restored_by = _objectSpread({}, versionsItem.start.restored_by); } } return versionsItem; } default: { return null; } } }).filter(item => !!item).reverse(); return parsedData; }; class Feed extends Base { /** * @property {AnnotationsAPI} */ /** * @property {VersionsAPI} */ /** * @property {CommentsAPI} */ /** * @property {AppActivityAPI} */ /** * @property {TasksNewAPI} */ /** * @property {TaskCollaboratorsAPI} */ /** * @property {TaskLinksAPI} */ /** * @property {ThreadedCommentsAPI} */ /** * @property {FileActivitiesAPI} */ /** * @property {BoxItem} */ /** * @property {ElementsXhrError} */ constructor(options) { super(options); _defineProperty(this, "updateAnnotation", (file, annotationId, text, status, permissions, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } if (!text && !status) { throw getMissingItemTextOrStatus(); } this.annotationsAPI = new AnnotationsAPI(this.options); this.file = file; this.errorCallback = errorCallback; const feedItemChanges = {}; if (text) { feedItemChanges.message = text; } if (status) { feedItemChanges.status = status; } this.updateFeedItem(_objectSpread(_objectSpread({}, feedItemChanges), {}, { isPending: true }), annotationId); this.annotationsAPI.updateAnnotation(this.file.id, annotationId, permissions, feedItemChanges, annotation => { const { replies, total_reply_count } = annotation, annotationBase = _objectWithoutProperties(annotation, _excluded3); this.updateFeedItem(_objectSpread(_objectSpread({}, annotationBase), {}, { isPending: false }), annotationId); if (!this.isDestroyed()) { successCallback(annotation); } }, (e, code) => { this.updateCommentErrorCallback(e, code, annotationId); }); }); /** * Error callback for updating a comment * * @param {ElementsXhrError} e - the error returned by the API * @param {string} code - the error code * @param {string} id - the id of either an annotation or comment * @return {void} */ _defineProperty(this, "updateCommentErrorCallback", (e, code, id) => { this.updateFeedItem(this.createFeedError(messages.commentUpdateErrorMessage), id); this.feedErrorCallback(true, e, code); }); /** * Error callback for updating a reply * * @param {ElementsXhrError} error - the error returned by the API * @param {string} code - the error code * @param {string} id - the id of the reply (comment) * @param {string} parentId - the id of either the parent item (an annotation or comment) * @return {void} */ _defineProperty(this, "updateReplyErrorCallback", (error, code, id, parentId) => { this.updateReplyItem(this.createFeedError(messages.commentUpdateErrorMessage), parentId, id); this.feedErrorCallback(true, error, code); }); /** * Error callback for fetching replies * * @param {ElementsXhrError} error - the error returned by the API * @param {string} code - the error code * @param {string} id - the id of either an annotation or comment * @return {void} */ _defineProperty(this, "fetchRepliesErrorCallback", (error, code, id) => { this.updateFeedItem(this.createFeedError(messages.repliesFetchErrorMessage), id); this.feedErrorCallback(true, error, code); }); _defineProperty(this, "deleteAnnotation", (file, annotationId, permissions, successCallBack, errorCallback) => { this.annotationsAPI = new AnnotationsAPI(this.options); if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.updateFeedItem({ isPending: true }, annotationId); this.annotationsAPI.deleteAnnotation(this.file.id, annotationId, permissions, this.deleteFeedItem.bind(this, annotationId, successCallBack), (error, code) => { // Reusing comment error handler since annotations are treated as comments to user this.deleteCommentErrorCallback(error, code, annotationId); }); }); /** * Callback for successful fetch of a comment * * @param {Function} resolve - resolve function * @param {Function} successCallback - success callback * @param {Comment} comment - comment data * @return {void} */ _defineProperty(this, "fetchThreadedCommentSuccessCallback", (resolve, successCallback, comment) => { successCallback(comment); resolve(); }); /** * Updates a task assignment * * @param {BoxItem} file - The file to which the task is assigned * @param {string} taskId - ID of task to be updated * @param {string} taskCollaboratorId - Task assignment ID * @param {TaskCollabStatus} taskCollaboratorStatus - New task assignment status * @param {Function} successCallback - the function which will be called on success * @param {Function} errorCallback - the function which will be called on error * @return {void} */ _defineProperty(this, "updateTaskCollaborator", (file, taskId, taskCollaboratorId, taskCollaboratorStatus, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.updateFeedItem({ isPending: true }, taskId); const collaboratorsApi = new TaskCollaboratorsAPI(this.options); this.taskCollaboratorsAPI.push(collaboratorsApi); const taskCollaboratorPayload = { id: taskCollaboratorId, status: taskCollaboratorStatus }; const handleError = (e, code) => { let errorMessage; switch (taskCollaboratorStatus) { case TASK_NEW_APPROVED: errorMessage = messages.taskApproveErrorMessage; break; case TASK_NEW_COMPLETED: errorMessage = messages.taskCompleteErrorMessage; break; case TASK_NEW_REJECTED: errorMessage = messages.taskRejectErrorMessage; break; default: errorMessage = messages.taskCompleteErrorMessage; } this.updateFeedItem(this.createFeedError(errorMessage, messages.taskActionErrorTitle), taskId); this.feedErrorCallback(true, e, code); }; collaboratorsApi.updateTaskCollaborator({ file, taskCollaborator: taskCollaboratorPayload, successCallback: taskCollab => { this.updateTaskCollaboratorSuccessCallback(taskId, file, taskCollab, successCallback, handleError); }, errorCallback: handleError }); }); /** * Updates the task assignment state of the updated task * * @param {string} taskId - Box task id * @param {TaskAssignment} updatedCollaborator - New task assignment from API * @param {Function} successCallback - the function which will be called on success * @return {void} */ _defineProperty(this, "updateTaskCollaboratorSuccessCallback", (taskId, file, updatedCollaborator, successCallback, errorCallback) => { this.tasksNewAPI = new TasksNewAPI(this.options); this.tasksNewAPI.getTask({ id: taskId, file, successCallback: task => { this.updateFeedItem(_objectSpread(_objectSpread({}, task), {}, { isPending: false }), taskId); successCallback(updatedCollaborator); }, errorCallback }); }); /** * Updates a task in the new API * * @param {BoxItem} file - The file to which the task is assigned * @param {string} task - The update task payload object * @param {Function} successCallback - the function which will be called on success * @param {Function} errorCallback - the function which will be called on error * @return {void} */ _defineProperty(this, "updateTaskNew", async (file, task, successCallback = noop, errorCallback = noop) => { if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.tasksNewAPI = new TasksNewAPI(this.options); this.updateFeedItem({ isPending: true }, task.id); try { // create request for the size of each group by ID // TODO: use async/await for both creating and editing tasks const groupInfoPromises = task.addedAssignees.filter(assignee => assignee.item && assignee.item.type === 'group').map(assignee => assignee.id).map(groupId => { return new GroupsAPI(this.options).getGroupCount({ file, group: { id: groupId } }); }); const groupCounts = await Promise.all(groupInfoPromises); const hasAnyGroupCountExceeded = groupCounts.some(groupInfo => groupInfo.total_count > TASK_MAX_GROUP_ASSIGNEES); const warning = { code: ERROR_CODE_GROUP_EXCEEDS_LIMIT, type: 'warning' }; if (hasAnyGroupCountExceeded) { this.feedErrorCallback(false, warning, ERROR_CODE_GROUP_EXCEEDS_LIMIT); return; } await new Promise((resolve, reject) => { this.tasksNewAPI.updateTaskWithDeps({ file, task, successCallback: resolve, errorCallback: reject }); }); await new Promise((resolve, reject) => { this.tasksNewAPI.getTask({ file, id: task.id, successCallback: taskData => { this.updateFeedItem(_objectSpread(_objectSpread({}, taskData), {}, { isPending: false }), task.id); resolve(); }, errorCallback: e => { this.updateFeedItem({ isPending: false }, task.id); this.feedErrorCallback(false, e, ERROR_CODE_UPDATE_TASK); reject(); } }); }); // everything succeeded, so call the passed in success callback if (!this.isDestroyed()) { successCallback(); } } catch (e) { this.updateFeedItem({ isPending: false }, task.id); this.feedErrorCallback(false, e, ERROR_CODE_UPDATE_TASK); } }); /** * Deletes a comment. * * @param {BoxItem} file - The file to which the comment belongs to * @param {string} commentId - Comment ID * @param {BoxCommentPermission} permissions - Permissions for the comment * @param {Function} successCallback - the function which will be called on success * @param {Function} errorCallback - the function which will be called on error * @return {void} */ _defineProperty(this, "deleteComment", (file, commentId, permissions, successCallback, errorCallback) => { this.commentsAPI = new CommentsAPI(this.options); if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.updateFeedItem({ isPending: true }, commentId); this.commentsAPI.deleteComment({ file, commentId, permissions, successCallback: this.deleteFeedItem.bind(this, commentId, successCallback), errorCallback: (e, code) => { this.deleteCommentErrorCallback(e, code, commentId); } }); }); /** * Deletes a threaded comment (using ThreadedComments API). * * @param {BoxItem} file - The file to which the comment belongs to * @param {string} commentId - Comment ID * @param {BoxCommentPermission} permissions - Permissions for the comment * @param {Function} successCallback - the function which will be called on success * @param {Function} errorCallback - the function which will be called on error * @return {void} */ _defineProperty(this, "deleteThreadedComment", (file, commentId, permissions, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.updateFeedItem({ isPending: true }, commentId); this.threadedCommentsAPI = new ThreadedCommentsAPI(this.options); this.threadedCommentsAPI.deleteComment({ fileId: file.id, commentId, permissions, successCallback: this.deleteFeedItem.bind(this, commentId, successCallback), errorCallback: (e, code) => { this.deleteCommentErrorCallback(e, code, commentId); } }); }); /** * Deletes a reply (using ThreadedComments API). * * @param {BoxItem} file - The file to which the comment belongs to * @param {string} id - id of the reply (comment) * @param {string} parentId - id of the parent feed item * @param {BoxCommentPermission} permissions - Permissions for the comment * @param {Function} successCallback - the function which will be called on success * @param {Function} errorCallback - the function which will be called on error * @return {void} */ _defineProperty(this, "deleteReply", (file, id, parentId, permissions, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.updateReplyItem({ isPending: true }, parentId, id); this.threadedCommentsAPI = new ThreadedCommentsAPI(this.options); this.threadedCommentsAPI.deleteComment({ fileId: file.id, commentId: id, permissions, successCallback: this.deleteReplySuccessCallback.bind(this, id, parentId, successCallback), errorCallback: (e, code) => { this.deleteReplyErrorCallback(e, code, id, parentId); } }); }); /** * Callback for successful deletion of a reply. * * @param {string} id - ID of the reply * @param {string} parentId - ID of the parent feed item * @param {Function} successCallback - success callback * @return {void} */ _defineProperty(this, "deleteReplySuccessCallback", (id, parentId, successCallback) => { this.modifyFeedItemRepliesCountBy(parentId, -1); this.deleteReplyItem(id, parentId, successCallback); }); /** * Error callback for deleting a comment * * @param {ElementsXhrError} e - the error returned by the API * @param {string} code - the error code * @param {string} commentId - the comment id * @return {void} */ _defineProperty(this, "deleteCommentErrorCallback", (e, code, commentId) => { this.updateFeedItem(this.createFeedError(messages.commentDeleteErrorMessage), commentId); this.feedErrorCallback(true, e, code); }); /** * Error callback for deleting a reply * * @param {ElementsXhrError} error - the error returned by the API * @param {string} code - the error code * @param {string} id - the reply (comment) id * @param {string} parentId - the comment id of the parent feed item * @return {void} */ _defineProperty(this, "deleteReplyErrorCallback", (error, code, id, parentId) => { this.updateReplyItem(this.createFeedError(messages.commentDeleteErrorMessage), parentId, id); this.feedErrorCallback(true, error, code); }); /** * Creates a task. * * @param {BoxItem} file - The file to which the task is assigned * @param {Object} currentUser - the user who performed the action * @param {string} message - Task text * @param {Array} assignees - List of assignees * @param {number} dueAt - Task's due date * @param {Function} successCallback - the function which will be called on success * @param {Function} errorCallback - the function which will be called on error * @return {void} */ _defineProperty(this, "createTaskNew", (file, currentUser, message, assignees, taskType, dueAt, completionRule, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; const uuid = uniqueId('task_'); let dueAtString; if (dueAt) { const dueAtDate = new Date(dueAt); dueAtString = dueAtDate.toISOString(); } // TODO: make pending task generator a function const pendingTask = { created_by: { type: 'task_collaborator', target: currentUser, id: uniqueId(), role: 'CREATOR', status: TASK_NEW_INITIAL_STATUS }, completion_rule: completionRule, created_at: new Date().toISOString(), due_at: dueAtString, id: uuid, description: message, type: FEED_ITEM_TYPE_TASK, assigned_to: { entries: assignees.map(assignee => ({ id: uniqueId(), target: _objectSpread(_objectSpread({}, assignee), {}, { avatar_url: '', type: 'user' }), status: TASK_NEW_INITIAL_STATUS, permissions: { can_delete: false, can_update: false }, role: 'ASSIGNEE', type: 'task_collaborator' })), limit: 10, next_marker: null }, permissions: { can_update: false, can_delete: false, can_create_task_collaborator: false, can_create_task_link: false }, task_links: { entries: [{ id: uniqueId(), type: 'task_link', target: _objectSpread({ type: 'file' }, file), permissions: { can_delete: false, can_update: false } }], limit: 1, next_marker: null }, task_type: taskType, status: TASK_NEW_NOT_STARTED }; const taskPayload = { description: message, due_at: dueAtString, task_type: taskType, completion_rule: completionRule }; // create request for the size of each group by ID const groupInfoPromises = assignees.filter(assignee => (assignee.item && assignee.item.type) === 'group').map(assignee => assignee.id).map(groupId => { return new GroupsAPI(this.options).getGroupCount({ file, group: { id: groupId } }); }); // Fetch each group size in parallel --> return an array of group sizes Promise.all(groupInfoPromises).then(groupCounts => { const hasAnyGroupCountExceeded = groupCounts.some(groupInfo => groupInfo.total_count > TASK_MAX_GROUP_ASSIGNEES); const warning = { code: ERROR_CODE_GROUP_EXCEEDS_LIMIT, type: 'warning' }; if (hasAnyGroupCountExceeded) { this.feedErrorCallback(false, warning, ERROR_CODE_GROUP_EXCEEDS_LIMIT); return; } this.tasksNewAPI = new TasksNewAPI(this.options); this.tasksNewAPI.createTaskWithDeps({ file, task: taskPayload, assignees, successCallback: taskWithDepsData => { this.addPendingItem(this.file.id, currentUser, pendingTask); this.updateFeedItem(_objectSpread(_objectSpread({}, taskWithDepsData), {}, { task_links: { entries: taskWithDepsData.task_links, next_marker: null, limit: 1 }, assigned_to: { entries: taskWithDepsData.assigned_to, next_marker: null, limit: taskWithDepsData.assigned_to.length }, isPending: false }), uuid); successCallback(taskWithDepsData); }, errorCallback: (e, code) => { this.feedErrorCallback(false, e, code); } }); }).catch(error => { this.feedErrorCallback(false, error, ERROR_CODE_CREATE_TASK); }); }); /** * Deletes a task in the new API * * @param {BoxItem} file - The file to which the task is assigned * @param {string} taskId - The task's id * @param {Function} successCallback - the function which will be called on success * @param {Function} errorCallback - the function which will be called on error * @return {void} */ _defineProperty(this, "deleteTaskNew", (file, task, successCallback = noop, errorCallback = noop) => { if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.tasksNewAPI = new TasksNewAPI(this.options); this.updateFeedItem({ isPending: true }, task.id); this.tasksNewAPI.deleteTask({ file, task, successCallback: this.deleteFeedItem.bind(this, task.id, successCallback), errorCallback: (e, code) => { this.updateFeedItem(this.createFeedError(messages.taskDeleteErrorMessage), task.id); this.feedErrorCallback(true, e, code); } }); }); /** * Deletes a feed item from the cache * * @param {string} id - The id of the feed item to be deleted * @param {Function} successCallback - function to be called after the delete */ _defineProperty(this, "deleteFeedItem", (id, successCallback = noop) => { const cachedItems = this.getCachedItems(this.file.id); if (cachedItems) { const feedItems = cachedItems.items.filter(feedItem => feedItem.id !== id); this.setCachedItems(this.file.id, feedItems); if (!this.isDestroyed()) { successCallback(id); } } }); /** * Deletes a reply from the cache * * @param {string} id - The id of the feed item to be deleted * @param {string} parentId - The id of the parent feed item * @param {Function} successCallback - function to be called after the delete */ _defineProperty(this, "deleteReplyItem", (id, parentId, successCallback = noop) => { const cachedItems = this.getCachedItems(this.file.id) || { items: [], errors: [] }; const feedItems = cachedItems.items.map(item => { if (item.id !== parentId) { return item; } if (item.type === FEED_ITEM_TYPE_ANNOTATION) { return getItemWithFilteredReplies(item, id); } if (item.type === FEED_ITEM_TYPE_COMMENT) { return getItemWithFilteredReplies(item, id); } return item; }); this.setCachedItems(this.file.id, feedItems); if (!this.isDestroyed()) { successCallback(id, parentId); } }); /** * Network error callback * * @param {boolean} hasError - true if the UI should display an error * @param {ElementsXhrError} e - the error returned by the API * @param {string} code - the error code for the error which occured * @return {void} */ _defineProperty(this, "feedErrorCallback", (hasError = false, e, code) => { if (hasError) { this.errors.push(_objectSpread(_objectSpread({}, e), {}, { code })); } if (!this.isDestroyed() && this.errorCallback) { this.errorCallback(e, code, { error: e, [IS_ERROR_DISPLAYED]: hasError }); } console.error(e); // eslint-disable-line no-console }); /** * Add a placeholder pending feed item. * * @param {string} id - the file id * @param {Object} currentUser - the user who performed the action * @param {Object} itemBase - Base properties for item to be added to the feed as pending. * @return {void} */ _defineProperty(this, "addPendingItem", (id, currentUser, itemBase) => { if (!currentUser) { throw getBadUserError(); } const date = new Date().toISOString(); const pendingFeedItem = _objectSpread({ created_at: date, created_by: currentUser, modified_at: date, isPending: true }, itemBase); const cachedItems = this.getCachedItems(this.file.id); const feedItems = cachedItems ? cachedItems.items : []; const feedItemsWithPendingItem = [...feedItems, pendingFeedItem]; this.setCachedItems(id, feedItemsWithPendingItem); return pendingFeedItem; }); /** * Add a placeholder pending comment (reply). * * @param {string} parentId - id of parent comment or annotation * @param {Object} currentUser - the user who performed the action * @param {Object} commentBase - Base properties for reply (comment) to be added to the feed as pending. * @return {Comment} - newly created pending reply */ _defineProperty(this, "addPendingReply", (parentId, currentUser, commentBase) => { if (!this.file.id) { throw getBadItemError(); } if (!currentUser) { throw getBadUserError(); } const date = new Date().toISOString(); const pendingReply = _objectSpread({ created_at: date, created_by: currentUser, modified_at: date, isPending: true }, commentBase); const cachedItems = this.getCachedItems(this.file.id); if (cachedItems) { const updatedFeedItems = cachedItems.items.map(item => { if (item.id === parentId && item.type === FEED_ITEM_TYPE_COMMENT) { return getItemWithPendingReply(item, pendingReply); } if (item.id === parentId && item.type === FEED_ITEM_TYPE_ANNOTATION) { return getItemWithPendingReply(item, pendingReply); } return item; }); this.setCachedItems(this.file.id, updatedFeedItems); } return pendingReply; }); /** * Callback for successful creation of a Comment. * * @param {Comment} commentData - API returned Comment * @param {string} id - ID of the feed item to update with the new comment data * @return {void} */ _defineProperty(this, "createCommentSuccessCallback", (commentData, id, successCallback) => { const { message = '', tagged_message = '' } = commentData; // Comment component uses tagged_message only commentData.tagged_message = tagged_message || message; this.updateFeedItem(_objectSpread(_objectSpread({}, commentData), {}, { isPending: false }), id); if (!this.isDestroyed()) { successCallback(commentData); } }); /** * Callback for failed creation of a Comment. * * @param {Object} e - The axios error * @param {string} code - the error code * @param {string} id - ID of the feed item to update * @return {void} */ _defineProperty(this, "createCommentErrorCallback", (e, code, id) => { const errorMessage = e.status === HTTP_STATUS_CODE_CONFLICT ? messages.commentCreateConflictMessage : messages.commentCreateErrorMessage; this.updateFeedItem(this.createFeedError(errorMessage), id); this.feedErrorCallback(false, e, code); }); /** * Callback for successful creation of a Comment. * * @param {Comment} commentData - API returned Comment * @param {string} parentId - ID of the parent feed item * @param {string} id - ID of the reply to update with the new comment data * @param {Function} successCallback - success callback * @return {void} */ _defineProperty(this, "createReplySuccessCallback", (commentData, parentId, id, successCallback) => { this.updateReplyItem(_objectSpread(_objectSpread({}, commentData), {}, { isPending: false }), parentId, id); if (!this.isDestroyed()) { successCallback(commentData); } }); /** * Callback for failed creation of a reply. * * @param {ElementsXhrError} error - The axios error * @param {string} code - the error code * @param {string} parentId - ID of the parent feed item * @param {string} id - ID of the feed item to update * @return {void} */ _defineProperty(this, "createReplyErrorCallback", (error, code, parentId, id) => { const errorMessage = error.status === HTTP_STATUS_CODE_CONFLICT ? messages.commentCreateConflictMessage : messages.commentCreateErrorMessage; this.updateReplyItem(this.createFeedError(errorMessage), parentId, id); this.feedErrorCallback(false, error, code); }); /** * Replace a feed item with new feed item data. * * @param {Object} updates - The new data to be applied to the feed item. * @param {string} id - ID of the feed item to replace. * @return {void} */ _defineProperty(this, "updateFeedItem", (updates, id) => { if (!this.file.id) { throw getBadItemError(); } const cachedItems = this.getCachedItems(this.file.id); if (cachedItems) { const updatedFeedItems = cachedItems.items.map(item => { if (item.id === id) { return _objectSpread(_objectSpread({}, item), updates); } return item; }); this.setCachedItems(this.file.id, updatedFeedItems); return updatedFeedItems; } return null; }); /** * Replace a reply of feed item with new comment data. * * @param {Object} replyUpdates - New data to be applied to the reply. * @param {string} parentId - ID of the parent feed item. * @param {string} id - ID of the reply to replace. * @return {void} */ _defineProperty(this, "updateReplyItem", (replyUpdates, parentId, id) => { if (!this.file.id) { throw getBadItemError(); } const getItemWithUpdatedReply = (item, replyId, updates) => { const updatedItem = _objectSpread({}, item); if (updatedItem.replies) { updatedItem.replies = updatedItem.replies.map(reply => { if (reply.id === replyId) { return _objectSpread(_objectSpread({}, reply), updates); } return reply; }); } return updatedItem; }; const cachedItems = this.getCachedItems(this.file.id); if (cachedItems) { const updatedFeedItems = cachedItems.items.map(item => { if (item.id === parentId && item.type === FEED_ITEM_TYPE_COMMENT) { return getItemWithUpdatedReply(item, id, replyUpdates); } if (item.id === parentId && item.type === FEED_ITEM_TYPE_ANNOTATION) { return getItemWithUpdatedReply(item, id, replyUpdates); } return item; }); this.setCachedItems(this.file.id, updatedFeedItems); } }); /** * Create a comment, and make a pending item to be replaced once the API is successful. * * @param {BoxItem} file - The file to which the task is assigned * @param {Object} currentUser - the user who performed the action * @param {string} text - the comment text * @param {boolean} hasMention - true if there is an @mention in the text * @param {Function} successCallback - the success callback * @param {Function} errorCallback - the error callback * @return {void} */ _defineProperty(this, "createComment", (file, currentUser, text, hasMention, successCallback, errorCallback) => { const uuid = uniqueId('comment_'); const commentData = { id: uuid, tagged_message: text, type: FEED_ITEM_TYPE_COMMENT }; if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.addPendingItem(this.file.id, currentUser, commentData); const message = {}; if (hasMention) { message.taggedMessage = text; } else { message.message = text; } this.commentsAPI = new CommentsAPI(this.options); this.commentsAPI.createComment(_objectSpread(_objectSpread({ file }, message), {}, { successCallback: comment => { this.createCommentSuccessCallback(comment, uuid, successCallback); }, errorCallback: (e, code) => { this.createCommentErrorCallback(e, code, uuid); } })); }); /** * Create a threaded comment (using ThreadedComments API), * and make a pending item to be replaced once the API is successful. * * @param {BoxItem} file - The file to which the task is assigned * @param {Object} currentUser - the user who performed the action * @param {string} text - the comment text * @param {Function} successCallback - the success callback * @param {Function} errorCallback - the error callback * @return {void} */ _defineProperty(this, "createThreadedComment", (file, currentUser, text, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } const uuid = uniqueId('comment_'); const commentData = { id: uuid, tagged_message: text, type: FEED_ITEM_TYPE_COMMENT }; this.file = file; this.errorCallback = errorCallback; this.addPendingItem(this.file.id, currentUser, commentData); this.threadedCommentsAPI = new ThreadedCommentsAPI(this.options); this.threadedCommentsAPI.createComment({ file, message: text, successCallback: comment => { this.createCommentSuccessCallback(comment, uuid, successCallback); }, errorCallback: (e, code) => { this.createCommentErrorCallback(e, code, uuid); } }); }); /** * Update a comment * * @param {BoxItem} file - The file to which the task is assigned * @param {string} commentId - Comment ID * @param {string} text - the comment text * @param {boolean} hasMention - true if there is an @mention in the text * @param {BoxCommentPermission} permissions - Permissions to attach to the app activity items * @param {Function} successCallback - the success callback * @param {Function} errorCallback - the error callback * @return {void} */ _defineProperty(this, "updateComment", (file, commentId, text, hasMention, permissions, successCallback, errorCallback) => { const commentData = { tagged_message: text }; if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.updateFeedItem(_objectSpread(_objectSpread({}, commentData), {}, { isPending: true }), commentId); const message = {}; if (hasMention) { message.tagged_message = text; } else { message.message = text; } this.commentsAPI = new CommentsAPI(this.options); this.commentsAPI.updateComment(_objectSpread(_objectSpread({ file, commentId, permissions }, message), {}, { successCallback: comment => { // use the request payload instead of response in the // feed item update because response may not contain // the tagged version of the message this.updateFeedItem(_objectSpread(_objectSpread({}, message), {}, { isPending: false }), commentId); if (!this.isDestroyed()) { successCallback(comment); } }, errorCallback: (e, code) => { this.updateCommentErrorCallback(e, code, commentId); } })); }); /** * Update a threaded comment * * @param {BoxItem} file - The file to which the task is assigned * @param {string} commentId - Comment ID * @param {string} text - the comment text * @param {FeedItemStatus} status - status of the comment * @param {BoxCommentPermission} permissions - Permissions to attach to the app activity items * @param {Function} successCallback - the success callback * @param {Function} errorCallback - the error callback * @return {void} */ _defineProperty(this, "updateThreadedComment", (file, commentId, text, status, permissions, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } if (!text && !status) { throw getMissingItemTextOrStatus(); } const commentData = {}; if (text) { commentData.tagged_message = text; } if (status) { commentData.status = status; } this.file = file; this.errorCallback = errorCallback; this.updateFeedItem(_objectSpread(_objectSpread({}, commentData), {}, { isPending: true }), commentId); this.threadedCommentsAPI = new ThreadedCommentsAPI(this.options); this.threadedCommentsAPI.updateComment({ fileId: file.id, commentId, permissions, message: text, status, successCallback: comment => { const { replies, total_reply_count } = comment, commentBase = _objectWithoutProperties(comment, _excluded4); this.updateFeedItem(_objectSpread(_objectSpread({}, commentBase), {}, { isPending: false }), commentId); if (!this.isDestroyed()) { successCallback(comment); } }, errorCallback: (e, code) => { this.updateCommentErrorCallback(e, code, commentId); } }); }); /** * Update a reply * * @param {BoxItem} file - The file to which the reply with its parent is assigned * @param {string} id - id of the reply * @param {string} parentId - id of the parent item * @param {string} text - the updated text * @param {BoxCommentPermission} permissions - Permissions to attach to the app activity items * @param {Function} successCallback - the success callback * @param {Function} errorCallback - the error callback * @return {void} */ _defineProperty(this, "updateReply", (file, id, parentId, text, permissions, successCallback, errorCallback) => { if (!file.id) { throw getBadItemError(); } this.file = file; this.errorCallback = errorCallback; this.updateReplyItem({ tagged_message: text, isPending: true }, parentId, id); this.threadedCommentsAPI = new ThreadedCommentsAPI(this.options); this.threadedCommentsAPI.updateComment({ fileId: file.id, commentId: id, permissions, message: text, undefined, successCallback: comment => { this.updateReplyItem(_objectSpread(_objectSpread({}, comment), {}, { isPending: false }), parentId, id); if (!this.isDestroyed()) { successCallback(comment); } }, errorCallback: (error, code) => { this.updateReplyErrorCallback(error, code, id, parentId); } }); }); /** * Modify feed item replies count * * @param {string} id - id of the item * @param {number} n - number to modify the count by * @return {void} */ _defineProperty(this, "modifyFeedItemRepliesCountBy", (id, n) => { if (!this.file.id) { throw getBadItemError(); } const { items: feedItems = [] } = this.getCachedItems(this.file.id) || {}; const feedItem = feedItems.find(({ id: itemId }) => itemId === id); if (!feedItem || feedItem.type !== 'annotation' && feedItem.type !== 'comment') { return; } const newReplyCount = (feedItem.total_reply_count || 0) + n; if (newReplyCount >= 0) { this.updateFeedItem({ total_reply_count: newReplyCount }, id); } }); /** * Deletes an app activity item. * * @param {