diff --git a/config/lib/multer.js b/config/lib/multer.js index 68c61fa9..e7c5b333 100644 --- a/config/lib/multer.js +++ b/config/lib/multer.js @@ -78,9 +78,11 @@ module.exports.createUploadAttachFilename = function (req, file, cb) { } if (fs.existsSync(config.uploads.attach.file.dest + filename)) { - var err = new Error(); - err.code = 'FILE_ALREADY_EXISTS'; - cb(err, null); + var ext = file.originalname.replace(/^.+\./, ''); + var regex = new RegExp(ext, 'g'); + filename = filename.replace(regex, Date.now() + '.' + ext); + + cb(null, filename); } else { cb(null, filename); } diff --git a/modules/core/client/app/trans-string-en.js b/modules/core/client/app/trans-string-en.js index e4817731..97fd743f 100644 --- a/modules/core/client/app/trans-string-en.js +++ b/modules/core/client/app/trans-string-en.js @@ -680,6 +680,15 @@ TODAY_NEW_COUNT_ALL: '(Today: {{topic}} topics, {{reply}} replies)', TODAY_NEW_COUNT_TOPIC: '(Today: {{topic}} topics)', TODAY_NEW_COUNT_REPLY: '(Today: {{reply}} replies)', + UPLOAD_ATTACH_SUCCESSFULLY: 'Upload attach file successfully', + UPLOAD_ATTACH_FAILED: 'Upload attach file failed', + ATTACH_UPLOAD_TOOLTIP: 'Attach files by dragging & dropping to here, The picture file will be displayed automatically, Others file can only be downloaded.', + ATTACH_UPLOADING: 'Uploading', + ATTACH_LIST_HEADER: 'Attach files list:', + ATTACH_FILE_SIZE: 'Size:', + ATTACH_DOWN_COUNT: 'Downloaded:', + ATTACHE_DOWNLOAD_SUCCESSFULLY: 'Attach file download successfully', + ATTACHE_DOWNLOAD_FAILED: 'Attach file download failed', CATEGORY: { AFFAIRS: 'Affairs', diff --git a/modules/core/client/app/trans-string-zh.js b/modules/core/client/app/trans-string-zh.js index edc6de7b..0465e7c1 100644 --- a/modules/core/client/app/trans-string-zh.js +++ b/modules/core/client/app/trans-string-zh.js @@ -680,6 +680,15 @@ TODAY_NEW_COUNT_ALL: '(今日: {{topic}} 话题, {{reply}} 回复)', TODAY_NEW_COUNT_TOPIC: '(今日: {{topic}} 话题)', TODAY_NEW_COUNT_REPLY: '(今日: {{reply}} 回复)', + UPLOAD_ATTACH_SUCCESSFULLY: '文件上传成功', + UPLOAD_ATTACH_FAILED: '文件上传失败', + ATTACH_UPLOAD_TOOLTIP: '上传附件请将文件拖到这里面, 图片文件将会被自动显示, 其它类文件只能被下载.', + ATTACH_UPLOADING: '正在上传', + ATTACH_LIST_HEADER: '附件清单:', + ATTACH_FILE_SIZE: '文件大小:', + ATTACH_DOWN_COUNT: '下载次数:', + ATTACHE_DOWNLOAD_SUCCESSFULLY: '附件文件下载成功', + ATTACHE_DOWNLOAD_FAILED: '附件文件下载失败', CATEGORY: { AFFAIRS: '站务区', diff --git a/modules/core/client/directives/mt-markdown-editor.client.directive.js b/modules/core/client/directives/mt-markdown-editor.client.directive.js index 8a84ccda..983ba68a 100644 --- a/modules/core/client/directives/mt-markdown-editor.client.directive.js +++ b/modules/core/client/directives/mt-markdown-editor.client.directive.js @@ -4,12 +4,13 @@ angular.module('core') .directive('mtMarkdownEditor', mtMarkdownEditor); - mtMarkdownEditor.$inject = ['localStorageService']; + mtMarkdownEditor.$inject = ['localStorageService', '$compile', 'NotifycationService', '$timeout']; - function mtMarkdownEditor(localStorageService) { + function mtMarkdownEditor(localStorageService, $compile, NotifycationService, $timeout) { var directive = { restrict: 'A', require: 'ngModel', + replace: true, link: link }; @@ -25,6 +26,83 @@ fullscreen: {enable: false}, onChange: function (e) { ngModel.$setViewValue($('#' + attrs.mtMarkdownEditor)[0].value); + }, + onShow: function (e) { + scope.uFile = undefined; + scope.uProgress = 0; + scope.uFiles = []; + scope.uImages = []; + + var eleUploadTip = angular.element('
{{\'FORUMS.ATTACH_UPLOAD_TOOLTIP\' | translate}}
'); + var eleUploadBegin = angular.element('
{{\'FORUMS.ATTACH_UPLOADING\' | translate}}: {{uFile.name}}
'); + var eleUploadList = angular.element('
  1. {{f.name}} 
'); + + //$compile(eleUploadTip)(scope); + //$compile(eleUploadBegin)(scope); + //$compile(eleUploadList)(scope); + + $('.md-editor').append(eleUploadTip); + $('.md-editor').append(eleUploadBegin); + $('.md-editor').append(eleUploadList); + + scope.removeAttach = function (idx) { + scope.uFiles.splice(idx, 1); + }; + + $('.md-editor').bind('dragenter', function (evt) { + evt.stopPropagation(); + evt.preventDefault(); + }); + $('.md-editor').bind('dragover', function (evt) { + evt.stopPropagation(); + evt.preventDefault(); + }); + + $('.md-editor').bind('drop', function (evt) { + evt.stopPropagation(); + evt.preventDefault(); + + if (attrs.uploadMethod) { + scope.uFile = evt.originalEvent.dataTransfer.files[0]; + scope.uProgress = 0; + + scope.$eval(attrs.uploadMethod, { + editor: e, + ufile: scope.uFile, + progressback: function (pr) { + scope.uProgress = pr; + }, + callback: function (fn) { + var uFile = { + name: fn, + size: scope.uFile.size + }; + var status = ''; + var ext = uFile.name.replace(/^.+\./, '').toLowerCase(); + if (ext === 'jpg' || ext === 'jpeg' || ext === 'bmp' || ext === 'gif' || ext === 'png') { + status = '\n![' + uFile.name + '](/modules/forums/client/attach/temp/' + uFile.name + ')\n'; + e.replaceSelection(status); + ngModel.$setViewValue($('#' + attrs.mtMarkdownEditor)[0].value); + scope.uImages.push(uFile); + } else { + scope.uFiles.push(uFile); + } + + scope.uFile = undefined; + scope.uProgress = 0; + NotifycationService.showSuccessNotify('FORUMS.UPLOAD_ATTACH_SUCCESSFULLY'); + }, + errback: function (err) { + scope.uFile = undefined; + scope.uProgress = 0; + NotifycationService.showErrorNotify(err.data, 'FORUMS.UPLOAD_ATTACH_FAILED'); + } + }); + } + return false; + }); + + $compile($('.md-editor').contents())(scope); } }); } diff --git a/modules/forums/client/controllers/forums-post.client.controller.js b/modules/forums/client/controllers/forums-post.client.controller.js index d14a0306..d895ce92 100644 --- a/modules/forums/client/controllers/forums-post.client.controller.js +++ b/modules/forums/client/controllers/forums-post.client.controller.js @@ -5,10 +5,10 @@ .module('forums') .controller('ForumsPostController', ForumsPostController); - ForumsPostController.$inject = ['$scope', '$state', '$translate', 'Authentication', 'MeanTorrentConfig', 'ForumsService', 'SideOverlay', '$filter', 'NotifycationService', + ForumsPostController.$inject = ['$scope', '$state', '$translate', 'Authentication', 'MeanTorrentConfig', 'ForumsService', 'Upload', '$timeout', 'NotifycationService', 'marked', 'ModalConfirmService', '$stateParams', 'TopicsService']; - function ForumsPostController($scope, $state, $translate, Authentication, MeanTorrentConfig, ForumsService, SideOverlay, $filter, NotifycationService, + function ForumsPostController($scope, $state, $translate, Authentication, MeanTorrentConfig, ForumsService, Upload, $timeout, NotifycationService, marked, ModalConfirmService, $stateParams, TopicsService) { var vm = this; vm.forumsConfig = MeanTorrentConfig.meanTorrentConfig.forumsConfig; @@ -49,8 +49,25 @@ return false; } + var uf = []; + angular.forEach($scope.uFiles, function (f) { + uf.push({ + filename: f.name, + filesize: f.size + }); + }); + + var uimg = []; + angular.forEach($scope.uImages, function (f) { + uimg.push({ + filename: f.name + }); + }); + var post = new TopicsService(vm.postFields); post.forum = vm.forum._id; + post._attach = uf; + post._uImage = uimg; post.$save(function (response) { successCallback(response); @@ -60,6 +77,9 @@ function successCallback(res) { vm.postFields = {}; + $scope.uFiles = []; + $scope.uImages = []; + $scope.$broadcast('show-errors-reset', 'vm.postForm'); NotifycationService.showSuccessNotify('FORUMS.POST_TOPIC_SUCCESSFULLY'); $state.go('forums.topic', {forumId: vm.forum._id, topicId: res._id}); @@ -70,5 +90,31 @@ } }; + /** + * uploadAttach + * @param editor + * @param ufile + * @param callback + */ + vm.uploadAttach = function (editor, ufile, progressback, callback, errback) { + Upload.upload({ + url: '/api/attach/upload', + data: { + newAttachFile: ufile + } + }).then(function (res) { + if (callback) { + callback(res.data.filename); + } + }, function (res) { + if (errback && res.status > 0) { + errback(res); + } + }, function (evt) { + if (progressback) { + progressback(parseInt(100.0 * evt.loaded / evt.total, 10)); + } + }); + }; } }()); diff --git a/modules/forums/client/controllers/forums-topic.client.controller.js b/modules/forums/client/controllers/forums-topic.client.controller.js index bd47b5e0..5b1d66cf 100644 --- a/modules/forums/client/controllers/forums-topic.client.controller.js +++ b/modules/forums/client/controllers/forums-topic.client.controller.js @@ -6,12 +6,13 @@ .controller('ForumsTopicController', ForumsTopicController); ForumsTopicController.$inject = ['$scope', '$state', '$translate', 'Authentication', 'MeanTorrentConfig', 'ForumsService', 'ScoreLevelService', '$timeout', 'NotifycationService', - 'marked', 'ModalConfirmService', '$stateParams', 'TopicsService', 'localStorageService', '$compile', 'RepliesService', '$filter']; + 'marked', 'ModalConfirmService', '$stateParams', 'TopicsService', 'localStorageService', '$compile', 'RepliesService', '$filter', 'Upload', 'DownloadService']; function ForumsTopicController($scope, $state, $translate, Authentication, MeanTorrentConfig, ForumsService, ScoreLevelService, $timeout, NotifycationService, - marked, ModalConfirmService, $stateParams, TopicsService, localStorageService, $compile, RepliesService, $filter) { + marked, ModalConfirmService, $stateParams, TopicsService, localStorageService, $compile, RepliesService, $filter, Upload, DownloadService) { var vm = this; vm.forumsConfig = MeanTorrentConfig.meanTorrentConfig.forumsConfig; + vm.appConfig = MeanTorrentConfig.meanTorrentConfig.app; vm.user = Authentication.user; vm.forumPath = []; vm.postReplyFields = {}; @@ -333,9 +334,26 @@ return false; } + var uf = []; + angular.forEach($scope.uFiles, function (f) { + uf.push({ + filename: f.name, + filesize: f.size + }); + }); + + var uimg = []; + angular.forEach($scope.uImages, function (f) { + uimg.push({ + filename: f.name + }); + }); + var reply = new RepliesService(vm.postReplyFields); reply.forum = vm.forum._id; reply.topic = vm.topic._id; + reply._attach = uf; + reply._uImage = uimg; reply.$save(function (response) { successCallback(response); @@ -346,6 +364,9 @@ function successCallback(res) { vm.postReplyFields = {}; vm.topic = res; + $scope.uFiles = []; + $scope.uImages = []; + $scope.$broadcast('show-errors-reset', 'vm.replyForm'); NotifycationService.showSuccessNotify('FORUMS.POST_REPLY_SUCCESSFULLY'); } @@ -391,5 +412,53 @@ vm.topic = res; }); }; + + /** + * uploadAttach + * @param editor + * @param ufile + * @param callback + */ + vm.uploadAttach = function (editor, ufile, progressback, callback, errback) { + Upload.upload({ + url: '/api/attach/upload', + data: { + newAttachFile: ufile + } + }).then(function (res) { + if (callback) { + callback(res.data.filename); + } + }, function (res) { + if (errback && res.status > 0) { + errback(res); + } + }, function (evt) { + if (progressback) { + progressback(parseInt(100.0 * evt.loaded / evt.total, 10)); + } + }); + }; + + /** + * downloadAttach + * @param t + * @param r + * @param af + */ + vm.downloadAttach = function (t, r, af) { + var url = '/api/attach/' + vm.topic._id.toString(); + url += r ? '/' + r._id.toString() : ''; + url += '?attachId=' + af._id.toString(); + + DownloadService.downloadFile(url, null, function (status) { + if (status === 200) { + NotifycationService.showSuccessNotify('FORUMS.ATTACHE_DOWNLOAD_SUCCESSFULLY'); + } + }, function (err) { + console.log(err); + NotifycationService.showErrorNotify(err.data.message, 'FORUMS.ATTACHE_DOWNLOAD_FAILED'); + }); + }; } }()); diff --git a/modules/forums/client/less/forum.less b/modules/forums/client/less/forum.less index c8250d1f..34fbce28 100644 --- a/modules/forums/client/less/forum.less +++ b/modules/forums/client/less/forum.less @@ -424,6 +424,29 @@ max-width: 100%; height: auto; } + .attach-list { + border: solid 1px #ddd; + margin-top: 20px; + .attach-header { + height: 40px; + background-color: #f5f5f5; + border-bottom: 1px dashed #ddd; + padding: 8px 15px; + font-weight: 500; + } + ol { + margin-bottom: 0; + li { + font-size: 12px; + margin: 10px; + span { + &:not(:first-child) { + color: #999; + } + } + } + } + } } } .reply-avatar { @@ -459,15 +482,74 @@ } .md-editor { + background-color: #f5f5f5; textarea { border-bottom: none; background-color: #fff; - min-height: 250px; + min-height: 300px; + max-height: 800px; padding: 5px; } .md-preview { padding: 5px; border-bottom: none; + img { + max-width: 100%; + height: auto; + } + } + .attach-info { + color: #666; + height: 40px; + background-color: #f5f5f5; + border-top: 1px dashed #ddd; + .attach-upload-tooltip { + margin: 10px; + } + } + .attach-list { + color: #666; + min-height: 40px; + background-color: #f5f5f5; + border-top: 1px dashed #ddd; + ol { + margin-bottom: 0; + li { + font-size: 12px; + color: @brand-primary; + margin: 10px; + i { + cursor: pointer; + &:hover { + color: @mt-base-color; + } + } + } + } + } + .upload-info { + position: relative; + color: #666; + height: 40px; + background-color: #f5f5f5; + border-top: 1px dashed #ddd; + i { + color: @mt-base-color; + float: left; + margin: 11px 5px; + } + .attach-upload-progress { + height: 30px; + margin: 5px 30px; + max-width: ~"calc(100% - 40px)"; + background-color: @brand-info; + } + .attach-upload-filename { + position: absolute; + top: 10px; + margin-left: 35px; + background-color: transparent; + } } } diff --git a/modules/forums/client/views/post.client.view.html b/modules/forums/client/views/post.client.view.html index a32395e5..80af48dd 100644 --- a/modules/forums/client/views/post.client.view.html +++ b/modules/forums/client/views/post.client.view.html @@ -52,7 +52,8 @@
- +

{{ 'FORUMS.PC_REQUIRED' | translate}}

diff --git a/modules/forums/client/views/topic.client.view.html b/modules/forums/client/views/topic.client.view.html index d3181d3e..5f81d93b 100644 --- a/modules/forums/client/views/topic.client.view.html +++ b/modules/forums/client/views/topic.client.view.html @@ -54,7 +54,8 @@ {{'FORUMS.BTN_POST_NEW_REPLY' | translate}} {{ vm.topic.readOnly ? 'FORUMS.BTN_UNSET_READONLY' : 'FORUMS.BTN_SET_READONLY' | translate}} + href="#" + ng-click="vm.toggleReadonly(vm.topic);">{{ vm.topic.readOnly ? 'FORUMS.BTN_UNSET_READONLY' : 'FORUMS.BTN_SET_READONLY' | translate}}
+
+
+
    +
  1. + {{af.filename}} ( + {{'FORUMS.ATTACH_FILE_SIZE' | translate}} {{af.filesize | bytes}} + {{'FORUMS.ATTACH_DOWN_COUNT' | translate}} {{af.downCount}} ) +
  2. +
+

[ {{vm.topic.updatedBy.displayName}} {{ 'COMMENT_EDITED_INFO' | translate}} {{vm.topic.updatedAt | date:'yyyy-MM-dd HH:mm:ss'}} ]

@@ -176,6 +187,16 @@
+
+
+
    +
  1. + {{af.filename}} ( + {{'FORUMS.ATTACH_FILE_SIZE' | translate}} {{af.filesize | bytes}} + {{'FORUMS.ATTACH_DOWN_COUNT' | translate}} {{af.downCount}} ) +
  2. +
+

[ {{rep.updatedBy.displayName}} {{ 'COMMENT_EDITED_INFO' | translate}} {{rep.updatedAt | date:'yyyy-MM-dd HH:mm:ss'}} ]

@@ -201,13 +222,14 @@ -
+
+ mt-markdown-editor="postReplyContent" + upload-method="vm.uploadAttach(editor, ufile, progressback, callback, errback);" required>

{{ 'FORUMS.PRC_REQUIRED' | translate}}

diff --git a/modules/forums/server/models/topic.server.model.js b/modules/forums/server/models/topic.server.model.js index 52aef3ca..e6438ec7 100644 --- a/modules/forums/server/models/topic.server.model.js +++ b/modules/forums/server/models/topic.server.model.js @@ -15,6 +15,10 @@ var AttachSchema = new Schema({ default: '', trim: true }, + filesize: { + type: Number, + default: 0 + }, downCount: { type: Number, default: 0