diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade index 975d21e84..f915e8bb5 100644 --- a/client/components/cards/attachments.jade +++ b/client/components/cards/attachments.jade @@ -69,6 +69,9 @@ template(name="attachmentActionsPopup") else | {{_ 'add-cover'}} if currentUser.isBoardAdmin + a.js-rename + i.fa.fa-pencil-square-o + | {{_ 'rename'}} a.js-confirm-delete i.fa.fa-close | {{_ 'delete'}} @@ -85,3 +88,8 @@ template(name="attachmentActionsPopup") a.js-move-storage-gridfs i.fa.fa-arrow-right | {{_ 'attachment-move-storage-gridfs'}} + +template(name="attachmentRenamePopup") + input.js-edit-attachment-name(type='text' autofocus value="{{getNameWithoutExtension}}" dir="auto") + .edit-controls.clearfix + button.primary.confirm.js-submit-edit-attachment-name(type="submit") {{_ 'save'}} diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index 8c78b363d..2279ae932 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -134,6 +134,7 @@ BlazeComponent.extendComponent({ events() { return [ { + 'click .js-rename': Popup.open('attachmentRename'), 'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() { Attachments.remove(this._id); Popup.back(2); @@ -158,3 +159,31 @@ BlazeComponent.extendComponent({ ] } }).register('attachmentActionsPopup'); + +BlazeComponent.extendComponent({ + getNameWithoutExtension() { + const ret = this.data().name.replace(new RegExp("\." + this.data().extension + "$"), ""); + return ret; + }, + events() { + return [ + { + 'keydown input.js-edit-attachment-name'(evt) { + // enter = save + if (evt.keyCode === 13) { + this.find('button[type=submit]').click(); + } + }, + 'click button.js-submit-edit-attachment-name'(event) { + // save button pressed + event.preventDefault(); + const name = this.$('.js-edit-attachment-name')[0] + .value + .trim() + this.data().extensionWithDot; + Meteor.call('renameAttachment', this.data()._id, name); + Popup.back(2); + }, + } + ] + } +}).register('attachmentRenamePopup'); diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index a4f2d204c..8e706103b 100644 --- a/imports/i18n/data/en.i18n.json +++ b/imports/i18n/data/en.i18n.json @@ -1177,5 +1177,6 @@ "size": "Size", "storage": "Storage", "action": "Action", - "board-title": "Board Title" + "board-title": "Board Title", + "attachmentRenamePopup-title": "Attachment Rename" } diff --git a/models/attachments.js b/models/attachments.js index 22e3c979f..73c5f58b1 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -4,7 +4,7 @@ import { createBucket } from './lib/grid/createBucket'; import fs from 'fs'; import path from 'path'; import { AttachmentStoreStrategyFilesystem, AttachmentStoreStrategyGridFs} from '/models/lib/attachmentStoreStrategy'; -import FileStoreStrategyFactory, {moveToStorage, STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS} from '/models/lib/fileStoreStrategy'; +import FileStoreStrategyFactory, {moveToStorage, rename, STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS} from '/models/lib/fileStoreStrategy'; let attachmentBucket; let storagePath; @@ -87,6 +87,13 @@ if (Meteor.isServer) { const fileObj = Attachments.findOne({_id: fileObjId}); moveToStorage(fileObj, storageDestination, fileStoreStrategyFactory); }, + renameAttachment(fileObjId, newName) { + check(fileObjId, String); + check(newName, String); + + const fileObj = Attachments.findOne({_id: fileObjId}); + rename(fileObj, newName, fileStoreStrategyFactory); + }, }); Meteor.startup(() => { diff --git a/models/cards.js b/models/cards.js index 0b926f4b6..73b0d48a1 100644 --- a/models/cards.js +++ b/models/cards.js @@ -739,17 +739,15 @@ Cards.helpers({ }, attachments() { + let id = this._id; if (this.isLinkedCard()) { - return Attachments.find( - { 'meta.cardId': this.linkedId }, - { sort: { uploadedAt: -1 } }, - ).each(); - } else { - return Attachments.find( - { 'meta.cardId': this._id }, - { sort: { uploadedAt: -1 } }, - ).each(); + id = this.linkedId; } + let ret = Attachments.find( + { 'meta.cardId': id }, + { sort: { uploadedAt: -1 } }, + ).each(); + return ret; }, cover() { diff --git a/models/lib/fileStoreStrategy.js b/models/lib/fileStoreStrategy.js index ebfbb4c9a..e6805e9b7 100644 --- a/models/lib/fileStoreStrategy.js +++ b/models/lib/fileStoreStrategy.js @@ -114,6 +114,13 @@ class FileStoreStrategy { unlink() { } + /** rename the file (physical) + * @li at database the filename is updated after this method + * @param newFilePath the new file path + */ + rename(newFilePath) { + } + /** return the storage name * @return the storage name */ @@ -287,6 +294,14 @@ export class FileStoreStrategyFilesystem extends FileStoreStrategy { fs.unlink(filePath, () => {}); } + /** rename the file (physical) + * @li at database the filename is updated after this method + * @param newFilePath the new file path + */ + rename(newFilePath) { + fs.renameSync(this.fileObj.versions[this.versionName].path, newFilePath); + } + /** return the storage name * @return the storage name */ @@ -389,3 +404,16 @@ export const copyFile = function(fileObj, newCardId, fileStoreStrategyFactory) { readStream.pipe(writeStream); }; + +export const rename = function(fileObj, newName, fileStoreStrategyFactory) { + Object.keys(fileObj.versions).forEach(versionName => { + const strategy = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName); + const newFilePath = strategy.getNewPath(fileStoreStrategyFactory.storagePath, newName); + strategy.rename(newFilePath); + + Attachments.update({ _id: fileObj._id }, { $set: { + "name": newName, + [`versions.${versionName}.path`]: newFilePath, + } }); + }); +};