import '../utils/MarkdownPreview' import './AttachmentEditor' import './UploadProgress' import { thenCreateComment, thenUpdateComment } from '../../api/comments' import { thenUploadFile } from '../../api/files' import { Droptarget } from '../mixins/Droptarget' import { UnitOfWorkTracker } from '../mixins/UnitOfWorkTracker' import { EventBus, Events } from './EventBus' const MAX_ATTACHMENTS = 5; const TEMPLATE =`
`; Vue.component('comment-editor', { template: TEMPLATE, mixins: [Droptarget, UnitOfWorkTracker], props: { user: Object, parentId: String, projectId: String, comment: Object, mode: { type: String, default: 'reply', // reply or update }, }, data() { return { msg: this.initialMsg(), attachments: this.initialAttachments(), uploads: { nbrOfActive: 0, nbrOfTotal: 0, total: 0, loaded: 0 }, } }, computed: { submitButtonText() { switch(this.mode) { case 'reply': return 'Send'; case 'update': return 'Update'; default: console.error('Unknown mode: ', this.mode); } }, submitButtonIcon() { if (this.isBusyWorking) { return 'pi-spin spin'; }else{ switch(this.mode) { case 'reply': return 'pi-paper-plane'; case 'update': return 'pi-check'; default: console.error('Unknown mode: ', this.mode); } } }, attachmentsAsObject() { let attachmentsObject = {}; for (let a of this.attachments) { attachmentsObject[a.slug] = {oid: a.oid}; } return attachmentsObject; }, allSlugs() { return this.attachments.map((a) => { return a['slug']; }); }, isMsgLongEnough() { return this.msg.length >= 5; }, isAttachmentsValid() { for (let att of this.attachments) { if(!att.isSlugValid) { return false; } } return true; }, isValid() { return this.isAttachmentsValid && this.isMsgLongEnough; }, canSubmit() { return this.isValid && !this.isBusyWorking; }, uploadProgressPercent() { if (this.uploads.nbrOfActive === 0 || this.uploads.total === 0) { return 100; } return this.uploads.loaded / this.uploads.total * 100; }, uploadProgressLabel() { if (this.uploadProgressPercent === 100) { return 'Processing' } if (this.uploads.nbrOfTotal === 1) { return 'Uploading file'; } else { let fileOf = this.uploads.nbrOfTotal - this.uploads.nbrOfActive + 1; return `Uploading ${fileOf}/${this.uploads.nbrOfTotal} files`; } }, }, watch:{ msg(){ this.autoSizeInputField(); } }, mounted() { if(this.comment) { this.$nextTick(function () { this.autoSizeInputField(); this.$refs.inputField.focus(); }) } }, methods: { initialMsg() { if (this.comment) { if (this.mode === 'reply') { return `***@${this.comment.user.full_name}*** `; } if (this.mode === 'update') { return this.comment.msg_markdown; } } return ''; }, initialAttachments() { // Transforming the attacmentobject to an array of attachments let attachmentsList = [] if(this.mode === 'update') { let attachmentsObj = this.comment.properties.attachments for (let k in attachmentsObj) { if (attachmentsObj.hasOwnProperty(k)) { let a = { slug: k, oid: attachmentsObj[k]['oid'], isSlugValid: true } attachmentsList.push(a); } } } return attachmentsList; }, submit() { if(!this.canSubmit) return; this.unitOfWork( this.thenSubmit() .fail((err) => {toastr.error(pillar.utils.messageFromError(err), 'Failed to submit comment')}) ); }, thenSubmit() { if (this.mode === 'reply') { return this.thenCreateComment(); } else { return this.thenUpdateComment(); } }, keyUp(e) { if ((e.keyCode == 13 || e.key === 'Enter') && e.ctrlKey) { this.submit(); } }, thenCreateComment() { return thenCreateComment(this.parentId, this.msg, this.attachmentsAsObject) .then((newComment) => { EventBus.$emit(Events.NEW_COMMENT, newComment); EventBus.$emit(Events.EDIT_DONE, newComment.id ); this.cleanUp(); }) }, thenUpdateComment() { return thenUpdateComment(this.comment.parent, this.comment.id, this.msg, this.attachmentsAsObject) .then((updatedComment) => { EventBus.$emit(Events.UPDATED_COMMENT, updatedComment); EventBus.$emit(Events.EDIT_DONE, updatedComment.id); this.cleanUp(); }) }, canHandleDrop(event) { let dataTransfer = event.dataTransfer; let items = [...dataTransfer.items]; let nbrOfAttachments = items.length + this.uploads.nbrOfActive + this.attachments.length; if(nbrOfAttachments > MAX_ATTACHMENTS) { // Exceeds the limit return false; } // Only files in drop return [...dataTransfer.items].reduce((prev, it) => { let isFile = it.kind === 'file' && !!it.type; return prev && isFile; }, !!items.length); }, onDrop(event) { let files = [...event.dataTransfer.files]; for (let f of files) { this.unitOfWork( this.thenUploadFile(f) .fail((err) => {toastr.error(pillar.utils.messageFromError(err), 'File upload failed')}) ); } }, thenUploadFile(file){ let lastReportedTotal = 0; let lastReportedLoaded = 0; let progressCB = (total, loaded) => { this.uploads.loaded += loaded - lastReportedLoaded; this.uploads.total += total - lastReportedTotal; lastReportedLoaded = loaded; lastReportedTotal = total; } this.uploads.nbrOfActive++; this.uploads.nbrOfTotal++; return thenUploadFile(this.projectId || this.comment.project, file, progressCB) .then((resp) => { let attachment = { slug: file.name, oid: resp['file_id'], isSlugValid: false } this.attachments.push(attachment); this.msg += this.getAttachmentMarkdown(attachment); }) .always(()=>{ this.uploads.nbrOfActive--; if(this.uploads.nbrOfActive === 0) { this.uploads.loaded = 0; this.uploads.total = 0; this.uploads.nbrOfTotal = 0; } }) }, getAttachmentMarkdown(attachment){ return `{attachment ${attachment.slug}}`; }, insertAttachment(oid){ let attachment = this.getAttachment(oid); this.msg += this.getAttachmentMarkdown(attachment); }, attachmentDelete(oid) { let attachment = this.getAttachment(oid); let markdownToRemove = this.getAttachmentMarkdown(attachment); this.msg = this.msg.replace(new RegExp(markdownToRemove,'g'), ''); this.attachments = this.attachments.filter((a) => {return a.oid !== oid}); }, attachmentRename(newName, oid) { let attachment = this.getAttachment(oid); let oldMarkdownAttachment = this.getAttachmentMarkdown(attachment); attachment.slug = newName; let newMarkdownAttachment = this.getAttachmentMarkdown(attachment); this.msg = this.msg.replace(new RegExp(oldMarkdownAttachment,'g'), newMarkdownAttachment); }, getAttachment(oid) { for (let a of this.attachments) { if (a.oid === oid) return a; } console.error('No attachment found:', oid); }, attachmentValidation(oid, isValid) { let attachment = this.getAttachment(oid); attachment.isSlugValid = isValid; }, cleanUp() { this.msg = ''; this.attachments = []; }, autoSizeInputField() { let elInputField = this.$refs.inputField; elInputField.style.cssText = 'height:auto; padding:0'; let newInputHeight = elInputField.scrollHeight + 20; elInputField.style.cssText = `height:${ newInputHeight }px`; } } });